mirror of
https://github.com/Ed94/Odin.git
synced 2026-06-13 01:21:38 -07:00
Merge remote-tracking branch 'offical/master'
This commit is contained in:
@@ -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.
|
||||
|
||||
#### [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/)
|
||||
|
||||
Documentation for the Odin language itself.
|
||||
|
||||
@@ -212,10 +212,24 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
case size == 0:
|
||||
err = .Mode_Not_Implemented
|
||||
return
|
||||
case (uintptr(old_data) & uintptr(alignment-1) == 0) && size < old_size:
|
||||
// shrink data in-place
|
||||
data = old_data[:size]
|
||||
return
|
||||
case uintptr(old_data) & uintptr(alignment-1) == 0:
|
||||
if size < old_size {
|
||||
// shrink data in-place
|
||||
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
|
||||
@@ -284,9 +298,10 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
|
||||
|
||||
if block := arena.curr_block; block != nil {
|
||||
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)
|
||||
block.used = temp.used
|
||||
arena.total_used -= amount_to_zero
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
// return 'nil' if there was no room to insert the entry
|
||||
@(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
|
||||
pos := map_desired_position(m^, h)
|
||||
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)
|
||||
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) {
|
||||
@@ -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 result == 0 {
|
||||
result = map_cell_index_dynamic(vs, info.vs, pos)
|
||||
}
|
||||
|
||||
kp := map_cell_index_dynamic(ks, info.ks, 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(k), rawptr(kp), 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)
|
||||
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)
|
||||
@@ -501,6 +510,7 @@ map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^
|
||||
if probe_distance < look_ahead {
|
||||
// probed can be made ideal while placing saved (ending condition)
|
||||
if result == 0 {
|
||||
key = k_dst
|
||||
result = v_dst
|
||||
}
|
||||
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 {
|
||||
// place saved, save probed
|
||||
if result == 0 {
|
||||
key = k_dst
|
||||
result = v_dst
|
||||
}
|
||||
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)
|
||||
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^)
|
||||
@@ -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^))
|
||||
}
|
||||
|
||||
value_ptr = rawptr(map_insert_hash_dynamic(m, info, hash, uintptr(key), uintptr(zero)))
|
||||
assert(value_ptr != nil)
|
||||
key_ptr = rawptr(map_cell_index_dynamic(map_data(m^), info.ks, map_desired_position(m^, hash)))
|
||||
kp, vp := map_insert_hash_dynamic_with_key(m, info, hash, uintptr(key), uintptr(zero))
|
||||
key_ptr = rawptr(kp)
|
||||
value_ptr = rawptr(vp)
|
||||
|
||||
m.len += 1
|
||||
just_inserted = true
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#+private
|
||||
package runtime
|
||||
|
||||
@(priority_index=-1e6)
|
||||
foreign import "system:Foundation.framework"
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
+6
-6
@@ -9,7 +9,7 @@ set -eu
|
||||
CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"dev-$(date +"%Y-%m")\""
|
||||
CXXFLAGS="$CXXFLAGS -std=c++14"
|
||||
DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value"
|
||||
LDFLAGS="$LDFLAGS -pthread -lm -lstdc++"
|
||||
LDFLAGS="$LDFLAGS -pthread -lm"
|
||||
OS_ARCH="$(uname -m)"
|
||||
OS_NAME="$(uname -s)"
|
||||
|
||||
@@ -95,15 +95,15 @@ Darwin)
|
||||
;;
|
||||
FreeBSD)
|
||||
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)
|
||||
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)
|
||||
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
|
||||
# NOTE: This is needed by the Linux release pipeline!
|
||||
# cp $(readlink -f $($LLVM_CONFIG --libfiles)) ./
|
||||
@@ -111,12 +111,12 @@ Linux)
|
||||
;;
|
||||
OpenBSD)
|
||||
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)"
|
||||
;;
|
||||
Haiku)
|
||||
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)"
|
||||
;;
|
||||
*)
|
||||
|
||||
@@ -114,3 +114,5 @@ CHAR_BIT :: 8
|
||||
va_list :: struct #align(16) {
|
||||
_: [4096]u8,
|
||||
}
|
||||
|
||||
FILE :: struct {}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package libc
|
||||
|
||||
import "core:c"
|
||||
import "core:io"
|
||||
|
||||
when ODIN_OS == .Windows {
|
||||
@@ -15,7 +16,7 @@ when ODIN_OS == .Windows {
|
||||
|
||||
// 7.21 Input/output
|
||||
|
||||
FILE :: struct {}
|
||||
FILE :: c.FILE
|
||||
|
||||
Whence :: enum int {
|
||||
SET = SEEK_SET,
|
||||
|
||||
@@ -46,8 +46,7 @@ init_with_contents :: proc(q: ^$Q/Queue($T), backing: []T) -> bool {
|
||||
cap = builtin.len(backing),
|
||||
allocator = {procedure=runtime.nil_allocator_proc, data=nil},
|
||||
}
|
||||
q.len = len(backing)
|
||||
q.offset = len(backing)
|
||||
q.len = builtin.len(backing)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#+build !netbsd
|
||||
#+build !darwin
|
||||
#+build !js
|
||||
#+build !wasi
|
||||
package crypto
|
||||
|
||||
HAS_RAND_BYTES :: false
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -118,10 +118,10 @@ _encode :: proc(out, data: []byte, ENC_TBL := ENC_TABLE, allocator := context.al
|
||||
|
||||
@(optimization_mode="favor_size")
|
||||
decode :: proc(
|
||||
data: string,
|
||||
DEC_TBL := DEC_TABLE,
|
||||
validate: Validate_Proc = _validate_default,
|
||||
allocator := context.allocator) -> (out: []byte, err: Error) {
|
||||
data: string,
|
||||
DEC_TBL := DEC_TABLE,
|
||||
validate: Validate_Proc = _validate_default,
|
||||
allocator := context.allocator) -> (out: []byte, err: Error) {
|
||||
if len(data) == 0 {
|
||||
return nil, .None
|
||||
}
|
||||
|
||||
+26
-4
@@ -314,7 +314,29 @@ assertf :: proc(condition: bool, fmt: string, args: ..any, loc := #caller_locati
|
||||
p = runtime.default_assertion_failure_proc
|
||||
}
|
||||
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)
|
||||
}
|
||||
@@ -332,7 +354,7 @@ panicf :: proc(fmt: string, args: ..any, loc := #caller_location) -> ! {
|
||||
p = runtime.default_assertion_failure_proc
|
||||
}
|
||||
message := tprintf(fmt, ..args)
|
||||
p("Panic", message, loc)
|
||||
p("panic", message, loc)
|
||||
}
|
||||
|
||||
// Creates a formatted C string
|
||||
@@ -1357,9 +1379,9 @@ _pad :: proc(fi: ^Info, s: string) {
|
||||
if fi.minus { // right pad
|
||||
io.write_string(fi.writer, s, &fi.n)
|
||||
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
|
||||
io.write_byte(fi.writer, '-', &fi.n)
|
||||
io.write_byte(fi.writer, s[0], &fi.n)
|
||||
fmt_write_padding(fi, width)
|
||||
io.write_string(fi.writer, s[1:], &fi.n)
|
||||
} else { // left pad
|
||||
|
||||
+33
-2
@@ -115,7 +115,7 @@ panicf :: proc(fmt_str: string, args: ..any, location := #caller_location) -> !
|
||||
}
|
||||
|
||||
@(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 {
|
||||
@(cold)
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
id := 1.0/d
|
||||
c[0, 0] = +m[1, 1] * id
|
||||
c[0, 1] = -m[1, 0] * id
|
||||
c[1, 0] = -m[0, 1] * id
|
||||
c[0, 1] = -m[0, 1] * id
|
||||
c[1, 0] = -m[1, 0] * id
|
||||
c[1, 1] = +m[0, 0] * id
|
||||
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]
|
||||
id := 1.0/d
|
||||
c[0, 0] = +m[1, 1] * id
|
||||
c[0, 1] = -m[1, 0] * id
|
||||
c[1, 0] = -m[0, 1] * id
|
||||
c[0, 1] = -m[0, 1] * id
|
||||
c[1, 0] = -m[1, 0] * id
|
||||
c[1, 1] = +m[0, 0] * id
|
||||
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]
|
||||
id := 1.0/d
|
||||
c[0, 0] = +m[1, 1] * id
|
||||
c[0, 1] = -m[1, 0] * id
|
||||
c[1, 0] = -m[0, 1] * id
|
||||
c[0, 1] = -m[0, 1] * id
|
||||
c[1, 0] = -m[1, 0] * id
|
||||
c[1, 1] = +m[0, 0] * id
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ Generator_Query_Info :: runtime.Random_Generator_Query_Info
|
||||
Default_Random_State :: runtime.Default_Random_State
|
||||
default_random_generator :: runtime.default_random_generator
|
||||
|
||||
@(require_results)
|
||||
create :: proc(seed: u64) -> (state: Default_Random_State) {
|
||||
seed := seed
|
||||
runtime.default_random_generator(&state)
|
||||
|
||||
+94
-3
@@ -785,6 +785,27 @@ delete_map :: proc(
|
||||
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.
|
||||
*/
|
||||
@@ -794,6 +815,8 @@ delete :: proc{
|
||||
delete_dynamic_array,
|
||||
delete_slice,
|
||||
delete_map,
|
||||
delete_soa_slice,
|
||||
delete_soa_dynamic_array,
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -900,8 +923,7 @@ make_dynamic_array :: proc(
|
||||
Allocate a dynamic array with initial length.
|
||||
|
||||
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
|
||||
`len`.
|
||||
backing allocator, and initial capacity and length specified by `len`.
|
||||
*/
|
||||
@(require_results)
|
||||
make_dynamic_array_len :: proc(
|
||||
@@ -910,7 +932,7 @@ make_dynamic_array_len :: proc(
|
||||
allocator := context.allocator,
|
||||
loc := #caller_location,
|
||||
) -> (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)
|
||||
}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
@@ -974,6 +1061,10 @@ make :: proc{
|
||||
make_dynamic_array_len_cap,
|
||||
make_map,
|
||||
make_multi_pointer,
|
||||
make_soa_slice,
|
||||
make_soa_dynamic_array,
|
||||
make_soa_dynamic_array_len,
|
||||
make_soa_dynamic_array_len_cap,
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -328,10 +328,24 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
|
||||
case size == 0:
|
||||
err = .Mode_Not_Implemented
|
||||
return
|
||||
case (uintptr(old_data) & uintptr(alignment-1) == 0) && size < old_size:
|
||||
// shrink data in-place
|
||||
data = old_data[:size]
|
||||
return
|
||||
case uintptr(old_data) & uintptr(alignment-1) == 0:
|
||||
if size < old_size {
|
||||
// shrink data in-place
|
||||
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
|
||||
@@ -402,9 +416,10 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
|
||||
|
||||
if block := arena.curr_block; block != nil {
|
||||
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])
|
||||
block.used = temp.used
|
||||
arena.total_used -= amount_to_zero
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -95,6 +95,7 @@ Resolve_Error :: enum u32 {
|
||||
}
|
||||
|
||||
DNS_Error :: enum u32 {
|
||||
None = 0,
|
||||
Invalid_Hostname_Error = 1,
|
||||
Invalid_Hosts_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{}
|
||||
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 {
|
||||
address: Address,
|
||||
port: int,
|
||||
|
||||
+16
-8
@@ -132,7 +132,14 @@ resolve_ip4 :: proc(hostname_and_maybe_port: string) -> (ep4: Endpoint, err: Net
|
||||
return
|
||||
}
|
||||
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 {
|
||||
err = .Unable_To_Resolve
|
||||
return
|
||||
@@ -159,7 +166,14 @@ resolve_ip6 :: proc(hostname_and_maybe_port: string) -> (ep6: Endpoint, err: Net
|
||||
return t, nil
|
||||
}
|
||||
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 {
|
||||
err = .Unable_To_Resolve
|
||||
return
|
||||
@@ -255,12 +269,6 @@ get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type
|
||||
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
|
||||
if recv_sz == 0 {
|
||||
continue
|
||||
|
||||
@@ -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) {
|
||||
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)
|
||||
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) {
|
||||
case 0:
|
||||
|
||||
@@ -35,6 +35,7 @@ Socket_Option :: enum c.int {
|
||||
Send_Buffer_Size = c.int(linux.Socket_Option.SNDBUF),
|
||||
Receive_Timeout = c.int(linux.Socket_Option.RCVTIMEO),
|
||||
Send_Timeout = c.int(linux.Socket_Option.SNDTIMEO),
|
||||
Broadcast = c.int(linux.Socket_Option.BROADCAST),
|
||||
}
|
||||
|
||||
// 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,
|
||||
.Keep_Alive,
|
||||
.Out_Of_Bounds_Data_Inline,
|
||||
.TCP_Nodelay:
|
||||
.TCP_Nodelay,
|
||||
.Broadcast:
|
||||
// TODO: verify whether these are options or not on Linux
|
||||
// .Broadcast, <-- yes
|
||||
// .Conditional_Accept,
|
||||
|
||||
@@ -1,20 +1,101 @@
|
||||
#+private
|
||||
package os2
|
||||
|
||||
import "core:sys/linux"
|
||||
|
||||
Read_Directory_Iterator_Impl :: struct {
|
||||
|
||||
prev_fi: File_Info,
|
||||
dirent_backing: []u8,
|
||||
dirent_buflen: int,
|
||||
dirent_off: int,
|
||||
index: int,
|
||||
}
|
||||
|
||||
|
||||
@(require_results)
|
||||
_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
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
_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) {
|
||||
if it == nil {
|
||||
return
|
||||
}
|
||||
delete(it.impl.dirent_backing, file_allocator())
|
||||
file_info_delete(it.impl.prev_fi, file_allocator())
|
||||
}
|
||||
|
||||
@@ -39,8 +39,11 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info
|
||||
}
|
||||
|
||||
n := len(fimpl.name)+1
|
||||
non_zero_resize(&it.impl.fullpath, n+len(sname))
|
||||
n += copy(it.impl.fullpath[n:], sname)
|
||||
if err := non_zero_resize(&it.impl.fullpath, n+len(sname)); err != nil {
|
||||
// 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[:]))
|
||||
ok = true
|
||||
@@ -60,7 +63,7 @@ _read_directory_iterator_create :: proc(f: ^File) -> (iter: Read_Directory_Itera
|
||||
iter.f = f
|
||||
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, "/")
|
||||
defer if err != nil { delete(iter.impl.fullpath) }
|
||||
|
||||
@@ -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^ = {}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ _set_env :: proc(key, v_new: string) -> bool {
|
||||
// wasn't in the environment in the first place.
|
||||
k_addr, v_addr := _kv_addr_from_val(v_curr, key)
|
||||
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 {
|
||||
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 {
|
||||
return false
|
||||
}
|
||||
@@ -129,7 +129,7 @@ _unset_env :: proc(key: string) -> bool {
|
||||
// if we got this far, the envrionment variable
|
||||
// existed AND was allocated by us.
|
||||
k_addr, _ := _kv_addr_from_val(v, key)
|
||||
heap_free(k_addr)
|
||||
runtime.heap_free(k_addr)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ _clear_env :: proc() {
|
||||
|
||||
for kv in _env {
|
||||
if !_is_in_org_env(kv) {
|
||||
heap_free(raw_data(kv))
|
||||
runtime.heap_free(raw_data(kv))
|
||||
}
|
||||
}
|
||||
clear(&_env)
|
||||
@@ -193,7 +193,7 @@ _build_env :: proc() {
|
||||
return
|
||||
}
|
||||
|
||||
_env = make(type_of(_env), heap_allocator())
|
||||
_env = make(type_of(_env), runtime.heap_allocator())
|
||||
cstring_env := _get_original_env()
|
||||
_org_env_begin = uintptr(rawptr(cstring_env[0]))
|
||||
for i := 0; cstring_env[i] != nil; i += 1 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -10,8 +10,12 @@ _error_string :: proc(errno: i32) -> string {
|
||||
return string(posix.strerror(posix.Errno(errno)))
|
||||
}
|
||||
|
||||
_get_platform_error :: proc() -> Error {
|
||||
#partial switch errno := posix.errno(); errno {
|
||||
_get_platform_error_from_errno :: proc() -> Error {
|
||||
return _get_platform_error_existing(posix.errno())
|
||||
}
|
||||
|
||||
_get_platform_error_existing :: proc(errno: posix.Errno) -> Error {
|
||||
#partial switch errno {
|
||||
case .EPERM:
|
||||
return .Permission_Denied
|
||||
case .EEXIST:
|
||||
@@ -32,3 +36,8 @@ _get_platform_error :: proc() -> Error {
|
||||
return Platform_Error(errno)
|
||||
}
|
||||
}
|
||||
|
||||
_get_platform_error :: proc{
|
||||
_get_platform_error_existing,
|
||||
_get_platform_error_from_errno,
|
||||
}
|
||||
|
||||
@@ -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
@@ -7,6 +7,13 @@ import "core:time"
|
||||
import "core:sync"
|
||||
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: File,
|
||||
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) {
|
||||
if len(p) == 0 {
|
||||
if len(p) <= 0 {
|
||||
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 {
|
||||
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) {
|
||||
if len(p) == 0 {
|
||||
if len(p) <= 0 {
|
||||
return 0, nil
|
||||
}
|
||||
if offset < 0 {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
_write :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) {
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
_write :: proc(f: ^File_Impl, p: []byte) -> (nt: i64, err: Error) {
|
||||
p := p
|
||||
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 -1, _get_platform_error(errno)
|
||||
}
|
||||
return i64(n), nil
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
_write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) {
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
_write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (nt: i64, err: Error) {
|
||||
if offset < 0 {
|
||||
return 0, .Invalid_Offset
|
||||
}
|
||||
n, errno := linux.pwrite(f.fd, p[:], offset)
|
||||
if errno != .NONE {
|
||||
return -1, _get_platform_error(errno)
|
||||
|
||||
p := p
|
||||
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) {
|
||||
|
||||
@@ -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
@@ -1,726 +1,6 @@
|
||||
#+private
|
||||
package os2
|
||||
|
||||
import "core:sys/linux"
|
||||
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(
|
||||
®ion_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(
|
||||
®ion_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 := ®ion.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(®ion.hdr.free_list[n])
|
||||
alloc_idx := region.hdr.free_list[free_idx]
|
||||
sync.atomic_store_explicit(®ion.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
|
||||
}
|
||||
import "base:runtime"
|
||||
|
||||
_heap_allocator_proc :: runtime.heap_allocator_proc
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#+private
|
||||
package os2
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
_heap_allocator_proc :: runtime.wasm_allocator_proc
|
||||
@@ -2,6 +2,8 @@ package os2
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
import "core:path/filepath"
|
||||
|
||||
Path_Separator :: _Path_Separator // OS-Specific
|
||||
Path_Separator_String :: _Path_Separator_String // 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) {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
#+private
|
||||
package os2
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
import "core:strings"
|
||||
import "core:strconv"
|
||||
import "base:runtime"
|
||||
import "core:sys/linux"
|
||||
|
||||
_Path_Separator :: '/'
|
||||
@@ -171,6 +172,25 @@ _set_working_directory :: proc(dir: string) -> Error {
|
||||
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) {
|
||||
PROC_FD_PATH :: "/proc/self/fd/"
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -81,7 +81,7 @@ _remove_all :: proc(path: string) -> Error {
|
||||
|
||||
fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator())
|
||||
if entry.d_type == .DIR {
|
||||
_remove_all(fullpath[:len(fullpath)-1])
|
||||
_remove_all(fullpath[:len(fullpath)-1]) or_return
|
||||
} else {
|
||||
if posix.unlink(cstring(raw_data(fullpath))) != .OK {
|
||||
return _get_platform_error()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -136,6 +136,26 @@ _set_working_directory :: proc(dir: string) -> (err: Error) {
|
||||
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
|
||||
|
||||
@(init)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -547,6 +547,11 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
|
||||
if _, errno = linux.dup2(stderr_fd, STDERR); errno != .NONE {
|
||||
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)
|
||||
assert(errno != nil)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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) {
|
||||
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 {
|
||||
return 0, get_last_error()
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
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 {
|
||||
return 0, _get_errno(int(result))
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#+build !wasi
|
||||
package filepath
|
||||
|
||||
import "core:os"
|
||||
|
||||
@@ -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,3 +1,4 @@
|
||||
#+build !wasi
|
||||
package filepath
|
||||
|
||||
import "core:os"
|
||||
|
||||
@@ -49,9 +49,6 @@ _futex_signal :: 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))
|
||||
#partial switch errno {
|
||||
case .NONE:
|
||||
|
||||
@@ -26,7 +26,7 @@ foreign Ntdll {
|
||||
BUT requires taking the return value of it and if it is non-zero
|
||||
converting that status to a DOS error and then SetLastError
|
||||
If this is not done, then things don't work as expected when
|
||||
and error occurs
|
||||
an error occurs
|
||||
|
||||
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 {
|
||||
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)
|
||||
return CustomWaitOnAddress(f, &expect, size_of(expect), &timeout)
|
||||
}
|
||||
|
||||
@@ -25,6 +25,10 @@ Block_createLocalWithParam :: proc (user_data: rawptr, user_proc: proc "c" (user
|
||||
b, _ := Block_createInternalWithParam(false, user_data, user_proc, {})
|
||||
return b
|
||||
}
|
||||
@(objc_type=Block, objc_name="invoke")
|
||||
Block_invoke :: proc "c" (self: ^Block, args: ..any) -> ^Object {
|
||||
return msgSend(^Object, self, "invoke:", ..args)
|
||||
}
|
||||
|
||||
@(private)
|
||||
Internal_Block_Literal_Base :: struct {
|
||||
|
||||
@@ -13,6 +13,23 @@ Data_init :: proc "c" (self: ^Data) -> ^Data {
|
||||
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")
|
||||
Data_mutableBytes :: proc "c" (self: ^Data) -> rawptr {
|
||||
return msgSend(rawptr, self, "mutableBytes")
|
||||
|
||||
@@ -18,6 +18,11 @@ Date_dateWithTimeIntervalSinceNow :: proc "c" (secs: TimeInterval) -> ^Date {
|
||||
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)
|
||||
Date_distantFuture :: proc "c" () -> ^Date {
|
||||
return msgSend(^Date, Date, "distantFuture")
|
||||
|
||||
@@ -30,6 +30,7 @@ MenuItem :: struct {using _: Object}
|
||||
MenuItem_alloc :: proc "c" () -> ^MenuItem {
|
||||
return msgSend(^MenuItem, MenuItem, "alloc")
|
||||
}
|
||||
|
||||
@(objc_type=MenuItem, objc_name="registerActionCallback", objc_is_class_method=true)
|
||||
MenuItem_registerActionCallback :: proc "c" (name: cstring, callback: MenuItemCallback) -> SEL {
|
||||
s := string(name)
|
||||
@@ -50,11 +51,21 @@ MenuItem_registerActionCallback :: proc "c" (name: cstring, callback: MenuItemCa
|
||||
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")
|
||||
MenuItem_init :: proc "c" (self: ^MenuItem) -> ^MenuItem {
|
||||
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")
|
||||
MenuItem_setKeyEquivalentModifierMask :: proc "c" (self: ^MenuItem, modifierMask: KeyEquivalentModifierMask) {
|
||||
msgSend(nil, self, "setKeyEquivalentModifierMask:", modifierMask)
|
||||
@@ -75,6 +86,11 @@ MenuItem_title :: proc "c" (self: ^MenuItem) -> ^String {
|
||||
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")
|
||||
|
||||
@@ -7,3 +7,13 @@ SavePanel :: struct{ using _: Panel }
|
||||
SavePanel_runModal :: proc "c" (self: ^SavePanel) -> ModalResponse {
|
||||
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")
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -28,3 +28,8 @@ URL_initFileURLWithPath :: proc "c" (self: ^URL, path: ^String) -> ^URL {
|
||||
URL_fileSystemRepresentation :: proc "c" (self: ^URL) -> cstring {
|
||||
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)
|
||||
}
|
||||
@@ -129,6 +129,10 @@ WindowDelegateTemplate :: struct {
|
||||
windowDidExitVersionBrowser: proc(notification: ^Notification),
|
||||
}
|
||||
|
||||
Window_Title_Visibility :: enum UInteger {
|
||||
Visible,
|
||||
Hidden,
|
||||
}
|
||||
|
||||
WindowDelegate :: struct { using _: Object } // This is not the same as NSWindowDelegate
|
||||
_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 {
|
||||
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")
|
||||
Window :: struct {using _: Responder}
|
||||
@@ -748,4 +756,28 @@ Window_hasTitleBar :: proc "c" (self: ^Window) -> BOOL {
|
||||
@(objc_type=Window, objc_name="orderedIndex")
|
||||
Window_orderedIndex :: proc "c" (self: ^Window) -> Integer {
|
||||
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)
|
||||
}
|
||||
@@ -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
|
||||
dirent_name :: proc "contextless" (dirent: ^Dirent) -> string #no_bounds_check {
|
||||
str := ([^]u8)(&dirent.name)
|
||||
// Note(flysand): The string size calculated above applies only to the ideal case
|
||||
// we subtract 1 byte from the string size, because a null terminator is guaranteed
|
||||
// to be present. But! That said, the dirents are aligned to 8 bytes and the padding
|
||||
// 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.
|
||||
str_size := int(dirent.reclen) - 1 - cast(int)offset_of(Dirent, name)
|
||||
// This skips *only* over the garbage, since if we're not garbage we're at nul terminator,
|
||||
// which skips this loop
|
||||
for str[str_size] != 0 {
|
||||
str_size -= 1
|
||||
// Dirents are aligned to 8 bytes, so there is guaranteed to be a null
|
||||
// terminator in the last 8 bytes.
|
||||
str_size := int(dirent.reclen) - cast(int)offset_of(Dirent, name)
|
||||
|
||||
trunc := min(str_size, 8)
|
||||
str_size -= trunc
|
||||
for _ in 0..<trunc {
|
||||
str_size += 1
|
||||
if str[str_size] == 0 {
|
||||
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])
|
||||
}
|
||||
|
||||
@@ -117,4 +113,4 @@ perf_cache_config :: #force_inline proc "contextless" (id: Perf_Hardware_Cache_I
|
||||
op: Perf_Hardware_Cache_Op_Id,
|
||||
res: Perf_Hardware_Cache_Result_Id) -> u64 {
|
||||
return u64(id) | (u64(op) << 8) | (u64(res) << 16)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,6 @@ foreign import "system:Dnsapi.lib"
|
||||
|
||||
@(default_calling_convention="system")
|
||||
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) ---
|
||||
}
|
||||
|
||||
@@ -123,6 +123,7 @@ foreign kernel32 {
|
||||
WaitCommEvent :: proc(handle: HANDLE, lpEvtMask: LPDWORD, lpOverlapped: LPOVERLAPPED) -> BOOL ---
|
||||
GetCommandLineW :: proc() -> LPCWSTR ---
|
||||
GetTempPathW :: proc(nBufferLength: DWORD, lpBuffer: LPCWSTR) -> DWORD ---
|
||||
GetTempFileNameW :: proc(lpPathName: LPCWSTR, lpPrefixString: LPCWSTR, uUnique: c_int, lpTempFileName: LPWSTR) -> c_uint ---
|
||||
GetCurrentProcess :: proc() -> HANDLE ---
|
||||
GetCurrentProcessId :: proc() -> DWORD ---
|
||||
GetCurrentThread :: proc() -> HANDLE ---
|
||||
@@ -1240,3 +1241,31 @@ GHND :: (GMEM_MOVEABLE | GMEM_ZEROINIT)
|
||||
GPTR :: (GMEM_FIXED | GMEM_ZEROINIT)
|
||||
|
||||
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 ---
|
||||
}
|
||||
|
||||
@@ -4576,6 +4576,31 @@ DNS_SRV_DATAA :: struct {
|
||||
_: 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 {
|
||||
sa_family: ADDRESS_FAMILY,
|
||||
sa_data: [14]CHAR,
|
||||
|
||||
@@ -682,11 +682,14 @@ find_aux :: proc(
|
||||
|
||||
// iterative matching which returns the 0th/1st match
|
||||
// rest has to be used from captures
|
||||
// assumes captures is zeroed on first iteration
|
||||
// resets captures to zero on last iteration
|
||||
gmatch :: proc(
|
||||
haystack: ^string,
|
||||
pattern: string,
|
||||
captures: ^[MAX_CAPTURES]Match,
|
||||
) -> (res: string, ok: bool) {
|
||||
haystack^ = haystack[captures[0].byte_end:]
|
||||
if len(haystack) > 0 {
|
||||
length, err := find_aux(haystack^, pattern, 0, false, captures)
|
||||
|
||||
@@ -695,10 +698,11 @@ gmatch :: proc(
|
||||
first := length > 1 ? 1 : 0
|
||||
cap := captures[first]
|
||||
res = haystack[cap.byte_start:cap.byte_end]
|
||||
haystack^ = haystack[cap.byte_end:]
|
||||
}
|
||||
}
|
||||
|
||||
if !ok {
|
||||
captures^ = {}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -794,11 +798,14 @@ gsub_with :: proc(
|
||||
gsub :: proc { gsub_builder, gsub_allocator }
|
||||
|
||||
// iterative find with zeroth capture only
|
||||
// assumes captures is zeroed on first iteration
|
||||
// resets captures to zero on last iteration
|
||||
gfind :: proc(
|
||||
haystack: ^string,
|
||||
pattern: string,
|
||||
captures: ^[MAX_CAPTURES]Match,
|
||||
) -> (res: string, ok: bool) {
|
||||
haystack^ = haystack[captures[0].byte_end:]
|
||||
if len(haystack) > 0 {
|
||||
length, err := find_aux(haystack^, pattern, 0, true, captures)
|
||||
|
||||
@@ -806,10 +813,11 @@ gfind :: proc(
|
||||
ok = true
|
||||
cap := captures[0]
|
||||
res = haystack[cap.byte_start:cap.byte_end]
|
||||
haystack^ = haystack[cap.byte_end:]
|
||||
}
|
||||
}
|
||||
|
||||
if !ok {
|
||||
captures^ = {}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
return i.time > j.time
|
||||
return i.time < j.time
|
||||
}
|
||||
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 {
|
||||
|
||||
+4
-1
@@ -251,7 +251,10 @@ gb_internal void big_int_from_string(BigInt *dst, String const &s, bool *success
|
||||
exp *= 10;
|
||||
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) {
|
||||
|
||||
+26
-4
@@ -472,6 +472,7 @@ struct BuildContext {
|
||||
bool ignore_microsoft_magic;
|
||||
bool linker_map_file;
|
||||
|
||||
bool use_single_module;
|
||||
bool use_separate_modules;
|
||||
bool module_per_file;
|
||||
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);
|
||||
|
||||
#if defined(GB_SYSTEM_WINDOWS)
|
||||
if (bc->optimization_level <= 0) {
|
||||
if (!is_arch_wasm()) {
|
||||
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.
|
||||
@@ -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) {
|
||||
switch (build_context.metrics.os) {
|
||||
case TargetOs_linux:
|
||||
@@ -2133,11 +2137,29 @@ gb_internal bool init_build_paths(String init_filename) {
|
||||
case TargetOs_openbsd:
|
||||
case TargetOs_netbsd:
|
||||
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");
|
||||
return false;
|
||||
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");
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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));
|
||||
}
|
||||
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;
|
||||
return nullptr;
|
||||
|
||||
+5
-5
@@ -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) {
|
||||
check_not_tuple(c, operand);
|
||||
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_QuoEq:
|
||||
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;
|
||||
} 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;
|
||||
}
|
||||
/*fallthrough*/
|
||||
@@ -2023,14 +2023,14 @@ gb_internal bool check_binary_op(CheckerContext *c, Operand *o, Token op) {
|
||||
case Token_ModEq:
|
||||
case Token_ModModEq:
|
||||
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;
|
||||
}
|
||||
if (!is_type_integer(type)) {
|
||||
error(op, "Operator '%.*s' is only allowed with integers", LIT(op.string));
|
||||
return false;
|
||||
} 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;
|
||||
}
|
||||
break;
|
||||
|
||||
+2
-1
@@ -685,7 +685,8 @@ gb_internal void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast *
|
||||
ST_ALIGN(min_field_align);
|
||||
ST_ALIGN(max_field_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)",
|
||||
cast(long long)struct_type->Struct.custom_align,
|
||||
cast(long long)struct_type->Struct.custom_min_field_align);
|
||||
|
||||
+18
-3
@@ -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?
|
||||
// Is >256 KiB good enough?
|
||||
if (sz > 1ll<<18) {
|
||||
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);
|
||||
bool is_ref = false;
|
||||
if((e->flags & EntityFlag_ForValue) != 0) {
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -5184,6 +5196,9 @@ gb_internal void check_add_foreign_import_decl(CheckerContext *ctx, Ast *decl) {
|
||||
if (ac.foreign_import_priority_index != 0) {
|
||||
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);
|
||||
if (extra_linker_flags.len != 0) {
|
||||
e->LibraryName.extra_linker_flags = extra_linker_flags;
|
||||
|
||||
@@ -140,6 +140,7 @@ struct AttributeContext {
|
||||
bool instrumentation_enter : 1;
|
||||
bool instrumentation_exit : 1;
|
||||
bool rodata : 1;
|
||||
bool ignore_duplicates : 1;
|
||||
u32 optimization_mode; // ProcedureOptimizationMode
|
||||
i64 foreign_import_priority_index;
|
||||
String extra_linker_flags;
|
||||
|
||||
@@ -274,6 +274,7 @@ struct Entity {
|
||||
Slice<String> paths;
|
||||
String name;
|
||||
i64 priority_index;
|
||||
bool ignore_duplicates;
|
||||
String extra_linker_flags;
|
||||
} LibraryName;
|
||||
i32 Nil;
|
||||
|
||||
+32
-1
@@ -449,6 +449,26 @@ gb_internal i32 linker_stage(LinkerData *gen) {
|
||||
if (extra_linker_flags.len != 0) {
|
||||
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) {
|
||||
lib = string_trim_whitespace(lib);
|
||||
if (lib.len == 0) {
|
||||
@@ -536,7 +556,18 @@ gb_internal i32 linker_stage(LinkerData *gen) {
|
||||
}
|
||||
array_add(&gen->output_object_paths, obj_file);
|
||||
} 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1147,14 +1147,14 @@ gb_internal void lb_finalize_objc_names(lbProcedure *p) {
|
||||
String name = entry.key;
|
||||
args[0] = lb_const_value(m, t_cstring, exact_value_string(name));
|
||||
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) {
|
||||
String name = entry.key;
|
||||
args[0] = lb_const_value(m, t_cstring, exact_value_string(name));
|
||||
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);
|
||||
|
||||
@@ -143,6 +143,11 @@ struct lbPadType {
|
||||
LLVMTypeRef type;
|
||||
};
|
||||
|
||||
struct lbObjcRef {
|
||||
Entity * entity;
|
||||
lbAddr local_module_addr;
|
||||
};
|
||||
|
||||
struct lbModule {
|
||||
LLVMModuleRef mod;
|
||||
LLVMContextRef ctx;
|
||||
@@ -196,8 +201,8 @@ struct lbModule {
|
||||
RecursiveMutex debug_values_mutex;
|
||||
PtrMap<void *, LLVMMetadataRef> debug_values;
|
||||
|
||||
StringMap<lbAddr> objc_classes;
|
||||
StringMap<lbAddr> objc_selectors;
|
||||
StringMap<lbObjcRef> objc_classes;
|
||||
StringMap<lbObjcRef> objc_selectors;
|
||||
|
||||
PtrMap<Type *, lbAddr> map_cell_info_map; // address of runtime.Map_Info
|
||||
PtrMap<Type *, lbAddr> map_info_map; // address of runtime.Map_Cell_Info
|
||||
|
||||
+11
-10
@@ -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) {
|
||||
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 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;
|
||||
|
||||
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) {
|
||||
case Type_Slice: {
|
||||
Type *slice_type = type;
|
||||
|
||||
@@ -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) {
|
||||
lbAddr *found = string_map_get(&p->module->objc_selectors, name);
|
||||
lbObjcRef *found = string_map_get(&p->module->objc_selectors, name);
|
||||
if (found) {
|
||||
return *found;
|
||||
} 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;
|
||||
return found->local_module_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) {
|
||||
@@ -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) {
|
||||
lbAddr *found = string_map_get(&p->module->objc_classes, name);
|
||||
lbObjcRef *found = string_map_get(&p->module->objc_classes, name);
|
||||
if (found) {
|
||||
return *found;
|
||||
} 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;
|
||||
return found->local_module_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) {
|
||||
@@ -2196,23 +2218,7 @@ gb_internal lbValue lb_handle_objc_id(lbProcedure *p, Ast *expr) {
|
||||
GB_ASSERT(e->kind == Entity_TypeName);
|
||||
String name = e->TypeName.objc_class_name;
|
||||
|
||||
lbAddr *found = string_map_get(&p->module->objc_classes, 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_addr_load(p, lb_handle_objc_find_or_register_class(p, name));
|
||||
}
|
||||
|
||||
return lb_build_expr(p, expr);
|
||||
|
||||
+27
-3
@@ -331,6 +331,7 @@ enum BuildFlagKind {
|
||||
BuildFlag_UseRADLink,
|
||||
BuildFlag_Linker,
|
||||
BuildFlag_UseSeparateModules,
|
||||
BuildFlag_UseSingleModule,
|
||||
BuildFlag_NoThreadedChecker,
|
||||
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_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_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_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:
|
||||
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;
|
||||
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:
|
||||
build_context.no_threaded_checker = true;
|
||||
break;
|
||||
@@ -1801,7 +1814,10 @@ gb_internal void check_defines(BuildContext *bc, Checker *c) {
|
||||
if (!found) {
|
||||
ERROR_BLOCK();
|
||||
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.");
|
||||
}
|
||||
|
||||
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>")) {
|
||||
print_usage_line(2, "Defines a scalar boolean, integer or string as global constant.");
|
||||
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 (print_flag("-use-separate-modules")) {
|
||||
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 on Windows for '-o:none' and '-o:minimal' builds.");
|
||||
print_usage_line(2, "This is the default behaviour 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
@@ -4773,7 +4773,9 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha
|
||||
|
||||
case Type_BitSet:
|
||||
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);
|
||||
} else {
|
||||
str = gb_string_append_fmt(str, "%lld", type->BitSet.lower);
|
||||
|
||||
@@ -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]))
|
||||
}
|
||||
Vendored
+1
-2
@@ -7,7 +7,6 @@
|
||||
package vendor_commonmark
|
||||
|
||||
import "core:c"
|
||||
import "core:c/libc"
|
||||
import "base:runtime"
|
||||
|
||||
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.
|
||||
|
||||
@(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) {
|
||||
|
||||
Vendored
+6
-6
@@ -1580,14 +1580,14 @@ SHADER_COMPONENT_MAPPING :: enum u32 {
|
||||
FORCE_VALUE_1 = 5,
|
||||
}
|
||||
ENCODE_SHADER_4_COMPONENT_MAPPING :: #force_inline proc "contextless" (Src0, Src1, Src2, Src3: u32) -> u32 {
|
||||
return (Src0 & SHADER_COMPONENT_MAPPING_MASK) |
|
||||
((Src1 & SHADER_COMPONENT_MAPPING_MASK) << SHADER_COMPONENT_MAPPING_SHIFT) |
|
||||
((Src2 & SHADER_COMPONENT_MAPPING_MASK) << (SHADER_COMPONENT_MAPPING_SHIFT * 2)) |
|
||||
((Src3 & SHADER_COMPONENT_MAPPING_MASK) << (SHADER_COMPONENT_MAPPING_SHIFT * 3)) |
|
||||
SHADER_COMPONENT_MAPPING_ALWAYS_SET_BIT_AVOIDING_ZEROMEM_MISTAKES
|
||||
return (Src0 & SHADER_COMPONENT_MAPPING_MASK) |
|
||||
((Src1 & SHADER_COMPONENT_MAPPING_MASK) << SHADER_COMPONENT_MAPPING_SHIFT) |
|
||||
((Src2 & SHADER_COMPONENT_MAPPING_MASK) << (SHADER_COMPONENT_MAPPING_SHIFT * 2)) |
|
||||
((Src3 & SHADER_COMPONENT_MAPPING_MASK) << (SHADER_COMPONENT_MAPPING_SHIFT * 3)) |
|
||||
SHADER_COMPONENT_MAPPING_ALWAYS_SET_BIT_AVOIDING_ZEROMEM_MISTAKES
|
||||
}
|
||||
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]
|
||||
|
||||
Vendored
+1
@@ -71,6 +71,7 @@ foreign glfw {
|
||||
GetPrimaryMonitor :: proc() -> MonitorHandle ---
|
||||
GetMonitors :: proc(count: ^c.int) -> [^]MonitorHandle ---
|
||||
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) ---
|
||||
GetMonitorContentScale :: proc(monitor: MonitorHandle, xscale, yscale: ^f32) ---
|
||||
|
||||
|
||||
Vendored
+4
@@ -33,6 +33,10 @@ GetMonitorPos :: proc "c" (monitor: MonitorHandle) -> (xpos, ypos: c.int) {
|
||||
glfw.GetMonitorPos(monitor, &xpos, &ypos)
|
||||
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) {
|
||||
glfw.GetMonitorPhysicalSize(monitor, &widthMM, &heightMM)
|
||||
return
|
||||
|
||||
+8
-5
@@ -351,8 +351,11 @@ device_id :: struct #raw_union {
|
||||
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
|
||||
|
||||
@@ -364,10 +367,10 @@ device_info :: struct {
|
||||
|
||||
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. */
|
||||
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. */
|
||||
sampleRate: u32, /* If set to 0, all sample rates are supported. */
|
||||
flags: u32, /* A combination of MA_DATA_FORMAT_FLAG_* flags. */
|
||||
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. */
|
||||
sampleRate: u32, /* If set to 0, all sample rates are supported. */
|
||||
flags: data_format_flags, /* A combination of MA_DATA_FORMAT_FLAG_* flags. */
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Vendored
+31
-29
@@ -11,20 +11,22 @@ Engine
|
||||
************************************************************************************************************************************************************/
|
||||
|
||||
/* Sound flags. */
|
||||
sound_flags :: enum c.int {
|
||||
sound_flag :: enum c.int {
|
||||
/* Resource manager flags. */
|
||||
STREAM = 0x00000001, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM */
|
||||
DECODE = 0x00000002, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE */
|
||||
ASYNC = 0x00000004, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC */
|
||||
WAIT_INIT = 0x00000008, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT */
|
||||
UNKNOWN_LENGTH = 0x00000010, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH */
|
||||
STREAM = 0, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM */
|
||||
DECODE = 1, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE */
|
||||
ASYNC = 2, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC */
|
||||
WAIT_INIT = 3, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT */
|
||||
UNKNOWN_LENGTH = 4, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH */
|
||||
|
||||
/* 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_PITCH = 0x00002000, /* Disable pitch shifting with ma_sound_set_pitch() and ma_sound_group_set_pitch(). This is an optimization. */
|
||||
NO_SPATIALIZATION = 0x00004000, /* Disable spatialization. */
|
||||
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 = 13, /* Disable pitch shifting with ma_sound_set_pitch() and ma_sound_group_set_pitch(). This is an optimization. */
|
||||
NO_SPATIALIZATION = 14, /* Disable spatialization. */
|
||||
}
|
||||
|
||||
sound_flags :: bit_set[sound_flag; u32]
|
||||
|
||||
ENGINE_MAX_LISTENERS :: 4
|
||||
|
||||
LISTENER_INDEX_CLOSEST :: 255
|
||||
@@ -81,7 +83,7 @@ engine_node :: struct {
|
||||
|
||||
@(default_calling_convention="c", link_prefix="ma_")
|
||||
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_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_config :: struct {
|
||||
pFilePath: cstring, /* 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. */
|
||||
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. */
|
||||
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). */
|
||||
pFilePath: cstring, /* 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. */
|
||||
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. */
|
||||
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). */
|
||||
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. */
|
||||
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. */
|
||||
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. */
|
||||
initialSeekPointInPCMFrames: u64, /* Initializes the sound such that it's seeked to this location by default. */
|
||||
rangeBegInPCMFrames: u64,
|
||||
rangeEndInPCMFrames: u64,
|
||||
loopPointBegInPCMFrames: u64,
|
||||
@@ -148,14 +150,14 @@ sound_inlined :: struct {
|
||||
|
||||
@(default_calling_convention="c", link_prefix="ma_")
|
||||
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_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_w :: proc(pEngine: ^engine, pFilePath: [^]c.wchar_t, flags: u32, 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_from_data_source :: proc(pEngine: ^engine, pDataSource: ^data_source, flags: u32, pGroup: ^sound_group, 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: sound_flags, pGroup: ^sound_group, pDoneFence: ^fence, 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: sound_flags, pGroup: ^sound_group, pSound: ^sound) -> result ---
|
||||
sound_init_ex :: proc(pEngine: ^engine, pConfig: ^sound_config, pSound: ^sound) -> result ---
|
||||
sound_uninit :: proc(pSound: ^sound) ---
|
||||
sound_get_engine :: proc(pSound: ^sound) -> ^engine ---
|
||||
@@ -239,11 +241,11 @@ sound_group :: distinct sound
|
||||
|
||||
@(default_calling_convention="c", link_prefix="ma_")
|
||||
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_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_uninit :: proc(pGroup: ^sound_group) ---
|
||||
sound_group_get_engine :: proc(pGroup: ^sound_group) -> ^engine ---
|
||||
|
||||
Vendored
+8
-6
@@ -108,7 +108,7 @@ job :: struct {
|
||||
pDataBufferNode: rawptr /*ma_resource_manager_data_buffer_node**/,
|
||||
pFilePath: cstring,
|
||||
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. */
|
||||
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. */
|
||||
@@ -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.
|
||||
*/
|
||||
job_queue_flags :: enum c.int {
|
||||
NON_BLOCKING = 0x00000001,
|
||||
job_queue_flag :: enum c.int {
|
||||
NON_BLOCKING = 0,
|
||||
}
|
||||
|
||||
job_queue_flags :: bit_set[job_queue_flag; u32]
|
||||
|
||||
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. */
|
||||
}
|
||||
|
||||
USE_EXPERIMENTAL_LOCK_FREE_JOB_QUEUE :: false
|
||||
|
||||
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. */
|
||||
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. */
|
||||
@@ -222,7 +224,7 @@ job_queue :: struct {
|
||||
|
||||
@(default_calling_convention="c", link_prefix="ma_")
|
||||
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_init_preallocated :: proc(pConfig: ^job_queue_config, pHeap: rawptr, pQueue: ^job_queue) -> result ---
|
||||
|
||||
Vendored
+16
-8
@@ -22,14 +22,16 @@ NODE_BUS_COUNT_UNKNOWN :: 255
|
||||
node :: struct {}
|
||||
|
||||
/* Node flags. */
|
||||
node_flags :: enum c.int {
|
||||
PASSTHROUGH = 0x00000001,
|
||||
CONTINUOUS_PROCESSING = 0x00000002,
|
||||
ALLOW_NULL_INPUT = 0x00000004,
|
||||
DIFFERENT_PROCESSING_RATES = 0x00000008,
|
||||
SILENT_OUTPUT = 0x00000010,
|
||||
node_flag :: enum c.int {
|
||||
PASSTHROUGH = 0,
|
||||
CONTINUOUS_PROCESSING = 1,
|
||||
ALLOW_NULL_INPUT = 2,
|
||||
DIFFERENT_PROCESSING_RATES = 3,
|
||||
SILENT_OUTPUT = 4,
|
||||
}
|
||||
|
||||
node_flags :: bit_set[node_flag; u32]
|
||||
|
||||
/* The playback state of a node. Either started or stopped. */
|
||||
node_state :: enum c.int {
|
||||
started = 0,
|
||||
@@ -75,7 +77,7 @@ node_vtable :: struct {
|
||||
Flags describing characteristics of the node. This is currently just a placeholder for some
|
||||
ideas for later on.
|
||||
*/
|
||||
flags: u32,
|
||||
flags: node_flags,
|
||||
}
|
||||
|
||||
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`. */
|
||||
}
|
||||
|
||||
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
|
||||
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. */
|
||||
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. */
|
||||
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. */
|
||||
|
||||
+14
-10
@@ -10,14 +10,16 @@ Resource Manager
|
||||
|
||||
************************************************************************************************************************************************************/
|
||||
|
||||
resource_manager_data_source_flags :: enum c.int {
|
||||
STREAM = 0x00000001, /* 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. */
|
||||
ASYNC = 0x00000004, /* 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(). */
|
||||
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. */
|
||||
resource_manager_data_source_flag :: enum c.int {
|
||||
STREAM = 0, /* When set, does not load the entire data source in memory. Disk I/O will happen on job threads. */
|
||||
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 = 2, /* When set, the resource manager will load the data source asynchronously. */
|
||||
WAIT_INIT = 3, /* When set, waits for initialization of the underlying data source before returning from ma_resource_manager_data_source_init(). */
|
||||
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.
|
||||
*/
|
||||
@@ -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. */
|
||||
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. */
|
||||
NON_BLOCKING = 0x00000001,
|
||||
NON_BLOCKING = 0,
|
||||
|
||||
/* 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 {
|
||||
pFilePath: cstring,
|
||||
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. */
|
||||
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. */
|
||||
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. */
|
||||
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. */
|
||||
|
||||
Vendored
+7
-2
@@ -119,7 +119,12 @@ offset_pcm_frames_const_ptr_f32 :: #force_inline proc "c" (p: [^]f32, offsetInFr
|
||||
|
||||
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 {
|
||||
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,
|
||||
onGetLength: proc "c" (pDataSource: ^data_source, pLength: ^u64) -> 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
|
||||
|
||||
Vendored
+9
-7
@@ -16,11 +16,13 @@ appropriate for a given situation.
|
||||
vfs :: struct {}
|
||||
vfs_file :: distinct handle
|
||||
|
||||
open_mode_flags :: enum c.int {
|
||||
READ = 0x00000001,
|
||||
WRITE = 0x00000002,
|
||||
open_mode_flag :: enum c.int {
|
||||
READ = 0,
|
||||
WRITE = 1,
|
||||
}
|
||||
|
||||
open_mode_flags :: bit_set[open_mode_flag; u32]
|
||||
|
||||
seek_origin :: enum c.int {
|
||||
start,
|
||||
current,
|
||||
@@ -32,8 +34,8 @@ file_info :: struct {
|
||||
}
|
||||
|
||||
vfs_callbacks :: struct {
|
||||
onOpen: proc "c" (pVFS: ^vfs, pFilePath: cstring, openMode: u32, pFile: ^vfs_file) -> result,
|
||||
onOpenW: proc "c" (pVFS: ^vfs, pFilePath: [^]c.wchar_t, 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: open_mode_flags, pFile: ^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,
|
||||
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_")
|
||||
foreign lib {
|
||||
vfs_open :: proc(pVFS: ^vfs, pFilePath: cstring, openMode: u32, pFile: ^vfs_file) -> result ---
|
||||
vfs_open_w :: proc(pVFS: ^vfs, pFilePath: [^]c.wchar_t, 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: open_mode_flags, pFile: ^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_write :: proc(pVFS: ^vfs, file: vfs_file, pSrc: rawptr, sizeInBytes: c.size_t, pBytesWritten: ^c.size_t) -> result ---
|
||||
|
||||
Vendored
+2
-2
@@ -1205,7 +1205,7 @@ foreign lib {
|
||||
|
||||
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
|
||||
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
|
||||
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)
|
||||
@@ -1256,7 +1256,7 @@ foreign lib {
|
||||
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
|
||||
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
|
||||
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!)
|
||||
|
||||
Vendored
+2
@@ -26,8 +26,10 @@ import "core:c"
|
||||
import "base:intrinsics"
|
||||
|
||||
when ODIN_OS == .Windows {
|
||||
@(ignore_duplicates)
|
||||
foreign import lib "SDL2.lib"
|
||||
} else {
|
||||
@(ignore_duplicates)
|
||||
foreign import lib "system:SDL2"
|
||||
}
|
||||
|
||||
|
||||
Vendored
+2
@@ -3,8 +3,10 @@ package sdl2
|
||||
import "core:c"
|
||||
|
||||
when ODIN_OS == .Windows {
|
||||
@(ignore_duplicates)
|
||||
foreign import lib "SDL2.lib"
|
||||
} else {
|
||||
@(ignore_duplicates)
|
||||
foreign import lib "system:SDL2"
|
||||
}
|
||||
|
||||
|
||||
Vendored
+2
@@ -3,8 +3,10 @@ package sdl2
|
||||
import "core:c"
|
||||
|
||||
when ODIN_OS == .Windows {
|
||||
@(ignore_duplicates)
|
||||
foreign import lib "SDL2.lib"
|
||||
} else {
|
||||
@(ignore_duplicates)
|
||||
foreign import lib "system:SDL2"
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user