diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e547959aa..41f548119 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,8 @@ jobs: ./odin test tests/core/speed.odin -file -all-packages -vet -strict-style -disallow-do -o:speed -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true (cd tests/issues; ./run.sh) + ./odin check tests/benchmark -vet -strict-style -no-entry-point + build_freebsd: name: FreeBSD Build, Check, and Test runs-on: ubuntu-latest @@ -64,6 +66,7 @@ jobs: ./odin test tests/core/speed.odin -file -all-packages -vet -strict-style -disallow-do -o:speed -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true (cd tests/issues; ./run.sh) + ./odin check tests/benchmark -vet -strict-style -no-entry-point ci: strategy: fail-fast: false @@ -128,6 +131,8 @@ jobs: cd tests/issues ./run.sh + - name: Check benchmarks + run: ./odin check tests/benchmark -vet -strict-style -no-entry-point - name: Odin check examples/all for Linux i386 run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_i386 if: matrix.os == 'ubuntu-latest' @@ -203,6 +208,11 @@ jobs: run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true + - name: Check benchmarks + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat + odin check tests/benchmark -vet -strict-style -no-entry-point - name: Odin documentation tests shell: cmd run: | @@ -257,16 +267,16 @@ jobs: run: sudo apt-get install -y qemu-user qemu-user-static gcc-12-riscv64-linux-gnu libc6-riscv64-cross - name: Odin run - run: ./odin run examples/demo -vet -strict-style -disallow-do -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" + run: ./odin run examples/demo -vet -strict-style -disallow-do -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath - name: Odin run -debug - run: ./odin run examples/demo -debug -vet -strict-style -disallow-do -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" + run: ./odin run examples/demo -debug -vet -strict-style -disallow-do -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath - name: Normal Core library tests - run: ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" + run: ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath - name: Optimized Core library tests - run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" + run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath - name: Internals tests - run: ./odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" + run: ./odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 0c5526d0f..314711efb 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -36,43 +36,55 @@ jobs: cp -r bin dist cp -r examples dist - name: Upload artifact - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 with: + include-hidden-files: true name: windows_artifacts path: dist - build_ubuntu: - name: Ubuntu Build + build_linux: + name: Linux Build if: github.repository == 'odin-lang/Odin' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: jirutka/setup-alpine@v1 + with: + branch: v3.20 - name: (Linux) Download LLVM run: | - wget https://apt.llvm.org/llvm.sh - chmod +x llvm.sh - sudo ./llvm.sh 18 - echo "/usr/lib/llvm-18/bin" >> $GITHUB_PATH + apk add --no-cache \ + musl-dev llvm18-dev clang18 git mold lz4 \ + libxml2-static llvm18-static zlib-static zstd-static \ + make + shell: alpine.sh --root {0} - name: build odin - run: make nightly + # NOTE: this build does slow compile times because of musl + run: ci/build_linux_static.sh + shell: alpine.sh {0} - name: Odin run run: ./odin run examples/demo - name: Copy artifacts run: | - mkdir dist - cp odin dist - cp LICENSE dist - cp -r shared dist - cp -r base dist - cp -r core dist - cp -r vendor dist - cp -r examples dist - # Zipping so executable permissions are retained, see https://github.com/actions/upload-artifact/issues/38 - zip -r dist.zip dist + FILE="odin-linux-amd64-nightly+$(date -I)" + mkdir $FILE + cp odin $FILE + cp LICENSE $FILE + cp -r shared $FILE + cp -r base $FILE + cp -r core $FILE + cp -r vendor $FILE + cp -r examples $FILE + # Creating a tarball so executable permissions are retained, see https://github.com/actions/upload-artifact/issues/38 + tar -czvf dist.tar.gz $FILE + - name: Odin run + run: | + FILE="odin-linux-amd64-nightly+$(date -I)" + $FILE/odin run examples/demo - name: Upload artifact - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 with: - name: ubuntu_artifacts - path: dist.zip + name: linux_artifacts + path: dist.tar.gz build_macos: name: MacOS Build if: github.repository == 'odin-lang/Odin' @@ -89,24 +101,27 @@ jobs: run: CXXFLAGS="-L/usr/lib/system -L/usr/lib" make nightly - name: Bundle run: | - mkdir dist - cp odin dist - cp LICENSE dist - cp -r shared dist - cp -r base dist - cp -r core dist - cp -r vendor dist - cp -r examples dist - dylibbundler -b -x dist/odin -d dist/libs -od -p @executable_path/libs - # Zipping so executable permissions are retained, see https://github.com/actions/upload-artifact/issues/38 - zip -r dist.zip dist + FILE="odin-macos-amd64-nightly+$(date -I)" + mkdir $FILE + cp odin $FILE + cp LICENSE $FILE + cp -r shared $FILE + cp -r base $FILE + cp -r core $FILE + cp -r vendor $FILE + cp -r examples $FILE + dylibbundler -b -x $FILE/odin -d $FILE/libs -od -p @executable_path/libs + # Creating a tarball so executable permissions are retained, see https://github.com/actions/upload-artifact/issues/38 + tar -czvf dist.tar.gz $FILE - name: Odin run - run: ./dist/odin run examples/demo + run: | + FILE="odin-macos-amd64-nightly+$(date -I)" + $FILE/odin run examples/demo - name: Upload artifact - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 with: name: macos_artifacts - path: dist.zip + path: dist.tar.gz build_macos_arm: name: MacOS ARM Build if: github.repository == 'odin-lang/Odin' @@ -123,30 +138,33 @@ jobs: run: CXXFLAGS="-L/usr/lib/system -L/usr/lib" make nightly - name: Bundle run: | - mkdir dist - cp odin dist - cp LICENSE dist - cp -r shared dist - cp -r base dist - cp -r core dist - cp -r vendor dist - cp -r examples dist - dylibbundler -b -x dist/odin -d dist/libs -od -p @executable_path/libs - # Zipping so executable permissions are retained, see https://github.com/actions/upload-artifact/issues/38 - zip -r dist.zip dist + FILE="odin-macos-arm64-nightly+$(date -I)" + mkdir $FILE + cp odin $FILE + cp LICENSE $FILE + cp -r shared $FILE + cp -r base $FILE + cp -r core $FILE + cp -r vendor $FILE + cp -r examples $FILE + dylibbundler -b -x $FILE/odin -d $FILE/libs -od -p @executable_path/libs + # Creating a tarball so executable permissions are retained, see https://github.com/actions/upload-artifact/issues/38 + tar -czvf dist.tar.gz $FILE - name: Odin run - run: ./dist/odin run examples/demo + run: | + FILE="odin-macos-arm64-nightly+$(date -I)" + $FILE/odin run examples/demo - name: Upload artifact - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 with: name: macos_arm_artifacts - path: dist.zip + path: dist.tar.gz upload_b2: runs-on: [ubuntu-latest] - needs: [build_windows, build_macos, build_macos_arm, build_ubuntu] + needs: [build_windows, build_macos, build_macos_arm, build_linux] steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v5 with: python-version: '3.8.x' @@ -160,24 +178,33 @@ jobs: run: python -c "import sys; print(sys.version)" - name: Download Windows artifacts - uses: actions/download-artifact@v1 + + uses: actions/download-artifact@v4.1.7 with: name: windows_artifacts + path: windows_artifacts - name: Download Ubuntu artifacts - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4.1.7 with: - name: ubuntu_artifacts + name: linux_artifacts + path: linux_artifacts - name: Download macOS artifacts - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4.1.7 with: name: macos_artifacts + path: macos_artifacts - name: Download macOS arm artifacts - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4.1.7 with: name: macos_arm_artifacts + path: macos_arm_artifacts + + - name: Debug + run: | + tree -L 2 - name: Create archives and upload shell: bash @@ -187,9 +214,10 @@ jobs: BUCKET: ${{ secrets.B2_BUCKET }} DAYS_TO_KEEP: ${{ secrets.B2_DAYS_TO_KEEP }} run: | + file linux_artifacts/dist.tar.gz python3 ci/nightly.py artifact windows-amd64 windows_artifacts/ - python3 ci/nightly.py artifact ubuntu-amd64 ubuntu_artifacts/dist.zip - python3 ci/nightly.py artifact macos-amd64 macos_artifacts/dist.zip - python3 ci/nightly.py artifact macos-arm64 macos_arm_artifacts/dist.zip + python3 ci/nightly.py artifact linux-amd64 linux_artifacts/dist.tar.gz + python3 ci/nightly.py artifact macos-amd64 macos_artifacts/dist.tar.gz + python3 ci/nightly.py artifact macos-arm64 macos_arm_artifacts/dist.tar.gz python3 ci/nightly.py prune python3 ci/nightly.py json diff --git a/.gitignore b/.gitignore index bfd5abeed..75bb61c43 100644 --- a/.gitignore +++ b/.gitignore @@ -266,6 +266,9 @@ bin/ *.exe *.obj *.pdb +*.res +desktop.ini +Thumbs.db # - Linux/MacOS odin diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin index 3cf99bbd2..744a899c0 100644 --- a/base/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -1,5 +1,5 @@ // This is purely for documentation -//+build ignore +#+build ignore package intrinsics // Package-Related diff --git a/base/runtime/core.odin b/base/runtime/core.odin index 90e049b0c..a5a3a4d8c 100644 --- a/base/runtime/core.odin +++ b/base/runtime/core.odin @@ -18,7 +18,7 @@ // This could change at a later date if the all these data structures are // implemented within the compiler rather than in this "preload" file // -//+no-instrumentation +#+no-instrumentation package runtime import "base:intrinsics" diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 8157afe09..3dad2fdbc 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -6,6 +6,39 @@ import "base:intrinsics" Maybe :: union($T: typeid) {T} +/* +Recovers the containing/parent struct from a pointer to one of its fields. +Works by "walking back" to the struct's starting address using the offset between the field and the struct. + +Inputs: +- ptr: Pointer to the field of a container struct +- T: The type of the container struct +- field_name: The name of the field in the `T` struct + +Returns: +- A pointer to the container struct based on a pointer to a field in it + +Example: + package container_of + import "base:runtime" + + Node :: struct { + value: int, + prev: ^Node, + next: ^Node, + } + + main :: proc() { + node: Node + field_ptr := &node.next + container_struct_ptr: ^Node = runtime.container_of(field_ptr, Node, "next") + assert(container_struct_ptr == &node) + assert(uintptr(field_ptr) - uintptr(container_struct_ptr) == size_of(node.value) + size_of(node.prev)) + } + +Output: + ^Node +*/ @(builtin, require_results) container_of :: #force_inline proc "contextless" (ptr: $P/^$Field_Type, $T: typeid, $field_name: string) -> ^T where intrinsics.type_has_field(T, field_name), @@ -350,12 +383,23 @@ _make_dynamic_array_len_cap :: proc(array: ^Raw_Dynamic_Array, size_of_elem, ali return } -// `make_map` allocates and initializes a map. Like `new`, the first argument is a type, not a value. +// `make_map` initializes a map with an allocator. Like `new`, the first argument is a type, not a value. // Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. // // Note: Prefer using the procedure group `make`. @(builtin, require_results) -make_map :: proc($T: typeid/map[$K]$E, #any_int capacity: int = 1< (m: T, err: Allocator_Error) #optional_allocator_error { +make_map :: proc($T: typeid/map[$K]$E, allocator := context.allocator) -> (m: T) { + m.allocator = allocator + return m +} + +// `make_map_cap` initializes a map with an allocator and allocates space using `capacity`. +// Like `new`, the first argument is a type, not a value. +// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. +// +// Note: Prefer using the procedure group `make`. +@(builtin, require_results) +make_map_cap :: proc($T: typeid/map[$K]$E, #any_int capacity: int = 1< (m: T, err: Allocator_Error) #optional_allocator_error { make_map_expr_error_loc(loc, capacity) context.allocator = allocator @@ -392,6 +436,7 @@ make :: proc{ make_dynamic_array_len, make_dynamic_array_len_cap, make_map, + make_map_cap, make_multi_pointer, make_soa_slice, @@ -913,7 +958,7 @@ card :: proc "contextless" (s: $S/bit_set[$E; $U]) -> int { @builtin @(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 { // NOTE(bill): This is wrapped in a procedure call // to improve performance to make the CPU not @@ -952,7 +997,7 @@ unimplemented :: proc(message := "", loc := #caller_location) -> ! { @builtin @(disabled=ODIN_DISABLE_ASSERT) -assert_contextless :: proc "contextless" (condition: bool, message := "", loc := #caller_location) { +assert_contextless :: proc "contextless" (condition: bool, message := #caller_expression(condition), loc := #caller_location) { if !condition { // NOTE(bill): This is wrapped in a procedure call // to improve performance to make the CPU not diff --git a/base/runtime/entry_unix.odin b/base/runtime/entry_unix.odin index 5dfd37f99..e2223d5d6 100644 --- a/base/runtime/entry_unix.odin +++ b/base/runtime/entry_unix.odin @@ -1,6 +1,6 @@ -//+private -//+build linux, darwin, freebsd, openbsd, netbsd, haiku -//+no-instrumentation +#+private +#+build linux, darwin, freebsd, openbsd, netbsd, haiku +#+no-instrumentation package runtime import "base:intrinsics" diff --git a/base/runtime/entry_wasm.odin b/base/runtime/entry_wasm.odin index 99cd8201d..52bb0e072 100644 --- a/base/runtime/entry_wasm.odin +++ b/base/runtime/entry_wasm.odin @@ -1,6 +1,6 @@ -//+private -//+build wasm32, wasm64p32 -//+no-instrumentation +#+private +#+build wasm32, wasm64p32 +#+no-instrumentation package runtime import "base:intrinsics" diff --git a/base/runtime/entry_windows.odin b/base/runtime/entry_windows.odin index 1e2dcc21a..8eb4cc7d1 100644 --- a/base/runtime/entry_windows.odin +++ b/base/runtime/entry_windows.odin @@ -1,6 +1,6 @@ -//+private -//+build windows -//+no-instrumentation +#+private +#+build windows +#+no-instrumentation package runtime import "base:intrinsics" diff --git a/base/runtime/heap_allocator.odin b/base/runtime/heap_allocator.odin index a0a984f10..4b04dffef 100644 --- a/base/runtime/heap_allocator.odin +++ b/base/runtime/heap_allocator.odin @@ -20,25 +20,27 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, // aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr, old_size: int, zero_memory := true) -> ([]byte, Allocator_Error) { + // Not(flysand): We need to reserve enough space for alignment, which + // includes the user data itself, the space to store the pointer to + // allocation start, as well as the padding required to align both + // the user data and the pointer. a := max(alignment, align_of(rawptr)) - space := size + a - 1 - + space := a-1 + size_of(rawptr) + size allocated_mem: rawptr - force_copy := old_ptr != nil && a > align_of(rawptr) + force_copy := old_ptr != nil && alignment > align_of(rawptr) - if !force_copy && old_ptr != nil { + if old_ptr != nil && !force_copy { original_old_ptr := ([^]rawptr)(old_ptr)[-1] - allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr)) + allocated_mem = heap_resize(original_old_ptr, space) } else { - allocated_mem = heap_alloc(space+size_of(rawptr), zero_memory) + allocated_mem = heap_alloc(space, zero_memory) } aligned_mem := rawptr(([^]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 { + aligned_ptr := (ptr + uintptr(a)-1) & ~(uintptr(a)-1) + if allocated_mem == nil { aligned_free(old_ptr) aligned_free(allocated_mem) return nil, .Out_Of_Memory @@ -48,7 +50,7 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, ([^]rawptr)(aligned_mem)[-1] = allocated_mem if force_copy { - mem_copy_non_overlapping(aligned_mem, old_ptr, old_size) + mem_copy_non_overlapping(aligned_mem, old_ptr, min(old_size, size)) aligned_free(old_ptr) } diff --git a/base/runtime/heap_allocator_orca.odin b/base/runtime/heap_allocator_orca.odin index 9e719bcd0..be032a207 100644 --- a/base/runtime/heap_allocator_orca.odin +++ b/base/runtime/heap_allocator_orca.odin @@ -1,5 +1,5 @@ -//+build orca -//+private +#+build orca +#+private package runtime foreign { diff --git a/base/runtime/heap_allocator_other.odin b/base/runtime/heap_allocator_other.odin index 8a7ad1a47..507dbf318 100644 --- a/base/runtime/heap_allocator_other.odin +++ b/base/runtime/heap_allocator_other.odin @@ -1,5 +1,5 @@ -//+build js, wasi, freestanding, essence -//+private +#+build js, wasi, freestanding, essence +#+private package runtime _heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr { diff --git a/base/runtime/heap_allocator_unix.odin b/base/runtime/heap_allocator_unix.odin index 60af9e761..d4590d2dd 100644 --- a/base/runtime/heap_allocator_unix.odin +++ b/base/runtime/heap_allocator_unix.odin @@ -1,5 +1,5 @@ -//+build linux, darwin, freebsd, openbsd, netbsd, haiku -//+private +#+build linux, darwin, freebsd, openbsd, netbsd, haiku +#+private package runtime when ODIN_OS == .Darwin { diff --git a/base/runtime/internal.odin b/base/runtime/internal.odin index ff60cf547..59811a525 100644 --- a/base/runtime/internal.odin +++ b/base/runtime/internal.odin @@ -1,4 +1,4 @@ -//+vet !cast +#+vet !cast package runtime import "base:intrinsics" @@ -118,16 +118,15 @@ mem_copy_non_overlapping :: proc "contextless" (dst, src: rawptr, len: int) -> r DEFAULT_ALIGNMENT :: 2*align_of(rawptr) mem_alloc_bytes :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { - if size == 0 { - return nil, nil - } - if allocator.procedure == nil { + assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc) + if size == 0 || allocator.procedure == nil{ return nil, nil } return allocator.procedure(allocator.data, .Alloc, size, alignment, nil, 0, loc) } mem_alloc :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { + assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc) if size == 0 || allocator.procedure == nil { return nil, nil } @@ -135,6 +134,7 @@ mem_alloc :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, a } mem_alloc_non_zeroed :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { + assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc) if size == 0 || allocator.procedure == nil { return nil, nil } @@ -174,6 +174,7 @@ mem_free_all :: #force_inline proc(allocator := context.allocator, loc := #calle } _mem_resize :: #force_inline proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, should_zero: bool, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { + assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc) if allocator.procedure == nil { return nil, nil } @@ -215,9 +216,11 @@ _mem_resize :: #force_inline proc(ptr: rawptr, old_size, new_size: int, alignmen } mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { + assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc) return _mem_resize(ptr, old_size, new_size, alignment, allocator, true, loc) } non_zero_mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { + assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc) return _mem_resize(ptr, old_size, new_size, alignment, allocator, false, loc) } diff --git a/base/runtime/os_specific_bsd.odin b/base/runtime/os_specific_bsd.odin index 46ce51166..5d198484b 100644 --- a/base/runtime/os_specific_bsd.odin +++ b/base/runtime/os_specific_bsd.odin @@ -1,5 +1,5 @@ -//+build freebsd, openbsd, netbsd -//+private +#+build freebsd, openbsd, netbsd +#+private package runtime foreign import libc "system:c" diff --git a/base/runtime/os_specific_darwin.odin b/base/runtime/os_specific_darwin.odin index 1bf29e785..907899d7c 100644 --- a/base/runtime/os_specific_darwin.odin +++ b/base/runtime/os_specific_darwin.odin @@ -1,5 +1,5 @@ -//+build darwin -//+private +#+build darwin +#+private package runtime import "base:intrinsics" diff --git a/base/runtime/os_specific_freestanding.odin b/base/runtime/os_specific_freestanding.odin index 08ca4aa55..f975f61b8 100644 --- a/base/runtime/os_specific_freestanding.odin +++ b/base/runtime/os_specific_freestanding.odin @@ -1,5 +1,5 @@ -//+build freestanding -//+private +#+build freestanding +#+private package runtime // TODO(bill): reimplement `os.write` diff --git a/base/runtime/os_specific_haiku.odin b/base/runtime/os_specific_haiku.odin index f8dafac3d..0d1ec7a03 100644 --- a/base/runtime/os_specific_haiku.odin +++ b/base/runtime/os_specific_haiku.odin @@ -1,5 +1,5 @@ -//+build haiku -//+private +#+build haiku +#+private package runtime foreign import libc "system:c" diff --git a/base/runtime/os_specific_js.odin b/base/runtime/os_specific_js.odin index d35753604..f1d12c808 100644 --- a/base/runtime/os_specific_js.odin +++ b/base/runtime/os_specific_js.odin @@ -1,5 +1,5 @@ -//+build js -//+private +#+build js +#+private package runtime foreign import "odin_env" diff --git a/base/runtime/os_specific_linux.odin b/base/runtime/os_specific_linux.odin index 146e647fb..d7b7371a7 100644 --- a/base/runtime/os_specific_linux.odin +++ b/base/runtime/os_specific_linux.odin @@ -1,4 +1,4 @@ -//+private +#+private package runtime import "base:intrinsics" diff --git a/base/runtime/os_specific_orca.odin b/base/runtime/os_specific_orca.odin index b6f5930ab..876d7d44b 100644 --- a/base/runtime/os_specific_orca.odin +++ b/base/runtime/os_specific_orca.odin @@ -1,5 +1,5 @@ -//+build orca -//+private +#+build orca +#+private package runtime import "base:intrinsics" diff --git a/base/runtime/os_specific_wasi.odin b/base/runtime/os_specific_wasi.odin index b85d7adea..aa562050c 100644 --- a/base/runtime/os_specific_wasi.odin +++ b/base/runtime/os_specific_wasi.odin @@ -1,5 +1,5 @@ -//+build wasi -//+private +#+build wasi +#+private package runtime foreign import wasi "wasi_snapshot_preview1" diff --git a/base/runtime/os_specific_windows.odin b/base/runtime/os_specific_windows.odin index 6da569aee..b966193ca 100644 --- a/base/runtime/os_specific_windows.odin +++ b/base/runtime/os_specific_windows.odin @@ -1,5 +1,5 @@ -//+build windows -//+private +#+build windows +#+private package runtime foreign import kernel32 "system:Kernel32.lib" diff --git a/base/runtime/procs_darwin.odin b/base/runtime/procs_darwin.odin index 497978a76..4f4903d47 100644 --- a/base/runtime/procs_darwin.odin +++ b/base/runtime/procs_darwin.odin @@ -1,4 +1,4 @@ -//+private +#+private package runtime foreign import "system:Foundation.framework" diff --git a/base/runtime/procs_js.odin b/base/runtime/procs_js.odin index d3e12410c..58bed808d 100644 --- a/base/runtime/procs_js.odin +++ b/base/runtime/procs_js.odin @@ -1,4 +1,4 @@ -//+build js +#+build js package runtime init_default_context_for_js: Context diff --git a/base/runtime/procs_wasm.odin b/base/runtime/procs_wasm.odin index 9f2e9befc..7e03656ca 100644 --- a/base/runtime/procs_wasm.odin +++ b/base/runtime/procs_wasm.odin @@ -1,4 +1,4 @@ -//+build wasm32, wasm64p32 +#+build wasm32, wasm64p32 package runtime @(private="file") @@ -14,33 +14,57 @@ ti_uint :: struct #raw_union { } @(link_name="__ashlti3", linkage="strong") -__ashlti3 :: proc "contextless" (la, ha: u64, b_: u32) -> i128 { - bits_in_dword :: size_of(u32)*8 - b := u32(b_) +__ashlti3 :: proc "contextless" (a: i128, b: u32) -> i128 { + bits :: 64 - input, result: ti_int - input.lo, input.hi = la, ha - if b & bits_in_dword != 0 { + input: ti_int = --- + result: ti_int = --- + input.all = a + if b & bits != 0 { result.lo = 0 - result.hi = input.lo << (b-bits_in_dword) + result.hi = input.lo << (b-bits) } else { if b == 0 { - return input.all + return a } result.lo = input.lo<>(bits_in_dword-b)) + result.hi = (input.hi<>(bits-b)) } return result.all } +__ashlti3_unsigned :: proc "contextless" (a: u128, b: u32) -> u128 { + return cast(u128)__ashlti3(cast(i128)a, b) +} + +@(link_name="__mulddi3", linkage="strong") +__mulddi3 :: proc "contextless" (a, b: u64) -> i128 { + r: ti_int + bits :: 32 + + mask :: ~u64(0) >> bits + r.lo = (a & mask) * (b & mask) + t := r.lo >> bits + r.lo &= mask + t += (a >> bits) * (b & mask) + r.lo += (t & mask) << bits + r.hi = t >> bits + t = r.lo >> bits + r.lo &= mask + t += (b >> bits) * (a & mask) + r.lo += (t & mask) << bits + r.hi += t >> bits + r.hi += (a >> bits) * (b >> bits) + return r.all +} @(link_name="__multi3", linkage="strong") -__multi3 :: proc "contextless" (la, ha, lb, hb: u64) -> i128 { +__multi3 :: proc "contextless" (a, b: i128) -> i128 { x, y, r: ti_int - x.lo, x.hi = la, ha - y.lo, y.hi = lb, hb - r.all = i128(x.lo * y.lo) // TODO this is incorrect + x.all = a + y.all = b + r.all = __mulddi3(x.lo, y.lo) r.hi += x.hi*y.lo + x.lo*y.hi return r.all } @@ -54,18 +78,16 @@ udivti3 :: proc "c" (la, ha, lb, hb: u64) -> u128 { } @(link_name="__lshrti3", linkage="strong") -__lshrti3 :: proc "c" (la, ha: u64, b: u32) -> i128 { - bits :: size_of(u32)*8 +__lshrti3 :: proc "c" (a: i128, b: u32) -> i128 { + bits :: 64 input, result: ti_int - input.lo = la - input.hi = ha - + input.all = a if b & bits != 0 { result.hi = 0 result.lo = input.hi >> (b - bits) } else if b == 0 { - return input.all + return a } else { result.hi = input.hi >> b result.lo = (input.hi << (bits - b)) | (input.lo >> b) diff --git a/base/runtime/procs_windows_amd64.odin b/base/runtime/procs_windows_amd64.odin index ea495f5fa..81d2cfb5d 100644 --- a/base/runtime/procs_windows_amd64.odin +++ b/base/runtime/procs_windows_amd64.odin @@ -1,5 +1,5 @@ -//+private -//+no-instrumentation +#+private +#+no-instrumentation package runtime foreign import kernel32 "system:Kernel32.lib" diff --git a/base/runtime/procs_windows_i386.odin b/base/runtime/procs_windows_i386.odin index 10422cf07..99c314228 100644 --- a/base/runtime/procs_windows_i386.odin +++ b/base/runtime/procs_windows_i386.odin @@ -1,5 +1,5 @@ -//+private -//+no-instrumentation +#+private +#+no-instrumentation package runtime @require foreign import "system:int64.lib" diff --git a/base/runtime/wasm_allocator.odin b/base/runtime/wasm_allocator.odin index 6bafaa489..574f3dd06 100644 --- a/base/runtime/wasm_allocator.odin +++ b/base/runtime/wasm_allocator.odin @@ -1,4 +1,4 @@ -//+build wasm32, wasm64p32 +#+build wasm32, wasm64p32 package runtime import "base:intrinsics" diff --git a/build.bat b/build.bat index b458c0c67..55c71ca9f 100644 --- a/build.bat +++ b/build.bat @@ -19,12 +19,12 @@ if "%VSCMD_ARG_TGT_ARCH%" neq "x64" ( ) ) -for /f "usebackq tokens=1,2 delims=,=- " %%i in (`wmic os get LocalDateTime /value`) do @if %%i==LocalDateTime ( - set CURR_DATE_TIME=%%j +for /f %%i in ('powershell get-date -format "{yyyyMMdd}"') do ( + set CURR_DATE_TIME=%%i ) - set curr_year=%CURR_DATE_TIME:~0,4% set curr_month=%CURR_DATE_TIME:~4,2% +set curr_day=%CURR_DATE_TIME:~6,2% :: Make sure this is a decent name and not generic set exe_name=odin.exe @@ -45,7 +45,19 @@ if "%2" == "1" ( set nightly=0 ) -set odin_version_raw="dev-%curr_year%-%curr_month%" +if %release_mode% equ 0 ( + set V1=%curr_year% + set V2=%curr_month% + set V3=%curr_day% +) else ( + set V1=%curr_year% + set V2=%curr_month% + set V3=0 +) +set V4=0 +set odin_version_full="%V1%.%V2%.%V3%.%V4%" +set odin_version_raw="dev-%V1%-%V2%" + set compiler_flags= -nologo -Oi -TP -fp:precise -Gm- -MP -FC -EHsc- -GR- -GF rem Parse source code as utf-8 even on shift-jis and other codepages @@ -53,18 +65,30 @@ rem See https://learn.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-a set compiler_flags= %compiler_flags% /utf-8 set compiler_defines= -DODIN_VERSION_RAW=\"%odin_version_raw%\" +rem fileversion is defined as {Major,Minor,Build,Private: u16} so a bit limited +set rc_flags=-nologo ^ +-DV1=%V1% -DV2=%V2% -DV3=%V3% -DV4=%V4% ^ +-DVF=%odin_version_full% -DNIGHTLY=%nightly% + +where /Q git.exe || goto skip_git_hash if not exist .git\ goto skip_git_hash for /f "tokens=1,2" %%i IN ('git show "--pretty=%%cd %%h" "--date=format:%%Y-%%m" --no-patch --no-notes HEAD') do ( set odin_version_raw=dev-%%i set GIT_SHA=%%j ) -if %ERRORLEVEL% equ 0 set compiler_defines=%compiler_defines% -DGIT_SHA=\"%GIT_SHA%\" +if %ERRORLEVEL% equ 0 ( + set compiler_defines=%compiler_defines% -DGIT_SHA=\"%GIT_SHA%\" + set rc_flags=%rc_flags% -DGIT_SHA=%GIT_SHA% -DVP=%odin_version_raw%:%GIT_SHA% +) else ( + set rc_flags=%rc_flags% -DVP=%odin_version_raw% +) :skip_git_hash if %nightly% equ 1 set compiler_defines=%compiler_defines% -DNIGHTLY if %release_mode% EQU 0 ( rem Debug set compiler_flags=%compiler_flags% -Od -MDd -Z7 + set rc_flags=%rc_flags% -D_DEBUG ) else ( rem Release set compiler_flags=%compiler_flags% -O2 -MT -Z7 set compiler_defines=%compiler_defines% -DNO_ARRAY_BOUNDS_CHECK @@ -82,6 +106,8 @@ set libs= ^ kernel32.lib ^ Synchronization.lib ^ bin\llvm\windows\LLVM-C.lib +set odin_res=misc\odin.res +set odin_rc=misc\odin.rc rem DO NOT TOUCH! rem THIS TILDE STUFF IS FOR DEVELOPMENT ONLY! @@ -93,7 +119,7 @@ if %tilde_backend% EQU 1 ( rem DO NOT TOUCH! -set linker_flags= -incremental:no -opt:ref -subsystem:console +set linker_flags= -incremental:no -opt:ref -subsystem:console -MANIFEST:EMBED if %release_mode% EQU 0 ( rem Debug set linker_flags=%linker_flags% -debug /NATVIS:src\odin_compiler.natvis @@ -102,19 +128,21 @@ if %release_mode% EQU 0 ( rem Debug ) set compiler_settings=%compiler_includes% %compiler_flags% %compiler_warnings% %compiler_defines% -set linker_settings=%libs% %linker_flags% +set linker_settings=%libs% %odin_res% %linker_flags% del *.pdb > NUL 2> NUL del *.ilk > NUL 2> NUL +rc %rc_flags% %odin_rc% cl %compiler_settings% "src\main.cpp" "src\libtommath.cpp" /link %linker_settings% -OUT:%exe_name% +mt -nologo -inputresource:%exe_name%;#1 -manifest misc\odin.manifest -outputresource:%exe_name%;#1 -validate_manifest -identity:"odin, processorArchitecture=amd64, version=%odin_version_full%, type=win32" if %errorlevel% neq 0 goto end_of_build call build_vendor.bat if %errorlevel% neq 0 goto end_of_build rem If the demo doesn't run for you and your CPU is more than a decade old, try -microarch:native -if %release_mode% EQU 0 odin run examples/demo -vet -strict-style -- Hellope World +if %release_mode% EQU 0 odin run examples/demo -vet -strict-style -resource:examples/demo/demo.rc -- Hellope World rem Many non-compiler devs seem to run debug build but don't realize. if %release_mode% EQU 0 echo: & echo Debug compiler built. Note: run "build.bat release" if you want a faster, release mode compiler. diff --git a/build_odin.sh b/build_odin.sh index f283dc441..c06004ea8 100755 --- a/build_odin.sh +++ b/build_odin.sh @@ -130,7 +130,7 @@ build_odin() { EXTRAFLAGS="-O3" ;; release-native) - if [ "$OS_ARCH" = "arm64" ]; then + if [ "$OS_ARCH" = "arm64" ] || [ "$OS_ARCH" = "aarch64" ]; then # Use preferred flag for Arm (ie arm64 / aarch64 / etc) EXTRAFLAGS="-O3 -mcpu=native" else diff --git a/ci/build_linux_static.sh b/ci/build_linux_static.sh new file mode 100755 index 000000000..f821cbb59 --- /dev/null +++ b/ci/build_linux_static.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env sh +# Intended for use in Alpine containers, see the "nightly" Github action for a list of dependencies + +CXX="clang++-18" +LLVM_CONFIG="llvm-config-18" + +DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value" + +CPPFLAGS="-DODIN_VERSION_RAW=\"dev-$(date +"%Y-%m")\"" +CXXFLAGS="-std=c++14 $($LLVM_CONFIG --cxxflags --ldflags)" + +LDFLAGS="-static -lm -lzstd -lz -lffi -pthread -ldl -fuse-ld=mold" +LDFLAGS="$LDFLAGS $($LLVM_CONFIG --link-static --ldflags --libs --system-libs --libfiles)" +LDFLAGS="$LDFLAGS -Wl,-rpath=\$ORIGIN" + +EXTRAFLAGS="-DNIGHTLY -O3" + +set -x +$CXX src/main.cpp src/libtommath.cpp $DISABLED_WARNINGS $CPPFLAGS $CXXFLAGS $EXTRAFLAGS $LDFLAGS -o odin diff --git a/ci/nightly.py b/ci/nightly.py index 7bd32899d..923c4e36d 100644 --- a/ci/nightly.py +++ b/ci/nightly.py @@ -2,7 +2,7 @@ import os import sys from zipfile import ZipFile, ZIP_DEFLATED from b2sdk.v2 import InMemoryAccountInfo, B2Api -from datetime import datetime +from datetime import datetime, timezone import json UPLOAD_FOLDER = "nightly/" @@ -22,7 +22,7 @@ def auth() -> bool: pass # Not yet authenticated err = b2_api.authorize_account("production", application_key_id, application_key) - return err == None + return err is None def get_bucket(): if not auth(): sys.exit(1) @@ -32,30 +32,35 @@ def remove_prefix(text: str, prefix: str) -> str: return text[text.startswith(prefix) and len(prefix):] def create_and_upload_artifact_zip(platform: str, artifact: str) -> int: - now = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0) - destination_zip_name = "odin-{}-nightly+{}.zip".format(platform, now.strftime("%Y-%m-%d")) + now = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0) - source_zip_name = artifact - if not artifact.endswith(".zip"): - print(f"Creating archive {destination_zip_name} from {artifact} and uploading to {bucket_name}") + source_archive: str + destination_name = f'odin-{platform}-nightly+{now.strftime("%Y-%m-%d")}' - source_zip_name = destination_zip_name - with ZipFile(source_zip_name, mode='w', compression=ZIP_DEFLATED, compresslevel=9) as z: + if platform.startswith("linux") or platform.startswith("macos"): + destination_name += ".tar.gz" + source_archive = artifact + else: + destination_name += ".zip" + source_archive = destination_name + + print(f"Creating archive {destination_name} from {artifact} and uploading to {bucket_name}") + with ZipFile(source_archive, mode='w', compression=ZIP_DEFLATED, compresslevel=9) as z: for root, directory, filenames in os.walk(artifact): for file in filenames: file_path = os.path.join(root, file) zip_path = os.path.join("dist", os.path.relpath(file_path, artifact)) z.write(file_path, zip_path) - if not os.path.exists(source_zip_name): - print(f"Error: Newly created ZIP archive {source_zip_name} not found.") - return 1 + if not os.path.exists(source_archive): + print(f"Error: archive {source_archive} not found.") + return 1 - print("Uploading {} to {}".format(source_zip_name, UPLOAD_FOLDER + destination_zip_name)) + print("Uploading {} to {}".format(source_archive, UPLOAD_FOLDER + destination_name)) bucket = get_bucket() res = bucket.upload_local_file( - source_zip_name, # Local file to upload - "nightly/" + destination_zip_name, # B2 destination path + source_archive, # Local file to upload + "nightly/" + destination_name, # B2 destination path ) return 0 @@ -65,8 +70,8 @@ def prune_artifacts(): bucket = get_bucket() for file, _ in bucket.ls(UPLOAD_FOLDER, latest_only=False): # Timestamp is in milliseconds - date = datetime.fromtimestamp(file.upload_timestamp / 1_000.0).replace(hour=0, minute=0, second=0, microsecond=0) - now = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0) + date = datetime.fromtimestamp(file.upload_timestamp / 1_000.0, tz=timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0) + now = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0) delta = now - date if delta.days > int(days_to_keep): @@ -100,7 +105,7 @@ def update_nightly_json(): 'sizeInBytes': size, }) - now = datetime.utcnow().isoformat() + now = datetime.now(timezone.utc).isoformat() nightly = json.dumps({ 'last_updated' : now, @@ -137,4 +142,4 @@ if __name__ == "__main__": elif command == "json": res = update_nightly_json() - sys.exit(res) \ No newline at end of file + sys.exit(res) diff --git a/core/bytes/bytes.odin b/core/bytes/bytes.odin index 45eb44307..c0d25bcce 100644 --- a/core/bytes/bytes.odin +++ b/core/bytes/bytes.odin @@ -334,7 +334,7 @@ Inputs: Returns: - index: The index of the byte `c`, or -1 if it was not found. */ -index_byte :: proc(s: []byte, c: byte) -> (index: int) #no_bounds_check { +index_byte :: proc "contextless" (s: []byte, c: byte) -> (index: int) #no_bounds_check { i, l := 0, len(s) // Guard against small strings. On modern systems, it is ALWAYS @@ -469,7 +469,7 @@ Inputs: Returns: - index: The index of the byte `c`, or -1 if it was not found. */ -last_index_byte :: proc(s: []byte, c: byte) -> int #no_bounds_check { +last_index_byte :: proc "contextless" (s: []byte, c: byte) -> int #no_bounds_check { i := len(s) // Guard against small strings. On modern systems, it is ALWAYS diff --git a/core/c/frontend/tokenizer/tokenizer.odin b/core/c/frontend/tokenizer/tokenizer.odin index 2415e06a0..558077717 100644 --- a/core/c/frontend/tokenizer/tokenizer.odin +++ b/core/c/frontend/tokenizer/tokenizer.odin @@ -291,7 +291,7 @@ scan_escape :: proc(t: ^Tokenizer) -> bool { n -= 1 } - if x > max || 0xd800 <= x && x <= 0xe000 { + if x > max || 0xd800 <= x && x <= 0xdfff { error_offset(t, offset, "escape sequence is an invalid Unicode code point") return false } diff --git a/core/c/libc/errno.odin b/core/c/libc/errno.odin index 843b2f1b6..de429a6ec 100644 --- a/core/c/libc/errno.odin +++ b/core/c/libc/errno.odin @@ -98,6 +98,14 @@ when ODIN_OS == .Haiku { ERANGE :: B_POSIX_ERROR_BASE + 17 } +when ODIN_OS == .JS { + _ :: libc + _get_errno :: proc "c" () -> ^int { + @(static) errno: int + return &errno + } +} + // Odin has no way to make an identifier "errno" behave as a function call to // read the value, or to produce an lvalue such that you can assign a different // error value to errno. To work around this, just expose it as a function like diff --git a/core/c/libc/stdatomic.odin b/core/c/libc/stdatomic.odin index 8dc243b78..43a0e51df 100644 --- a/core/c/libc/stdatomic.odin +++ b/core/c/libc/stdatomic.odin @@ -235,7 +235,7 @@ atomic_compare_exchange_weak :: #force_inline proc(object, expected: ^$T, desire return ok } -atomic_compare_exchange_weak_explicit :: #force_inline proc(object, expected: ^$T, desited: T, success, failure: memory_order) -> bool { +atomic_compare_exchange_weak_explicit :: #force_inline proc(object, expected: ^$T, desired: T, success, failure: memory_order) -> bool { assert(failure != .release) assert(failure != .acq_rel) diff --git a/core/c/libc/stdio.odin b/core/c/libc/stdio.odin index 019389b0d..a94a53696 100644 --- a/core/c/libc/stdio.odin +++ b/core/c/libc/stdio.odin @@ -89,6 +89,30 @@ when ODIN_OS == .Linux { } } +when ODIN_OS == .JS { + fpos_t :: struct #raw_union { _: [16]char, _: longlong, _: double, } + + _IOFBF :: 0 + _IOLBF :: 1 + _IONBF :: 2 + + BUFSIZ :: 1024 + + EOF :: int(-1) + + FOPEN_MAX :: 1000 + + FILENAME_MAX :: 4096 + + L_tmpnam :: 20 + + SEEK_SET :: 0 + SEEK_CUR :: 1 + SEEK_END :: 2 + + TMP_MAX :: 308915776 +} + when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { fpos_t :: distinct i64 diff --git a/core/c/libc/stdlib.odin b/core/c/libc/stdlib.odin index 08c6fa6f0..98280e44b 100644 --- a/core/c/libc/stdlib.odin +++ b/core/c/libc/stdlib.odin @@ -10,6 +10,9 @@ when ODIN_OS == .Windows { foreign import libc "system:c" } +@(require) +import "base:runtime" + when ODIN_OS == .Windows { RAND_MAX :: 0x7fff @@ -145,6 +148,10 @@ aligned_alloc :: #force_inline proc "c" (alignment, size: size_t) -> rawptr { _aligned_malloc :: proc(size, alignment: size_t) -> rawptr --- } return _aligned_malloc(size=size, alignment=alignment) + } else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 { + context = runtime.default_context() + data, _ := runtime.mem_alloc_bytes(auto_cast size, auto_cast alignment) + return raw_data(data) } else { foreign libc { aligned_alloc :: proc(alignment, size: size_t) -> rawptr --- @@ -160,6 +167,9 @@ aligned_free :: #force_inline proc "c" (ptr: rawptr) { _aligned_free :: proc(ptr: rawptr) --- } _aligned_free(ptr) + } else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 { + context = runtime.default_context() + runtime.mem_free(ptr) } else { free(ptr) } diff --git a/core/c/libc/string.odin b/core/c/libc/string.odin index cde9c7e6b..4ec4f3a7a 100644 --- a/core/c/libc/string.odin +++ b/core/c/libc/string.odin @@ -12,6 +12,7 @@ when ODIN_OS == .Windows { foreign import libc "system:c" } +@(default_calling_convention="c") foreign libc { // 7.24.2 Copying functions memcpy :: proc(s1, s2: rawptr, n: size_t) -> rawptr --- diff --git a/core/c/libc/time.odin b/core/c/libc/time.odin index 48def707e..6828793ec 100644 --- a/core/c/libc/time.odin +++ b/core/c/libc/time.odin @@ -45,7 +45,7 @@ when ODIN_OS == .Windows { } } -when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku { +when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku || ODIN_OS == .JS { @(default_calling_convention="c") foreign libc { // 7.27.2 Time manipulation functions diff --git a/core/c/libc/wctype.odin b/core/c/libc/wctype.odin index a41fe7fac..b96410b4c 100644 --- a/core/c/libc/wctype.odin +++ b/core/c/libc/wctype.odin @@ -14,7 +14,7 @@ when ODIN_OS == .Windows { wctrans_t :: distinct wchar_t wctype_t :: distinct ushort -} else when ODIN_OS == .Linux { +} else when ODIN_OS == .Linux || ODIN_OS == .JS { wctrans_t :: distinct intptr_t wctype_t :: distinct ulong diff --git a/core/compress/zlib/zlib.odin b/core/compress/zlib/zlib.odin index 2dc9e81df..be8a7d7d3 100644 --- a/core/compress/zlib/zlib.odin +++ b/core/compress/zlib/zlib.odin @@ -1,4 +1,4 @@ -//+vet !using-param +#+vet !using-param package compress_zlib /* diff --git a/core/container/bit_array/bit_array.odin b/core/container/bit_array/bit_array.odin index 9a76dc78f..85b941d12 100644 --- a/core/container/bit_array/bit_array.odin +++ b/core/container/bit_array/bit_array.odin @@ -259,6 +259,7 @@ Inputs: unsafe_unset :: proc(b: ^Bit_Array, bit: int) #no_bounds_check { b.bits[bit >> INDEX_SHIFT] &~= 1 << uint(bit & INDEX_MASK) } + /* A helper function to create a Bit Array with optional bias, in case your smallest index is non-zero (including negative). @@ -276,22 +277,50 @@ Returns: - ba: Allocates a bit_Array, backing data is set to `max-min / 64` indices, rounded up (eg 65 - 0 allocates for [2]u64). */ create :: proc(max_index: int, min_index: int = 0, allocator := context.allocator) -> (res: ^Bit_Array, ok: bool) #optional_ok { - context.allocator = allocator size_in_bits := max_index - min_index if size_in_bits < 0 { return {}, false } + res = new(Bit_Array, allocator) + ok = init(res, max_index, min_index, allocator) + res.free_pointer = true + + if !ok { free(res, allocator) } + + return +} + +/* +A helper function to initialize a Bit Array with optional bias, in case your smallest index is non-zero (including negative). + +The range of bits created by this procedure is `min_index.. (ok: bool) { + size_in_bits := max_index - min_index + + if size_in_bits < 0 { return false } + legs := size_in_bits >> INDEX_SHIFT - if size_in_bits & INDEX_MASK > 0 {legs+=1} - bits, err := make([dynamic]u64, legs) - ok = err == mem.Allocator_Error.None - res = new(Bit_Array) + if size_in_bits & INDEX_MASK > 0 { legs += 1 } + + bits, err := make([dynamic]u64, legs, allocator) + ok = err == nil + res.bits = bits res.bias = min_index res.length = max_index - min_index - res.free_pointer = true + res.free_pointer = false return } + /* Sets all values in the Bit_Array to zero. diff --git a/core/crypto/_aes/hw_intel/api.odin b/core/crypto/_aes/hw_intel/api.odin index 1796bb093..52669cb35 100644 --- a/core/crypto/_aes/hw_intel/api.odin +++ b/core/crypto/_aes/hw_intel/api.odin @@ -1,4 +1,4 @@ -//+build amd64 +#+build amd64 package aes_hw_intel import "core:sys/info" diff --git a/core/crypto/_aes/hw_intel/ghash.odin b/core/crypto/_aes/hw_intel/ghash.odin index d61e71b3a..4320dd59b 100644 --- a/core/crypto/_aes/hw_intel/ghash.odin +++ b/core/crypto/_aes/hw_intel/ghash.odin @@ -20,7 +20,7 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -//+build amd64 +#+build amd64 package aes_hw_intel import "base:intrinsics" diff --git a/core/crypto/_aes/hw_intel/hw_intel_keysched.odin b/core/crypto/_aes/hw_intel/hw_intel_keysched.odin index 911dffbd5..bdf0d3066 100644 --- a/core/crypto/_aes/hw_intel/hw_intel_keysched.odin +++ b/core/crypto/_aes/hw_intel/hw_intel_keysched.odin @@ -20,7 +20,7 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -//+build amd64 +#+build amd64 package aes_hw_intel import "base:intrinsics" diff --git a/core/crypto/_chacha20/simd128/chacha20_simd128.odin b/core/crypto/_chacha20/simd128/chacha20_simd128.odin index 2f91ac52a..fe0d0d518 100644 --- a/core/crypto/_chacha20/simd128/chacha20_simd128.odin +++ b/core/crypto/_chacha20/simd128/chacha20_simd128.odin @@ -51,7 +51,7 @@ _ROT_16: simd.u32x4 : {16, 16, 16, 16} when ODIN_ENDIAN == .Big { @(private = "file") - _increment_counter :: #force_inline proc "contextless" (ctx: ^Context) -> simd.u32x4 { + _increment_counter :: #force_inline proc "contextless" (ctx: ^_chacha20.Context) -> simd.u32x4 { // In the Big Endian case, the low and high portions in the vector // are flipped, so the 64-bit addition can't be done with a simple // vector add. diff --git a/core/crypto/_chacha20/simd256/chacha20_simd256.odin b/core/crypto/_chacha20/simd256/chacha20_simd256.odin index 10f2d75fe..ccb02a947 100644 --- a/core/crypto/_chacha20/simd256/chacha20_simd256.odin +++ b/core/crypto/_chacha20/simd256/chacha20_simd256.odin @@ -1,4 +1,4 @@ -//+build amd64 +#+build amd64 package chacha20_simd256 import "base:intrinsics" diff --git a/core/crypto/_chacha20/simd256/chacha20_simd256_stub.odin b/core/crypto/_chacha20/simd256/chacha20_simd256_stub.odin index 039d6cb96..ce673b42b 100644 --- a/core/crypto/_chacha20/simd256/chacha20_simd256_stub.odin +++ b/core/crypto/_chacha20/simd256/chacha20_simd256_stub.odin @@ -1,4 +1,4 @@ -//+build !amd64 +#+build !amd64 package chacha20_simd256 import "base:intrinsics" diff --git a/core/crypto/_sha3/sp800_185.odin b/core/crypto/_sha3/sp800_185.odin index f32398d5c..a96f78cc1 100644 --- a/core/crypto/_sha3/sp800_185.odin +++ b/core/crypto/_sha3/sp800_185.odin @@ -81,16 +81,18 @@ bytepad :: proc(ctx: ^Context, x_strings: [][]byte, w: int) { // 2. while len(z) mod 8 ≠ 0: // z = z || 0 - // 3. while (len(z)/8) mod w ≠ 0: + // 3. while (len(z)/8) mod w != 0: // z = z || 00000000 z_len := u128(z_hi) << 64 | u128(z_lo) z_rem := int(z_len % u128(w)) - pad := _PAD[:w - z_rem] + if z_rem != 0 { + pad := _PAD[:w - z_rem] - // We just add the padding to the state, instead of returning z. - // - // 4. return z. - update(ctx, pad) + // We just add the padding to the state, instead of returning z. + // + // 4. return z. + update(ctx, pad) + } } encode_string :: #force_inline proc(ctx: ^Context, s: []byte) -> (u64, u64) { diff --git a/core/crypto/aes/aes_ctr_hw_intel.odin b/core/crypto/aes/aes_ctr_hw_intel.odin index 1c9e815ad..415758b24 100644 --- a/core/crypto/aes/aes_ctr_hw_intel.odin +++ b/core/crypto/aes/aes_ctr_hw_intel.odin @@ -1,4 +1,4 @@ -//+build amd64 +#+build amd64 package aes import "base:intrinsics" diff --git a/core/crypto/aes/aes_ecb_hw_intel.odin b/core/crypto/aes/aes_ecb_hw_intel.odin index b2ff36a0c..f1d44a25f 100644 --- a/core/crypto/aes/aes_ecb_hw_intel.odin +++ b/core/crypto/aes/aes_ecb_hw_intel.odin @@ -1,4 +1,4 @@ -//+build amd64 +#+build amd64 package aes import "base:intrinsics" diff --git a/core/crypto/aes/aes_gcm_hw_intel.odin b/core/crypto/aes/aes_gcm_hw_intel.odin index ffd8ed642..4cb5ab3b2 100644 --- a/core/crypto/aes/aes_gcm_hw_intel.odin +++ b/core/crypto/aes/aes_gcm_hw_intel.odin @@ -1,4 +1,4 @@ -//+build amd64 +#+build amd64 package aes import "base:intrinsics" diff --git a/core/crypto/aes/aes_impl_hw_gen.odin b/core/crypto/aes/aes_impl_hw_gen.odin index 3557b1aae..0c9ec6edc 100644 --- a/core/crypto/aes/aes_impl_hw_gen.odin +++ b/core/crypto/aes/aes_impl_hw_gen.odin @@ -1,4 +1,4 @@ -//+build !amd64 +#+build !amd64 package aes @(private = "file") diff --git a/core/crypto/aes/aes_impl_hw_intel.odin b/core/crypto/aes/aes_impl_hw_intel.odin index 39ea2dc8d..0f1fa6143 100644 --- a/core/crypto/aes/aes_impl_hw_intel.odin +++ b/core/crypto/aes/aes_impl_hw_intel.odin @@ -1,4 +1,4 @@ -//+build amd64 +#+build amd64 package aes import "core:crypto/_aes/hw_intel" diff --git a/core/crypto/hash/hash.odin b/core/crypto/hash/hash.odin index f7671270a..d47f0ab46 100644 --- a/core/crypto/hash/hash.odin +++ b/core/crypto/hash/hash.odin @@ -1,16 +1,15 @@ package crypto_hash /* - Copyright 2021 zhibog - Made available under the BSD-3 license. + Copyright 2021 zhibog + Made available under the BSD-3 license. - List of contributors: - zhibog, dotbmp: Initial implementation. + List of contributors: + zhibog, dotbmp: Initial implementation. */ import "core:io" import "core:mem" -import "core:os" // hash_bytes will hash the given input and return the computed digest // in a newly allocated slice. @@ -87,36 +86,3 @@ hash_stream :: proc( return dst, io.Error.None } - -// hash_file will read the file provided by the given handle and return the -// computed digest in a newly allocated slice. -hash_file :: proc( - algorithm: Algorithm, - hd: os.Handle, - load_at_once := false, - allocator := context.allocator, -) -> ( - []byte, - io.Error, -) { - if !load_at_once { - return hash_stream(algorithm, os.stream_from_handle(hd), allocator) - } - - buf, ok := os.read_entire_file(hd, allocator) - if !ok { - return nil, io.Error.Unknown - } - defer delete(buf, allocator) - - return hash_bytes(algorithm, buf, allocator), io.Error.None -} - -hash :: proc { - hash_stream, - hash_file, - hash_bytes, - hash_string, - hash_bytes_to_buffer, - hash_string_to_buffer, -} diff --git a/core/crypto/hash/hash_freestanding.odin b/core/crypto/hash/hash_freestanding.odin new file mode 100644 index 000000000..bec3c4eee --- /dev/null +++ b/core/crypto/hash/hash_freestanding.odin @@ -0,0 +1,10 @@ +#+build freestanding +package crypto_hash + +hash :: proc { + hash_stream, + hash_bytes, + hash_string, + hash_bytes_to_buffer, + hash_string_to_buffer, +} diff --git a/core/crypto/hash/hash_os.odin b/core/crypto/hash/hash_os.odin new file mode 100644 index 000000000..d54e657ad --- /dev/null +++ b/core/crypto/hash/hash_os.odin @@ -0,0 +1,38 @@ +#+build !freestanding +package crypto_hash + +import "core:io" +import "core:os" + +// hash_file will read the file provided by the given handle and return the +// computed digest in a newly allocated slice. +hash_file :: proc( + algorithm: Algorithm, + hd: os.Handle, + load_at_once := false, + allocator := context.allocator, +) -> ( + []byte, + io.Error, +) { + if !load_at_once { + return hash_stream(algorithm, os.stream_from_handle(hd), allocator) + } + + buf, ok := os.read_entire_file(hd, allocator) + if !ok { + return nil, io.Error.Unknown + } + defer delete(buf, allocator) + + return hash_bytes(algorithm, buf, allocator), io.Error.None +} + +hash :: proc { + hash_stream, + hash_file, + hash_bytes, + hash_string, + hash_bytes_to_buffer, + hash_string_to_buffer, +} diff --git a/core/crypto/rand_bsd.odin b/core/crypto/rand_bsd.odin index 641b72933..78a6fcaaf 100644 --- a/core/crypto/rand_bsd.odin +++ b/core/crypto/rand_bsd.odin @@ -1,4 +1,4 @@ -//+build freebsd, openbsd, netbsd +#+build freebsd, openbsd, netbsd package crypto foreign import libc "system:c" diff --git a/core/crypto/rand_generic.odin b/core/crypto/rand_generic.odin index 46fb881b3..ef578f5c0 100644 --- a/core/crypto/rand_generic.odin +++ b/core/crypto/rand_generic.odin @@ -1,10 +1,10 @@ -//+build !linux -//+build !windows -//+build !openbsd -//+build !freebsd -//+build !netbsd -//+build !darwin -//+build !js +#+build !linux +#+build !windows +#+build !openbsd +#+build !freebsd +#+build !netbsd +#+build !darwin +#+build !js package crypto HAS_RAND_BYTES :: false diff --git a/core/debug/trace/trace_cpp.odin b/core/debug/trace/trace_cpp.odin index dc723184a..8ef377cef 100644 --- a/core/debug/trace/trace_cpp.odin +++ b/core/debug/trace/trace_cpp.odin @@ -1,5 +1,5 @@ -//+private file -//+build linux, darwin +#+private file +#+build linux, darwin package debug_trace import "base:intrinsics" diff --git a/core/debug/trace/trace_nil.odin b/core/debug/trace/trace_nil.odin index ca8bd7817..8ee96720e 100644 --- a/core/debug/trace/trace_nil.odin +++ b/core/debug/trace/trace_nil.odin @@ -1,6 +1,6 @@ -//+build !windows -//+build !linux -//+build !darwin +#+build !windows +#+build !linux +#+build !darwin package debug_trace import "base:runtime" diff --git a/core/debug/trace/trace_windows.odin b/core/debug/trace/trace_windows.odin index de1461e96..c9868e338 100644 --- a/core/debug/trace/trace_windows.odin +++ b/core/debug/trace/trace_windows.odin @@ -1,5 +1,5 @@ -//+private -//+build windows +#+private +#+build windows package debug_trace import "base:intrinsics" diff --git a/core/dynlib/lib_js.odin b/core/dynlib/lib_js.odin index bfc724c12..698cfee9c 100644 --- a/core/dynlib/lib_js.odin +++ b/core/dynlib/lib_js.odin @@ -1,5 +1,5 @@ -//+build js -//+private +#+build js +#+private package dynlib _load_library :: proc(path: string, global_symbols := false) -> (Library, bool) { diff --git a/core/dynlib/lib_unix.odin b/core/dynlib/lib_unix.odin index 8adaadb2d..f467d730d 100644 --- a/core/dynlib/lib_unix.odin +++ b/core/dynlib/lib_unix.odin @@ -1,5 +1,5 @@ -//+build linux, darwin, freebsd, openbsd, netbsd -//+private +#+build linux, darwin, freebsd, openbsd, netbsd +#+private package dynlib import "core:os" diff --git a/core/dynlib/lib_windows.odin b/core/dynlib/lib_windows.odin index b41abe3b2..6c41a1a75 100644 --- a/core/dynlib/lib_windows.odin +++ b/core/dynlib/lib_windows.odin @@ -1,5 +1,5 @@ -//+build windows -//+private +#+build windows +#+private package dynlib import win32 "core:sys/windows" diff --git a/core/encoding/cbor/unmarshal.odin b/core/encoding/cbor/unmarshal.odin index c54660839..bf27171f4 100644 --- a/core/encoding/cbor/unmarshal.odin +++ b/core/encoding/cbor/unmarshal.odin @@ -675,10 +675,6 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, return case reflect.Type_Info_Map: - if !reflect.is_string(t.key) { - return _unsupported(v, hdr) - } - raw_map := (^mem.Raw_Map)(v.data) if raw_map.allocator.procedure == nil { raw_map.allocator = context.allocator @@ -695,43 +691,31 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, new_len := uintptr(min(scap, runtime.map_len(raw_map^)+length)) runtime.map_reserve_dynamic(raw_map, t.map_info, new_len) or_return } - - // Temporary memory to unmarshal keys into before inserting them into the map. + + // Temporary memory to unmarshal values into before inserting them into the map. elem_backing := mem.alloc_bytes_non_zeroed(t.value.size, t.value.align, context.temp_allocator) or_return defer delete(elem_backing, context.temp_allocator) - map_backing_value := any{raw_data(elem_backing), t.value.id} - for idx := 0; unknown || idx < length; idx += 1 { - // Decode key, keys can only be strings. - key: string - if keyv, kerr := decode_key(d, v); unknown && kerr == .Break { - break - } else if kerr != nil { - err = kerr - return - } else { - key = keyv - } + // Temporary memory to unmarshal keys into. + key_backing := mem.alloc_bytes_non_zeroed(t.key.size, t.key.align, context.temp_allocator) or_return + defer delete(key_backing, context.temp_allocator) + key_backing_value := any{raw_data(key_backing), t.key.id} + for idx := 0; unknown || idx < length; idx += 1 { if unknown || idx > scap { // Reserve space for new element so we can return allocator errors. new_len := uintptr(runtime.map_len(raw_map^)+1) runtime.map_reserve_dynamic(raw_map, t.map_info, new_len) or_return } + mem.zero_slice(key_backing) + _unmarshal_value(d, key_backing_value, _decode_header(r) or_return) or_return + mem.zero_slice(elem_backing) _unmarshal_value(d, map_backing_value, _decode_header(r) or_return) or_return - key_ptr := rawptr(&key) - key_cstr: cstring - if reflect.is_cstring(t.key) { - assert_safe_for_cstring(key) - key_cstr = cstring(raw_data(key)) - key_ptr = &key_cstr - } - - set_ptr := runtime.__dynamic_map_set_without_hash(raw_map, t.map_info, key_ptr, map_backing_value.data) + set_ptr := runtime.__dynamic_map_set_without_hash(raw_map, t.map_info, key_backing_value.data, map_backing_value.data) // We already reserved space for it, so this shouldn't fail. assert(set_ptr != nil) } diff --git a/core/encoding/ini/ini.odin b/core/encoding/ini/ini.odin index 2bb7996a3..c32b1deb5 100644 --- a/core/encoding/ini/ini.odin +++ b/core/encoding/ini/ini.odin @@ -154,6 +154,7 @@ write_section :: proc(w: io.Writer, name: string, n_written: ^int = nil) -> (n: io.write_byte (w, '[', &n) or_return io.write_string(w, name, &n) or_return io.write_byte (w, ']', &n) or_return + io.write_byte (w, '\n', &n) or_return return } diff --git a/core/encoding/json/unmarshal.odin b/core/encoding/json/unmarshal.odin index 738e20c68..8a50989f4 100644 --- a/core/encoding/json/unmarshal.odin +++ b/core/encoding/json/unmarshal.odin @@ -312,13 +312,18 @@ unmarshal_value :: proc(p: ^Parser, v: any) -> (err: Unmarshal_Error) { case .String: advance_token(p) - str := unquote_string(token, p.spec, p.allocator) or_return - if unmarshal_string_token(p, any{v.data, ti.id}, str, ti) { - return nil + str := unquote_string(token, p.spec, p.allocator) or_return + dest := any{v.data, ti.id} + if !unmarshal_string_token(p, dest, str, ti) { + delete(str, p.allocator) + return UNSUPPORTED_TYPE } - delete(str, p.allocator) - return UNSUPPORTED_TYPE + switch destv in dest { + case string, cstring: + case: delete(str, p.allocator) + } + return nil case .Open_Brace: return unmarshal_object(p, v, .Close_Brace) diff --git a/core/flags/errors_bsd.odin b/core/flags/errors_bsd.odin index 1fe6de90b..4d98d2ee4 100644 --- a/core/flags/errors_bsd.odin +++ b/core/flags/errors_bsd.odin @@ -1,4 +1,4 @@ -//+build netbsd, openbsd +#+build netbsd, openbsd package flags import "base:runtime" diff --git a/core/flags/errors_nonbsd.odin b/core/flags/errors_nonbsd.odin index e129aff74..28912b57f 100644 --- a/core/flags/errors_nonbsd.odin +++ b/core/flags/errors_nonbsd.odin @@ -1,5 +1,5 @@ -//+build !netbsd -//+build !openbsd +#+build !netbsd +#+build !openbsd package flags import "base:runtime" diff --git a/core/flags/internal_assignment.odin b/core/flags/internal_assignment.odin index be3997ef1..12ddb876f 100644 --- a/core/flags/internal_assignment.odin +++ b/core/flags/internal_assignment.odin @@ -1,4 +1,4 @@ -//+private +#+private package flags import "base:intrinsics" diff --git a/core/flags/internal_parsing.odin b/core/flags/internal_parsing.odin index 7a769b17c..4e49f45b0 100644 --- a/core/flags/internal_parsing.odin +++ b/core/flags/internal_parsing.odin @@ -1,4 +1,4 @@ -//+private +#+private package flags import "core:container/bit_array" diff --git a/core/flags/internal_rtti.odin b/core/flags/internal_rtti.odin index 4c1db5d0b..1c559ca55 100644 --- a/core/flags/internal_rtti.odin +++ b/core/flags/internal_rtti.odin @@ -1,4 +1,4 @@ -//+private +#+private package flags import "base:intrinsics" diff --git a/core/flags/internal_rtti_nonbsd.odin b/core/flags/internal_rtti_nonbsd.odin index 0044898d5..e1286186b 100644 --- a/core/flags/internal_rtti_nonbsd.odin +++ b/core/flags/internal_rtti_nonbsd.odin @@ -1,6 +1,6 @@ -//+private -//+build !netbsd -//+build !openbsd +#+private +#+build !netbsd +#+build !openbsd package flags import "core:net" diff --git a/core/flags/internal_validation.odin b/core/flags/internal_validation.odin index b71cf9fe7..afd05331c 100644 --- a/core/flags/internal_validation.odin +++ b/core/flags/internal_validation.odin @@ -1,4 +1,4 @@ -//+private +#+private package flags @require import "base:runtime" diff --git a/core/fmt/example.odin b/core/fmt/example.odin index 503e64f2b..6929e9be7 100644 --- a/core/fmt/example.odin +++ b/core/fmt/example.odin @@ -1,4 +1,4 @@ -//+build ignore +#+build ignore package custom_formatter_example import "core:fmt" import "core:io" diff --git a/core/fmt/fmt_js.odin b/core/fmt/fmt_js.odin index acf218eb5..ce90fbfe7 100644 --- a/core/fmt/fmt_js.odin +++ b/core/fmt/fmt_js.odin @@ -1,4 +1,4 @@ -//+build js +#+build js package fmt import "core:bufio" diff --git a/core/fmt/fmt_os.odin b/core/fmt/fmt_os.odin index 9de0d43be..a481061f1 100644 --- a/core/fmt/fmt_os.odin +++ b/core/fmt/fmt_os.odin @@ -1,6 +1,6 @@ -//+build !freestanding -//+build !js -//+build !orca +#+build !freestanding +#+build !js +#+build !orca package fmt import "base:runtime" diff --git a/core/image/bmp/bmp_js.odin b/core/image/bmp/bmp_js.odin index d87a7d2d5..fa5d59095 100644 --- a/core/image/bmp/bmp_js.odin +++ b/core/image/bmp/bmp_js.odin @@ -1,4 +1,4 @@ -//+build js +#+build js package core_image_bmp load :: proc{load_from_bytes, load_from_context} diff --git a/core/image/bmp/bmp_os.odin b/core/image/bmp/bmp_os.odin index d20abc685..70a85a784 100644 --- a/core/image/bmp/bmp_os.odin +++ b/core/image/bmp/bmp_os.odin @@ -1,4 +1,4 @@ -//+build !js +#+build !js package core_image_bmp import "core:os" diff --git a/core/image/general_js.odin b/core/image/general_js.odin index 841d9c200..abf9812c0 100644 --- a/core/image/general_js.odin +++ b/core/image/general_js.odin @@ -1,4 +1,4 @@ -//+build js +#+build js package image load :: proc{ diff --git a/core/image/general_os.odin b/core/image/general_os.odin index e1fe440a4..98eb5bdbe 100644 --- a/core/image/general_os.odin +++ b/core/image/general_os.odin @@ -1,4 +1,4 @@ -//+build !js +#+build !js package image import "core:os" diff --git a/core/image/netpbm/netpbm.odin b/core/image/netpbm/netpbm.odin index ab3945ad7..a9dc6599a 100644 --- a/core/image/netpbm/netpbm.odin +++ b/core/image/netpbm/netpbm.odin @@ -1,4 +1,4 @@ -//+vet !using-stmt +#+vet !using-stmt package netpbm import "core:bytes" diff --git a/core/image/netpbm/netpbm_js.odin b/core/image/netpbm/netpbm_js.odin index 7db17a05d..7d475cf62 100644 --- a/core/image/netpbm/netpbm_js.odin +++ b/core/image/netpbm/netpbm_js.odin @@ -1,4 +1,4 @@ -//+build js +#+build js package netpbm load :: proc { diff --git a/core/image/netpbm/netpbm_os.odin b/core/image/netpbm/netpbm_os.odin index 609f1ea1f..2cf2439ac 100644 --- a/core/image/netpbm/netpbm_os.odin +++ b/core/image/netpbm/netpbm_os.odin @@ -1,4 +1,4 @@ -//+build !js +#+build !js package netpbm import "core:os" diff --git a/core/image/png/png.odin b/core/image/png/png.odin index 02aef1087..2d3665e94 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -8,7 +8,7 @@ */ -//+vet !using-stmt +#+vet !using-stmt package png import "core:compress" diff --git a/core/image/png/png_js.odin b/core/image/png/png_js.odin index 57c27fc64..dd9e74526 100644 --- a/core/image/png/png_js.odin +++ b/core/image/png/png_js.odin @@ -1,4 +1,4 @@ -//+build js +#+build js package png load :: proc{load_from_bytes, load_from_context} diff --git a/core/image/png/png_os.odin b/core/image/png/png_os.odin index cc65e7b42..8e0706206 100644 --- a/core/image/png/png_os.odin +++ b/core/image/png/png_os.odin @@ -1,4 +1,4 @@ -//+build !js +#+build !js package png import "core:os" diff --git a/core/image/qoi/qoi_js.odin b/core/image/qoi/qoi_js.odin index 2c23cc17a..4a69e98a0 100644 --- a/core/image/qoi/qoi_js.odin +++ b/core/image/qoi/qoi_js.odin @@ -1,4 +1,4 @@ -//+build js +#+build js package qoi save :: proc{save_to_buffer} diff --git a/core/image/qoi/qoi_os.odin b/core/image/qoi/qoi_os.odin index efcec6c52..c85fdd839 100644 --- a/core/image/qoi/qoi_os.odin +++ b/core/image/qoi/qoi_os.odin @@ -1,4 +1,4 @@ -//+build !js +#+build !js package qoi import "core:os" diff --git a/core/image/tga/tga_js.odin b/core/image/tga/tga_js.odin index d98b241a7..9261be8c6 100644 --- a/core/image/tga/tga_js.odin +++ b/core/image/tga/tga_js.odin @@ -1,4 +1,4 @@ -//+build js +#+build js package tga save :: proc{save_to_buffer} diff --git a/core/image/tga/tga_os.odin b/core/image/tga/tga_os.odin index 12747a684..a78998105 100644 --- a/core/image/tga/tga_os.odin +++ b/core/image/tga/tga_os.odin @@ -1,4 +1,4 @@ -//+build !js +#+build !js package tga import "core:os" diff --git a/core/log/file_console_logger.odin b/core/log/file_console_logger.odin index f05f7a258..e45f99523 100644 --- a/core/log/file_console_logger.odin +++ b/core/log/file_console_logger.odin @@ -1,5 +1,5 @@ -//+build !freestanding -//+build !orca +#+build !freestanding +#+build !orca package log import "core:encoding/ansi" diff --git a/core/math/big/tune.odin b/core/math/big/tune.odin index 5938dafde..eab36b951 100644 --- a/core/math/big/tune.odin +++ b/core/math/big/tune.odin @@ -7,7 +7,7 @@ The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks. */ -//+build ignore +#+build ignore package math_big import "core:time" diff --git a/core/math/linalg/glsl/linalg_glsl.odin b/core/math/linalg/glsl/linalg_glsl.odin index 363a95887..5444f89e2 100644 --- a/core/math/linalg/glsl/linalg_glsl.odin +++ b/core/math/linalg/glsl/linalg_glsl.odin @@ -22,9 +22,9 @@ F32_EPSILON :: 1e-7 F64_EPSILON :: 1e-15 // Odin matrices are stored internally as Column-Major, which matches OpenGL/GLSL by default -mat2 :: distinct matrix[2, 2]f32 -mat3 :: distinct matrix[3, 3]f32 -mat4 :: distinct matrix[4, 4]f32 +mat2 :: matrix[2, 2]f32 +mat3 :: matrix[3, 3]f32 +mat4 :: matrix[4, 4]f32 mat2x2 :: mat2 mat3x3 :: mat3 mat4x4 :: mat4 @@ -33,52 +33,52 @@ mat4x4 :: mat4 // but they match how GLSL and OpenGL defines them in name // Odin: matrix[R, C]f32 // GLSL: matCxR -mat3x2 :: distinct matrix[2, 3]f32 -mat4x2 :: distinct matrix[2, 4]f32 -mat2x3 :: distinct matrix[3, 2]f32 -mat4x3 :: distinct matrix[3, 4]f32 -mat2x4 :: distinct matrix[4, 2]f32 -mat3x4 :: distinct matrix[4, 3]f32 +mat3x2 :: matrix[2, 3]f32 +mat4x2 :: matrix[2, 4]f32 +mat2x3 :: matrix[3, 2]f32 +mat4x3 :: matrix[3, 4]f32 +mat2x4 :: matrix[4, 2]f32 +mat3x4 :: matrix[4, 3]f32 -vec2 :: distinct [2]f32 -vec3 :: distinct [3]f32 -vec4 :: distinct [4]f32 +vec2 :: [2]f32 +vec3 :: [3]f32 +vec4 :: [4]f32 -ivec2 :: distinct [2]i32 -ivec3 :: distinct [3]i32 -ivec4 :: distinct [4]i32 +ivec2 :: [2]i32 +ivec3 :: [3]i32 +ivec4 :: [4]i32 -uvec2 :: distinct [2]u32 -uvec3 :: distinct [3]u32 -uvec4 :: distinct [4]u32 +uvec2 :: [2]u32 +uvec3 :: [3]u32 +uvec4 :: [4]u32 -bvec2 :: distinct [2]bool -bvec3 :: distinct [3]bool -bvec4 :: distinct [4]bool +bvec2 :: [2]bool +bvec3 :: [3]bool +bvec4 :: [4]bool -quat :: distinct quaternion128 +quat :: quaternion128 // Double Precision (f64) Floating Point Types -dmat2 :: distinct matrix[2, 2]f64 -dmat3 :: distinct matrix[3, 3]f64 -dmat4 :: distinct matrix[4, 4]f64 +dmat2 :: matrix[2, 2]f64 +dmat3 :: matrix[3, 3]f64 +dmat4 :: matrix[4, 4]f64 dmat2x2 :: dmat2 dmat3x3 :: dmat3 dmat4x4 :: dmat4 -dmat3x2 :: distinct matrix[2, 3]f64 -dmat4x2 :: distinct matrix[2, 4]f64 -dmat2x3 :: distinct matrix[3, 2]f64 -dmat4x3 :: distinct matrix[3, 4]f64 -dmat2x4 :: distinct matrix[4, 2]f64 -dmat3x4 :: distinct matrix[4, 3]f64 +dmat3x2 :: matrix[2, 3]f64 +dmat4x2 :: matrix[2, 4]f64 +dmat2x3 :: matrix[3, 2]f64 +dmat4x3 :: matrix[3, 4]f64 +dmat2x4 :: matrix[4, 2]f64 +dmat3x4 :: matrix[4, 3]f64 -dvec2 :: distinct [2]f64 -dvec3 :: distinct [3]f64 -dvec4 :: distinct [4]f64 +dvec2 :: [2]f64 +dvec3 :: [3]f64 +dvec4 :: [4]f64 -dquat :: distinct quaternion256 +dquat :: quaternion256 cos :: proc{ cos_f32, diff --git a/core/math/linalg/hlsl/linalg_hlsl.odin b/core/math/linalg/hlsl/linalg_hlsl.odin index f5e8bf147..a89fdddd3 100644 --- a/core/math/linalg/hlsl/linalg_hlsl.odin +++ b/core/math/linalg/hlsl/linalg_hlsl.odin @@ -21,89 +21,89 @@ LN10 :: 2.30258509299404568401799145468436421 FLOAT_EPSILON :: 1e-7 DOUBLE_EPSILON :: 1e-15 -// Aliases (not distinct) of types +// Aliases (not distict) of types float :: f32 double :: f64 int :: builtin.i32 uint :: builtin.u32 // Odin matrices are stored internally as Column-Major, which matches the internal layout of HLSL by default -float1x1 :: distinct matrix[1, 1]float -float2x2 :: distinct matrix[2, 2]float -float3x3 :: distinct matrix[3, 3]float -float4x4 :: distinct matrix[4, 4]float +float1x1 :: matrix[1, 1]float +float2x2 :: matrix[2, 2]float +float3x3 :: matrix[3, 3]float +float4x4 :: matrix[4, 4]float -float1x2 :: distinct matrix[1, 2]float -float1x3 :: distinct matrix[1, 3]float -float1x4 :: distinct matrix[1, 4]float -float2x1 :: distinct matrix[2, 1]float -float2x3 :: distinct matrix[2, 3]float -float2x4 :: distinct matrix[2, 4]float -float3x1 :: distinct matrix[3, 1]float -float3x2 :: distinct matrix[3, 2]float -float3x4 :: distinct matrix[3, 4]float -float4x1 :: distinct matrix[4, 1]float -float4x2 :: distinct matrix[4, 2]float -float4x3 :: distinct matrix[4, 3]float +float1x2 :: matrix[1, 2]float +float1x3 :: matrix[1, 3]float +float1x4 :: matrix[1, 4]float +float2x1 :: matrix[2, 1]float +float2x3 :: matrix[2, 3]float +float2x4 :: matrix[2, 4]float +float3x1 :: matrix[3, 1]float +float3x2 :: matrix[3, 2]float +float3x4 :: matrix[3, 4]float +float4x1 :: matrix[4, 1]float +float4x2 :: matrix[4, 2]float +float4x3 :: matrix[4, 3]float -float2 :: distinct [2]float -float3 :: distinct [3]float -float4 :: distinct [4]float +float2 :: [2]float +float3 :: [3]float +float4 :: [4]float -int2 :: distinct [2]int -int3 :: distinct [3]int -int4 :: distinct [4]int +int2 :: [2]int +int3 :: [3]int +int4 :: [4]int -uint2 :: distinct [2]uint -uint3 :: distinct [3]uint -uint4 :: distinct [4]uint +uint2 :: [2]uint +uint3 :: [3]uint +uint4 :: [4]uint -bool2 :: distinct [2]bool -bool3 :: distinct [3]bool -bool4 :: distinct [4]bool +bool2 :: [2]bool +bool3 :: [3]bool +bool4 :: [4]bool // Double Precision (double) Floating Point Types -double1x1 :: distinct matrix[1, 1]double -double2x2 :: distinct matrix[2, 2]double -double3x3 :: distinct matrix[3, 3]double -double4x4 :: distinct matrix[4, 4]double +double1x1 :: matrix[1, 1]double +double2x2 :: matrix[2, 2]double +double3x3 :: matrix[3, 3]double +double4x4 :: matrix[4, 4]double -double1x2 :: distinct matrix[1, 2]double -double1x3 :: distinct matrix[1, 3]double -double1x4 :: distinct matrix[1, 4]double -double2x1 :: distinct matrix[2, 1]double -double2x3 :: distinct matrix[2, 3]double -double2x4 :: distinct matrix[2, 4]double -double3x1 :: distinct matrix[3, 1]double -double3x2 :: distinct matrix[3, 2]double -double3x4 :: distinct matrix[3, 4]double -double4x1 :: distinct matrix[4, 1]double -double4x2 :: distinct matrix[4, 2]double -double4x3 :: distinct matrix[4, 3]double +double1x2 :: matrix[1, 2]double +double1x3 :: matrix[1, 3]double +double1x4 :: matrix[1, 4]double +double2x1 :: matrix[2, 1]double +double2x3 :: matrix[2, 3]double +double2x4 :: matrix[2, 4]double +double3x1 :: matrix[3, 1]double +double3x2 :: matrix[3, 2]double +double3x4 :: matrix[3, 4]double +double4x1 :: matrix[4, 1]double +double4x2 :: matrix[4, 2]double +double4x3 :: matrix[4, 3]double -double2 :: distinct [2]double -double3 :: distinct [3]double -double4 :: distinct [4]double +double2 :: [2]double +double3 :: [3]double +double4 :: [4]double -int1x1 :: distinct matrix[1, 1]int -int2x2 :: distinct matrix[2, 2]int -int3x3 :: distinct matrix[3, 3]int -int4x4 :: distinct matrix[4, 4]int +int1x1 :: matrix[1, 1]int +int2x2 :: matrix[2, 2]int +int3x3 :: matrix[3, 3]int +int4x4 :: matrix[4, 4]int -int1x2 :: distinct matrix[1, 2]int -int1x3 :: distinct matrix[1, 3]int -int1x4 :: distinct matrix[1, 4]int -int2x1 :: distinct matrix[2, 1]int -int2x3 :: distinct matrix[2, 3]int -int2x4 :: distinct matrix[2, 4]int -int3x1 :: distinct matrix[3, 1]int -int3x2 :: distinct matrix[3, 2]int -int3x4 :: distinct matrix[3, 4]int -int4x1 :: distinct matrix[4, 1]int -int4x2 :: distinct matrix[4, 2]int -int4x3 :: distinct matrix[4, 3]int +int1x2 :: matrix[1, 2]int +int1x3 :: matrix[1, 3]int +int1x4 :: matrix[1, 4]int +int2x1 :: matrix[2, 1]int +int2x3 :: matrix[2, 3]int +int2x4 :: matrix[2, 4]int +int3x1 :: matrix[3, 1]int +int3x2 :: matrix[3, 2]int +int3x4 :: matrix[3, 4]int +int4x1 :: matrix[4, 1]int +int4x2 :: matrix[4, 2]int +int4x3 :: matrix[4, 3]int cos :: proc{ cos_float, diff --git a/core/math/math.odin b/core/math/math.odin index f5e904da6..0e21afa67 100644 --- a/core/math/math.odin +++ b/core/math/math.odin @@ -444,11 +444,11 @@ bias :: proc "contextless" (t, b: $T) -> T where intrinsics.type_is_numeric(T) { return t / (((1/b) - 2) * (1 - t) + 1) } @(require_results) -gain :: proc "contextless" (t, g: $T) -> T where intrinsics.type_is_numeric(T) { +gain :: proc "contextless" (t, g: $T) -> T where intrinsics.type_is_float(T) { if t < 0.5 { - return bias(t*2, g)*0.5 + return bias(t*2, g) * 0.5 } - return bias(t*2 - 1, 1 - g)*0.5 + 0.5 + return bias(t*2 - 1, 1 - g) * 0.5 + 0.5 } diff --git a/core/math/math_basic.odin b/core/math/math_basic.odin index 041efd272..2584df71f 100644 --- a/core/math/math_basic.odin +++ b/core/math/math_basic.odin @@ -1,4 +1,4 @@ -//+build !js +#+build !js package math import "base:intrinsics" diff --git a/core/math/math_basic_js.odin b/core/math/math_basic_js.odin index 5b9adabcd..2604ebc8b 100644 --- a/core/math/math_basic_js.odin +++ b/core/math/math_basic_js.odin @@ -1,4 +1,4 @@ -//+build js +#+build js package math import "base:intrinsics" diff --git a/core/math/noise/internal.odin b/core/math/noise/internal.odin index bd97bd45c..f75c0ee87 100644 --- a/core/math/noise/internal.odin +++ b/core/math/noise/internal.odin @@ -4,7 +4,7 @@ Ported from https://github.com/KdotJPG/OpenSimplex2. Copyright 2022 Yuki2 (https://github.com/NoahR02) */ -//+private +#+private package math_noise /* diff --git a/core/mem/alloc.odin b/core/mem/alloc.odin index e51d971e1..fac58daaf 100644 --- a/core/mem/alloc.odin +++ b/core/mem/alloc.odin @@ -2,109 +2,711 @@ package mem import "base:runtime" -// NOTE(bill, 2019-12-31): These are defined in `package runtime` as they are used in the `context`. This is to prevent an import definition cycle. +//NOTE(bill, 2019-12-31): These are defined in `package runtime` as they are used in the `context`. This is to prevent an import definition cycle. + +/* +A request to allocator procedure. + +This type represents a type of allocation request made to an allocator +procedure. There is one allocator procedure per allocator, and this value is +used to discriminate between different functions of the allocator. + +The type is defined as follows: + + Allocator_Mode :: enum byte { + Alloc, + Alloc_Non_Zeroed, + Free, + Free_All, + Resize, + Resize_Non_Zeroed, + Query_Features, + } + +Depending on which value is used, the allocator procedure will perform different +functions: + +- `Alloc`: Allocates a memory region with a given `size` and `alignment`. +- `Alloc_Non_Zeroed`: Same as `Alloc` without explicit zero-initialization of + the memory region. +- `Free`: Free a memory region located at address `ptr` with a given `size`. +- `Free_All`: Free all memory allocated using this allocator. +- `Resize`: Resize a memory region located at address `old_ptr` with size + `old_size` to be `size` bytes in length and have the specified `alignment`, + in case a re-alllocation occurs. +- `Resize_Non_Zeroed`: Same as `Resize`, without explicit zero-initialization. +*/ Allocator_Mode :: runtime.Allocator_Mode -/* -Allocator_Mode :: enum byte { - Alloc, - Free, - Free_All, - Resize, - Query_Features, - Alloc_Non_Zeroed, - Resize_Non_Zeroed, -} -*/ +/* +A set of allocator features. + +This type represents values that contain a set of features an allocator has. +Currently the type is defined as follows: + + Allocator_Mode_Set :: distinct bit_set[Allocator_Mode]; +*/ Allocator_Mode_Set :: runtime.Allocator_Mode_Set -/* -Allocator_Mode_Set :: distinct bit_set[Allocator_Mode]; -*/ +/* +Allocator information. + +This type represents information about a given allocator at a specific point +in time. Currently the type is defined as follows: + + Allocator_Query_Info :: struct { + pointer: rawptr, + size: Maybe(int), + alignment: Maybe(int), + } + +- `pointer`: Pointer to a backing buffer. +- `size`: Size of the backing buffer. +- `alignment`: The allocator's alignment. + +If not applicable, any of these fields may be `nil`. +*/ Allocator_Query_Info :: runtime.Allocator_Query_Info -/* -Allocator_Query_Info :: struct { - pointer: rawptr, - size: Maybe(int), - alignment: Maybe(int), -} -*/ -Allocator_Error :: runtime.Allocator_Error /* -Allocator_Error :: enum byte { - None = 0, - Out_Of_Memory = 1, - Invalid_Pointer = 2, - Invalid_Argument = 3, - Mode_Not_Implemented = 4, -} +An allocation request error. + +This type represents error values the allocators may return upon requests. + + Allocator_Error :: enum byte { + None = 0, + Out_Of_Memory = 1, + Invalid_Pointer = 2, + Invalid_Argument = 3, + Mode_Not_Implemented = 4, + } + +The meaning of the errors is as follows: + +- `None`: No error. +- `Out_Of_Memory`: Either: + 1. The allocator has ran out of the backing buffer, or the requested + allocation size is too large to fit into a backing buffer. + 2. The operating system error during memory allocation. + 3. The backing allocator was used to allocate a new backing buffer and the + backing allocator returned Out_Of_Memory. +- `Invalid_Pointer`: The pointer referring to a memory region does not belong + to any of the allocators backing buffers or does not point to a valid start + of an allocation made in that allocator. +- `Invalid_Argument`: Can occur if one of the arguments makes it impossible to + satisfy a request (i.e. having alignment larger than the backing buffer + of the allocation). +- `Mode_Not_Implemented`: The allocator does not support the specified + operation. For example, an arena does not support freeing individual + allocations. +*/ +Allocator_Error :: runtime.Allocator_Error + +/* +The allocator procedure. + +This type represents allocation procedures. An allocation procedure is a single +procedure, implementing all allocator functions such as allocating the memory, +freeing the memory, etc. + +Currently the type is defined as follows: + + Allocator_Proc :: #type proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size: int, + alignment: int, + old_memory: rawptr, + old_size: int, + location: Source_Code_Location = #caller_location, + ) -> ([]byte, Allocator_Error); + +The function of this procedure and the meaning of parameters depends on the +value of the `mode` parameter. For any operation the following constraints +apply: + +- The `alignment` must be a power of two. +- The `size` must be a positive integer. + +## 1. `.Alloc`, `.Alloc_Non_Zeroed` + +Allocates a memory region of size `size`, aligned on a boundary specified by +`alignment`. + +**Inputs**: +- `allocator_data`: Pointer to the allocator data. +- `mode`: `.Alloc` or `.Alloc_Non_Zeroed`. +- `size`: The desired size of the memory region. +- `alignment`: The desired alignmnet of the allocation. +- `old_memory`: Unused, should be `nil`. +- `old_size`: Unused, should be 0. + +**Returns**: +1. The memory region, if allocated successfully, or `nil` otherwise. +2. An error, if allocation failed. + +**Note**: The nil allocator may return `nil`, even if no error is returned. +Always check both the error and the allocated buffer. + +**Note**: The `.Alloc` mode is required to be implemented for an allocator +and can not return a `.Mode_Not_Implemented` error. + +## 2. `Free` + +Frees a memory region located at the address specified by `old_memory`. If the +allocator does not track sizes of allocations, the size should be specified in +the `old_size` parameter. + +**Inputs**: +- `allocator_data`: Pointer to the allocator data. +- `mode`: `.Free`. +- `size`: Unused, should be 0. +- `alignment`: Unused, should be 0. +- `old_memory`: Pointer to the memory region to free. +- `old_size`: The size of the memory region to free. This parameter is optional + if the allocator keeps track of the sizes of allocations. + +**Returns**: +1. `nil` +2. Error, if freeing failed. + +## 3. `Free_All` + +Frees all allocations, associated with the allocator, making it available for +further allocations using the same backing buffers. + +**Inputs**: +- `allocator_data`: Pointer to the allocator data. +- `mode`: `.Free_All`. +- `size`: Unused, should be 0. +- `alignment`: Unused, should be 0. +- `old_memory`: Unused, should be `nil`. +- `old_size`: Unused, should be `0`. + +**Returns**: +1. `nil`. +2. Error, if freeing failed. + +## 4. `Resize`, `Resize_Non_Zeroed` + +Resizes the memory region, of the size `old_size` located at the address +specified by `old_memory` to have the new size `size`. The slice of the new +memory region is returned from the procedure. The allocator may attempt to +keep the new memory region at the same address as the previous allocation, +however no such guarantee is made. Do not assume the new memory region will +be at the same address as the old memory region. + +If `old_memory` pointer is `nil`, this function acts just like `.Alloc` or +`.Alloc_Non_Zeroed`, using `size` and `alignment` to allocate a new memory +region. + +If `new_size` is `nil`, the procedure acts just like `.Free`, freeing the +memory region `old_size` bytes in length, located at the address specified by +`old_memory`. + +If the `old_memory` pointer is not aligned to the boundary specified by +`alignment`, the procedure relocates the buffer such that the reallocated +buffer is aligned to the boundary specified by `alignment`. + +**Inputs**: +- `allocator_data`: Pointer to the allocator data. +- `mode`: `.Resize` or `.Resize_All`. +- `size`: The desired new size of the memory region. +- `alignment`: The alignment of the new memory region, if its allocated +- `old_memory`: The pointer to the memory region to resize. +- `old_size`: The size of the memory region to resize. If the allocator + keeps track of the sizes of allocations, this parameter is optional. + +**Returns**: +1. The slice of the memory region after resize operation, if successfull, + `nil` otherwise. +2. An error, if the resize failed. + +**Note**: Some allocators may return `nil`, even if no error is returned. +Always check both the error and the allocated buffer. + +**Note**: if `old_size` is `0` and `old_memory` is `nil`, this operation is a +no-op, and should not return errors. */ Allocator_Proc :: runtime.Allocator_Proc -/* -Allocator_Proc :: #type proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, location: Source_Code_Location = #caller_location) -> ([]byte, Allocator_Error); -*/ +/* +Allocator. + +This type represents generic interface for all allocators. Currently this type +is defined as follows: + + Allocator :: struct { + procedure: Allocator_Proc, + data: rawptr, + } + +- `procedure`: Pointer to the allocation procedure. +- `data`: Pointer to the allocator data. +*/ Allocator :: runtime.Allocator -/* -Allocator :: struct { - procedure: Allocator_Proc, - data: rawptr, -} -*/ +/* +Default alignment. + +This value is the default alignment for all platforms that is used, if the +alignment is not specified explicitly. +*/ DEFAULT_ALIGNMENT :: 2*align_of(rawptr) +/* +Default page size. + +This value is the default page size for the current platform. +*/ DEFAULT_PAGE_SIZE :: 64 * 1024 when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 else 16 * 1024 when ODIN_OS == .Darwin && ODIN_ARCH == .arm64 else 4 * 1024 +/* +Allocate memory. + +This function allocates `size` bytes of memory, aligned to a boundary specified +by `alignment` using the allocator specified by `allocator`. + +If the `size` parameter is `0`, the operation is a no-op. + +**Inputs**: +- `size`: The desired size of the allocated memory region. +- `alignment`: The desired alignment of the allocated memory region. +- `allocator`: The allocator to allocate from. + +**Returns**: +1. Pointer to the allocated memory, or `nil` if allocation failed. +2. Error, if the allocation failed. + +**Errors**: +- `None`: If no error occurred. +- `Out_Of_Memory`: Occurs when the allocator runs out of space in any of its + backing buffers, the backing allocator has ran out of space, or an operating + system failure occurred. +- `Invalid_Argument`: If the supplied `size` is negative, alignment is not a + power of two. +*/ @(require_results) -alloc :: proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (rawptr, Allocator_Error) { +alloc :: proc( + size: int, + alignment: int = DEFAULT_ALIGNMENT, + allocator := context.allocator, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { data, err := runtime.mem_alloc(size, alignment, allocator, loc) return raw_data(data), err } +/* +Allocate memory. + +This function allocates `size` bytes of memory, aligned to a boundary specified +by `alignment` using the allocator specified by `allocator`. + +**Inputs**: +- `size`: The desired size of the allocated memory region. +- `alignment`: The desired alignment of the allocated memory region. +- `allocator`: The allocator to allocate from. + +**Returns**: +1. Slice of the allocated memory region, or `nil` if allocation failed. +2. Error, if the allocation failed. + +**Errors**: +- `None`: If no error occurred. +- `Out_Of_Memory`: Occurs when the allocator runs out of space in any of its + backing buffers, the backing allocator has ran out of space, or an operating + system failure occurred. +- `Invalid_Argument`: If the supplied `size` is negative, alignment is not a + power of two. +*/ @(require_results) -alloc_bytes :: proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { +alloc_bytes :: proc( + size: int, + alignment: int = DEFAULT_ALIGNMENT, + allocator := context.allocator, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { return runtime.mem_alloc(size, alignment, allocator, loc) } +/* +Allocate non-zeroed memory. + +This function allocates `size` bytes of memory, aligned to a boundary specified +by `alignment` using the allocator specified by `allocator`. This procedure +does not explicitly zero-initialize allocated memory region. + +**Inputs**: +- `size`: The desired size of the allocated memory region. +- `alignment`: The desired alignment of the allocated memory region. +- `allocator`: The allocator to allocate from. + +**Returns**: +1. Slice of the allocated memory region, or `nil` if allocation failed. +2. Error, if the allocation failed. + +**Errors**: +- `None`: If no error occurred. +- `Out_Of_Memory`: Occurs when the allocator runs out of space in any of its + backing buffers, the backing allocator has ran out of space, or an operating + system failure occurred. +- `Invalid_Argument`: If the supplied `size` is negative, alignment is not a + power of two. +*/ @(require_results) -alloc_bytes_non_zeroed :: proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { +alloc_bytes_non_zeroed :: proc( + size: int, + alignment: int = DEFAULT_ALIGNMENT, + allocator := context.allocator, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { return runtime.mem_alloc_non_zeroed(size, alignment, allocator, loc) } -free :: proc(ptr: rawptr, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { +/* +Free memory. + +This procedure frees memory region located at the address, specified by `ptr`, +allocated from the allocator specified by `allocator`. + +**Inputs**: +- `ptr`: Pointer to the memory region to free. +- `allocator`: The allocator to free to. + +**Returns**: +- The error, if freeing failed. + +**Errors**: +- `None`: When no error has occurred. +- `Invalid_Pointer`: The specified pointer is not owned by the specified allocator, + or does not point to a valid allocation. +- `Mode_Not_Implemented`: If the specified allocator does not support the `.Free` +mode. +*/ +free :: proc( + ptr: rawptr, + allocator := context.allocator, + loc := #caller_location, +) -> Allocator_Error { return runtime.mem_free(ptr, allocator, loc) } -free_with_size :: proc(ptr: rawptr, byte_count: int, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { - return runtime.mem_free_with_size(ptr, byte_count, allocator, loc) +/* +Free a memory region. + +This procedure frees `size` bytes of memory region located at the address, +specified by `ptr`, allocated from the allocator specified by `allocator`. + +If the `size` parameter is `0`, this call is equivalent to `free()`. + +**Inputs**: +- `ptr`: Pointer to the memory region to free. +- `size`: The size of the memory region to free. +- `allocator`: The allocator to free to. + +**Returns**: +- The error, if freeing failed. + +**Errors**: +- `None`: When no error has occurred. +- `Invalid_Pointer`: The specified pointer is not owned by the specified allocator, + or does not point to a valid allocation. +- `Mode_Not_Implemented`: If the specified allocator does not support the `.Free` +mode. +*/ +free_with_size :: proc( + ptr: rawptr, + size: int, + allocator := context.allocator, + loc := #caller_location, +) -> Allocator_Error { + return runtime.mem_free_with_size(ptr, size, allocator, loc) } -free_bytes :: proc(bytes: []byte, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { +/* +Free a memory region. + +This procedure frees memory region, specified by `bytes`, allocated from the +allocator specified by `allocator`. + +If the length of the specified slice is zero, the `.Invalid_Argument` error +is returned. + +**Inputs**: +- `bytes`: The memory region to free. +- `allocator`: The allocator to free to. + +**Returns**: +- The error, if freeing failed. + +**Errors**: +- `None`: When no error has occurred. +- `Invalid_Pointer`: The specified pointer is not owned by the specified allocator, + or does not point to a valid allocation. +- `Mode_Not_Implemented`: If the specified allocator does not support the `.Free` +mode. +*/ +free_bytes :: proc( + bytes: []byte, + allocator := context.allocator, + loc := #caller_location, +) -> Allocator_Error { return runtime.mem_free_bytes(bytes, allocator, loc) } +/* +Free all allocations. + +This procedure frees all allocations made on the allocator specified by +`allocator` to that allocator, making it available for further allocations. + +**Inputs**: +- `allocator`: The allocator to free to. + +**Errors**: +- `None`: When no error has occurred. +- `Mode_Not_Implemented`: If the specified allocator does not support the `.Free` +mode. +*/ free_all :: proc(allocator := context.allocator, loc := #caller_location) -> Allocator_Error { return runtime.mem_free_all(allocator, loc) } +/* +Resize a memory region. + +This procedure resizes a memory region, `old_size` bytes in size, located at +the address specified by `ptr`, such that it has a new size, specified by +`new_size` and and is aligned on a boundary specified by `alignment`. + +If the `ptr` parameter is `nil`, `resize()` acts just like `alloc()`, allocating +`new_size` bytes, aligned on a boundary specified by `alignment`. + +If the `new_size` parameter is `0`, `resize()` acts just like `free()`, freeing +the memory region `old_size` bytes in length, located at the address specified +by `ptr`. + +If the `old_memory` pointer is not aligned to the boundary specified by +`alignment`, the procedure relocates the buffer such that the reallocated +buffer is aligned to the boundary specified by `alignment`. + +**Inputs**: +- `ptr`: Pointer to the memory region to resize. +- `old_size`: Size of the memory region to resize. +- `new_size`: The desired size of the resized memory region. +- `alignment`: The desired alignment of the resized memory region. +- `allocator`: The owner of the memory region to resize. + +**Returns**: +1. The pointer to the resized memory region, if successfull, `nil` otherwise. +2. Error, if resize failed. + +**Errors**: +- `None`: No error. +- `Out_Of_Memory`: When the allocator's backing buffer or it's backing + allocator does not have enough space to fit in an allocation with the new + size, or an operating system failure occurs. +- `Invalid_Pointer`: The pointer referring to a memory region does not belong + to any of the allocators backing buffers or does not point to a valid start + of an allocation made in that allocator. +- `Invalid_Argument`: When `size` is negative, alignment is not a power of two, + or the `old_size` argument is incorrect. +- `Mode_Not_Implemented`: The allocator does not support the `.Realloc` mode. + +**Note**: if `old_size` is `0` and `old_memory` is `nil`, this operation is a +no-op, and should not return errors. +*/ @(require_results) -resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (rawptr, Allocator_Error) { +resize :: proc( + ptr: rawptr, + old_size: int, + new_size: int, + alignment: int = DEFAULT_ALIGNMENT, + allocator := context.allocator, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { data, err := runtime.mem_resize(ptr, old_size, new_size, alignment, allocator, loc) return raw_data(data), err } +/* +Resize a memory region without zero-initialization. + +This procedure resizes a memory region, `old_size` bytes in size, located at +the address specified by `ptr`, such that it has a new size, specified by +`new_size` and and is aligned on a boundary specified by `alignment`. + +If the `ptr` parameter is `nil`, `resize()` acts just like `alloc()`, allocating +`new_size` bytes, aligned on a boundary specified by `alignment`. + +If the `new_size` parameter is `0`, `resize()` acts just like `free()`, freeing +the memory region `old_size` bytes in length, located at the address specified +by `ptr`. + +If the `old_memory` pointer is not aligned to the boundary specified by +`alignment`, the procedure relocates the buffer such that the reallocated +buffer is aligned to the boundary specified by `alignment`. + +Unlike `resize()`, this procedure does not explicitly zero-initialize any new +memory. + +**Inputs**: +- `ptr`: Pointer to the memory region to resize. +- `old_size`: Size of the memory region to resize. +- `new_size`: The desired size of the resized memory region. +- `alignment`: The desired alignment of the resized memory region. +- `allocator`: The owner of the memory region to resize. + +**Returns**: +1. The pointer to the resized memory region, if successfull, `nil` otherwise. +2. Error, if resize failed. + +**Errors**: +- `None`: No error. +- `Out_Of_Memory`: When the allocator's backing buffer or it's backing + allocator does not have enough space to fit in an allocation with the new + size, or an operating system failure occurs. +- `Invalid_Pointer`: The pointer referring to a memory region does not belong + to any of the allocators backing buffers or does not point to a valid start + of an allocation made in that allocator. +- `Invalid_Argument`: When `size` is negative, alignment is not a power of two, + or the `old_size` argument is incorrect. +- `Mode_Not_Implemented`: The allocator does not support the `.Realloc` mode. + +**Note**: if `old_size` is `0` and `old_memory` is `nil`, this operation is a +no-op, and should not return errors. +*/ @(require_results) -resize_bytes :: proc(old_data: []byte, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { +resize_non_zeroed :: proc( + ptr: rawptr, + old_size: int, + new_size: int, + alignment: int = DEFAULT_ALIGNMENT, + allocator := context.allocator, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + data, err := runtime.non_zero_mem_resize(ptr, old_size, new_size, alignment, allocator, loc) + return raw_data(data), err +} + +/* +Resize a memory region. + +This procedure resizes a memory region, specified by `old_data`, such that it +has a new size, specified by `new_size` and and is aligned on a boundary +specified by `alignment`. + +If the `old_data` parameter is `nil`, `resize_bytes()` acts just like +`alloc_bytes()`, allocating `new_size` bytes, aligned on a boundary specified +by `alignment`. + +If the `new_size` parameter is `0`, `resize_bytes()` acts just like +`free_bytes()`, freeing the memory region specified by `old_data`. + +If the `old_memory` pointer is not aligned to the boundary specified by +`alignment`, the procedure relocates the buffer such that the reallocated +buffer is aligned to the boundary specified by `alignment`. + +**Inputs**: +- `old_data`: Pointer to the memory region to resize. +- `new_size`: The desired size of the resized memory region. +- `alignment`: The desired alignment of the resized memory region. +- `allocator`: The owner of the memory region to resize. + +**Returns**: +1. The resized memory region, if successfull, `nil` otherwise. +2. Error, if resize failed. + +**Errors**: +- `None`: No error. +- `Out_Of_Memory`: When the allocator's backing buffer or it's backing + allocator does not have enough space to fit in an allocation with the new + size, or an operating system failure occurs. +- `Invalid_Pointer`: The pointer referring to a memory region does not belong + to any of the allocators backing buffers or does not point to a valid start + of an allocation made in that allocator. +- `Invalid_Argument`: When `size` is negative, alignment is not a power of two, + or the `old_size` argument is incorrect. +- `Mode_Not_Implemented`: The allocator does not support the `.Realloc` mode. + +**Note**: if `old_size` is `0` and `old_memory` is `nil`, this operation is a +no-op, and should not return errors. +*/ +@(require_results) +resize_bytes :: proc( + old_data: []byte, + new_size: int, + alignment: int = DEFAULT_ALIGNMENT, + allocator := context.allocator, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { return runtime.mem_resize(raw_data(old_data), len(old_data), new_size, alignment, allocator, loc) } +/* +Resize a memory region. + +This procedure resizes a memory region, specified by `old_data`, such that it +has a new size, specified by `new_size` and and is aligned on a boundary +specified by `alignment`. + +If the `old_data` parameter is `nil`, `resize_bytes()` acts just like +`alloc_bytes()`, allocating `new_size` bytes, aligned on a boundary specified +by `alignment`. + +If the `new_size` parameter is `0`, `resize_bytes()` acts just like +`free_bytes()`, freeing the memory region specified by `old_data`. + +If the `old_memory` pointer is not aligned to the boundary specified by +`alignment`, the procedure relocates the buffer such that the reallocated +buffer is aligned to the boundary specified by `alignment`. + +Unlike `resize_bytes()`, this procedure does not explicitly zero-initialize +any new memory. + +**Inputs**: +- `old_data`: Pointer to the memory region to resize. +- `new_size`: The desired size of the resized memory region. +- `alignment`: The desired alignment of the resized memory region. +- `allocator`: The owner of the memory region to resize. + +**Returns**: +1. The resized memory region, if successfull, `nil` otherwise. +2. Error, if resize failed. + +**Errors**: +- `None`: No error. +- `Out_Of_Memory`: When the allocator's backing buffer or it's backing + allocator does not have enough space to fit in an allocation with the new + size, or an operating system failure occurs. +- `Invalid_Pointer`: The pointer referring to a memory region does not belong + to any of the allocators backing buffers or does not point to a valid start + of an allocation made in that allocator. +- `Invalid_Argument`: When `size` is negative, alignment is not a power of two, + or the `old_size` argument is incorrect. +- `Mode_Not_Implemented`: The allocator does not support the `.Realloc` mode. + +**Note**: if `old_size` is `0` and `old_memory` is `nil`, this operation is a +no-op, and should not return errors. +*/ +@(require_results) +resize_bytes_non_zeroed :: proc( + old_data: []byte, + new_size: int, + alignment: int = DEFAULT_ALIGNMENT, + allocator := context.allocator, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + return runtime.non_zero_mem_resize(raw_data(old_data), len(old_data), new_size, alignment, allocator, loc) +} + +/* +Query allocator features. +*/ @(require_results) query_features :: proc(allocator: Allocator, loc := #caller_location) -> (set: Allocator_Mode_Set) { if allocator.procedure != nil { @@ -114,8 +716,15 @@ query_features :: proc(allocator: Allocator, loc := #caller_location) -> (set: A return nil } +/* +Query allocator information. +*/ @(require_results) -query_info :: proc(pointer: rawptr, allocator: Allocator, loc := #caller_location) -> (props: Allocator_Query_Info) { +query_info :: proc( + pointer: rawptr, + allocator: Allocator, + loc := #caller_location, +) -> (props: Allocator_Query_Info) { props.pointer = pointer if allocator.procedure != nil { allocator.procedure(allocator.data, .Query_Info, 0, 0, &props, 0, loc) @@ -123,25 +732,62 @@ query_info :: proc(pointer: rawptr, allocator: Allocator, loc := #caller_locatio return } - - -delete_string :: proc(str: string, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { +/* +Free a string. +*/ +delete_string :: proc( + str: string, + allocator := context.allocator, + loc := #caller_location, +) -> Allocator_Error { return runtime.delete_string(str, allocator, loc) } -delete_cstring :: proc(str: cstring, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { + +/* +Free a cstring. +*/ +delete_cstring :: proc( + str: cstring, + allocator := context.allocator, + loc := #caller_location, +) -> Allocator_Error { return runtime.delete_cstring(str, allocator, loc) } -delete_dynamic_array :: proc(array: $T/[dynamic]$E, loc := #caller_location) -> Allocator_Error { + +/* +Free a dynamic array. +*/ +delete_dynamic_array :: proc( + array: $T/[dynamic]$E, + loc := #caller_location, +) -> Allocator_Error { return runtime.delete_dynamic_array(array, loc) } -delete_slice :: proc(array: $T/[]$E, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { + +/* +Free a slice. +*/ +delete_slice :: proc( + array: $T/[]$E, + allocator := context.allocator, + loc := #caller_location, +) -> Allocator_Error { return runtime.delete_slice(array, allocator, loc) } -delete_map :: proc(m: $T/map[$K]$V, loc := #caller_location) -> Allocator_Error { + +/* +Free a map. +*/ +delete_map :: proc( + m: $T/map[$K]$V, + loc := #caller_location, +) -> Allocator_Error { return runtime.delete_map(m, loc) } - +/* +Free. +*/ delete :: proc{ delete_string, delete_cstring, @@ -150,49 +796,177 @@ delete :: proc{ delete_map, } +/* +Allocate a new object. +This procedure allocates a new object of type `T` using an allocator specified +by `allocator`, and returns a pointer to the allocated object, if allocated +successfully, or `nil` otherwise. +*/ @(require_results) -new :: proc($T: typeid, allocator := context.allocator, loc := #caller_location) -> (^T, Allocator_Error) { +new :: proc( + $T: typeid, + allocator := context.allocator, + loc := #caller_location, +) -> (^T, Allocator_Error) { return new_aligned(T, align_of(T), allocator, loc) } + +/* +Allocate a new object with alignment. + +This procedure allocates a new object of type `T` using an allocator specified +by `allocator`, and returns a pointer, aligned on a boundary specified by +`alignment` to the allocated object, if allocated successfully, or `nil` +otherwise. +*/ @(require_results) -new_aligned :: proc($T: typeid, alignment: int, allocator := context.allocator, loc := #caller_location) -> (t: ^T, err: Allocator_Error) { +new_aligned :: proc( + $T: typeid, + alignment: int, + allocator := context.allocator, + loc := #caller_location, +) -> (t: ^T, err: Allocator_Error) { return runtime.new_aligned(T, alignment, allocator, loc) } + +/* +Allocate a new object and initialize it with a value. + +This procedure allocates a new object of type `T` using an allocator specified +by `allocator`, and returns a pointer, aligned on a boundary specified by +`alignment` to the allocated object, if allocated successfully, or `nil` +otherwise. The allocated object is initialized with `data`. +*/ @(require_results) -new_clone :: proc(data: $T, allocator := context.allocator, loc := #caller_location) -> (t: ^T, err: Allocator_Error) { +new_clone :: proc( + data: $T, + allocator := context.allocator, + loc := #caller_location, +) -> (t: ^T, err: Allocator_Error) { return runtime.new_clone(data, allocator, loc) } +/* +Allocate a new slice with alignment. + +This procedure allocates a new slice of type `T` with length `len`, aligned +on a boundary specified by `alignment` from an allocator specified by +`allocator`, and returns the allocated slice. +*/ @(require_results) -make_aligned :: proc($T: typeid/[]$E, #any_int len: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (slice: T, err: Allocator_Error) { +make_aligned :: proc( + $T: typeid/[]$E, + #any_int len: int, + alignment: int, + allocator := context.allocator, + loc := #caller_location, +) -> (slice: T, err: Allocator_Error) { return runtime.make_aligned(T, len, alignment, allocator, loc) } + +/* +Allocate a new slice. + +This procedure allocates a new slice of type `T` with length `len`, from an +allocator specified by `allocator`, and returns the allocated slice. +*/ @(require_results) -make_slice :: proc($T: typeid/[]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) { +make_slice :: proc( + $T: typeid/[]$E, + #any_int len: int, + allocator := context.allocator, + loc := #caller_location, +) -> (T, Allocator_Error) { return runtime.make_slice(T, len, allocator, loc) } + +/* +Allocate a dynamic array. + +This procedure creates a dynamic array of type `T`, with `allocator` as its +backing allocator, and initial length and capacity of `0`. +*/ @(require_results) -make_dynamic_array :: proc($T: typeid/[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) { +make_dynamic_array :: proc( + $T: typeid/[dynamic]$E, + allocator := context.allocator, + loc := #caller_location, +) -> (T, Allocator_Error) { return runtime.make_dynamic_array(T, allocator, loc) } + +/* +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`. +*/ @(require_results) -make_dynamic_array_len :: proc($T: typeid/[dynamic]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) { +make_dynamic_array_len :: proc( + $T: typeid/[dynamic]$E, + #any_int len: int, + allocator := context.allocator, + loc := #caller_location, +) -> (T, Allocator_Error) { return runtime.make_dynamic_array_len_cap(T, len, len, allocator, loc) } + +/* +Allocate a dynamic array with initial length and capacity. + +This procedure creates a 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_dynamic_array_len_cap :: proc($T: typeid/[dynamic]$E, #any_int len: int, #any_int cap: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) { +make_dynamic_array_len_cap :: proc( + $T: typeid/[dynamic]$E, + #any_int len: int, + #any_int cap: int, + allocator := context.allocator, + loc := #caller_location, +) -> (array: T, err: Allocator_Error) { return runtime.make_dynamic_array_len_cap(T, len, cap, allocator, loc) } + +/* +Allocate a map. + +This procedure creates a map of type `T` with initial capacity specified by +`cap`, that is using an allocator specified by `allocator` as its backing +allocator. +*/ @(require_results) -make_map :: proc($T: typeid/map[$K]$E, #any_int cap: int = 1< (m: T, err: Allocator_Error) { +make_map :: proc( + $T: typeid/map[$K]$E, + #any_int cap: int = 1< (m: T, err: Allocator_Error) { return runtime.make_map(T, cap, allocator, loc) } + +/* +Allocate a multi pointer. + +This procedure allocates a multipointer of type `T` pointing to `len` elements, +from an allocator specified by `allocator`. +*/ @(require_results) -make_multi_pointer :: proc($T: typeid/[^]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (mp: T, err: Allocator_Error) { +make_multi_pointer :: proc( + $T: typeid/[^]$E, + #any_int len: int, + allocator := context.allocator, + loc := #caller_location +) -> (mp: T, err: Allocator_Error) { return runtime.make_multi_pointer(T, len, allocator, loc) } +/* +Allocate. +*/ make :: proc{ make_slice, make_dynamic_array, @@ -202,26 +976,112 @@ make :: proc{ make_multi_pointer, } +/* +Default resize procedure. +When allocator does not support resize operation, but supports `.Alloc` and +`.Free`, this procedure is used to implement allocator's default behavior on +resize. + +The behavior of the function is as follows: + +- If `new_size` is `0`, the function acts like `free()`, freeing the memory + region of `old_size` bytes located at `old_memory`. +- If `old_memory` is `nil`, the function acts like `alloc()`, allocating + `new_size` bytes of memory aligned on a boundary specified by `alignment`. +- Otherwise, a new memory region of size `new_size` is allocated, then the + data from the old memory region is copied and the old memory region is + freed. +*/ @(require_results) -default_resize_align :: proc(old_memory: rawptr, old_size, new_size, alignment: int, allocator := context.allocator, loc := #caller_location) -> (res: rawptr, err: Allocator_Error) { +default_resize_align :: proc( + old_memory: rawptr, + old_size: int, + new_size: int, + alignment: int, + allocator := context.allocator, + loc := #caller_location, +) -> (res: rawptr, err: Allocator_Error) { data: []byte - data, err = default_resize_bytes_align(([^]byte)(old_memory)[:old_size], new_size, alignment, allocator, loc) + data, err = default_resize_bytes_align( + ([^]byte) (old_memory)[:old_size], + new_size, + alignment, + allocator, + loc, + ) res = raw_data(data) return } +/* +Default resize procedure. + +When allocator does not support resize operation, but supports +`.Alloc_Non_Zeroed` and `.Free`, this procedure is used to implement allocator's +default behavior on resize. + +Unlike `default_resize_align` no new memory is being explicitly +zero-initialized. + +The behavior of the function is as follows: + +- If `new_size` is `0`, the function acts like `free()`, freeing the memory + region of `old_size` bytes located at `old_memory`. +- If `old_memory` is `nil`, the function acts like `alloc()`, allocating + `new_size` bytes of memory aligned on a boundary specified by `alignment`. +- Otherwise, a new memory region of size `new_size` is allocated, then the + data from the old memory region is copied and the old memory region is + freed. +*/ @(require_results) -default_resize_bytes_align_non_zeroed :: proc(old_data: []byte, new_size, alignment: int, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { +default_resize_bytes_align_non_zeroed :: proc( + old_data: []byte, + new_size: int, + alignment: int, + allocator := context.allocator, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { return _default_resize_bytes_align(old_data, new_size, alignment, false, allocator, loc) } + +/* +Default resize procedure. + +When allocator does not support resize operation, but supports `.Alloc` and +`.Free`, this procedure is used to implement allocator's default behavior on +resize. + +The behavior of the function is as follows: + +- If `new_size` is `0`, the function acts like `free()`, freeing the memory + region specified by `old_data`. +- If `old_data` is `nil`, the function acts like `alloc()`, allocating + `new_size` bytes of memory aligned on a boundary specified by `alignment`. +- Otherwise, a new memory region of size `new_size` is allocated, then the + data from the old memory region is copied and the old memory region is + freed. +*/ @(require_results) -default_resize_bytes_align :: proc(old_data: []byte, new_size, alignment: int, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { +default_resize_bytes_align :: proc( + old_data: []byte, + new_size: int, + alignment: int, + allocator := context.allocator, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { return _default_resize_bytes_align(old_data, new_size, alignment, true, allocator, loc) } @(require_results) -_default_resize_bytes_align :: #force_inline proc(old_data: []byte, new_size, alignment: int, should_zero: bool, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { +_default_resize_bytes_align :: #force_inline proc( + old_data: []byte, + new_size: int, + alignment: int, + should_zero: bool, + allocator := context.allocator, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { old_memory := raw_data(old_data) old_size := len(old_data) if old_memory == nil { @@ -231,16 +1091,13 @@ _default_resize_bytes_align :: #force_inline proc(old_data: []byte, new_size, al return alloc_bytes_non_zeroed(new_size, alignment, allocator, loc) } } - if new_size == 0 { err := free_bytes(old_data, allocator, loc) return nil, err } - - if new_size == old_size { + if new_size == old_size && is_aligned(old_memory, alignment) { return old_data, .None } - new_memory : []byte err : Allocator_Error if should_zero { @@ -251,7 +1108,6 @@ _default_resize_bytes_align :: #force_inline proc(old_data: []byte, new_size, al if new_memory == nil || err != nil { return nil, err } - runtime.copy(new_memory, old_data) free_bytes(old_data, allocator, loc) return new_memory, err diff --git a/core/mem/allocators.odin b/core/mem/allocators.odin index a5b93ad05..13d509f1e 100644 --- a/core/mem/allocators.odin +++ b/core/mem/allocators.odin @@ -3,816 +3,56 @@ package mem import "base:intrinsics" import "base:runtime" -nil_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { - return nil, nil -} +/* +Nil allocator. +The `nil` allocator returns `nil` on every allocation attempt. This type of +allocator can be used in scenarios where memory doesn't need to be allocated, +but an attempt to allocate memory is not an error. +*/ +@(require_results) nil_allocator :: proc() -> Allocator { return Allocator{ procedure = nil_allocator_proc, - data = nil, + data = nil, } } -// Custom allocators - -Arena :: struct { - data: []byte, - offset: int, - peak_used: int, - temp_count: int, -} - -Arena_Temp_Memory :: struct { - arena: ^Arena, - prev_offset: int, -} - - -arena_init :: proc(a: ^Arena, data: []byte) { - a.data = data - a.offset = 0 - a.peak_used = 0 - a.temp_count = 0 -} - -@(deprecated="prefer 'mem.arena_init'") -init_arena :: proc(a: ^Arena, data: []byte) { - a.data = data - a.offset = 0 - a.peak_used = 0 - a.temp_count = 0 -} - -@(require_results) -arena_allocator :: proc(arena: ^Arena) -> Allocator { - return Allocator{ - procedure = arena_allocator_proc, - data = arena, - } -} - -arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, location := #caller_location) -> ([]byte, Allocator_Error) { - arena := cast(^Arena)allocator_data - - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - #no_bounds_check end := &arena.data[arena.offset] - - ptr := align_forward(end, uintptr(alignment)) - - total_size := size + ptr_sub((^byte)(ptr), (^byte)(end)) - - if arena.offset + total_size > len(arena.data) { - return nil, .Out_Of_Memory - } - - arena.offset += total_size - arena.peak_used = max(arena.peak_used, arena.offset) - if mode != .Alloc_Non_Zeroed { - zero(ptr, size) - } - return byte_slice(ptr, size), nil - - case .Free: - return nil, .Mode_Not_Implemented - - case .Free_All: - arena.offset = 0 - - case .Resize: - return default_resize_bytes_align(byte_slice(old_memory, old_size), size, alignment, arena_allocator(arena)) - - case .Resize_Non_Zeroed: - return default_resize_bytes_align_non_zeroed(byte_slice(old_memory, old_size), size, alignment, arena_allocator(arena)) - - case .Query_Features: - set := (^Allocator_Mode_Set)(old_memory) - if set != nil { - set^ = {.Alloc, .Alloc_Non_Zeroed, .Free_All, .Resize, .Resize_Non_Zeroed, .Query_Features} - } - return nil, nil - - case .Query_Info: - return nil, .Mode_Not_Implemented - } - - return nil, nil -} - -@(require_results) -begin_arena_temp_memory :: proc(a: ^Arena) -> Arena_Temp_Memory { - tmp: Arena_Temp_Memory - tmp.arena = a - tmp.prev_offset = a.offset - a.temp_count += 1 - return tmp -} - -end_arena_temp_memory :: proc(tmp: Arena_Temp_Memory) { - assert(tmp.arena.offset >= tmp.prev_offset) - assert(tmp.arena.temp_count > 0) - tmp.arena.offset = tmp.prev_offset - tmp.arena.temp_count -= 1 -} - - - -Scratch_Allocator :: struct { - data: []byte, - curr_offset: int, - prev_allocation: rawptr, - backup_allocator: Allocator, - leaked_allocations: [dynamic][]byte, -} - -scratch_allocator_init :: proc(s: ^Scratch_Allocator, size: int, backup_allocator := context.allocator) -> Allocator_Error { - s.data = make_aligned([]byte, size, 2*align_of(rawptr), backup_allocator) or_return - s.curr_offset = 0 - s.prev_allocation = nil - s.backup_allocator = backup_allocator - s.leaked_allocations.allocator = backup_allocator - return nil -} - -scratch_allocator_destroy :: proc(s: ^Scratch_Allocator) { - if s == nil { - return - } - for ptr in s.leaked_allocations { - free_bytes(ptr, s.backup_allocator) - } - delete(s.leaked_allocations) - delete(s.data, s.backup_allocator) - s^ = {} -} - -scratch_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { - - s := (^Scratch_Allocator)(allocator_data) - - if s.data == nil { - DEFAULT_BACKING_SIZE :: 4 * Megabyte - if !(context.allocator.procedure != scratch_allocator_proc && - context.allocator.data != allocator_data) { - panic("cyclic initialization of the scratch allocator with itself") - } - scratch_allocator_init(s, DEFAULT_BACKING_SIZE) - } - - size := size - - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - size = align_forward_int(size, alignment) - - switch { - case s.curr_offset+size <= len(s.data): - start := uintptr(raw_data(s.data)) - ptr := start + uintptr(s.curr_offset) - ptr = align_forward_uintptr(ptr, uintptr(alignment)) - if mode != .Alloc_Non_Zeroed { - zero(rawptr(ptr), size) - } - - s.prev_allocation = rawptr(ptr) - offset := int(ptr - start) - s.curr_offset = offset + size - return byte_slice(rawptr(ptr), size), nil - - case size <= len(s.data): - start := uintptr(raw_data(s.data)) - ptr := align_forward_uintptr(start, uintptr(alignment)) - if mode != .Alloc_Non_Zeroed { - zero(rawptr(ptr), size) - } - - s.prev_allocation = rawptr(ptr) - offset := int(ptr - start) - s.curr_offset = offset + size - return byte_slice(rawptr(ptr), size), nil - } - a := s.backup_allocator - if a.procedure == nil { - a = context.allocator - s.backup_allocator = a - } - - ptr, err := alloc_bytes(size, alignment, a, loc) - if err != nil { - return ptr, err - } - if s.leaked_allocations == nil { - s.leaked_allocations, err = make([dynamic][]byte, a) - } - append(&s.leaked_allocations, ptr) - - if logger := context.logger; logger.lowest_level <= .Warning { - if logger.procedure != nil { - logger.procedure(logger.data, .Warning, "mem.Scratch_Allocator resorted to backup_allocator" , logger.options, loc) - } - } - - return ptr, err - - case .Free: - if old_memory == nil { - return nil, nil - } - start := uintptr(raw_data(s.data)) - end := start + uintptr(len(s.data)) - old_ptr := uintptr(old_memory) - - if s.prev_allocation == old_memory { - s.curr_offset = int(uintptr(s.prev_allocation) - start) - s.prev_allocation = nil - return nil, nil - } - - if start <= old_ptr && old_ptr < end { - // NOTE(bill): Cannot free this pointer but it is valid - return nil, nil - } - - if len(s.leaked_allocations) != 0 { - for data, i in s.leaked_allocations { - ptr := raw_data(data) - if ptr == old_memory { - free_bytes(data, s.backup_allocator) - ordered_remove(&s.leaked_allocations, i) - return nil, nil - } - } - } - return nil, .Invalid_Pointer - // panic("invalid pointer passed to default_temp_allocator"); - - case .Free_All: - s.curr_offset = 0 - s.prev_allocation = nil - for ptr in s.leaked_allocations { - free_bytes(ptr, s.backup_allocator) - } - clear(&s.leaked_allocations) - - case .Resize, .Resize_Non_Zeroed: - begin := uintptr(raw_data(s.data)) - end := begin + uintptr(len(s.data)) - old_ptr := uintptr(old_memory) - if begin <= old_ptr && old_ptr < end && old_ptr+uintptr(size) < end { - s.curr_offset = int(old_ptr-begin)+size - return byte_slice(old_memory, size), nil - } - data, err := scratch_allocator_proc(allocator_data, .Alloc, size, alignment, old_memory, old_size, loc) - if err != nil { - return data, err - } - runtime.copy(data, byte_slice(old_memory, old_size)) - _, err = scratch_allocator_proc(allocator_data, .Free, 0, alignment, old_memory, old_size, loc) - return data, err - - case .Query_Features: - set := (^Allocator_Mode_Set)(old_memory) - if set != nil { - set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Resize_Non_Zeroed, .Query_Features} - } - return nil, nil - - case .Query_Info: - return nil, .Mode_Not_Implemented - } - - return nil, nil -} - -@(require_results) -scratch_allocator :: proc(allocator: ^Scratch_Allocator) -> Allocator { - return Allocator{ - procedure = scratch_allocator_proc, - data = allocator, - } -} - - - - - -Stack_Allocation_Header :: struct { - prev_offset: int, - padding: int, -} - -// Stack is a stack-like allocator which has a strict memory freeing order -Stack :: struct { - data: []byte, - prev_offset: int, - curr_offset: int, - peak_used: int, -} - -stack_init :: proc(s: ^Stack, data: []byte) { - s.data = data - s.prev_offset = 0 - s.curr_offset = 0 - s.peak_used = 0 -} - -@(deprecated="prefer 'mem.stack_init'") -init_stack :: proc(s: ^Stack, data: []byte) { - s.data = data - s.prev_offset = 0 - s.curr_offset = 0 - s.peak_used = 0 -} - -@(require_results) -stack_allocator :: proc(stack: ^Stack) -> Allocator { - return Allocator{ - procedure = stack_allocator_proc, - data = stack, - } -} - - -stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, location := #caller_location) -> ([]byte, Allocator_Error) { - s := cast(^Stack)allocator_data - - if s.data == nil { - return nil, .Invalid_Argument - } - - raw_alloc :: proc(s: ^Stack, size, alignment: int, zero_memory: bool) -> ([]byte, Allocator_Error) { - curr_addr := uintptr(raw_data(s.data)) + uintptr(s.curr_offset) - padding := calc_padding_with_header(curr_addr, uintptr(alignment), size_of(Stack_Allocation_Header)) - if s.curr_offset + padding + size > len(s.data) { - return nil, .Out_Of_Memory - } - s.prev_offset = s.curr_offset - s.curr_offset += padding - - next_addr := curr_addr + uintptr(padding) - header := (^Stack_Allocation_Header)(next_addr - size_of(Stack_Allocation_Header)) - header.padding = padding - header.prev_offset = s.prev_offset - - s.curr_offset += size - - s.peak_used = max(s.peak_used, s.curr_offset) - - if zero_memory { - zero(rawptr(next_addr), size) - } - return byte_slice(rawptr(next_addr), size), nil - } - - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - return raw_alloc(s, size, alignment, mode == .Alloc) - case .Free: - if old_memory == nil { - return nil, nil - } - start := uintptr(raw_data(s.data)) - end := start + uintptr(len(s.data)) - curr_addr := uintptr(old_memory) - - if !(start <= curr_addr && curr_addr < end) { - panic("Out of bounds memory address passed to stack allocator (free)") - } - - if curr_addr >= start+uintptr(s.curr_offset) { - // NOTE(bill): Allow double frees - return nil, nil - } - - header := (^Stack_Allocation_Header)(curr_addr - size_of(Stack_Allocation_Header)) - old_offset := int(curr_addr - uintptr(header.padding) - uintptr(raw_data(s.data))) - - if old_offset != header.prev_offset { - // panic("Out of order stack allocator free"); - return nil, .Invalid_Pointer - } - - s.curr_offset = old_offset - s.prev_offset = header.prev_offset - - case .Free_All: - s.prev_offset = 0 - s.curr_offset = 0 - - case .Resize, .Resize_Non_Zeroed: - if old_memory == nil { - return raw_alloc(s, size, alignment, mode == .Resize) - } - if size == 0 { - return nil, nil - } - - start := uintptr(raw_data(s.data)) - end := start + uintptr(len(s.data)) - curr_addr := uintptr(old_memory) - if !(start <= curr_addr && curr_addr < end) { - panic("Out of bounds memory address passed to stack allocator (resize)") - } - - if curr_addr >= start+uintptr(s.curr_offset) { - // NOTE(bill): Allow double frees - return nil, nil - } - - if old_size == size { - return byte_slice(old_memory, size), nil - } - - header := (^Stack_Allocation_Header)(curr_addr - size_of(Stack_Allocation_Header)) - old_offset := int(curr_addr - uintptr(header.padding) - uintptr(raw_data(s.data))) - - if old_offset != header.prev_offset { - data, err := raw_alloc(s, size, alignment, mode == .Resize) - if err == nil { - runtime.copy(data, byte_slice(old_memory, old_size)) - } - return data, err - } - - old_memory_size := uintptr(s.curr_offset) - (curr_addr - start) - assert(old_memory_size == uintptr(old_size)) - - diff := size - old_size - s.curr_offset += diff // works for smaller sizes too - if diff > 0 { - zero(rawptr(curr_addr + uintptr(diff)), diff) - } - - return byte_slice(old_memory, size), nil - - case .Query_Features: - set := (^Allocator_Mode_Set)(old_memory) - if set != nil { - set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Resize_Non_Zeroed, .Query_Features} - } - return nil, nil - case .Query_Info: - return nil, .Mode_Not_Implemented - } - +nil_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { return nil, nil } +/* +Panic allocator. - - - - -Small_Stack_Allocation_Header :: struct { - padding: u8, -} - -// Small_Stack is a stack-like allocator which uses the smallest possible header but at the cost of non-strict memory freeing order -Small_Stack :: struct { - data: []byte, - offset: int, - peak_used: int, -} - -small_stack_init :: proc(s: ^Small_Stack, data: []byte) { - s.data = data - s.offset = 0 - s.peak_used = 0 -} - -@(deprecated="prefer 'small_stack_init'") -init_small_stack :: proc(s: ^Small_Stack, data: []byte) { - s.data = data - s.offset = 0 - s.peak_used = 0 -} - +The panic allocator is a type of allocator that panics on any allocation +attempt. This type of allocator can be used in scenarios where memory should +not be allocated, and an attempt to allocate memory is an error. +*/ @(require_results) -small_stack_allocator :: proc(stack: ^Small_Stack) -> Allocator { +panic_allocator :: proc() -> Allocator { return Allocator{ - procedure = small_stack_allocator_proc, - data = stack, + procedure = panic_allocator_proc, + data = nil, } } -small_stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, location := #caller_location) -> ([]byte, Allocator_Error) { - s := cast(^Small_Stack)allocator_data - - if s.data == nil { - return nil, .Invalid_Argument - } - - align := clamp(alignment, 1, 8*size_of(Stack_Allocation_Header{}.padding)/2) - - raw_alloc :: proc(s: ^Small_Stack, size, alignment: int, zero_memory: bool) -> ([]byte, Allocator_Error) { - curr_addr := uintptr(raw_data(s.data)) + uintptr(s.offset) - padding := calc_padding_with_header(curr_addr, uintptr(alignment), size_of(Small_Stack_Allocation_Header)) - if s.offset + padding + size > len(s.data) { - return nil, .Out_Of_Memory - } - s.offset += padding - - next_addr := curr_addr + uintptr(padding) - header := (^Small_Stack_Allocation_Header)(next_addr - size_of(Small_Stack_Allocation_Header)) - header.padding = auto_cast padding - - s.offset += size - - s.peak_used = max(s.peak_used, s.offset) - - if zero_memory { - zero(rawptr(next_addr), size) - } - return byte_slice(rawptr(next_addr), size), nil - } - - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - return raw_alloc(s, size, align, mode == .Alloc) - case .Free: - if old_memory == nil { - return nil, nil - } - start := uintptr(raw_data(s.data)) - end := start + uintptr(len(s.data)) - curr_addr := uintptr(old_memory) - - if !(start <= curr_addr && curr_addr < end) { - // panic("Out of bounds memory address passed to stack allocator (free)"); - return nil, .Invalid_Pointer - } - - if curr_addr >= start+uintptr(s.offset) { - // NOTE(bill): Allow double frees - return nil, nil - } - - header := (^Small_Stack_Allocation_Header)(curr_addr - size_of(Small_Stack_Allocation_Header)) - old_offset := int(curr_addr - uintptr(header.padding) - uintptr(raw_data(s.data))) - - s.offset = old_offset - - case .Free_All: - s.offset = 0 - - case .Resize, .Resize_Non_Zeroed: - if old_memory == nil { - return raw_alloc(s, size, align, mode == .Resize) - } - if size == 0 { - return nil, nil - } - - start := uintptr(raw_data(s.data)) - end := start + uintptr(len(s.data)) - curr_addr := uintptr(old_memory) - if !(start <= curr_addr && curr_addr < end) { - // panic("Out of bounds memory address passed to stack allocator (resize)"); - return nil, .Invalid_Pointer - } - - if curr_addr >= start+uintptr(s.offset) { - // NOTE(bill): Treat as a double free - return nil, nil - } - - if old_size == size { - return byte_slice(old_memory, size), nil - } - - data, err := raw_alloc(s, size, align, mode == .Resize) - if err == nil { - runtime.copy(data, byte_slice(old_memory, old_size)) - } - return data, err - - case .Query_Features: - set := (^Allocator_Mode_Set)(old_memory) - if set != nil { - set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Resize_Non_Zeroed, .Query_Features} - } - return nil, nil - - case .Query_Info: - return nil, .Mode_Not_Implemented - } - - return nil, nil -} - - - - - -Dynamic_Pool :: struct { - block_size: int, - out_band_size: int, - alignment: int, - - unused_blocks: [dynamic]rawptr, - used_blocks: [dynamic]rawptr, - out_band_allocations: [dynamic]rawptr, - - current_block: rawptr, - current_pos: rawptr, - bytes_left: int, - - block_allocator: Allocator, -} - - -DYNAMIC_POOL_BLOCK_SIZE_DEFAULT :: 65536 -DYNAMIC_POOL_OUT_OF_BAND_SIZE_DEFAULT :: 6554 - - - -dynamic_pool_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { - pool := (^Dynamic_Pool)(allocator_data) - - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - return dynamic_pool_alloc_bytes(pool, size) - case .Free: - return nil, .Mode_Not_Implemented - case .Free_All: - dynamic_pool_free_all(pool) - return nil, nil - case .Resize, .Resize_Non_Zeroed: - if old_size >= size { - return byte_slice(old_memory, size), nil - } - data, err := dynamic_pool_alloc_bytes(pool, size) - if err == nil { - runtime.copy(data, byte_slice(old_memory, old_size)) - } - return data, err - - case .Query_Features: - set := (^Allocator_Mode_Set)(old_memory) - if set != nil { - set^ = {.Alloc, .Alloc_Non_Zeroed, .Free_All, .Resize, .Resize_Non_Zeroed, .Query_Features, .Query_Info} - } - return nil, nil - - case .Query_Info: - info := (^Allocator_Query_Info)(old_memory) - if info != nil && info.pointer != nil { - info.size = pool.block_size - info.alignment = pool.alignment - return byte_slice(info, size_of(info^)), nil - } - return nil, nil - } - return nil, nil -} - - -@(require_results) -dynamic_pool_allocator :: proc(pool: ^Dynamic_Pool) -> Allocator { - return Allocator{ - procedure = dynamic_pool_allocator_proc, - data = pool, - } -} - -dynamic_pool_init :: proc(pool: ^Dynamic_Pool, - block_allocator := context.allocator, - array_allocator := context.allocator, - block_size := DYNAMIC_POOL_BLOCK_SIZE_DEFAULT, - out_band_size := DYNAMIC_POOL_OUT_OF_BAND_SIZE_DEFAULT, - alignment := 8) { - pool.block_size = block_size - pool.out_band_size = out_band_size - pool.alignment = alignment - pool.block_allocator = block_allocator - pool.out_band_allocations.allocator = array_allocator - pool. unused_blocks.allocator = array_allocator - pool. used_blocks.allocator = array_allocator -} - -dynamic_pool_destroy :: proc(pool: ^Dynamic_Pool) { - dynamic_pool_free_all(pool) - delete(pool.unused_blocks) - delete(pool.used_blocks) - delete(pool.out_band_allocations) - - zero(pool, size_of(pool^)) -} - - -@(require_results) -dynamic_pool_alloc :: proc(pool: ^Dynamic_Pool, bytes: int) -> (rawptr, Allocator_Error) { - data, err := dynamic_pool_alloc_bytes(pool, bytes) - return raw_data(data), err -} - -@(require_results) -dynamic_pool_alloc_bytes :: proc(p: ^Dynamic_Pool, bytes: int) -> ([]byte, Allocator_Error) { - cycle_new_block :: proc(p: ^Dynamic_Pool) -> (err: Allocator_Error) { - if p.block_allocator.procedure == nil { - panic("You must call pool_init on a Pool before using it") - } - - if p.current_block != nil { - append(&p.used_blocks, p.current_block) - } - - new_block: rawptr - if len(p.unused_blocks) > 0 { - new_block = pop(&p.unused_blocks) - } else { - data: []byte - data, err = p.block_allocator.procedure(p.block_allocator.data, Allocator_Mode.Alloc, - p.block_size, p.alignment, - nil, 0) - new_block = raw_data(data) - } - - p.bytes_left = p.block_size - p.current_pos = new_block - p.current_block = new_block - return - } - - n := align_formula(bytes, p.alignment) - if n > p.block_size { - return nil, .Invalid_Argument - } - if n >= p.out_band_size { - assert(p.block_allocator.procedure != nil) - memory, err := p.block_allocator.procedure(p.block_allocator.data, Allocator_Mode.Alloc, - p.block_size, p.alignment, - nil, 0) - if memory != nil { - append(&p.out_band_allocations, raw_data(memory)) - } - return memory, err - } - - if p.bytes_left < n { - err := cycle_new_block(p) - if err != nil { - return nil, err - } - if p.current_block == nil { - return nil, .Out_Of_Memory - } - } - - memory := p.current_pos - p.current_pos = ([^]byte)(p.current_pos)[n:] - p.bytes_left -= n - return ([^]byte)(memory)[:bytes], nil -} - - -dynamic_pool_reset :: proc(p: ^Dynamic_Pool) { - if p.current_block != nil { - append(&p.unused_blocks, p.current_block) - p.current_block = nil - } - - for block in p.used_blocks { - append(&p.unused_blocks, block) - } - clear(&p.used_blocks) - - for a in p.out_band_allocations { - free(a, p.block_allocator) - } - clear(&p.out_band_allocations) - - p.bytes_left = 0 // Make new allocations call `cycle_new_block` again. -} - -dynamic_pool_free_all :: proc(p: ^Dynamic_Pool) { - dynamic_pool_reset(p) - - for block in p.unused_blocks { - free(block, p.block_allocator) - } - clear(&p.unused_blocks) -} - - -panic_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int,loc := #caller_location) -> ([]byte, Allocator_Error) { - +panic_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { switch mode { case .Alloc: if size > 0 { @@ -836,7 +76,6 @@ panic_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, } case .Free_All: panic("mem: panic allocator, .Free_All called", loc=loc) - case .Query_Features: set := (^Allocator_Mode_Set)(old_memory) if set != nil { @@ -847,33 +86,1871 @@ panic_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, case .Query_Info: panic("mem: panic allocator, .Query_Info called", loc=loc) } - return nil, nil } + +/* +Arena allocator data. +*/ +Arena :: struct { + data: []byte, + offset: int, + peak_used: int, + temp_count: int, +} + +/* +Arena allocator. + +The arena allocator (also known as a linear allocator, bump allocator, +region allocator) is an allocator that uses a single backing buffer for +allocations. + +The buffer is being used contiguously, from start by end. Each subsequent +allocation occupies the next adjacent region of memory in the buffer. Since +arena allocator does not keep track of any metadata associated with the +allocations and their locations, it is impossible to free individual +allocations. + +The arena allocator can be used for temporary allocations in frame-based memory +management. Games are one example of such applications. A global arena can be +used for any temporary memory allocations, and at the end of each frame all +temporary allocations are freed. Since no temporary object is going to live +longer than a frame, no lifetimes are violated. +*/ @(require_results) -panic_allocator :: proc() -> Allocator { +arena_allocator :: proc(arena: ^Arena) -> Allocator { return Allocator{ - procedure = panic_allocator_proc, - data = nil, + procedure = arena_allocator_proc, + data = arena, } } +/* +Initialize an arena. + +This procedure initializes the arena `a` with memory region `data` as it's +backing buffer. +*/ +arena_init :: proc(a: ^Arena, data: []byte) { + a.data = data + a.offset = 0 + a.peak_used = 0 + a.temp_count = 0 +} + +@(deprecated="prefer 'mem.arena_init'") +init_arena :: proc(a: ^Arena, data: []byte) { + a.data = data + a.offset = 0 + a.peak_used = 0 + a.temp_count = 0 +} + +/* +Allocate memory from an arena. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment` from an arena `a`. The allocated memory is zero-initialized. +This procedure returns a pointer to the newly allocated memory region. +*/ +@(require_results) +arena_alloc :: proc( + a: ^Arena, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := arena_alloc_bytes(a, size, alignment, loc) + return raw_data(bytes), err +} + +/* +Allocate memory from an arena. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment` from an arena `a`. The allocated memory is zero-initialized. +This procedure returns a slice of the newly allocated memory region. +*/ +@(require_results) +arena_alloc_bytes :: proc( + a: ^Arena, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + bytes, err := arena_alloc_bytes_non_zeroed(a, size, alignment, loc) + if bytes != nil { + zero_slice(bytes) + } + return bytes, err +} + +/* +Allocate non-initialized memory from an arena. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment` from an arena `a`. The allocated memory is not explicitly +zero-initialized. This procedure returns a pointer to the newly allocated +memory region. +*/ +@(require_results) +arena_alloc_non_zeroed :: proc( + a: ^Arena, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := arena_alloc_bytes_non_zeroed(a, size, alignment, loc) + return raw_data(bytes), err +} + +/* +Allocate non-initialized memory from an arena. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment` from an arena `a`. The allocated memory is not explicitly +zero-initialized. This procedure returns a slice of the newly allocated +memory region. +*/ +@(require_results) +arena_alloc_bytes_non_zeroed :: proc( + a: ^Arena, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location +) -> ([]byte, Allocator_Error) { + if a.data == nil { + panic("Arena is not initialized", loc) + } + #no_bounds_check end := &a.data[a.offset] + ptr := align_forward(end, uintptr(alignment)) + total_size := size + ptr_sub((^byte)(ptr), (^byte)(end)) + if a.offset + total_size > len(a.data) { + return nil, .Out_Of_Memory + } + a.offset += total_size + a.peak_used = max(a.peak_used, a.offset) + return byte_slice(ptr, size), nil +} + +/* +Free all memory to an arena. +*/ +arena_free_all :: proc(a: ^Arena) { + a.offset = 0 +} + +arena_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size: int, + alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + arena := cast(^Arena)allocator_data + switch mode { + case .Alloc: + return arena_alloc_bytes(arena, size, alignment, loc) + case .Alloc_Non_Zeroed: + return arena_alloc_bytes_non_zeroed(arena, size, alignment, loc) + case .Free: + return nil, .Mode_Not_Implemented + case .Free_All: + arena_free_all(arena) + case .Resize: + return default_resize_bytes_align(byte_slice(old_memory, old_size), size, alignment, arena_allocator(arena), loc) + case .Resize_Non_Zeroed: + return default_resize_bytes_align_non_zeroed(byte_slice(old_memory, old_size), size, alignment, arena_allocator(arena), loc) + case .Query_Features: + set := (^Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Alloc, .Alloc_Non_Zeroed, .Free_All, .Resize, .Resize_Non_Zeroed, .Query_Features} + } + return nil, nil + case .Query_Info: + return nil, .Mode_Not_Implemented + } + return nil, nil +} + +/* +Temporary memory region of arena. + +Temporary memory regions of arena act as "savepoints" for arena. When one is +created, the subsequent allocations are done inside the temporary memory +region. When `end_arena_temp_memory` is called, the arena is rolled back, and +all of the memory that was allocated from the arena will be freed. + +Multiple temporary memory regions can exist at the same time for an arena. +*/ +Arena_Temp_Memory :: struct { + arena: ^Arena, + prev_offset: int, +} + +/* +Start a temporary memory region. + +This procedure creates a temporary memory region. After a temporary memory +region is created, all allocations are said to be *inside* the temporary memory +region, until `end_arena_temp_memory` is called. +*/ +@(require_results) +begin_arena_temp_memory :: proc(a: ^Arena) -> Arena_Temp_Memory { + tmp: Arena_Temp_Memory + tmp.arena = a + tmp.prev_offset = a.offset + a.temp_count += 1 + return tmp +} + +/* +End a temporary memory region. + +This procedure ends the temporary memory region for an arena. All of the +allocations *inside* the temporary memory region will be freed to the arena. +*/ +end_arena_temp_memory :: proc(tmp: Arena_Temp_Memory) { + assert(tmp.arena.offset >= tmp.prev_offset) + assert(tmp.arena.temp_count > 0) + tmp.arena.offset = tmp.prev_offset + tmp.arena.temp_count -= 1 +} + +/* Preserved for compatibility */ +Scratch_Allocator :: Scratch +scratch_allocator_init :: scratch_init +scratch_allocator_destroy :: scratch_destroy + +/* +Scratch allocator data. +*/ +Scratch :: struct { + data: []byte, + curr_offset: int, + prev_allocation: rawptr, + backup_allocator: Allocator, + leaked_allocations: [dynamic][]byte, +} + +/* +Scratch allocator. + +The scratch allocator works in a similar way to the `Arena` allocator. The +scratch allocator has a backing buffer, that is being allocated in +contiguous regions, from start to end. + +Each subsequent allocation will be the next adjacent region of memory in the +backing buffer. If the allocation doesn't fit into the remaining space of the +backing buffer, this allocation is put at the start of the buffer, and all +previous allocations will become invalidated. If the allocation doesn't fit +into the backing buffer as a whole, it will be allocated using a backing +allocator, and pointer to the allocated memory region will be put into the +`leaked_allocations` array. + +The `leaked_allocations` array is managed by the `context` allocator. +*/ +@(require_results) +scratch_allocator :: proc(allocator: ^Scratch) -> Allocator { + return Allocator{ + procedure = scratch_allocator_proc, + data = allocator, + } +} + +/* +Initialize scratch allocator. +*/ +scratch_init :: proc(s: ^Scratch, size: int, backup_allocator := context.allocator) -> Allocator_Error { + s.data = make_aligned([]byte, size, 2*align_of(rawptr), backup_allocator) or_return + s.curr_offset = 0 + s.prev_allocation = nil + s.backup_allocator = backup_allocator + s.leaked_allocations.allocator = backup_allocator + return nil +} + +/* +Free all data associated with a scratch allocator. +*/ +scratch_destroy :: proc(s: ^Scratch) { + if s == nil { + return + } + for ptr in s.leaked_allocations { + free_bytes(ptr, s.backup_allocator) + } + delete(s.leaked_allocations) + delete(s.data, s.backup_allocator) + s^ = {} +} + +/* +Allocate memory from scratch allocator. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment`. The allocated memory region is zero-initialized. This procedure +returns a pointer to the allocated memory region. +*/ +@(require_results) +scratch_alloc :: proc( + s: ^Scratch, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := scratch_alloc_bytes(s, size, alignment, loc) + return raw_data(bytes), err +} + +/* +Allocate memory from scratch allocator. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment`. The allocated memory region is zero-initialized. This procedure +returns a slice of the allocated memory region. +*/ +@(require_results) +scratch_alloc_bytes :: proc( + s: ^Scratch, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + bytes, err := scratch_alloc_bytes_non_zeroed(s, size, alignment, loc) + if bytes != nil { + zero_slice(bytes) + } + return bytes, err +} + +/* +Allocate non-initialized memory from scratch allocator. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment`. The allocated memory region is not explicitly zero-initialized. +This procedure returns a pointer to the allocated memory region. +*/ +@(require_results) +scratch_alloc_non_zeroed :: proc( + s: ^Scratch, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := scratch_alloc_bytes_non_zeroed(s, size, alignment, loc) + return raw_data(bytes), err +} + +/* +Allocate non-initialized memory from scratch allocator. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment`. The allocated memory region is not explicitly zero-initialized. +This procedure returns a slice of the allocated memory region. +*/ +@(require_results) +scratch_alloc_bytes_non_zeroed :: proc( + s: ^Scratch, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + if s.data == nil { + DEFAULT_BACKING_SIZE :: 4 * Megabyte + if !(context.allocator.procedure != scratch_allocator_proc && context.allocator.data != s) { + panic("cyclic initialization of the scratch allocator with itself", loc) + } + scratch_init(s, DEFAULT_BACKING_SIZE) + } + size := size + size = align_forward_int(size, alignment) + if size <= len(s.data) { + offset := uintptr(0) + if s.curr_offset+size <= len(s.data) { + offset = uintptr(s.curr_offset) + } else { + offset = 0 + } + start := uintptr(raw_data(s.data)) + ptr := align_forward_uintptr(offset+start, uintptr(alignment)) + s.prev_allocation = rawptr(ptr) + s.curr_offset = int(offset) + size + return byte_slice(rawptr(ptr), size), nil + } else { + a := s.backup_allocator + if a.procedure == nil { + a = context.allocator + s.backup_allocator = a + } + ptr, err := alloc_bytes_non_zeroed(size, alignment, a, loc) + if err != nil { + return ptr, err + } + if s.leaked_allocations == nil { + s.leaked_allocations, err = make([dynamic][]byte, a) + } + append(&s.leaked_allocations, ptr) + if logger := context.logger; logger.lowest_level <= .Warning { + if logger.procedure != nil { + logger.procedure(logger.data, .Warning, "mem.Scratch resorted to backup_allocator" , logger.options, loc) + } + } + return ptr, err + } +} + +/* +Free memory to the scratch allocator. + +This procedure frees the memory region allocated at pointer `ptr`. + +If `ptr` is not the latest allocation and is not a leaked allocation, this +operation is a no-op. +*/ +scratch_free :: proc(s: ^Scratch, ptr: rawptr, loc := #caller_location) -> Allocator_Error { + if s.data == nil { + panic("Free on an uninitialized scratch allocator", loc) + } + if ptr == nil { + return nil + } + start := uintptr(raw_data(s.data)) + end := start + uintptr(len(s.data)) + old_ptr := uintptr(ptr) + if s.prev_allocation == ptr { + s.curr_offset = int(uintptr(s.prev_allocation) - start) + s.prev_allocation = nil + return nil + } + if start <= old_ptr && old_ptr < end { + // NOTE(bill): Cannot free this pointer but it is valid + return nil + } + if len(s.leaked_allocations) != 0 { + for data, i in s.leaked_allocations { + ptr := raw_data(data) + if ptr == ptr { + free_bytes(data, s.backup_allocator, loc) + ordered_remove(&s.leaked_allocations, i, loc) + return nil + } + } + } + return .Invalid_Pointer +} + +/* +Free all memory to the scratch allocator. +*/ +scratch_free_all :: proc(s: ^Scratch, loc := #caller_location) { + s.curr_offset = 0 + s.prev_allocation = nil + for ptr in s.leaked_allocations { + free_bytes(ptr, s.backup_allocator, loc) + } + clear(&s.leaked_allocations) +} + +/* +Resize an allocation. + +This procedure resizes a memory region, defined by its location, `old_memory`, +and its size, `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any is zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `scratch_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `scratch_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the pointer to the resized memory region. +*/ +@(require_results) +scratch_resize :: proc( + s: ^Scratch, + old_memory: rawptr, + old_size: int, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location +) -> (rawptr, Allocator_Error) { + bytes, err := scratch_resize_bytes(s, byte_slice(old_memory, old_size), size, alignment, loc) + return raw_data(bytes), err +} + +/* +Resize an allocation. + +This procedure resizes a memory region, specified by `old_data`, to have a size +`size` and alignment `alignment`. The newly allocated memory, if any is +zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `scratch_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `scratch_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the slice of the resized memory region. +*/ +@(require_results) +scratch_resize_bytes :: proc( + s: ^Scratch, + old_data: []byte, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location +) -> ([]byte, Allocator_Error) { + bytes, err := scratch_resize_bytes_non_zeroed(s, old_data, size, alignment, loc) + if bytes != nil && size > len(old_data) { + zero_slice(bytes[size:]) + } + return bytes, err +} + +/* +Resize an allocation without zero-initialization. + +This procedure resizes a memory region, defined by its location, `old_memory`, +and its size, `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any is not explicitly zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `scratch_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `scratch_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the pointer to the resized memory region. +*/ +@(require_results) +scratch_resize_non_zeroed :: proc( + s: ^Scratch, + old_memory: rawptr, + old_size: int, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location +) -> (rawptr, Allocator_Error) { + bytes, err := scratch_resize_bytes_non_zeroed(s, byte_slice(old_memory, old_size), size, alignment, loc) + return raw_data(bytes), err +} + +/* +Resize an allocation. + +This procedure resizes a memory region, specified by `old_data`, to have a size +`size` and alignment `alignment`. The newly allocated memory, if any is not +explicitly zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `scratch_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `scratch_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the slice of the resized memory region. +*/ +@(require_results) +scratch_resize_bytes_non_zeroed :: proc( + s: ^Scratch, + old_data: []byte, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location +) -> ([]byte, Allocator_Error) { + old_memory := raw_data(old_data) + old_size := len(old_data) + if s.data == nil { + DEFAULT_BACKING_SIZE :: 4 * Megabyte + if !(context.allocator.procedure != scratch_allocator_proc && context.allocator.data != s) { + panic("cyclic initialization of the scratch allocator with itself", loc) + } + scratch_init(s, DEFAULT_BACKING_SIZE) + } + begin := uintptr(raw_data(s.data)) + end := begin + uintptr(len(s.data)) + old_ptr := uintptr(old_memory) + if begin <= old_ptr && old_ptr < end && old_ptr+uintptr(size) < end { + s.curr_offset = int(old_ptr-begin)+size + return byte_slice(old_memory, size), nil + } + data, err := scratch_alloc_bytes_non_zeroed(s, size, alignment, loc) + if err != nil { + return data, err + } + runtime.copy(data, byte_slice(old_memory, old_size)) + err = scratch_free(s, old_memory, loc) + return data, err +} + +scratch_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + s := (^Scratch)(allocator_data) + size := size + switch mode { + case .Alloc: + return scratch_alloc_bytes(s, size, alignment, loc) + case .Alloc_Non_Zeroed: + return scratch_alloc_bytes_non_zeroed(s, size, alignment, loc) + case .Free: + return nil, scratch_free(s, old_memory, loc) + case .Free_All: + scratch_free_all(s, loc) + case .Resize: + return scratch_resize_bytes(s, byte_slice(old_memory, old_size), size, alignment, loc) + case .Resize_Non_Zeroed: + return scratch_resize_bytes_non_zeroed(s, byte_slice(old_memory, old_size), size, alignment, loc) + case .Query_Features: + set := (^Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Resize_Non_Zeroed, .Query_Features} + } + return nil, nil + case .Query_Info: + return nil, .Mode_Not_Implemented + } + return nil, nil +} + +/* +Stack allocator data. +*/ +Stack :: struct { + data: []byte, + prev_offset: int, + curr_offset: int, + peak_used: int, +} + +/* +Header of a stack allocation. +*/ +Stack_Allocation_Header :: struct { + prev_offset: int, + padding: int, +} + +/* +Stack allocator. + +The stack allocator is an allocator that allocates data in the backing buffer +linearly, from start to end. Each subsequent allocation will get the next +adjacent memory region. + +Unlike arena allocator, the stack allocator saves allocation metadata and has +a strict freeing order. Only the last allocated element can be freed. After the +last allocated element is freed, the next previous allocated element becomes +available for freeing. + +The metadata is stored in the allocation headers, that are located before the +start of each allocated memory region. Each header points to the start of the +previous allocation header. +*/ +@(require_results) +stack_allocator :: proc(stack: ^Stack) -> Allocator { + return Allocator{ + procedure = stack_allocator_proc, + data = stack, + } +} + +/* +Initialize the stack allocator. + +This procedure initializes the stack allocator with a backing buffer specified +by `data` parameter. +*/ +stack_init :: proc(s: ^Stack, data: []byte) { + s.data = data + s.prev_offset = 0 + s.curr_offset = 0 + s.peak_used = 0 +} + +@(deprecated="prefer 'mem.stack_init'") +init_stack :: proc(s: ^Stack, data: []byte) { + s.data = data + s.prev_offset = 0 + s.curr_offset = 0 + s.peak_used = 0 +} + +/* +Allocate memory from stack. + +This procedure allocates `size` bytes of memory, aligned to the boundary +specified by `alignment`. The allocated memory is zero-initialized. This +procedure returns the pointer to the allocated memory. +*/ +@(require_results) +stack_alloc :: proc( + s: ^Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location +) -> (rawptr, Allocator_Error) { + bytes, err := stack_alloc_bytes(s, size, alignment, loc) + return raw_data(bytes), err +} + +/* +Allocate memory from stack. + +This procedure allocates `size` bytes of memory, aligned to the boundary +specified by `alignment`. The allocated memory is zero-initialized. This +procedure returns the slice of the allocated memory. +*/ +@(require_results) +stack_alloc_bytes :: proc( + s: ^Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location +) -> ([]byte, Allocator_Error) { + bytes, err := stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + if bytes != nil { + zero_slice(bytes) + } + return bytes, err +} + +/* +Allocate memory from stack. + +This procedure allocates `size` bytes of memory, aligned to the boundary +specified by `alignment`. The allocated memory is not explicitly +zero-initialized. This procedure returns the pointer to the allocated memory. +*/ +@(require_results) +stack_alloc_non_zeroed :: proc( + s: ^Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location +) -> (rawptr, Allocator_Error) { + bytes, err := stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + return raw_data(bytes), err +} + +/* +Allocate memory from stack. + +This procedure allocates `size` bytes of memory, aligned to the boundary +specified by `alignment`. The allocated memory is not explicitly +zero-initialized. This procedure returns the slice of the allocated memory. +*/ +@(require_results) +stack_alloc_bytes_non_zeroed :: proc( + s: ^Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location +) -> ([]byte, Allocator_Error) { + if s.data == nil { + panic("Stack allocation on an uninitialized stack allocator", loc) + } + curr_addr := uintptr(raw_data(s.data)) + uintptr(s.curr_offset) + padding := calc_padding_with_header( + curr_addr, + uintptr(alignment), + size_of(Stack_Allocation_Header), + ) + if s.curr_offset + padding + size > len(s.data) { + return nil, .Out_Of_Memory + } + s.prev_offset = s.curr_offset + s.curr_offset += padding + next_addr := curr_addr + uintptr(padding) + header := (^Stack_Allocation_Header)(next_addr - size_of(Stack_Allocation_Header)) + header.padding = padding + header.prev_offset = s.prev_offset + s.curr_offset += size + s.peak_used = max(s.peak_used, s.curr_offset) + return byte_slice(rawptr(next_addr), size), nil +} + +/* +Free memory to the stack. + +This procedure frees the memory region starting at `old_memory` to the stack. +If the freeing does is an out of order freeing, the `.Invalid_Pointer` error +is returned. +*/ +stack_free :: proc( + s: ^Stack, + old_memory: rawptr, + loc := #caller_location, +) -> (Allocator_Error) { + if s.data == nil { + panic("Stack free on an uninitialized stack allocator", loc) + } + if old_memory == nil { + return nil + } + start := uintptr(raw_data(s.data)) + end := start + uintptr(len(s.data)) + curr_addr := uintptr(old_memory) + if !(start <= curr_addr && curr_addr < end) { + panic("Out of bounds memory address passed to stack allocator (free)", loc) + } + if curr_addr >= start+uintptr(s.curr_offset) { + // NOTE(bill): Allow double frees + return nil + } + header := (^Stack_Allocation_Header)(curr_addr - size_of(Stack_Allocation_Header)) + old_offset := int(curr_addr - uintptr(header.padding) - uintptr(raw_data(s.data))) + if old_offset != header.prev_offset { + // panic("Out of order stack allocator free"); + return .Invalid_Pointer + } + s.curr_offset = old_offset + s.prev_offset = header.prev_offset + return nil +} + +/* +Free all allocations to the stack. +*/ +stack_free_all :: proc(s: ^Stack, loc := #caller_location) { + s.prev_offset = 0 + s.curr_offset = 0 +} + +/* +Resize an allocation. + +This procedure resizes a memory region, defined by its location, `old_memory`, +and its size, `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any is zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `stack_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `stack_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the pointer to the resized memory region. +*/ +@(require_results) +stack_resize :: proc( + s: ^Stack, + old_memory: rawptr, + old_size: int, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := stack_resize_bytes(s, byte_slice(old_memory, old_size), size, alignment) + return raw_data(bytes), err +} + +/* +Resize an allocation. + +This procedure resizes a memory region, specified by the `old_data` parameter +to have a size `size` and alignment `alignment`. The newly allocated memory, +if any is zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `stack_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `stack_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the slice of the resized memory region. +*/ +@(require_results) +stack_resize_bytes :: proc( + s: ^Stack, + old_data: []byte, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + bytes, err := stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + if bytes != nil { + if old_data == nil { + zero_slice(bytes) + } else if size > len(old_data) { + zero_slice(bytes[len(old_data):]) + } + } + return bytes, err +} + +/* +Resize an allocation without zero-initialization. + +This procedure resizes a memory region, defined by its location, `old_memory`, +and its size, `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any is not explicitly zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `stack_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `stack_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the pointer to the resized memory region. +*/ +@(require_results) +stack_resize_non_zeroed :: proc( + s: ^Stack, + old_memory: rawptr, + old_size: int, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := stack_resize_bytes_non_zeroed(s, byte_slice(old_memory, old_size), size, alignment) + return raw_data(bytes), err +} + +/* +Resize an allocation without zero-initialization. + +This procedure resizes a memory region, specified by the `old_data` parameter +to have a size `size` and alignment `alignment`. The newly allocated memory, +if any is not explicitly zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `stack_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `stack_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the slice of the resized memory region. +*/ +@(require_results) +stack_resize_bytes_non_zeroed :: proc( + s: ^Stack, + old_data: []byte, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + old_memory := raw_data(old_data) + old_size := len(old_data) + if s.data == nil { + panic("Stack free all on an uninitialized stack allocator", loc) + } + if old_memory == nil { + return stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + } + if size == 0 { + return nil, nil + } + start := uintptr(raw_data(s.data)) + end := start + uintptr(len(s.data)) + curr_addr := uintptr(old_memory) + if !(start <= curr_addr && curr_addr < end) { + panic("Out of bounds memory address passed to stack allocator (resize)") + } + if curr_addr >= start+uintptr(s.curr_offset) { + // NOTE(bill): Allow double frees + return nil, nil + } + if old_size == size { + return byte_slice(old_memory, size), nil + } + header := (^Stack_Allocation_Header)(curr_addr - size_of(Stack_Allocation_Header)) + old_offset := int(curr_addr - uintptr(header.padding) - uintptr(raw_data(s.data))) + if old_offset != header.prev_offset { + data, err := stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + if err == nil { + runtime.copy(data, byte_slice(old_memory, old_size)) + } + return data, err + } + old_memory_size := uintptr(s.curr_offset) - (curr_addr - start) + assert(old_memory_size == uintptr(old_size)) + diff := size - old_size + s.curr_offset += diff // works for smaller sizes too + if diff > 0 { + zero(rawptr(curr_addr + uintptr(diff)), diff) + } + return byte_slice(old_memory, size), nil +} + +stack_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size: int, + alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + s := cast(^Stack)allocator_data + if s.data == nil { + return nil, .Invalid_Argument + } + switch mode { + case .Alloc: + return stack_alloc_bytes(s, size, alignment, loc) + case .Alloc_Non_Zeroed: + return stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + case .Free: + return nil, stack_free(s, old_memory, loc) + case .Free_All: + stack_free_all(s, loc) + case .Resize: + return stack_resize_bytes(s, byte_slice(old_memory, old_size), size, alignment, loc) + case .Resize_Non_Zeroed: + return stack_resize_bytes_non_zeroed(s, byte_slice(old_memory, old_size), size, alignment, loc) + case .Query_Features: + set := (^Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Resize_Non_Zeroed, .Query_Features} + } + return nil, nil + case .Query_Info: + return nil, .Mode_Not_Implemented + } + return nil, nil +} +/* +Allocation header of the small stack allocator. +*/ +Small_Stack_Allocation_Header :: struct { + padding: u8, +} +/* +Small stack allocator data. +*/ +Small_Stack :: struct { + data: []byte, + offset: int, + peak_used: int, +} + +/* +Initialize small stack. + +This procedure initializes the small stack allocator with `data` as its backing +buffer. +*/ +small_stack_init :: proc(s: ^Small_Stack, data: []byte) { + s.data = data + s.offset = 0 + s.peak_used = 0 +} + +@(deprecated="prefer 'small_stack_init'") +init_small_stack :: proc(s: ^Small_Stack, data: []byte) { + s.data = data + s.offset = 0 + s.peak_used = 0 +} + +/* +Small stack allocator. + +The small stack allocator is just like a stack allocator, with the only +difference being an extremely small header size. Unlike the stack allocator, +small stack allows out-of order freeing of memory. + +The memory is allocated in the backing buffer linearly, from start to end. +Each subsequent allocation will get the next adjacent memory region. + +The metadata is stored in the allocation headers, that are located before the +start of each allocated memory region. Each header contains the amount of +padding bytes between that header and end of the previous allocation. +*/ +@(require_results) +small_stack_allocator :: proc(stack: ^Small_Stack) -> Allocator { + return Allocator{ + procedure = small_stack_allocator_proc, + data = stack, + } +} + +/* +Allocate memory from small stack. + +This procedure allocates `size` bytes of memory aligned to a boundary specified +by `alignment`. The allocated memory is zero-initialized. This procedure +returns a pointer to the allocated memory region. +*/ +@(require_results) +small_stack_alloc :: proc( + s: ^Small_Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := small_stack_alloc_bytes(s, size, alignment, loc) + return raw_data(bytes), err +} + +/* +Allocate memory from small stack. + +This procedure allocates `size` bytes of memory aligned to a boundary specified +by `alignment`. The allocated memory is zero-initialized. This procedure +returns a slice of the allocated memory region. +*/ +@(require_results) +small_stack_alloc_bytes :: proc( + s: ^Small_Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + bytes, err := small_stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + if bytes != nil { + zero_slice(bytes) + } + return bytes, err +} + +/* +Allocate memory from small stack. + +This procedure allocates `size` bytes of memory aligned to a boundary specified +by `alignment`. The allocated memory is not explicitly zero-initialized. This +procedure returns a pointer to the allocated memory region. +*/ +@(require_results) +small_stack_alloc_non_zeroed :: proc( + s: ^Small_Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := small_stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + return raw_data(bytes), err +} + +/* +Allocate memory from small stack. + +This procedure allocates `size` bytes of memory aligned to a boundary specified +by `alignment`. The allocated memory is not explicitly zero-initialized. This +procedure returns a slice of the allocated memory region. +*/ +@(require_results) +small_stack_alloc_bytes_non_zeroed :: proc( + s: ^Small_Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + if s.data == nil { + panic("Small stack is not initialized", loc) + } + alignment := alignment + alignment = clamp(alignment, 1, 8*size_of(Stack_Allocation_Header{}.padding)/2) + curr_addr := uintptr(raw_data(s.data)) + uintptr(s.offset) + padding := calc_padding_with_header(curr_addr, uintptr(alignment), size_of(Small_Stack_Allocation_Header)) + if s.offset + padding + size > len(s.data) { + return nil, .Out_Of_Memory + } + s.offset += padding + next_addr := curr_addr + uintptr(padding) + header := (^Small_Stack_Allocation_Header)(next_addr - size_of(Small_Stack_Allocation_Header)) + header.padding = auto_cast padding + s.offset += size + s.peak_used = max(s.peak_used, s.offset) + return byte_slice(rawptr(next_addr), size), nil +} + +/* +Allocate memory from small stack. + +This procedure allocates `size` bytes of memory aligned to a boundary specified +by `alignment`. The allocated memory is not explicitly zero-initialized. This +procedure returns a slice of the allocated memory region. +*/ +small_stack_free :: proc( + s: ^Small_Stack, + old_memory: rawptr, + loc := #caller_location, +) -> Allocator_Error { + if s.data == nil { + panic("Small stack is not initialized", loc) + } + if old_memory == nil { + return nil + } + start := uintptr(raw_data(s.data)) + end := start + uintptr(len(s.data)) + curr_addr := uintptr(old_memory) + if !(start <= curr_addr && curr_addr < end) { + // panic("Out of bounds memory address passed to stack allocator (free)"); + return .Invalid_Pointer + } + if curr_addr >= start+uintptr(s.offset) { + // NOTE(bill): Allow double frees + return nil + } + header := (^Small_Stack_Allocation_Header)(curr_addr - size_of(Small_Stack_Allocation_Header)) + old_offset := int(curr_addr - uintptr(header.padding) - uintptr(raw_data(s.data))) + s.offset = old_offset + return nil +} + +/* +Free all memory to small stack. +*/ +small_stack_free_all :: proc(s: ^Small_Stack) { + s.offset = 0 +} + +/* +Resize an allocation. + +This procedure resizes a memory region, defined by its location, `old_memory`, +and its size, `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any is zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `small_stack_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `small_stack_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the pointer to the resized memory region. +*/ +@(require_results) +small_stack_resize :: proc( + s: ^Small_Stack, + old_memory: rawptr, + old_size: int, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := small_stack_resize_bytes(s, byte_slice(old_memory, old_size), size, alignment, loc) + return raw_data(bytes), err +} + +/* +Resize an allocation. + +This procedure resizes a memory region, specified by the `old_data` parameter +to have a size `size` and alignment `alignment`. The newly allocated memory, +if any is zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `small_stack_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `small_stack_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the slice of the resized memory region. +*/ +@(require_results) +small_stack_resize_bytes :: proc( + s: ^Small_Stack, + old_data: []byte, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + bytes, err := small_stack_resize_bytes_non_zeroed(s, old_data, size, alignment, loc) + if bytes != nil { + if old_data == nil { + zero_slice(bytes) + } else if size > len(old_data) { + zero_slice(bytes[len(old_data):]) + } + } + return bytes, err +} + +/* +Resize an allocation without zero-initialization. + +This procedure resizes a memory region, defined by its location, `old_memory`, +and its size, `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any is not explicitly zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `small_stack_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `small_stack_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the pointer to the resized memory region. +*/ +@(require_results) +small_stack_resize_non_zeroed :: proc( + s: ^Small_Stack, + old_memory: rawptr, + old_size: int, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := small_stack_resize_bytes_non_zeroed(s, byte_slice(old_memory, old_size), size, alignment, loc) + return raw_data(bytes), err +} + +/* +Resize an allocation without zero-initialization. + +This procedure resizes a memory region, specified by the `old_data` parameter +to have a size `size` and alignment `alignment`. The newly allocated memory, +if any is not explicitly zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `small_stack_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `small_stack_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the slice of the resized memory region. +*/ +@(require_results) +small_stack_resize_bytes_non_zeroed :: proc( + s: ^Small_Stack, + old_data: []byte, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + if s.data == nil { + panic("Small stack is not initialized", loc) + } + old_memory := raw_data(old_data) + old_size := len(old_data) + alignment := alignment + alignment = clamp(alignment, 1, 8*size_of(Stack_Allocation_Header{}.padding)/2) + if old_memory == nil { + return small_stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + } + if size == 0 { + return nil, nil + } + start := uintptr(raw_data(s.data)) + end := start + uintptr(len(s.data)) + curr_addr := uintptr(old_memory) + if !(start <= curr_addr && curr_addr < end) { + // panic("Out of bounds memory address passed to stack allocator (resize)"); + return nil, .Invalid_Pointer + } + if curr_addr >= start+uintptr(s.offset) { + // NOTE(bill): Treat as a double free + return nil, nil + } + if old_size == size { + return byte_slice(old_memory, size), nil + } + data, err := small_stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + if err == nil { + runtime.copy(data, byte_slice(old_memory, old_size)) + } + return data, err + +} + +small_stack_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + s := cast(^Small_Stack)allocator_data + if s.data == nil { + return nil, .Invalid_Argument + } + switch mode { + case .Alloc: + return small_stack_alloc_bytes(s, size, alignment, loc) + case .Alloc_Non_Zeroed: + return small_stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + case .Free: + return nil, small_stack_free(s, old_memory, loc) + case .Free_All: + small_stack_free_all(s) + case .Resize: + return small_stack_resize_bytes(s, byte_slice(old_memory, old_size), size, alignment, loc) + case .Resize_Non_Zeroed: + return small_stack_resize_bytes_non_zeroed(s, byte_slice(old_memory, old_size), size, alignment, loc) + case .Query_Features: + set := (^Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Resize_Non_Zeroed, .Query_Features} + } + return nil, nil + case .Query_Info: + return nil, .Mode_Not_Implemented + } + return nil, nil +} + + +/* Preserved for compatibility */ +Dynamic_Pool :: Dynamic_Arena +DYNAMIC_POOL_BLOCK_SIZE_DEFAULT :: DYNAMIC_ARENA_BLOCK_SIZE_DEFAULT +DYNAMIC_POOL_OUT_OF_BAND_SIZE_DEFAULT :: DYNAMIC_ARENA_OUT_OF_BAND_SIZE_DEFAULT +dynamic_pool_allocator_proc :: dynamic_arena_allocator_proc +dynamic_pool_free_all :: dynamic_arena_free_all +dynamic_pool_reset :: dynamic_arena_reset +dynamic_pool_alloc_bytes :: dynamic_arena_alloc_bytes +dynamic_pool_alloc :: dynamic_arena_alloc +dynamic_pool_init :: dynamic_arena_init +dynamic_pool_allocator :: dynamic_arena_allocator +dynamic_pool_destroy :: dynamic_arena_destroy + +/* +Default block size for dynamic arena. +*/ +DYNAMIC_ARENA_BLOCK_SIZE_DEFAULT :: 65536 + +/* +Default out-band size of the dynamic arena. +*/ +DYNAMIC_ARENA_OUT_OF_BAND_SIZE_DEFAULT :: 6554 + +/* +Dynamic arena allocator data. +*/ +Dynamic_Arena :: struct { + block_size: int, + out_band_size: int, + alignment: int, + unused_blocks: [dynamic]rawptr, + used_blocks: [dynamic]rawptr, + out_band_allocations: [dynamic]rawptr, + current_block: rawptr, + current_pos: rawptr, + bytes_left: int, + block_allocator: Allocator, +} + +/* +Initialize a dynamic arena. + +This procedure initializes a dynamic arena. The specified `block_allocator` +will be used to allocate arena blocks, and `array_allocator` to allocate +arrays of blocks and out-band blocks. The blocks have the default size of +`block_size` and out-band threshold will be `out_band_size`. All allocations +will be aligned to a boundary specified by `alignment`. +*/ +dynamic_arena_init :: proc( + pool: ^Dynamic_Arena, + block_allocator := context.allocator, + array_allocator := context.allocator, + block_size := DYNAMIC_ARENA_BLOCK_SIZE_DEFAULT, + out_band_size := DYNAMIC_ARENA_OUT_OF_BAND_SIZE_DEFAULT, + alignment := DEFAULT_ALIGNMENT, +) { + pool.block_size = block_size + pool.out_band_size = out_band_size + pool.alignment = alignment + pool.block_allocator = block_allocator + pool.out_band_allocations.allocator = array_allocator + pool.unused_blocks.allocator = array_allocator + pool.used_blocks.allocator = array_allocator +} + +/* +Dynamic arena allocator. + +The dynamic arena allocator uses blocks of a specific size, allocated on-demand +using the block allocator. This allocator acts similarly to arena. All +allocations in a block happen contiguously, from start to end. If an allocation +does not fit into the remaining space of the block, and its size is smaller +than the specified out-band size, a new block is allocated using the +`block_allocator` and the allocation is performed from a newly-allocated block. + +If an allocation has bigger size than the specified out-band size, a new block +is allocated such that the allocation fits into this new block. This is referred +to as an *out-band allocation*. The out-band blocks are kept separately from +normal blocks. + +Just like arena, the dynamic arena does not support freeing of individual +objects. +*/ +@(require_results) +dynamic_arena_allocator :: proc(a: ^Dynamic_Arena) -> Allocator { + return Allocator{ + procedure = dynamic_arena_allocator_proc, + data = a, + } +} + +/* +Destroy a dynamic arena. + +This procedure frees all allocations, made on a dynamic arena, including the +unused blocks, as well as the arrays for storing blocks. +*/ +dynamic_arena_destroy :: proc(a: ^Dynamic_Arena) { + dynamic_arena_free_all(a) + delete(a.unused_blocks) + delete(a.used_blocks) + delete(a.out_band_allocations) + zero(a, size_of(a^)) +} + +@(private="file") +_dynamic_arena_cycle_new_block :: proc(a: ^Dynamic_Arena, loc := #caller_location) -> (err: Allocator_Error) { + if a.block_allocator.procedure == nil { + panic("You must call arena_init on a Pool before using it", loc) + } + if a.current_block != nil { + append(&a.used_blocks, a.current_block, loc=loc) + } + new_block: rawptr + if len(a.unused_blocks) > 0 { + new_block = pop(&a.unused_blocks) + } else { + data: []byte + data, err = a.block_allocator.procedure( + a.block_allocator.data, + Allocator_Mode.Alloc, + a.block_size, + a.alignment, + nil, + 0, + ) + new_block = raw_data(data) + } + a.bytes_left = a.block_size + a.current_pos = new_block + a.current_block = new_block + return +} + +/* +Allocate memory from a dynamic arena. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment` from a dynamic arena `a`. The allocated memory is +zero-initialized. This procedure returns a pointer to the newly allocated memory +region. +*/ +@(private, require_results) +dynamic_arena_alloc :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> (rawptr, Allocator_Error) { + data, err := dynamic_arena_alloc_bytes(a, size, loc) + return raw_data(data), err +} + +/* +Allocate memory from a dynamic arena. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment` from a dynamic arena `a`. The allocated memory is +zero-initialized. This procedure returns a slice of the newly allocated memory +region. +*/ +@(require_results) +dynamic_arena_alloc_bytes :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { + bytes, err := dynamic_arena_alloc_bytes_non_zeroed(a, size, loc) + if bytes != nil { + zero_slice(bytes) + } + return bytes, err +} + +/* +Allocate non-initialized memory from a dynamic arena. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment` from a dynamic arena `a`. The allocated memory is not explicitly +zero-initialized. This procedure returns a pointer to the newly allocated +memory region. +*/ +@(require_results) +dynamic_arena_alloc_non_zeroed :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> (rawptr, Allocator_Error) { + data, err := dynamic_arena_alloc_bytes_non_zeroed(a, size, loc) + return raw_data(data), err +} + +/* +Allocate non-initialized memory from a dynamic arena. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment` from a dynamic arena `a`. The allocated memory is not explicitly +zero-initialized. This procedure returns a slice of the newly allocated +memory region. +*/ +@(require_results) +dynamic_arena_alloc_bytes_non_zeroed :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { + n := align_formula(size, a.alignment) + if n > a.block_size { + return nil, .Invalid_Argument + } + if n >= a.out_band_size { + assert(a.block_allocator.procedure != nil, "Backing block allocator must be initialized", loc=loc) + memory, err := alloc_bytes_non_zeroed(a.block_size, a.alignment, a.block_allocator, loc) + if memory != nil { + append(&a.out_band_allocations, raw_data(memory), loc = loc) + } + return memory, err + } + if a.bytes_left < n { + err := _dynamic_arena_cycle_new_block(a, loc) + if err != nil { + return nil, err + } + if a.current_block == nil { + return nil, .Out_Of_Memory + } + } + memory := a.current_pos + a.current_pos = ([^]byte)(a.current_pos)[n:] + a.bytes_left -= n + return ([^]byte)(memory)[:size], nil +} + +/* +Reset the dynamic arena. + +This procedure frees all the allocations, owned by the dynamic arena, excluding +the unused blocks. +*/ +dynamic_arena_reset :: proc(a: ^Dynamic_Arena, loc := #caller_location) { + if a.current_block != nil { + append(&a.unused_blocks, a.current_block, loc=loc) + a.current_block = nil + } + for block in a.used_blocks { + append(&a.unused_blocks, block, loc=loc) + } + clear(&a.used_blocks) + for allocation in a.out_band_allocations { + free(allocation, a.block_allocator, loc=loc) + } + clear(&a.out_band_allocations) + a.bytes_left = 0 // Make new allocations call `_dynamic_arena_cycle_new_block` again. +} + +/* +Free all memory from a dynamic arena. + +This procedure frees all the allocations, owned by the dynamic arena, including +the unused blocks. +*/ +dynamic_arena_free_all :: proc(a: ^Dynamic_Arena, loc := #caller_location) { + dynamic_arena_reset(a) + for block in a.unused_blocks { + free(block, a.block_allocator, loc) + } + clear(&a.unused_blocks) +} + +/* +Resize an allocation. + +This procedure resizes a memory region, defined by its location, `old_memory`, +and its size, `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any is zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `dynamic_arena_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `dynamic_arena_free()`, freeing +the memory region located at an address specified by `old_memory`. + +This procedure returns the pointer to the resized memory region. +*/ +@(require_results) +dynamic_arena_resize :: proc( + a: ^Dynamic_Arena, + old_memory: rawptr, + old_size: int, + size: int, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := dynamic_arena_resize_bytes(a, byte_slice(old_memory, old_size), size, loc) + return raw_data(bytes), err +} + +/* +Resize an allocation. + +This procedure resizes a memory region, specified by `old_data`, to have a size +`size` and alignment `alignment`. The newly allocated memory, if any is +zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `dynamic_arena_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `dynamic_arena_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the slice of the resized memory region. +*/ +@(require_results) +dynamic_arena_resize_bytes :: proc( + a: ^Dynamic_Arena, + old_data: []byte, + size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + bytes, err := dynamic_arena_resize_bytes_non_zeroed(a, old_data, size, loc) + if bytes != nil { + if old_data == nil { + zero_slice(bytes) + } else if size > len(old_data) { + zero_slice(bytes[len(old_data):]) + } + } + return bytes, err +} + +/* +Resize an allocation without zero-initialization. + +This procedure resizes a memory region, defined by its location, `old_memory`, +and its size, `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any is not explicitly zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `dynamic_arena_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `dynamic_arena_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the pointer to the resized memory region. +*/ +@(require_results) +dynamic_arena_resize_non_zeroed :: proc( + a: ^Dynamic_Arena, + old_memory: rawptr, + old_size: int, + size: int, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := dynamic_arena_resize_bytes_non_zeroed(a, byte_slice(old_memory, old_size), size, loc) + return raw_data(bytes), err +} + +/* +Resize an allocation. + +This procedure resizes a memory region, specified by `old_data`, to have a size +`size` and alignment `alignment`. The newly allocated memory, if any is not +explicitly zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `dynamic_arena_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `dynamic_arena_free()`, freeing +the memory region located at an address specified by `old_memory`. + +This procedure returns the slice of the resized memory region. +*/ +@(require_results) +dynamic_arena_resize_bytes_non_zeroed :: proc( + a: ^Dynamic_Arena, + old_data: []byte, + size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + old_memory := raw_data(old_data) + old_size := len(old_data) + if old_size >= size { + return byte_slice(old_memory, size), nil + } + data, err := dynamic_arena_alloc_bytes_non_zeroed(a, size, loc) + if err == nil { + runtime.copy(data, byte_slice(old_memory, old_size)) + } + return data, err +} + +dynamic_arena_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size: int, + alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + arena := (^Dynamic_Arena)(allocator_data) + switch mode { + case .Alloc: + return dynamic_arena_alloc_bytes(arena, size, loc) + case .Alloc_Non_Zeroed: + return dynamic_arena_alloc_bytes_non_zeroed(arena, size, loc) + case .Free: + return nil, .Mode_Not_Implemented + case .Free_All: + dynamic_arena_free_all(arena, loc) + case .Resize: + return dynamic_arena_resize_bytes(arena, byte_slice(old_memory, old_size), size, loc) + case .Resize_Non_Zeroed: + return dynamic_arena_resize_bytes_non_zeroed(arena, byte_slice(old_memory, old_size), size, loc) + case .Query_Features: + set := (^Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Alloc, .Alloc_Non_Zeroed, .Free_All, .Resize, .Resize_Non_Zeroed, .Query_Features, .Query_Info} + } + return nil, nil + case .Query_Info: + info := (^Allocator_Query_Info)(old_memory) + if info != nil && info.pointer != nil { + info.size = arena.block_size + info.alignment = arena.alignment + return byte_slice(info, size_of(info^)), nil + } + return nil, nil + } + return nil, nil +} + + +/* +Header of the buddy block. +*/ Buddy_Block :: struct #align(align_of(uint)) { size: uint, is_free: bool, } +/* +Obtain the next buddy block. +*/ @(require_results) buddy_block_next :: proc(block: ^Buddy_Block) -> ^Buddy_Block { return (^Buddy_Block)(([^]byte)(block)[block.size:]) } +/* +Split the block into two, by truncating the given block to a given size. +*/ @(require_results) buddy_block_split :: proc(block: ^Buddy_Block, size: uint) -> ^Buddy_Block { block := block @@ -894,12 +1971,14 @@ buddy_block_split :: proc(block: ^Buddy_Block, size: uint) -> ^Buddy_Block { return nil } +/* +Coalesce contiguous blocks in a range of blocks into one. +*/ buddy_block_coalescence :: proc(head, tail: ^Buddy_Block) { for { // Keep looping until there are no more buddies to coalesce block := head buddy := buddy_block_next(block) - no_coalescence := true for block < tail && buddy < tail { // make sure the buddies are within the range if block.is_free && buddy.is_free && block.size == buddy.size { @@ -922,28 +2001,26 @@ buddy_block_coalescence :: proc(head, tail: ^Buddy_Block) { } } } - if no_coalescence { return } } } - +/* +Find the best block for storing a given size in a range of blocks. +*/ @(require_results) buddy_block_find_best :: proc(head, tail: ^Buddy_Block, size: uint) -> ^Buddy_Block { assert(size != 0) - best_block: ^Buddy_Block block := head // left buddy := buddy_block_next(block) // right - // The entire memory section between head and tail is free, // just call 'buddy_block_split' to get the allocation if buddy == tail && block.is_free { return buddy_block_split(block, size) } - // Find the block which is the 'best_block' to requested allocation sized for block < tail && buddy < tail { // make sure the buddies are within the range // If both buddies are free, coalesce them together @@ -954,7 +2031,6 @@ buddy_block_find_best :: proc(head, tail: ^Buddy_Block, size: uint) -> ^Buddy_Bl if size <= block.size && (best_block == nil || block.size <= best_block.size) { best_block = block } - block = buddy_block_next(buddy) if block < tail { // Delay the buddy block for the next iteration @@ -962,21 +2038,17 @@ buddy_block_find_best :: proc(head, tail: ^Buddy_Block, size: uint) -> ^Buddy_Bl } continue } - - if block.is_free && size <= block.size && (best_block == nil || block.size <= best_block.size) { best_block = block } - if buddy.is_free && size <= buddy.size && (best_block == nil || buddy.size < best_block.size) { // If each buddy are the same size, then it makes more sense // to pick the buddy as it "bounces around" less best_block = buddy } - - if (block.size <= buddy.size) { + if block.size <= buddy.size { block = buddy_block_next(buddy) if (block < tail) { // Delay the buddy block for the next iteration @@ -988,23 +2060,33 @@ buddy_block_find_best :: proc(head, tail: ^Buddy_Block, size: uint) -> ^Buddy_Bl buddy = buddy_block_next(buddy) } } - if best_block != nil { // This will handle the case if the 'best_block' is also the perfect fit return buddy_block_split(best_block, size) } - // Maybe out of memory return nil } - +/* +The buddy allocator data. +*/ Buddy_Allocator :: struct { - head: ^Buddy_Block, - tail: ^Buddy_Block, + head: ^Buddy_Block, + tail: ^Buddy_Block, alignment: uint, } +/* +Buddy allocator. + +The buddy allocator is a type of allocator that splits the backing buffer into +multiple regions called buddy blocks. Initially, the allocator only has one +block with the size of the backing buffer. Upon each allocation, the allocator +finds the smallest block that can fit the size of requested memory region, and +splits the block according to the allocation size. If no block can be found, +the contiguous free blocks are coalesced and the search is performed again. +*/ @(require_results) buddy_allocator :: proc(b: ^Buddy_Allocator) -> Allocator { return Allocator{ @@ -1013,48 +2095,97 @@ buddy_allocator :: proc(b: ^Buddy_Allocator) -> Allocator { } } -buddy_allocator_init :: proc(b: ^Buddy_Allocator, data: []byte, alignment: uint) { - assert(data != nil) - assert(is_power_of_two(uintptr(len(data)))) - assert(is_power_of_two(uintptr(alignment))) +/* +Initialize the buddy allocator. +This procedure initializes the buddy allocator `b` with a backing buffer `data` +and block alignment specified by `alignment`. +*/ +buddy_allocator_init :: proc(b: ^Buddy_Allocator, data: []byte, alignment: uint, loc := #caller_location) { + assert(data != nil) + assert(is_power_of_two(uintptr(len(data))), "Size of the backing buffer must be power of two", loc) + assert(is_power_of_two(uintptr(alignment)), "Alignment must be a power of two", loc) alignment := alignment if alignment < size_of(Buddy_Block) { alignment = size_of(Buddy_Block) } - ptr := raw_data(data) - assert(uintptr(ptr) % uintptr(alignment) == 0, "data is not aligned to minimum alignment") - + assert(uintptr(ptr) % uintptr(alignment) == 0, "data is not aligned to minimum alignment", loc) b.head = (^Buddy_Block)(ptr) - b.head.size = len(data) b.head.is_free = true - b.tail = buddy_block_next(b.head) - b.alignment = alignment } +/* +Get required block size to fit in the allocation as well as the alignment padding. +*/ @(require_results) buddy_block_size_required :: proc(b: ^Buddy_Allocator, size: uint) -> uint { size := size actual_size := b.alignment size += size_of(Buddy_Block) size = align_forward_uint(size, b.alignment) - for size > actual_size { actual_size <<= 1 } - return actual_size } +/* +Allocate memory from a buddy allocator. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment`. The allocated memory region is zero-initialized. This procedure +returns a pointer to the allocated memory region. +*/ @(require_results) -buddy_allocator_alloc :: proc(b: ^Buddy_Allocator, size: uint, zeroed: bool) -> ([]byte, Allocator_Error) { +buddy_allocator_alloc :: proc(b: ^Buddy_Allocator, size: uint) -> (rawptr, Allocator_Error) { + bytes, err := buddy_allocator_alloc_bytes(b, size) + return raw_data(bytes), err +} + +/* +Allocate memory from a buddy allocator. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment`. The allocated memory region is zero-initialized. This procedure +returns a slice of the allocated memory region. +*/ +@(require_results) +buddy_allocator_alloc_bytes :: proc(b: ^Buddy_Allocator, size: uint) -> ([]byte, Allocator_Error) { + bytes, err := buddy_allocator_alloc_bytes_non_zeroed(b, size) + if bytes != nil { + zero_slice(bytes) + } + return bytes, err +} + +/* +Allocate non-initialized memory from a buddy allocator. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment`. The allocated memory region is not explicitly zero-initialized. +This procedure returns a pointer to the allocated memory region. +*/ +@(require_results) +buddy_allocator_alloc_non_zeroed :: proc(b: ^Buddy_Allocator, size: uint) -> (rawptr, Allocator_Error) { + bytes, err := buddy_allocator_alloc_bytes_non_zeroed(b, size) + return raw_data(bytes), err +} + +/* +Allocate non-initialized memory from a buddy allocator. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment`. The allocated memory region is not explicitly zero-initialized. +This procedure returns a slice of the allocated memory region. +*/ +@(require_results) +buddy_allocator_alloc_bytes_non_zeroed :: proc(b: ^Buddy_Allocator, size: uint) -> ([]byte, Allocator_Error) { if size != 0 { actual_size := buddy_block_size_required(b, size) - found := buddy_block_find_best(b.head, b.tail, actual_size) if found != nil { // Try to coalesce all the free buddy blocks and then search again @@ -1065,60 +2196,71 @@ buddy_allocator_alloc :: proc(b: ^Buddy_Allocator, size: uint, zeroed: bool) -> return nil, .Out_Of_Memory } found.is_free = false - data := ([^]byte)(found)[b.alignment:][:size] - if zeroed { - zero_slice(data) - } return data, nil } return nil, nil } +/* +Free memory to the buddy allocator. + +This procedure frees the memory region allocated at pointer `ptr`. + +If `ptr` is not the latest allocation and is not a leaked allocation, this +operation is a no-op. +*/ buddy_allocator_free :: proc(b: ^Buddy_Allocator, ptr: rawptr) -> Allocator_Error { if ptr != nil { if !(b.head <= ptr && ptr <= b.tail) { return .Invalid_Pointer } - block := (^Buddy_Block)(([^]byte)(ptr)[-b.alignment:]) block.is_free = true - buddy_block_coalescence(b.head, b.tail) } return nil } -buddy_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int,loc := #caller_location) -> ([]byte, Allocator_Error) { +/* +Free all memory to the buddy allocator. +*/ +buddy_allocator_free_all :: proc(b: ^Buddy_Allocator) { + alignment := b.alignment + head := ([^]byte)(b.head) + tail := ([^]byte)(b.tail) + data := head[:ptr_sub(tail, head)] + buddy_allocator_init(b, data, alignment) +} +buddy_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { b := (^Buddy_Allocator)(allocator_data) - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - return buddy_allocator_alloc(b, uint(size), mode == .Alloc) + case .Alloc: + return buddy_allocator_alloc_bytes(b, uint(size)) + case .Alloc_Non_Zeroed: + return buddy_allocator_alloc_bytes_non_zeroed(b, uint(size)) case .Resize: - return default_resize_bytes_align(byte_slice(old_memory, old_size), size, alignment, buddy_allocator(b)) + return default_resize_bytes_align(byte_slice(old_memory, old_size), size, alignment, buddy_allocator(b), loc) case .Resize_Non_Zeroed: - return default_resize_bytes_align_non_zeroed(byte_slice(old_memory, old_size), size, alignment, buddy_allocator(b)) + return default_resize_bytes_align_non_zeroed(byte_slice(old_memory, old_size), size, alignment, buddy_allocator(b), loc) case .Free: return nil, buddy_allocator_free(b, old_memory) case .Free_All: - - alignment := b.alignment - head := ([^]byte)(b.head) - tail := ([^]byte)(b.tail) - data := head[:ptr_sub(tail, head)] - buddy_allocator_init(b, data, alignment) - + buddy_allocator_free_all(b) case .Query_Features: set := (^Allocator_Mode_Set)(old_memory) if set != nil { set^ = {.Query_Features, .Alloc, .Alloc_Non_Zeroed, .Resize, .Resize_Non_Zeroed, .Free, .Free_All, .Query_Info} } return nil, nil - case .Query_Info: info := (^Allocator_Query_Info)(old_memory) if info != nil && info.pointer != nil { @@ -1126,7 +2268,6 @@ buddy_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, if !(b.head <= ptr && ptr <= b.tail) { return nil, .Invalid_Pointer } - block := (^Buddy_Block)(([^]byte)(ptr)[-b.alignment:]) info.size = int(block.size) info.alignment = int(b.alignment) @@ -1134,6 +2275,83 @@ buddy_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, } return nil, nil } - return nil, nil } + +// An allocator that keeps track of allocation sizes and passes it along to resizes. +// This is useful if you are using a library that needs an equivalent of `realloc` but want to use +// the Odin allocator interface. +// +// You want to wrap your allocator into this one if you are trying to use any allocator that relies +// on the old size to work. +// +// The overhead of this allocator is an extra max(alignment, size_of(Header)) bytes allocated for each allocation, these bytes are +// used to store the size and original pointer. +Compat_Allocator :: struct { + parent: Allocator, +} + +compat_allocator_init :: proc(rra: ^Compat_Allocator, allocator := context.allocator) { + rra.parent = allocator +} + +compat_allocator :: proc(rra: ^Compat_Allocator) -> Allocator { + return Allocator{ + data = rra, + procedure = compat_allocator_proc, + } +} + +compat_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, + location := #caller_location) -> (data: []byte, err: Allocator_Error) { + size, old_size := size, old_size + + Header :: struct { + size: int, + ptr: rawptr, + } + + rra := (^Compat_Allocator)(allocator_data) + switch mode { + case .Alloc, .Alloc_Non_Zeroed: + a := max(alignment, size_of(Header)) + size += a + assert(size >= 0, "overflow") + + allocation := rra.parent.procedure(rra.parent.data, mode, size, alignment, old_memory, old_size, location) or_return + #no_bounds_check data = allocation[a:] + + ([^]Header)(raw_data(data))[-1] = { + size = size, + ptr = raw_data(allocation), + } + return + + case .Free: + header := ([^]Header)(old_memory)[-1] + return rra.parent.procedure(rra.parent.data, mode, size, alignment, header.ptr, header.size, location) + + case .Resize, .Resize_Non_Zeroed: + header := ([^]Header)(old_memory)[-1] + + a := max(alignment, size_of(header)) + size += a + assert(size >= 0, "overflow") + + allocation := rra.parent.procedure(rra.parent.data, mode, size, alignment, header.ptr, header.size, location) or_return + #no_bounds_check data = allocation[a:] + + ([^]Header)(raw_data(data))[-1] = { + size = size, + ptr = raw_data(allocation), + } + return + + case .Free_All, .Query_Info, .Query_Features: + return rra.parent.procedure(rra.parent.data, mode, size, alignment, old_memory, old_size, location) + + case: unreachable() + } +} diff --git a/core/mem/doc.odin b/core/mem/doc.odin index 44c93f798..98755d797 100644 --- a/core/mem/doc.odin +++ b/core/mem/doc.odin @@ -1,34 +1,114 @@ /* -package mem implements various types of allocators. +The `mem` package implements various allocators and provides utility procedures +for dealing with memory, pointers and slices. +The documentation below describes basic concepts, applicable to the `mem` +package. -An example of how to use the `Tracking_Allocator` to track subsequent allocations -in your program and report leaks and bad frees: +## Pointers, multipointers, and slices -Example: - package foo +A *pointer* is an abstraction of an *address*, a numberic value representing the +location of an object in memory. That object is said to be *pointed to* by the +pointer. To obtain the address of a pointer, cast it to `uintptr`. - import "core:mem" - import "core:fmt" +A multipointer is a pointer that points to multiple objects. Unlike a pointer, +a multipointer can be indexed, but does not have a definite length. A slice is +a pointer that points to multiple objects equipped with the length, specifying +the amount of objects a slice points to. - _main :: proc() { - // do stuff - } +When object's values are read through a pointer, that operation is called a +*load* operation. When memory is read through a pointer, that operation is +called a *store* operation. Both of these operations can be called a *memory +access operation*. - main :: proc() { - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - defer mem.tracking_allocator_destroy(&track) - context.allocator = mem.tracking_allocator(&track) +## Allocators - _main() +In C and C++ memory models, allocations of objects in memory are typically +treated individually with a generic allocator (The `malloc` procedure). Which in +some scenarios can lead to poor cache utilization, slowdowns on individual +objects' memory management and growing complexity of the code needing to keep +track of the pointers and their lifetimes. - for _, leak in track.allocation_map { - fmt.printf("%v leaked %m\n", leak.location, leak.size) - } - for bad_free in track.bad_free_array { - fmt.printf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory) - } - } +Using different kinds of *allocators* for different purposes can solve these +problems. The allocators are typically optimized for specific use-cases and +can potentially simplify the memory management code. + +For example, in the context of making a game, having an Arena allocator could +simplify allocations of any temporary memory, because the programmer doesn't +have to keep track of which objects need to be freed every time they are +allocated, because at the end of every frame the whole allocator is reset to +its initial state and all objects are freed at once. + +The allocators have different kinds of restrictions on object lifetimes, sizes, +alignment and can be a significant gain, if used properly. Odin supports +allocators on a language level. + +Operations such as `new`, `free` and `delete` by default will use +`context.allocator`, which can be overridden by the user. When an override +happens all called procedures will inherit the new context and use the same +allocator. + +We will define one concept to simplify the description of some allocator-related +procedures, which is ownership. If the memory was allocated via a specific +allocator, that allocator is said to be the *owner* of that memory region. To +note, unlike Rust, in Odin the memory ownership model is not strict. + +## Alignment + +An address is said to be *aligned to `N` bytes*, if the addresses's numeric +value is divisible by `N`. The number `N` in this case can be referred to as +the *alignment boundary*. Typically an alignment is a power of two integer +value. + +A *natural alignment* of an object is typically equal to its size. For example +a 16 bit integer has a natural alignment of 2 bytes. When an object is not +located on its natural alignment boundary, accesses to that object are +considered *unaligned*. + +Some machines issue a hardware **exception**, or experience **slowdowns** when a +memory access operation occurs from an unaligned address. Examples of such +operations are: + +- SIMD instructions on x86. These instructions require all memory accesses to be + on an address that is aligned to 16 bytes. +- On ARM unaligned loads have an extra cycle penalty. + +As such, many operations that allocate memory in this package allow to +explicitly specify the alignment of allocated pointers/slices. The default +alignment for all operations is specified in a constant `mem.DEFAULT_ALIGNMENT`. + +## Zero by default + +Whenever new memory is allocated, via an allocator, or on the stack, by default +Odin will zero-initialize that memory, even if it wasn't explicitly +initialized. This allows for some convenience in certain scenarios and ease of +debugging, which will not be described in detail here. + +However zero-initialization can be a cause of slowdowns, when allocating large +buffers. For this reason, allocators have `*_non_zeroed` modes of allocation +that allow the user to request for uninitialized memory and will avoid a +relatively expensive zero-filling of the buffer. + +## Naming conventions + +The word `size` is used to denote the **size in bytes**. The word `length` is +used to denote the count of objects. + +The allocation procedures use the following conventions: + +- If the name contains `alloc_bytes` or `resize_bytes`, then the procedure takes + in slice parameters and returns slices. +- If the procedure name contains `alloc` or `resize`, then the procedure takes + in a raw pointer and returns raw pointers. +- If the procedure name contains `free_bytes`, then the procedure takes in a + slice. +- If the procedure name contains `free`, then the procedure takes in a pointer. + +Higher-level allocation procedures follow the following naming scheme: + +- `new`: Allocates a single object +- `free`: Free a single object (opposite of `new`) +- `make`: Allocate a group of objects +- `delete`: Free a group of objects (opposite of `make`) */ package mem diff --git a/core/mem/mem.odin b/core/mem/mem.odin index d423cc1eb..67ed56c39 100644 --- a/core/mem/mem.odin +++ b/core/mem/mem.odin @@ -3,49 +3,185 @@ package mem import "base:runtime" import "base:intrinsics" -Byte :: runtime.Byte -Kilobyte :: runtime.Kilobyte -Megabyte :: runtime.Megabyte -Gigabyte :: runtime.Gigabyte -Terabyte :: runtime.Terabyte -Petabyte :: runtime.Petabyte -Exabyte :: runtime.Exabyte +/* +The size, in bytes, of a single byte. +This constant is equal to the value of `1`. +*/ +Byte :: runtime.Byte + +/* +The size, in bytes, of one kilobyte. + +This constant is equal to the amount of bytes in one kilobyte (also known as +kibibyte), which is equal to 1024 bytes. +*/ +Kilobyte :: runtime.Kilobyte + +/* +The size, in bytes, of one megabyte. + +This constant is equal to the amount of bytes in one megabyte (also known as +mebibyte), which is equal to 1024 kilobyte. +*/ +Megabyte :: runtime.Megabyte + +/* +The size, in bytes, of one gigabyte. + +This constant is equal to the amount of bytes in one gigabyte (also known as +gibiibyte), which is equal to 1024 megabytes. +*/ +Gigabyte :: runtime.Gigabyte + +/* +The size, in bytes, of one terabyte. + +This constant is equal to the amount of bytes in one terabyte (also known as +tebiibyte), which is equal to 1024 gigabytes. +*/ +Terabyte :: runtime.Terabyte + +/* +The size, in bytes, of one petabyte. + +This constant is equal to the amount of bytes in one petabyte (also known as +pebiibyte), which is equal to 1024 terabytes. +*/ +Petabyte :: runtime.Petabyte + +/* +The size, in bytes, of one exabyte. + +This constant is equal to the amount of bytes in one exabyte (also known as +exbibyte), which is equal to 1024 petabytes. +*/ +Exabyte :: runtime.Exabyte + +/* +Set each byte of a memory range to a specific value. + +This procedure copies value specified by the `value` parameter into each of the +`len` bytes of a memory range, located at address `data`. + +This procedure returns the pointer to `data`. +*/ set :: proc "contextless" (data: rawptr, value: byte, len: int) -> rawptr { return runtime.memset(data, i32(value), len) } + +/* +Set each byte of a memory range to zero. + +This procedure copies the value `0` into the `len` bytes of a memory range, +starting at address `data`. + +This procedure returns the pointer to `data`. +*/ zero :: proc "contextless" (data: rawptr, len: int) -> rawptr { intrinsics.mem_zero(data, len) return data } + +/* +Set each byte of a memory range to zero. + +This procedure copies the value `0` into the `len` bytes of a memory range, +starting at address `data`. + +This procedure returns the pointer to `data`. + +Unlike the `zero()` procedure, which can be optimized away or reordered by the +compiler under certain circumstances, `zero_explicit()` procedure can not be +optimized away or reordered with other memory access operations, and the +compiler assumes volatile semantics of the memory. +*/ zero_explicit :: proc "contextless" (data: rawptr, len: int) -> rawptr { // This routine tries to avoid the compiler optimizing away the call, - // so that it is always executed. It is intended to provided + // so that it is always executed. It is intended to provide // equivalent semantics to those provided by the C11 Annex K 3.7.4.1 // memset_s call. intrinsics.mem_zero_volatile(data, len) // Use the volatile mem_zero intrinsics.atomic_thread_fence(.Seq_Cst) // Prevent reordering return data } + +/* +Zero-fill the memory of an object. + +This procedure sets each byte of the object pointed to by the pointer `item` +to zero, and returns the pointer to `item`. +*/ zero_item :: proc "contextless" (item: $P/^$T) -> P { intrinsics.mem_zero(item, size_of(T)) return item } + +/* +Zero-fill the memory of the slice. + +This procedure sets each byte of the slice pointed to by the slice `data` +to zero, and returns the slice `data`. +*/ zero_slice :: proc "contextless" (data: $T/[]$E) -> T { zero(raw_data(data), size_of(E)*len(data)) return data } +/* +Copy bytes from one memory range to another. +This procedure copies `len` bytes of data, from the memory range pointed to by +the `src` pointer into the memory range pointed to by the `dst` pointer, and +returns the `dst` pointer. +*/ copy :: proc "contextless" (dst, src: rawptr, len: int) -> rawptr { intrinsics.mem_copy(dst, src, len) return dst } + +/* +Copy bytes between two non-overlapping memory ranges. + +This procedure copies `len` bytes of data, from the memory range pointed to by +the `src` pointer into the memory range pointed to by the `dst` pointer, and +returns the `dst` pointer. + +This is a slightly more optimized version of the `copy` procedure that requires +that memory ranges specified by the parameters to this procedure are not +overlapping. If the memory ranges specified by `dst` and `src` pointers overlap, +the behavior of this function may be unpredictable. +*/ copy_non_overlapping :: proc "contextless" (dst, src: rawptr, len: int) -> rawptr { intrinsics.mem_copy_non_overlapping(dst, src, len) return dst } +/* +Compare two memory ranges defined by slices. + +This procedure performs a byte-by-byte comparison between memory ranges +specified by slices `a` and `b`, and returns a value, specifying their relative +ordering. + +If the return value is: +- Equal to `-1`, then `a` is "smaller" than `b`. +- Equal to `+1`, then `a` is "bigger" than `b`. +- Equal to `0`, then `a` and `b` are equal. + +The comparison is performed as follows: +1. Each byte, upto `min(len(a), len(b))` bytes is compared between `a` and `b`. + - If the byte in slice `a` is smaller than a byte in slice `b`, then comparison + stops and this procedure returns `-1`. + - If the byte in slice `a` is bigger than a byte in slice `b`, then comparison + stops and this procedure returns `+1`. + - Otherwise the comparison continues until `min(len(a), len(b))` are compared. +2. If all the bytes in the range are equal, then the lengths of the slices are + compared. + - If the length of slice `a` is smaller than the length of slice `b`, then `-1` is returned. + - If the length of slice `b` is smaller than the length of slice `b`, then `+1` is returned. + - Otherwise `0` is returned. +*/ @(require_results) compare :: proc "contextless" (a, b: []byte) -> int { res := compare_byte_ptrs(raw_data(a), raw_data(b), min(len(a), len(b))) @@ -57,16 +193,89 @@ compare :: proc "contextless" (a, b: []byte) -> int { return res } +/* +Compare two memory ranges defined by byte pointers. + +This procedure performs a byte-by-byte comparison between memory ranges of size +`n` located at addresses `a` and `b`, and returns a value, specifying their relative +ordering. + +If the return value is: +- Equal to `-1`, then `a` is "smaller" than `b`. +- Equal to `+1`, then `a` is "bigger" than `b`. +- Equal to `0`, then `a` and `b` are equal. + +The comparison is performed as follows: +1. Each byte, upto `n` bytes is compared between `a` and `b`. + - If the byte in `a` is smaller than a byte in `b`, then comparison stops + and this procedure returns `-1`. + - If the byte in `a` is bigger than a byte in `b`, then comparison stops + and this procedure returns `+1`. + - Otherwise the comparison continues until `n` bytes are compared. +2. If all the bytes in the range are equal, this procedure returns `0`. +*/ @(require_results) compare_byte_ptrs :: proc "contextless" (a, b: ^byte, n: int) -> int #no_bounds_check { return runtime.memory_compare(a, b, n) } +/* +Compare two memory ranges defined by pointers. + +This procedure performs a byte-by-byte comparison between memory ranges of size +`n` located at addresses `a` and `b`, and returns a value, specifying their relative +ordering. + +If the return value is: +- Equal to `-1`, then `a` is "smaller" than `b`. +- Equal to `+1`, then `a` is "bigger" than `b`. +- Equal to `0`, then `a` and `b` are equal. + +The comparison is performed as follows: +1. Each byte, upto `n` bytes is compared between `a` and `b`. + - If the byte in `a` is smaller than a byte in `b`, then comparison stops + and this procedure returns `-1`. + - If the byte in `a` is bigger than a byte in `b`, then comparison stops + and this procedure returns `+1`. + - Otherwise the comparison continues until `n` bytes are compared. +2. If all the bytes in the range are equal, this procedure returns `0`. +*/ +@(require_results) +compare_ptrs :: proc "contextless" (a, b: rawptr, n: int) -> int { + return compare_byte_ptrs((^byte)(a), (^byte)(b), n) +} + +/* +Check whether two objects are equal on binary level. + +This procedure checks whether the memory ranges occupied by objects `a` and +`b` are equal. See `compare_byte_ptrs()` for how this comparison is done. +*/ +@(require_results) +simple_equal :: proc "contextless" (a, b: $T) -> bool where intrinsics.type_is_simple_compare(T) { + a, b := a, b + return compare_byte_ptrs((^byte)(&a), (^byte)(&b), size_of(T)) == 0 +} + +/* +Check if the memory range defined by a slice is zero-filled. + +This procedure checks whether every byte, pointed to by the slice, specified +by the parameter `data`, is zero. If all bytes of the slice are zero, this +procedure returns `true`. Otherwise this procedure returns `false`. +*/ @(require_results) check_zero :: proc(data: []byte) -> bool { return check_zero_ptr(raw_data(data), len(data)) } +/* +Check if the memory range defined defined by a pointer is zero-filled. + +This procedure checks whether each of the `len` bytes, starting at address +`ptr` is zero. If all bytes of this range are zero, this procedure returns +`true`. Otherwise this procedure returns `false`. +*/ @(require_results) check_zero_ptr :: proc(ptr: rawptr, len: int) -> bool { switch { @@ -81,57 +290,99 @@ check_zero_ptr :: proc(ptr: rawptr, len: int) -> bool { case 4: return intrinsics.unaligned_load((^u32)(ptr)) == 0 case 8: return intrinsics.unaligned_load((^u64)(ptr)) == 0 } - start := uintptr(ptr) start_aligned := align_forward_uintptr(start, align_of(uintptr)) end := start + uintptr(len) end_aligned := align_backward_uintptr(end, align_of(uintptr)) - for b in start.. bool where intrinsics.type_is_simple_compare(T) { - a, b := a, b - return compare_byte_ptrs((^byte)(&a), (^byte)(&b), size_of(T)) == 0 -} +/* +Offset a given pointer by a given amount. -@(require_results) -compare_ptrs :: proc "contextless" (a, b: rawptr, n: int) -> int { - return compare_byte_ptrs((^byte)(a), (^byte)(b), n) -} +This procedure offsets the pointer `ptr` to an object of type `T`, by the amount +of bytes specified by `offset*size_of(T)`, and returns the pointer `ptr`. +**Note**: Prefer to use multipointer types, if possible. +*/ ptr_offset :: intrinsics.ptr_offset + +/* +Offset a given pointer by a given amount backwards. + +This procedure offsets the pointer `ptr` to an object of type `T`, by the amount +of bytes specified by `offset*size_of(T)` in the negative direction, and +returns the pointer `ptr`. +*/ ptr_sub :: intrinsics.ptr_sub +/* +Construct a slice from pointer and length. + +This procedure creates a slice, that points to `len` amount of objects located +at an address, specified by `ptr`. +*/ @(require_results) slice_ptr :: proc "contextless" (ptr: ^$T, len: int) -> []T { return ([^]T)(ptr)[:len] } +/* +Construct a byte slice from raw pointer and length. + +This procedure creates a byte slice, that points to `len` amount of bytes +located at an address specified by `data`. +*/ @(require_results) byte_slice :: #force_inline proc "contextless" (data: rawptr, #any_int len: int) -> []byte { return ([^]u8)(data)[:max(len, 0)] } +/* +Create a byte slice from pointer and length. + +This procedure creates a byte slice, pointing to `len` objects, starting from +the address specified by `ptr`. +*/ +@(require_results) +ptr_to_bytes :: proc "contextless" (ptr: ^$T, len := 1) -> []byte { + return transmute([]byte)Raw_Slice{ptr, len*size_of(T)} +} + +/* +Obtain the slice, pointing to the contents of `any`. + +This procedure returns the slice, pointing to the contents of the specified +value of the `any` type. +*/ +@(require_results) +any_to_bytes :: proc "contextless" (val: any) -> []byte { + ti := type_info_of(val.id) + size := ti != nil ? ti.size : 0 + return transmute([]byte)Raw_Slice{val.data, size} +} + +/* +Obtain a byte slice from any slice. + +This procedure returns a slice, that points to the same bytes as the slice, +specified by `slice` and returns the resulting byte slice. +*/ @(require_results) slice_to_bytes :: proc "contextless" (slice: $E/[]$T) -> []byte { s := transmute(Raw_Slice)slice @@ -139,6 +390,15 @@ slice_to_bytes :: proc "contextless" (slice: $E/[]$T) -> []byte { return transmute([]byte)s } +/* +Transmute slice to a different type. + +This procedure performs an operation similar to transmute, returning a slice of +type `T` that points to the same bytes as the slice specified by `slice` +parameter. Unlike plain transmute operation, this procedure adjusts the length +of the resulting slice, such that the resulting slice points to the correct +amount of objects to cover the memory region pointed to by `slice`. +*/ @(require_results) slice_data_cast :: proc "contextless" ($T: typeid/[]$A, slice: $S/[]$B) -> T { when size_of(A) == 0 || size_of(B) == 0 { @@ -150,12 +410,25 @@ slice_data_cast :: proc "contextless" ($T: typeid/[]$A, slice: $S/[]$B) -> T { } } +/* +Obtain data and length of a slice. + +This procedure returns the pointer to the start of the memory region pointed to +by slice `slice` and the length of the slice. +*/ @(require_results) slice_to_components :: proc "contextless" (slice: $E/[]$T) -> (data: ^T, len: int) { s := transmute(Raw_Slice)slice return (^T)(s.data), s.len } +/* +Create a dynamic array from slice. + +This procedure creates a dynamic array, using slice `backing` as the backing +buffer for the dynamic array. The resulting dynamic array can not grow beyond +the size of the specified slice. +*/ @(require_results) buffer_from_slice :: proc "contextless" (backing: $T/[]$E) -> [dynamic]E { return transmute([dynamic]E)Raw_Dynamic_Array{ @@ -169,19 +442,12 @@ buffer_from_slice :: proc "contextless" (backing: $T/[]$E) -> [dynamic]E { } } -@(require_results) -ptr_to_bytes :: proc "contextless" (ptr: ^$T, len := 1) -> []byte { - return transmute([]byte)Raw_Slice{ptr, len*size_of(T)} -} - -@(require_results) -any_to_bytes :: proc "contextless" (val: any) -> []byte { - ti := type_info_of(val.id) - size := ti != nil ? ti.size : 0 - return transmute([]byte)Raw_Slice{val.data, size} -} - +/* +Check whether a number is a power of two. +This procedure checks whether a given pointer-sized unsigned integer contains +a power-of-two value. +*/ @(require_results) is_power_of_two :: proc "contextless" (x: uintptr) -> bool { if x <= 0 { @@ -190,66 +456,167 @@ is_power_of_two :: proc "contextless" (x: uintptr) -> bool { return (x & (x-1)) == 0 } +/* +Check if a pointer is aligned. + +This procedure checks whether a pointer `x` is aligned to a boundary specified +by `align`, and returns `true` if the pointer is aligned, and false otherwise. +*/ +is_aligned :: proc "contextless" (x: rawptr, align: int) -> bool { + p := uintptr(x) + return (p & (1< uintptr { + assert(is_power_of_two(align)) + return (ptr + align-1) & ~(align-1) +} + +/* +Align pointer forward. + +This procedure returns the next address after `ptr`, that is located on the +alignment boundary specified by `align`. If `ptr` is already aligned to `align` +bytes, `ptr` is returned. + +The specified alignment must be a power of 2. +*/ @(require_results) align_forward :: proc(ptr: rawptr, align: uintptr) -> rawptr { return rawptr(align_forward_uintptr(uintptr(ptr), align)) } -@(require_results) -align_forward_uintptr :: proc(ptr, align: uintptr) -> uintptr { - assert(is_power_of_two(align)) +/* +Align int forward. - p := ptr - modulo := p & (align-1) - if modulo != 0 { - p += align - modulo - } - return p -} +This procedure returns the next address after `ptr`, that is located on the +alignment boundary specified by `align`. If `ptr` is already aligned to `align` +bytes, `ptr` is returned. +The specified alignment must be a power of 2. +*/ @(require_results) align_forward_int :: proc(ptr, align: int) -> int { return int(align_forward_uintptr(uintptr(ptr), uintptr(align))) } + +/* +Align uint forward. + +This procedure returns the next address after `ptr`, that is located on the +alignment boundary specified by `align`. If `ptr` is already aligned to `align` +bytes, `ptr` is returned. + +The specified alignment must be a power of 2. +*/ @(require_results) align_forward_uint :: proc(ptr, align: uint) -> uint { return uint(align_forward_uintptr(uintptr(ptr), uintptr(align))) } +/* +Align uintptr backwards. + +This procedure returns the previous address before `ptr`, that is located on the +alignment boundary specified by `align`. If `ptr` is already aligned to `align` +bytes, `ptr` is returned. + +The specified alignment must be a power of 2. +*/ +@(require_results) +align_backward_uintptr :: proc(ptr, align: uintptr) -> uintptr { + assert(is_power_of_two(align)) + return ptr & ~(align-1) +} + +/* +Align rawptr backwards. + +This procedure returns the previous address before `ptr`, that is located on the +alignment boundary specified by `align`. If `ptr` is already aligned to `align` +bytes, `ptr` is returned. + +The specified alignment must be a power of 2. +*/ @(require_results) align_backward :: proc(ptr: rawptr, align: uintptr) -> rawptr { return rawptr(align_backward_uintptr(uintptr(ptr), align)) } -@(require_results) -align_backward_uintptr :: proc(ptr, align: uintptr) -> uintptr { - return align_forward_uintptr(ptr - align + 1, align) -} +/* +Align int backwards. +This procedure returns the previous address before `ptr`, that is located on the +alignment boundary specified by `align`. If `ptr` is already aligned to `align` +bytes, `ptr` is returned. + +The specified alignment must be a power of 2. +*/ @(require_results) align_backward_int :: proc(ptr, align: int) -> int { return int(align_backward_uintptr(uintptr(ptr), uintptr(align))) } + +/* +Align uint backwards. + +This procedure returns the previous address before `ptr`, that is located on the +alignment boundary specified by `align`. If `ptr` is already aligned to `align` +bytes, `ptr` is returned. + +The specified alignment must be a power of 2. +*/ @(require_results) align_backward_uint :: proc(ptr, align: uint) -> uint { return uint(align_backward_uintptr(uintptr(ptr), uintptr(align))) } +/* +Create a context with a given allocator. + +This procedure returns a copy of the current context with the allocator replaced +by the allocator `a`. +*/ @(require_results) context_from_allocator :: proc(a: Allocator) -> type_of(context) { context.allocator = a return context } +/* +Copy the value from a pointer into a value. + +This procedure copies the object of type `T` pointed to by the pointer `ptr` +into a new stack-allocated value and returns that value. +*/ @(require_results) reinterpret_copy :: proc "contextless" ($T: typeid, ptr: rawptr) -> (value: T) { copy(&value, ptr, size_of(T)) return } +/* +Dynamic array with a fixed capacity buffer. +This type represents dynamic arrays with a fixed-size backing buffer. Upon +allocating memory beyond reaching the maximum capacity, allocations from fixed +byte buffers return `nil` and no error. +*/ Fixed_Byte_Buffer :: distinct [dynamic]byte +/* +Create a fixed byte buffer from a slice. +*/ @(require_results) make_fixed_byte_buffer :: proc "contextless" (backing: []byte) -> Fixed_Byte_Buffer { s := transmute(Raw_Slice)backing @@ -264,40 +631,60 @@ make_fixed_byte_buffer :: proc "contextless" (backing: []byte) -> Fixed_Byte_Buf return transmute(Fixed_Byte_Buffer)d } +/* +General-purpose align formula. - +This procedure is equivalent to `align_forward`, but it does not require the +alignment to be a power of two. +*/ @(require_results) align_formula :: proc "contextless" (size, align: int) -> int { result := size + align-1 return result - result%align } +/* +Calculate the padding for header preceding aligned data. + +This procedure returns the padding, following the specified pointer `ptr` that +will be able to fit in a header of the size `header_size`, immediately +preceding the memory region, aligned on a boundary specified by `align`. See +the following diagram for a visual representation. + + header size + |<------>| + +---+--------+------------- - - - + | HEADER | DATA... + +---+--------+------------- - - - + ^ ^ + |<---------->| + | padding | + ptr aligned ptr + +The function takes in `ptr` and `header_size`, as well as the required +alignment for `DATA`. The return value of the function is the padding between +`ptr` and `aligned_ptr` that will be able to fit the header. +*/ @(require_results) calc_padding_with_header :: proc "contextless" (ptr: uintptr, align: uintptr, header_size: int) -> int { p, a := ptr, align modulo := p & (a-1) - padding := uintptr(0) if modulo != 0 { padding = a - modulo } - needed_space := uintptr(header_size) if padding < needed_space { needed_space -= padding - if needed_space & (a-1) > 0 { padding += align * (1+(needed_space/align)) } else { padding += align * (needed_space/align) } } - return int(padding) } - - @(require_results, deprecated="prefer 'slice.clone'") clone_slice :: proc(slice: $T/[]$E, allocator := context.allocator, loc := #caller_location) -> (new_slice: T) { new_slice, _ = make(T, len(slice), allocator, loc) diff --git a/core/mem/mutex_allocator.odin b/core/mem/mutex_allocator.odin index 591703eab..7361016c3 100644 --- a/core/mem/mutex_allocator.odin +++ b/core/mem/mutex_allocator.odin @@ -1,19 +1,33 @@ -//+build !freestanding +#+build !freestanding, wasm32, wasm64p32 package mem import "core:sync" +/* +The data for mutex allocator. +*/ Mutex_Allocator :: struct { backing: Allocator, mutex: sync.Mutex, } +/* +Initialize the mutex allocator. + +This procedure initializes the mutex allocator using `backin_allocator` as the +allocator that will be used to pass all allocation requests through. +*/ mutex_allocator_init :: proc(m: ^Mutex_Allocator, backing_allocator: Allocator) { m.backing = backing_allocator m.mutex = {} } +/* +Mutex allocator. +The mutex allocator is a wrapper for allocators that is used to serialize all +allocator requests across multiple threads. +*/ @(require_results) mutex_allocator :: proc(m: ^Mutex_Allocator) -> Allocator { return Allocator{ @@ -22,11 +36,16 @@ mutex_allocator :: proc(m: ^Mutex_Allocator) -> Allocator { } } -mutex_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> (result: []byte, err: Allocator_Error) { +mutex_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size: int, + alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> (result: []byte, err: Allocator_Error) { m := (^Mutex_Allocator)(allocator_data) - sync.mutex_guard(&m.mutex) return m.backing.procedure(m.backing.data, mode, size, alignment, old_memory, old_size, loc) } diff --git a/core/mem/raw.odin b/core/mem/raw.odin index f56206957..41c91555e 100644 --- a/core/mem/raw.odin +++ b/core/mem/raw.odin @@ -3,26 +3,100 @@ package mem import "base:builtin" import "base:runtime" -Raw_Any :: runtime.Raw_Any -Raw_String :: runtime.Raw_String -Raw_Cstring :: runtime.Raw_Cstring -Raw_Slice :: runtime.Raw_Slice -Raw_Dynamic_Array :: runtime.Raw_Dynamic_Array -Raw_Map :: runtime.Raw_Map -Raw_Soa_Pointer :: runtime.Raw_Soa_Pointer +/* +Memory layout of the `any` type. +*/ +Raw_Any :: runtime.Raw_Any -Raw_Complex32 :: runtime.Raw_Complex32 -Raw_Complex64 :: runtime.Raw_Complex64 -Raw_Complex128 :: runtime.Raw_Complex128 -Raw_Quaternion64 :: runtime.Raw_Quaternion64 +/* +Memory layout of the `string` type. +*/ +Raw_String :: runtime.Raw_String + +/* +Memory layout of the `cstring` type. +*/ +Raw_Cstring :: runtime.Raw_Cstring + +/* +Memory layout of `[]T` types. +*/ +Raw_Slice :: runtime.Raw_Slice + +/* +Memory layout of `[dynamic]T` types. +*/ +Raw_Dynamic_Array :: runtime.Raw_Dynamic_Array + +/* +Memory layout of `map[K]V` types. +*/ +Raw_Map :: runtime.Raw_Map + +/* +Memory layout of `#soa []T` types. +*/ +Raw_Soa_Pointer :: runtime.Raw_Soa_Pointer + +/* +Memory layout of the `complex32` type. +*/ +Raw_Complex32 :: runtime.Raw_Complex32 + +/* +Memory layout of the `complex64` type. +*/ +Raw_Complex64 :: runtime.Raw_Complex64 + +/* +Memory layout of the `complex128` type. +*/ +Raw_Complex128 :: runtime.Raw_Complex128 + +/* +Memory layout of the `quaternion64` type. +*/ +Raw_Quaternion64 :: runtime.Raw_Quaternion64 + +/* +Memory layout of the `quaternion128` type. +*/ Raw_Quaternion128 :: runtime.Raw_Quaternion128 + +/* +Memory layout of the `quaternion256` type. +*/ Raw_Quaternion256 :: runtime.Raw_Quaternion256 -Raw_Quaternion64_Vector_Scalar :: runtime.Raw_Quaternion64_Vector_Scalar + +/* +Memory layout of the `quaternion64` type. +*/ +Raw_Quaternion64_Vector_Scalar :: runtime.Raw_Quaternion64_Vector_Scalar + +/* +Memory layout of the `quaternion128` type. +*/ Raw_Quaternion128_Vector_Scalar :: runtime.Raw_Quaternion128_Vector_Scalar + +/* +Memory layout of the `quaternion256` type. +*/ Raw_Quaternion256_Vector_Scalar :: runtime.Raw_Quaternion256_Vector_Scalar +/* +Create a value of the any type. + +This procedure creates a value with type `any` that points to an object with +typeid `id` located at an address specified by `data`. +*/ make_any :: proc "contextless" (data: rawptr, id: typeid) -> any { return transmute(any)Raw_Any{data, id} } +/* +Obtain pointer to the data. + +This procedure returns the pointer to the data of a slice, string, or a dynamic +array. +*/ raw_data :: builtin.raw_data diff --git a/core/mem/rollback_stack_allocator.odin b/core/mem/rollback_stack_allocator.odin index f5e428d87..43ef10fe9 100644 --- a/core/mem/rollback_stack_allocator.odin +++ b/core/mem/rollback_stack_allocator.odin @@ -1,52 +1,36 @@ package mem -// The Rollback Stack Allocator was designed for the test runner to be fast, -// able to grow, and respect the Tracking Allocator's requirement for -// individual frees. It is not overly concerned with fragmentation, however. -// -// It has support for expansion when configured with a block allocator and -// limited support for out-of-order frees. -// -// Allocation has constant-time best and usual case performance. -// At worst, it is linear according to the number of memory blocks. -// -// Allocation follows a first-fit strategy when there are multiple memory -// blocks. -// -// Freeing has constant-time best and usual case performance. -// At worst, it is linear according to the number of memory blocks and number -// of freed items preceding the last item in a block. -// -// Resizing has constant-time performance, if it's the last item in a block, or -// the new size is smaller. Naturally, this becomes linear-time if there are -// multiple blocks to search for the pointer's owning block. Otherwise, the -// allocator defaults to a combined alloc & free operation internally. -// -// Out-of-order freeing is accomplished by collapsing a run of freed items -// from the last allocation backwards. -// -// Each allocation has an overhead of 8 bytes and any extra bytes to satisfy -// the requested alignment. - import "base:runtime" +/* +Rollback stack default block size. +*/ ROLLBACK_STACK_DEFAULT_BLOCK_SIZE :: 4 * Megabyte -// This limitation is due to the size of `prev_ptr`, but it is only for the -// head block; any allocation in excess of the allocator's `block_size` is -// valid, so long as the block allocator can handle it. -// -// This is because allocations over the block size are not split up if the item -// within is freed; they are immediately returned to the block allocator. +/* +Rollback stack max head block size. + +This limitation is due to the size of `prev_ptr`, but it is only for the +head block; any allocation in excess of the allocator's `block_size` is +valid, so long as the block allocator can handle it. + +This is because allocations over the block size are not split up if the item +within is freed; they are immediately returned to the block allocator. +*/ ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE :: 2 * Gigabyte - +/* +Allocation header of the rollback stack allocator. +*/ Rollback_Stack_Header :: bit_field u64 { prev_offset: uintptr | 32, is_free: bool | 1, prev_ptr: uintptr | 31, } +/* +Block header of the rollback stack allocator. +*/ Rollback_Stack_Block :: struct { next_block: ^Rollback_Stack_Block, last_alloc: rawptr, @@ -54,13 +38,15 @@ Rollback_Stack_Block :: struct { buffer: []byte, } +/* +Rollback stack allocator data. +*/ Rollback_Stack :: struct { head: ^Rollback_Stack_Block, block_size: int, block_allocator: Allocator, } - @(private="file", require_results) rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool { start := raw_data(block.buffer) @@ -110,6 +96,9 @@ rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_ } } +/* +Free memory to a rollback stack allocator. +*/ @(private="file", require_results) rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error { parent, block, header := rb_find_ptr(stack, ptr) or_return @@ -128,6 +117,9 @@ rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error { return nil } +/* +Free all memory owned by the rollback stack allocator. +*/ @(private="file") rb_free_all :: proc(stack: ^Rollback_Stack) { for block := stack.head.next_block; block != nil; /**/ { @@ -141,45 +133,75 @@ rb_free_all :: proc(stack: ^Rollback_Stack) { stack.head.offset = 0 } -@(private="file", require_results) -rb_resize :: proc(stack: ^Rollback_Stack, ptr: rawptr, old_size, size, alignment: int) -> (result: []byte, err: Allocator_Error) { - if ptr != nil { - if block, _, ok := rb_find_last_alloc(stack, ptr); ok { - // `block.offset` should never underflow because it is contingent - // on `old_size` in the first place, assuming sane arguments. - assert(block.offset >= cast(uintptr)old_size, "Rollback Stack Allocator received invalid `old_size`.") - - if block.offset + cast(uintptr)size - cast(uintptr)old_size < cast(uintptr)len(block.buffer) { - // Prevent singleton allocations from fragmenting by forbidding - // them to shrink, removing the possibility of overflow bugs. - if len(block.buffer) <= stack.block_size { - block.offset += cast(uintptr)size - cast(uintptr)old_size - } - #no_bounds_check return (cast([^]byte)ptr)[:size], nil - } - } +/* +Allocate memory using the rollback stack allocator. +*/ +@(require_results) +rb_alloc :: proc( + stack: ^Rollback_Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := rb_alloc_bytes_non_zeroed(stack, size, alignment, loc) + if bytes != nil { + zero_slice(bytes) } - - result = rb_alloc(stack, size, alignment) or_return - runtime.mem_copy_non_overlapping(raw_data(result), ptr, old_size) - err = rb_free(stack, ptr) - - return + return raw_data(bytes), err } -@(private="file", require_results) -rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byte, err: Allocator_Error) { +/* +Allocate memory using the rollback stack allocator. +*/ +@(require_results) +rb_alloc_bytes :: proc( + stack: ^Rollback_Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + bytes, err := rb_alloc_bytes_non_zeroed(stack, size, alignment, loc) + if bytes != nil { + zero_slice(bytes) + } + return bytes, err +} + +/* +Allocate non-initialized memory using the rollback stack allocator. +*/ +@(require_results) +rb_alloc_non_zeroed :: proc( + stack: ^Rollback_Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := rb_alloc_bytes_non_zeroed(stack, size, alignment, loc) + return raw_data(bytes), err +} + +/* +Allocate non-initialized memory using the rollback stack allocator. +*/ +@(require_results) +rb_alloc_bytes_non_zeroed :: proc( + stack: ^Rollback_Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (result: []byte, err: Allocator_Error) { + assert(size >= 0, "Size must be positive or zero.", loc) + assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", loc) parent: ^Rollback_Stack_Block for block := stack.head; /**/; block = block.next_block { when !ODIN_DISABLE_ASSERT { allocated_new_block: bool } - if block == nil { if stack.block_allocator.procedure == nil { return nil, .Out_Of_Memory } - minimum_size_required := size_of(Rollback_Stack_Header) + size + alignment - 1 new_block_size := max(minimum_size_required, stack.block_size) block = rb_make_block(new_block_size, stack.block_allocator) or_return @@ -188,10 +210,8 @@ rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byt allocated_new_block = true } } - start := raw_data(block.buffer)[block.offset:] padding := cast(uintptr)calc_padding_with_header(cast(uintptr)start, cast(uintptr)alignment, size_of(Rollback_Stack_Header)) - if block.offset + padding + cast(uintptr)size > cast(uintptr)len(block.buffer) { when !ODIN_DISABLE_ASSERT { if allocated_new_block { @@ -201,54 +221,150 @@ rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byt parent = block continue } - header := cast(^Rollback_Stack_Header)(start[padding - size_of(Rollback_Stack_Header):]) ptr := start[padding:] - header^ = { prev_offset = block.offset, prev_ptr = uintptr(0) if block.last_alloc == nil else cast(uintptr)block.last_alloc - cast(uintptr)raw_data(block.buffer), is_free = false, } - block.last_alloc = ptr block.offset += padding + cast(uintptr)size - if len(block.buffer) > stack.block_size { // This block exceeds the allocator's standard block size and is considered a singleton. // Prevent any further allocations on it. block.offset = cast(uintptr)len(block.buffer) } - #no_bounds_check return ptr[:size], nil } - return nil, .Out_Of_Memory } +/* +Resize an allocation owned by rollback stack allocator. +*/ +@(require_results) +rb_resize :: proc( + stack: ^Rollback_Stack, + old_ptr: rawptr, + old_size: int, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := rb_resize_bytes_non_zeroed(stack, byte_slice(old_ptr, old_size), size, alignment, loc) + if bytes != nil { + if old_ptr == nil { + zero_slice(bytes) + } else if size > old_size { + zero_slice(bytes[old_size:]) + } + } + return raw_data(bytes), err +} + +/* +Resize an allocation owned by rollback stack allocator. +*/ +@(require_results) +rb_resize_bytes :: proc( + stack: ^Rollback_Stack, + old_memory: []byte, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]u8, Allocator_Error) { + bytes, err := rb_resize_bytes_non_zeroed(stack, old_memory, size, alignment, loc) + if bytes != nil { + if old_memory == nil { + zero_slice(bytes) + } else if size > len(old_memory) { + zero_slice(bytes[len(old_memory):]) + } + } + return bytes, err +} + +/* +Resize an allocation owned by rollback stack allocator without explicit +zero-initialization. +*/ +@(require_results) +rb_resize_non_zeroed :: proc( + stack: ^Rollback_Stack, + old_ptr: rawptr, + old_size: int, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := rb_resize_bytes_non_zeroed(stack, byte_slice(old_ptr, old_size), size, alignment, loc) + return raw_data(bytes), err +} + +/* +Resize an allocation owned by rollback stack allocator without explicit +zero-initialization. +*/ +@(require_results) +rb_resize_bytes_non_zeroed :: proc( + stack: ^Rollback_Stack, + old_memory: []byte, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (result: []byte, err: Allocator_Error) { + old_size := len(old_memory) + ptr := raw_data(old_memory) + assert(size >= 0, "Size must be positive or zero.", loc) + assert(old_size >= 0, "Old size must be positive or zero.", loc) + assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", loc) + if ptr != nil { + if block, _, ok := rb_find_last_alloc(stack, ptr); ok { + // `block.offset` should never underflow because it is contingent + // on `old_size` in the first place, assuming sane arguments. + assert(block.offset >= cast(uintptr)old_size, "Rollback Stack Allocator received invalid `old_size`.") + if block.offset + cast(uintptr)size - cast(uintptr)old_size < cast(uintptr)len(block.buffer) { + // Prevent singleton allocations from fragmenting by forbidding + // them to shrink, removing the possibility of overflow bugs. + if len(block.buffer) <= stack.block_size { + block.offset += cast(uintptr)size - cast(uintptr)old_size + } + #no_bounds_check return (ptr)[:size], nil + } + } + } + result = rb_alloc_bytes_non_zeroed(stack, size, alignment) or_return + runtime.mem_copy_non_overlapping(raw_data(result), ptr, old_size) + err = rb_free(stack, ptr) + return +} + @(private="file", require_results) rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stack_Block, err: Allocator_Error) { buffer := runtime.mem_alloc(size_of(Rollback_Stack_Block) + size, align_of(Rollback_Stack_Block), allocator) or_return - block = cast(^Rollback_Stack_Block)raw_data(buffer) #no_bounds_check block.buffer = buffer[size_of(Rollback_Stack_Block):] return } - +/* +Initialize the rollback stack allocator using a fixed backing buffer. +*/ rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, location := #caller_location) { MIN_SIZE :: size_of(Rollback_Stack_Block) + size_of(Rollback_Stack_Header) + size_of(rawptr) assert(len(buffer) >= MIN_SIZE, "User-provided buffer to Rollback Stack Allocator is too small.", location) - block := cast(^Rollback_Stack_Block)raw_data(buffer) block^ = {} #no_bounds_check block.buffer = buffer[size_of(Rollback_Stack_Block):] - stack^ = {} stack.head = block stack.block_size = len(block.buffer) } +/* +Initialize the rollback stack alocator using a backing block allocator. +*/ rollback_stack_init_dynamic :: proc( stack: ^Rollback_Stack, block_size : int = ROLLBACK_STACK_DEFAULT_BLOCK_SIZE, @@ -261,22 +377,25 @@ rollback_stack_init_dynamic :: proc( // size is insufficient; check only on platforms with big enough ints. assert(block_size <= ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE, "Rollback Stack Allocators cannot support head blocks larger than 2 gigabytes.", location) } - block := rb_make_block(block_size, block_allocator) or_return - stack^ = {} stack.head = block stack.block_size = block_size stack.block_allocator = block_allocator - return nil } +/* +Initialize the rollback stack. +*/ rollback_stack_init :: proc { rollback_stack_init_buffered, rollback_stack_init_dynamic, } +/* +Destroy a rollback stack. +*/ rollback_stack_destroy :: proc(stack: ^Rollback_Stack) { if stack.block_allocator.procedure != nil { rb_free_all(stack) @@ -285,6 +404,37 @@ rollback_stack_destroy :: proc(stack: ^Rollback_Stack) { stack^ = {} } +/* +Rollback stack allocator. + +The Rollback Stack Allocator was designed for the test runner to be fast, +able to grow, and respect the Tracking Allocator's requirement for +individual frees. It is not overly concerned with fragmentation, however. + +It has support for expansion when configured with a block allocator and +limited support for out-of-order frees. + +Allocation has constant-time best and usual case performance. +At worst, it is linear according to the number of memory blocks. + +Allocation follows a first-fit strategy when there are multiple memory +blocks. + +Freeing has constant-time best and usual case performance. +At worst, it is linear according to the number of memory blocks and number +of freed items preceding the last item in a block. + +Resizing has constant-time performance, if it's the last item in a block, or +the new size is smaller. Naturally, this becomes linear-time if there are +multiple blocks to search for the pointer's owning block. Otherwise, the +allocator defaults to a combined alloc & free operation internally. + +Out-of-order freeing is accomplished by collapsing a run of freed items +from the last allocation backwards. + +Each allocation has an overhead of 8 bytes and any extra bytes to satisfy +the requested alignment. +*/ @(require_results) rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator { return Allocator { @@ -294,48 +444,37 @@ rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator { } @(require_results) -rollback_stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, location := #caller_location, +rollback_stack_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, ) -> (result: []byte, err: Allocator_Error) { stack := cast(^Rollback_Stack)allocator_data - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - assert(size >= 0, "Size must be positive or zero.", location) - assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", location) - result = rb_alloc(stack, size, alignment) or_return - - if mode == .Alloc { - zero_slice(result) - } - + case .Alloc: + return rb_alloc_bytes(stack, size, alignment, loc) + case .Alloc_Non_Zeroed: + return rb_alloc_bytes_non_zeroed(stack, size, alignment, loc) case .Free: - err = rb_free(stack, old_memory) - + return nil, rb_free(stack, old_memory) case .Free_All: rb_free_all(stack) - - case .Resize, .Resize_Non_Zeroed: - assert(size >= 0, "Size must be positive or zero.", location) - assert(old_size >= 0, "Old size must be positive or zero.", location) - assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", location) - result = rb_resize(stack, old_memory, old_size, size, alignment) or_return - - #no_bounds_check if mode == .Resize && size > old_size { - zero_slice(result[old_size:]) - } - + return nil, nil + case .Resize: + return rb_resize_bytes(stack, byte_slice(old_memory, old_size), size, alignment, loc) + case .Resize_Non_Zeroed: + return rb_resize_bytes_non_zeroed(stack, byte_slice(old_memory, old_size), size, alignment, loc) case .Query_Features: set := (^Allocator_Mode_Set)(old_memory) if set != nil { set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Resize_Non_Zeroed} } return nil, nil - case .Query_Info: return nil, .Mode_Not_Implemented } - return } diff --git a/core/mem/tracking_allocator.odin b/core/mem/tracking_allocator.odin index 1b57e5fb4..cf780de3f 100644 --- a/core/mem/tracking_allocator.odin +++ b/core/mem/tracking_allocator.odin @@ -1,53 +1,88 @@ -//+build !freestanding +#+build !freestanding, wasm32, wasm64p32 package mem import "base:runtime" import "core:sync" +/* +Allocation entry for the tracking allocator. + +This structure stores the data related to an allocation. +*/ Tracking_Allocator_Entry :: struct { - memory: rawptr, - size: int, + // Pointer to an allocated region. + memory: rawptr, + // Size of the allocated memory region. + size: int, + // Requested alignment. alignment: int, - mode: Allocator_Mode, - err: Allocator_Error, + // Mode of the operation. + mode: Allocator_Mode, + // Error. + err: Allocator_Error, + // Location of the allocation. location: runtime.Source_Code_Location, } + +/* +Bad free entry for a tracking allocator. +*/ Tracking_Allocator_Bad_Free_Entry :: struct { - memory: rawptr, + // Pointer, on which free operation was called. + memory: rawptr, + // The source location of where the operation was called. location: runtime.Source_Code_Location, } -Tracking_Allocator :: struct { - backing: Allocator, - allocation_map: map[rawptr]Tracking_Allocator_Entry, - bad_free_array: [dynamic]Tracking_Allocator_Bad_Free_Entry, - mutex: sync.Mutex, - clear_on_free_all: bool, - total_memory_allocated: i64, - total_allocation_count: i64, - total_memory_freed: i64, - total_free_count: i64, - peak_memory_allocated: i64, +/* +Tracking allocator data. +*/ +Tracking_Allocator :: struct { + backing: Allocator, + allocation_map: map[rawptr]Tracking_Allocator_Entry, + bad_free_array: [dynamic]Tracking_Allocator_Bad_Free_Entry, + mutex: sync.Mutex, + clear_on_free_all: bool, + total_memory_allocated: i64, + total_allocation_count: i64, + total_memory_freed: i64, + total_free_count: i64, + peak_memory_allocated: i64, current_memory_allocated: i64, } +/* +Initialize the tracking allocator. + +This procedure initializes the tracking allocator `t` with a backing allocator +specified with `backing_allocator`. The `internals_allocator` will used to +allocate the tracked data. +*/ tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Allocator, internals_allocator := context.allocator) { t.backing = backing_allocator t.allocation_map.allocator = internals_allocator t.bad_free_array.allocator = internals_allocator - if .Free_All in query_features(t.backing) { t.clear_on_free_all = true } } +/* +Destroy the tracking allocator. +*/ tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) { delete(t.allocation_map) delete(t.bad_free_array) } +/* +Clear the tracking allocator. -// Clear only the current allocation data while keeping the totals intact. +This procedure clears the tracked data from a tracking allocator. + +**Note**: This procedure clears only the current allocation data while keeping +the totals intact. +*/ tracking_allocator_clear :: proc(t: ^Tracking_Allocator) { sync.mutex_lock(&t.mutex) clear(&t.allocation_map) @@ -56,7 +91,11 @@ tracking_allocator_clear :: proc(t: ^Tracking_Allocator) { sync.mutex_unlock(&t.mutex) } -// Reset all of a Tracking Allocator's allocation data back to zero. +/* +Reset the tracking allocator. + +Reset all of a Tracking Allocator's allocation data back to zero. +*/ tracking_allocator_reset :: proc(t: ^Tracking_Allocator) { sync.mutex_lock(&t.mutex) clear(&t.allocation_map) @@ -70,6 +109,39 @@ tracking_allocator_reset :: proc(t: ^Tracking_Allocator) { sync.mutex_unlock(&t.mutex) } +/* +Tracking allocator. + +The tracking allocator is an allocator wrapper that tracks memory allocations. +This allocator stores all the allocations in a map. Whenever a pointer that's +not inside of the map is freed, the `bad_free_array` entry is added. + +An example of how to use the `Tracking_Allocator` to track subsequent allocations +in your program and report leaks and bad frees: + +Example: + + package foo + + import "core:mem" + import "core:fmt" + + main :: proc() { + track: mem.Tracking_Allocator + mem.tracking_allocator_init(&track, context.allocator) + defer mem.tracking_allocator_destroy(&track) + context.allocator = mem.tracking_allocator(&track) + + do_stuff() + + for _, leak in track.allocation_map { + fmt.printf("%v leaked %m\n", leak.location, leak.size) + } + for bad_free in track.bad_free_array { + fmt.printf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory) + } + } +*/ @(require_results) tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator { return Allocator{ @@ -78,9 +150,14 @@ tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator { } } -tracking_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> (result: []byte, err: Allocator_Error) { +tracking_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> (result: []byte, err: Allocator_Error) { track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) { data.total_memory_allocated += i64(entry.size) data.total_allocation_count += 1 diff --git a/core/mem/virtual/virtual_linux.odin b/core/mem/virtual/virtual_linux.odin index 0b4532baa..3e0d7668b 100644 --- a/core/mem/virtual/virtual_linux.odin +++ b/core/mem/virtual/virtual_linux.odin @@ -1,5 +1,5 @@ -//+build linux -//+private +#+build linux +#+private package mem_virtual import "core:sys/linux" diff --git a/core/mem/virtual/virtual_other.odin b/core/mem/virtual/virtual_other.odin index 4fcb61b04..a57856975 100644 --- a/core/mem/virtual/virtual_other.odin +++ b/core/mem/virtual/virtual_other.odin @@ -1,10 +1,10 @@ -//+private -//+build !darwin -//+build !freebsd -//+build !openbsd -//+build !netbsd -//+build !linux -//+build !windows +#+private +#+build !darwin +#+build !freebsd +#+build !openbsd +#+build !netbsd +#+build !linux +#+build !windows package mem_virtual _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { diff --git a/core/mem/virtual/virtual_platform.odin b/core/mem/virtual/virtual_platform.odin index c2b505cd2..54c42ce4b 100644 --- a/core/mem/virtual/virtual_platform.odin +++ b/core/mem/virtual/virtual_platform.odin @@ -1,4 +1,4 @@ -//+private +#+private package mem_virtual Platform_Memory_Block :: struct { diff --git a/core/mem/virtual/virtual_posix.odin b/core/mem/virtual/virtual_posix.odin index 035763466..105849774 100644 --- a/core/mem/virtual/virtual_posix.odin +++ b/core/mem/virtual/virtual_posix.odin @@ -1,5 +1,5 @@ -//+build darwin, netbsd, freebsd, openbsd -//+private +#+build darwin, netbsd, freebsd, openbsd +#+private package mem_virtual import "core:sys/posix" diff --git a/core/mem/virtual/virtual_windows.odin b/core/mem/virtual/virtual_windows.odin index ee47a01a8..acd30ae33 100644 --- a/core/mem/virtual/virtual_windows.odin +++ b/core/mem/virtual/virtual_windows.odin @@ -1,5 +1,5 @@ -//+build windows -//+private +#+build windows +#+private package mem_virtual foreign import Kernel32 "system:Kernel32.lib" diff --git a/core/net/addr.odin b/core/net/addr.odin index 1972d8c22..c47c6f55e 100644 --- a/core/net/addr.odin +++ b/core/net/addr.odin @@ -1,4 +1,4 @@ -// +build windows, linux, darwin, freebsd +#+build windows, linux, darwin, freebsd package net /* diff --git a/core/net/common.odin b/core/net/common.odin index ed255356e..263fc770f 100644 --- a/core/net/common.odin +++ b/core/net/common.odin @@ -1,4 +1,4 @@ -// +build windows, linux, darwin, freebsd +#+build windows, linux, darwin, freebsd package net /* diff --git a/core/net/dns.odin b/core/net/dns.odin index e82b54262..ffb97fc5b 100644 --- a/core/net/dns.odin +++ b/core/net/dns.odin @@ -1,4 +1,4 @@ -// +build windows, linux, darwin, freebsd +#+build windows, linux, darwin, freebsd package net /* diff --git a/core/net/dns_unix.odin b/core/net/dns_unix.odin index 0448b8d9e..e4336e410 100644 --- a/core/net/dns_unix.odin +++ b/core/net/dns_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd +#+build linux, darwin, freebsd package net /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. diff --git a/core/net/dns_windows.odin b/core/net/dns_windows.odin index 1b7fe7196..2f3831767 100644 --- a/core/net/dns_windows.odin +++ b/core/net/dns_windows.odin @@ -1,4 +1,4 @@ -//+build windows +#+build windows package net /* diff --git a/core/net/errors_darwin.odin b/core/net/errors_darwin.odin index f2a0d6262..2905b44bc 100644 --- a/core/net/errors_darwin.odin +++ b/core/net/errors_darwin.odin @@ -1,5 +1,5 @@ +#+build darwin package net -// +build darwin /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. diff --git a/core/net/errors_freebsd.odin b/core/net/errors_freebsd.odin index 4830d1c03..486732a95 100644 --- a/core/net/errors_freebsd.odin +++ b/core/net/errors_freebsd.odin @@ -1,4 +1,4 @@ -//+build freebsd +#+build freebsd package net /* diff --git a/core/net/errors_linux.odin b/core/net/errors_linux.odin index 9047b4020..3cd51e6fd 100644 --- a/core/net/errors_linux.odin +++ b/core/net/errors_linux.odin @@ -1,5 +1,5 @@ +#+build linux package net -// +build linux /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. diff --git a/core/net/errors_windows.odin b/core/net/errors_windows.odin index ae928a05c..f41bcf888 100644 --- a/core/net/errors_windows.odin +++ b/core/net/errors_windows.odin @@ -1,5 +1,5 @@ +#+build windows package net -// +build windows /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. diff --git a/core/net/interface.odin b/core/net/interface.odin index 90444fb63..775a812f3 100644 --- a/core/net/interface.odin +++ b/core/net/interface.odin @@ -1,4 +1,4 @@ -// +build windows, linux, darwin, freebsd +#+build windows, linux, darwin, freebsd package net /* diff --git a/core/net/interface_darwin.odin b/core/net/interface_darwin.odin index 7a33682de..4921bc3fe 100644 --- a/core/net/interface_darwin.odin +++ b/core/net/interface_darwin.odin @@ -1,5 +1,5 @@ +#+build darwin package net -//+build darwin /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. diff --git a/core/net/interface_freebsd.odin b/core/net/interface_freebsd.odin index a9a125299..50e2d1a96 100644 --- a/core/net/interface_freebsd.odin +++ b/core/net/interface_freebsd.odin @@ -1,4 +1,4 @@ -//+build freebsd +#+build freebsd package net /* diff --git a/core/net/interface_linux.odin b/core/net/interface_linux.odin index c6df8f0a2..28724735b 100644 --- a/core/net/interface_linux.odin +++ b/core/net/interface_linux.odin @@ -1,5 +1,5 @@ +#+build linux package net -//+build linux /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. diff --git a/core/net/interface_windows.odin b/core/net/interface_windows.odin index 67da6d034..a6eb72846 100644 --- a/core/net/interface_windows.odin +++ b/core/net/interface_windows.odin @@ -1,5 +1,5 @@ +#+build windows package net -//+build windows /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. diff --git a/core/net/socket.odin b/core/net/socket.odin index e36c67d21..950c7ac11 100644 --- a/core/net/socket.odin +++ b/core/net/socket.odin @@ -1,4 +1,4 @@ -// +build windows, linux, darwin, freebsd +#+build windows, linux, darwin, freebsd package net /* @@ -134,6 +134,13 @@ listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TC return _listen_tcp(interface_endpoint, backlog) } +/* + Returns the endpoint that the given socket is listening / bound on. +*/ +bound_endpoint :: proc(socket: Any_Socket) -> (endpoint: Endpoint, err: Network_Error) { + return _bound_endpoint(socket) +} + accept_tcp :: proc(socket: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { return _accept_tcp(socket, options) } diff --git a/core/net/socket_darwin.odin b/core/net/socket_darwin.odin index a56d36de6..27927e973 100644 --- a/core/net/socket_darwin.odin +++ b/core/net/socket_darwin.odin @@ -1,5 +1,5 @@ +#+build darwin package net -// +build darwin /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. @@ -22,6 +22,7 @@ package net import "core:c" import "core:os" +import "core:sys/posix" import "core:time" Socket_Option :: enum c.int { @@ -87,8 +88,8 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio sockaddr := _endpoint_to_sockaddr(endpoint) res := os.connect(os.Socket(skt), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len)) if res != nil { - err = Dial_Error(os.is_platform_error(res) or_else -1) - return + close(skt) + return {}, Dial_Error(os.is_platform_error(res) or_else -1) } return @@ -119,6 +120,7 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_ family := family_from_endpoint(interface_endpoint) sock := create_socket(family, .TCP) or_return skt = sock.(TCP_Socket) + defer if err != nil { close(skt) } // NOTE(tetra): This is so that if we crash while the socket is open, we can // bypass the cooldown period, and allow the next run of the program to @@ -138,6 +140,19 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_ return } +@(private) +_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) { + addr: posix.sockaddr_storage + addr_len := posix.socklen_t(size_of(addr)) + res := posix.getsockname(posix.FD(any_socket_to_socket(sock)), (^posix.sockaddr)(&addr), &addr_len) + if res != .OK { + err = Listen_Error(posix.errno()) + return + } + ep = _sockaddr_to_endpoint((^os.SOCKADDR_STORAGE_LH)(&addr)) + return +} + @(private) _accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { sockaddr: os.SOCKADDR_STORAGE_LH diff --git a/core/net/socket_freebsd.odin b/core/net/socket_freebsd.odin index 00da5ec06..3a3774007 100644 --- a/core/net/socket_freebsd.odin +++ b/core/net/socket_freebsd.odin @@ -1,4 +1,4 @@ -//+build freebsd +#+build freebsd package net /* @@ -114,8 +114,8 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio sockaddr := _endpoint_to_sockaddr(endpoint) errno := freebsd.connect(cast(Fd)socket, &sockaddr, cast(freebsd.socklen_t)sockaddr.len) if errno != nil { - err = cast(Dial_Error)errno - return + close(socket) + return {}, cast(Dial_Error)errno } return @@ -137,6 +137,7 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T family := family_from_endpoint(interface_endpoint) new_socket := create_socket(family, .TCP) or_return socket = new_socket.(TCP_Socket) + defer if err != nil { close(socket) } bind(socket, interface_endpoint) or_return @@ -149,6 +150,20 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T return } +@(private) +_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) { + sockaddr: freebsd.Socket_Address_Storage + + errno := freebsd.getsockname(cast(Fd)any_socket_to_socket(sock), &sockaddr) + if errno != nil { + err = cast(Listen_Error)errno + return + } + + ep = _sockaddr_to_endpoint(&sockaddr) + return +} + @(private) _accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { sockaddr: freebsd.Socket_Address_Storage diff --git a/core/net/socket_linux.odin b/core/net/socket_linux.odin index 52f328814..b7816b0b6 100644 --- a/core/net/socket_linux.odin +++ b/core/net/socket_linux.odin @@ -1,5 +1,5 @@ +#+build linux package net -// +build linux /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. @@ -147,7 +147,8 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio addr := _unwrap_os_addr(endpoint) errno = linux.connect(linux.Fd(os_sock), &addr) if errno != .NONE { - return cast(TCP_Socket) os_sock, Dial_Error(errno) + close(cast(TCP_Socket) os_sock) + return {}, Dial_Error(errno) } // NOTE(tetra): Not vital to succeed; error ignored no_delay: b32 = cast(b32) options.no_delay @@ -166,40 +167,61 @@ _bind :: proc(sock: Any_Socket, endpoint: Endpoint) -> (Network_Error) { } @(private) -_listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (TCP_Socket, Network_Error) { +_listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket, err: Network_Error) { errno: linux.Errno assert(backlog > 0 && i32(backlog) < max(i32)) + // Figure out the address family and address of the endpoint ep_family := _unwrap_os_family(family_from_endpoint(endpoint)) ep_address := _unwrap_os_addr(endpoint) + // Create TCP socket os_sock: linux.Fd os_sock, errno = linux.socket(ep_family, .STREAM, {.CLOEXEC}, .TCP) if errno != .NONE { - // TODO(flysand): should return invalid file descriptor here casted as TCP_Socket - return {}, Create_Socket_Error(errno) + err = Create_Socket_Error(errno) + return } + socket = cast(TCP_Socket)os_sock + defer if err != nil { close(socket) } + // NOTE(tetra): This is so that if we crash while the socket is open, we can // bypass the cooldown period, and allow the next run of the program to // use the same address immediately. // // TODO(tetra, 2022-02-15): Confirm that this doesn't mean other processes can hijack the address! do_reuse_addr: b32 = true - errno = linux.setsockopt(os_sock, linux.SOL_SOCKET, linux.Socket_Option.REUSEADDR, &do_reuse_addr) - if errno != .NONE { - return cast(TCP_Socket) os_sock, Listen_Error(errno) + if errno = linux.setsockopt(os_sock, linux.SOL_SOCKET, linux.Socket_Option.REUSEADDR, &do_reuse_addr); errno != .NONE { + err = Listen_Error(errno) + return } + // Bind the socket to endpoint address - errno = linux.bind(os_sock, &ep_address) - if errno != .NONE { - return cast(TCP_Socket) os_sock, Bind_Error(errno) + if errno = linux.bind(os_sock, &ep_address); errno != .NONE { + err = Bind_Error(errno) + return } + // Listen on bound socket - errno = linux.listen(os_sock, cast(i32) backlog) - if errno != .NONE { - return cast(TCP_Socket) os_sock, Listen_Error(errno) + if errno = linux.listen(os_sock, cast(i32) backlog); errno != .NONE { + err = Listen_Error(errno) + return } - return cast(TCP_Socket) os_sock, nil + + return +} + +@(private) +_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) { + addr: linux.Sock_Addr_Any + errno := linux.getsockname(_unwrap_os_socket(sock), &addr) + if errno != .NONE { + err = Listen_Error(errno) + return + } + + ep = _wrap_os_addr(addr) + return } @(private) diff --git a/core/net/socket_windows.odin b/core/net/socket_windows.odin index 8ee75bc3b..747d5cab3 100644 --- a/core/net/socket_windows.odin +++ b/core/net/socket_windows.odin @@ -1,5 +1,5 @@ +#+build windows package net -// +build windows /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. @@ -80,8 +80,8 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio sockaddr := _endpoint_to_sockaddr(endpoint) res := win.connect(win.SOCKET(socket), &sockaddr, size_of(sockaddr)) if res < 0 { - err = Dial_Error(win.WSAGetLastError()) - return + close(socket) + return {}, Dial_Error(win.WSAGetLastError()) } if options.no_delay { @@ -107,6 +107,7 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T family := family_from_endpoint(interface_endpoint) sock := create_socket(family, .TCP) or_return socket = sock.(TCP_Socket) + defer if err != nil { close(socket) } // NOTE(tetra): While I'm not 100% clear on it, my understanding is that this will // prevent hijacking of the server's endpoint by other applications. @@ -120,6 +121,19 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T return } +@(private) +_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) { + sockaddr: win.SOCKADDR_STORAGE_LH + sockaddrlen := c.int(size_of(sockaddr)) + if win.getsockname(win.SOCKET(any_socket_to_socket(sock)), &sockaddr, &sockaddrlen) == win.SOCKET_ERROR { + err = Listen_Error(win.WSAGetLastError()) + return + } + + ep = _sockaddr_to_endpoint(&sockaddr) + return +} + @(private) _accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { for { @@ -368,4 +382,4 @@ _sockaddr_to_endpoint :: proc(native_addr: ^win.SOCKADDR_STORAGE_LH) -> (ep: End panic("native_addr is neither IP4 or IP6 address") } return -} \ No newline at end of file +} diff --git a/core/odin/ast/ast.odin b/core/odin/ast/ast.odin index 9088da0ea..f62feec8c 100644 --- a/core/odin/ast/ast.odin +++ b/core/odin/ast/ast.odin @@ -69,6 +69,7 @@ File :: struct { fullpath: string, src: string, + tags: [dynamic]tokenizer.Token, docs: ^Comment_Group, pkg_decl: ^Package_Decl, @@ -775,17 +776,18 @@ Dynamic_Array_Type :: struct { Struct_Type :: struct { using node: Expr, - tok_pos: tokenizer.Pos, - poly_params: ^Field_List, - align: ^Expr, - field_align: ^Expr, - where_token: tokenizer.Token, - where_clauses: []^Expr, - is_packed: bool, - is_raw_union: bool, - is_no_copy: bool, - fields: ^Field_List, - name_count: int, + tok_pos: tokenizer.Pos, + poly_params: ^Field_List, + align: ^Expr, + min_field_align: ^Expr, + max_field_align: ^Expr, + where_token: tokenizer.Token, + where_clauses: []^Expr, + is_packed: bool, + is_raw_union: bool, + is_no_copy: bool, + fields: ^Field_List, + name_count: int, } Union_Type_Kind :: enum u8 { diff --git a/core/odin/ast/clone.odin b/core/odin/ast/clone.odin index b0a1673b2..67f7ffa95 100644 --- a/core/odin/ast/clone.odin +++ b/core/odin/ast/clone.odin @@ -316,7 +316,8 @@ clone_node :: proc(node: ^Node) -> ^Node { case ^Struct_Type: r.poly_params = auto_cast clone(r.poly_params) r.align = clone(r.align) - r.field_align = clone(r.field_align) + r.min_field_align = clone(r.min_field_align) + r.max_field_align = clone(r.max_field_align) r.fields = auto_cast clone(r.fields) case ^Union_Type: r.poly_params = auto_cast clone(r.poly_params) diff --git a/core/odin/parser/file_tags.odin b/core/odin/parser/file_tags.odin index b12c3b5fd..84b172148 100644 --- a/core/odin/parser/file_tags.odin +++ b/core/odin/parser/file_tags.odin @@ -51,7 +51,7 @@ get_build_arch_from_string :: proc(str: string) -> runtime.Odin_Arch_Type { parse_file_tags :: proc(file: ast.File, allocator := context.allocator) -> (tags: File_Tags) { context.allocator = allocator - if file.docs == nil { + if file.docs == nil && file.tags == nil { return } @@ -95,11 +95,9 @@ parse_file_tags :: proc(file: ast.File, allocator := context.allocator) -> (tags build_project_names: [dynamic][]string defer shrink(&build_project_names) - for comment in file.docs.list { - if len(comment.text) < 3 || comment.text[:2] != "//" { - continue - } - text := comment.text[2:] + parse_tag :: proc(text: string, tags: ^File_Tags, build_kinds: ^[dynamic]Build_Kind, + build_project_name_strings: ^[dynamic]string, + build_project_names: ^[dynamic][]string) { i := 0 skip_whitespace(text, &i) @@ -124,14 +122,14 @@ parse_file_tags :: proc(file: ast.File, allocator := context.allocator) -> (tags groups_loop: for { index_start := len(build_project_name_strings) - defer append(&build_project_names, build_project_name_strings[index_start:]) + defer append(build_project_names, build_project_name_strings[index_start:]) for { skip_whitespace(text, &i) name_start := i switch next_char(text, &i) { - case 0, '\n': + case 0, '\r', '\n': i -= 1 break groups_loop case ',': @@ -143,10 +141,10 @@ parse_file_tags :: proc(file: ast.File, allocator := context.allocator) -> (tags } scan_value(text, &i) - append(&build_project_name_strings, text[name_start:i]) + append(build_project_name_strings, text[name_start:i]) } - append(&build_project_names, build_project_name_strings[index_start:]) + append(build_project_names, build_project_name_strings[index_start:]) } case "build": kinds_loop: for { @@ -156,7 +154,7 @@ parse_file_tags :: proc(file: ast.File, allocator := context.allocator) -> (tags arch_positive: runtime.Odin_Arch_Types arch_negative: runtime.Odin_Arch_Types - defer append(&build_kinds, Build_Kind{ + defer append(build_kinds, Build_Kind{ os = (os_positive == {} ? runtime.ALL_ODIN_OS_TYPES : os_positive) -os_negative, arch = (arch_positive == {} ? runtime.ALL_ODIN_ARCH_TYPES : arch_positive)-arch_negative, }) @@ -166,7 +164,7 @@ parse_file_tags :: proc(file: ast.File, allocator := context.allocator) -> (tags is_notted: bool switch next_char(text, &i) { - case 0, '\n': + case 0, '\r', '\n': i -= 1 break kinds_loop case ',': @@ -200,6 +198,27 @@ parse_file_tags :: proc(file: ast.File, allocator := context.allocator) -> (tags } } + if file.docs != nil { + for comment in file.docs.list { + if len(comment.text) < 3 || comment.text[:2] != "//" { + continue + } + text := comment.text[2:] + + parse_tag(text, &tags, &build_kinds, &build_project_name_strings, &build_project_names) + } + } + + for tag in file.tags { + if len(tag.text) < 3 || tag.text[:2] != "#+" { + continue + } + // Only skip # because parse_tag skips the plus + text := tag.text[1:] + + parse_tag(text, &tags, &build_kinds, &build_project_name_strings, &build_project_names) + } + tags.build = build_kinds[:] tags.build_project_name = build_project_names[:] diff --git a/core/odin/parser/parse_files.odin b/core/odin/parser/parse_files.odin index 5f455c749..d4e532ec7 100644 --- a/core/odin/parser/parse_files.odin +++ b/core/odin/parser/parse_files.odin @@ -77,9 +77,7 @@ parse_package :: proc(pkg: ^ast.Package, p: ^Parser = nil) -> bool { if !parse_file(p, file) { ok = false } - if file.pkg_decl == nil { - error(p, p.curr_tok.pos, "Expected a package declaration at the start of the file") - } else if pkg.name == "" { + if pkg.name == "" { pkg.name = file.pkg_decl.name } else if pkg.name != file.pkg_decl.name { error(p, file.pkg_decl.pos, "different package name, expected '%s', got '%s'", pkg.name, file.pkg_decl.name) diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index aab59c29d..5a7440339 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -161,11 +161,36 @@ parse_file :: proc(p: ^Parser, file: ^ast.File) -> bool { docs := p.lead_comment - p.file.pkg_token = expect_token(p, .Package) - if p.file.pkg_token.kind != .Package { - return false + invalid_pre_package_token: Maybe(tokenizer.Token) + + for p.curr_tok.kind != .Package && p.curr_tok.kind != .EOF { + if p.curr_tok.kind == .Comment { + consume_comment_groups(p, p.prev_tok) + } else if p.curr_tok.kind == .File_Tag { + append(&p.file.tags, p.curr_tok) + advance_token(p) + } else { + if invalid_pre_package_token == nil { + invalid_pre_package_token = p.curr_tok + } + + advance_token(p) + } } + if p.curr_tok.kind != .Package { + t := invalid_pre_package_token.? or_else p.curr_tok + error(p, t.pos, "Expected a package declaration at the start of the file") + return false + } + + p.file.pkg_token = expect_token(p, .Package) + + if ippt, ok := invalid_pre_package_token.?; ok { + error(p, ippt.pos, "Expected only comments or lines starting with '#+' before the package declaration") + return false + } + pkg_name := expect_token_after(p, .Ident, "package") if pkg_name.kind == .Ident { switch name := pkg_name.text; { @@ -2277,6 +2302,16 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { bd.name = name.text return bd + case "caller_expression": + bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name)) + bd.tok = tok + bd.name = name.text + + if peek_token_kind(p, .Open_Paren) { + return parse_call_expr(p, bd) + } + return bd + case "location", "exists", "load", "load_directory", "load_hash", "hash", "assert", "panic", "defined", "config": bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name)) bd.tok = tok @@ -2575,9 +2610,10 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { case .Struct: tok := expect_token(p, .Struct) - poly_params: ^ast.Field_List - align: ^ast.Expr - field_align: ^ast.Expr + poly_params: ^ast.Field_List + align: ^ast.Expr + min_field_align: ^ast.Expr + max_field_align: ^ast.Expr is_packed: bool is_raw_union: bool is_no_copy: bool @@ -2610,10 +2646,21 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { } align = parse_expr(p, true) case "field_align": - if field_align != nil { + if min_field_align != nil { error(p, tag.pos, "duplicate struct tag '#%s'", tag.text) } - field_align = parse_expr(p, true) + warn(p, tag.pos, "#field_align has been deprecated in favour of #min_field_align") + min_field_align = parse_expr(p, true) + case "min_field_align": + if min_field_align != nil { + error(p, tag.pos, "duplicate struct tag '#%s'", tag.text) + } + min_field_align = parse_expr(p, true) + case "max_field_align": + if max_field_align != nil { + error(p, tag.pos, "duplicate struct tag '#%s'", tag.text) + } + max_field_align = parse_expr(p, true) case "raw_union": if is_raw_union { error(p, tag.pos, "duplicate struct tag '#%s'", tag.text) @@ -2654,16 +2701,17 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { close := expect_closing_brace_of_field_list(p) st := ast.new(ast.Struct_Type, tok.pos, end_pos(close)) - st.poly_params = poly_params - st.align = align - st.field_align = field_align - st.is_packed = is_packed - st.is_raw_union = is_raw_union - st.is_no_copy = is_no_copy - st.fields = fields - st.name_count = name_count - st.where_token = where_token - st.where_clauses = where_clauses + st.poly_params = poly_params + st.align = align + st.min_field_align = min_field_align + st.max_field_align = max_field_align + st.is_packed = is_packed + st.is_raw_union = is_raw_union + st.is_no_copy = is_no_copy + st.fields = fields + st.name_count = name_count + st.where_token = where_token + st.where_clauses = where_clauses return st case .Union: @@ -3648,6 +3696,8 @@ parse_value_decl :: proc(p: ^Parser, names: []^ast.Expr, docs: ^ast.Comment_Grou } } + end := p.prev_tok + if p.expr_level >= 0 { end: ^ast.Expr if !is_mutable && len(values) > 0 { @@ -3667,7 +3717,7 @@ parse_value_decl :: proc(p: ^Parser, names: []^ast.Expr, docs: ^ast.Comment_Grou } } - decl := ast.new(ast.Value_Decl, names[0].pos, end_pos(p.prev_tok)) + decl := ast.new(ast.Value_Decl, names[0].pos, end_pos(end)) decl.docs = docs decl.names = names decl.type = type diff --git a/core/odin/tokenizer/token.odin b/core/odin/tokenizer/token.odin index cd8953841..48d08f127 100644 --- a/core/odin/tokenizer/token.odin +++ b/core/odin/tokenizer/token.odin @@ -32,6 +32,7 @@ Token_Kind :: enum u32 { Invalid, EOF, Comment, + File_Tag, B_Literal_Begin, Ident, // main @@ -166,6 +167,7 @@ tokens := [Token_Kind.COUNT]string { "Invalid", "EOF", "Comment", + "FileTag", "", "identifier", diff --git a/core/odin/tokenizer/tokenizer.odin b/core/odin/tokenizer/tokenizer.odin index 62170aa10..d4da82c56 100644 --- a/core/odin/tokenizer/tokenizer.odin +++ b/core/odin/tokenizer/tokenizer.odin @@ -206,6 +206,23 @@ scan_comment :: proc(t: ^Tokenizer) -> string { return string(lit) } +scan_file_tag :: proc(t: ^Tokenizer) -> string { + offset := t.offset - 1 + + for t.ch != '\n' { + if t.ch == '/' { + next := peek_byte(t, 0) + + if next == '/' || next == '*' { + break + } + } + advance_rune(t) + } + + return string(t.src[offset : t.offset]) +} + scan_identifier :: proc(t: ^Tokenizer) -> string { offset := t.offset @@ -314,7 +331,7 @@ scan_escape :: proc(t: ^Tokenizer) -> bool { n -= 1 } - if x > max || 0xd800 <= x && x <= 0xe000 { + if x > max || 0xd800 <= x && x <= 0xdfff { error(t, offset, "escape sequence is an invalid Unicode code point") return false } @@ -636,6 +653,9 @@ scan :: proc(t: ^Tokenizer) -> Token { if t.ch == '!' { kind = .Comment lit = scan_comment(t) + } else if t.ch == '+' { + kind = .File_Tag + lit = scan_file_tag(t) } case '/': kind = .Quo diff --git a/core/os/dir_unix.odin b/core/os/dir_unix.odin index b472e89b7..26e865204 100644 --- a/core/os/dir_unix.odin +++ b/core/os/dir_unix.odin @@ -1,4 +1,4 @@ -//+build darwin, linux, netbsd, freebsd, openbsd +#+build darwin, linux, netbsd, freebsd, openbsd package os import "core:strings" diff --git a/core/os/os2/allocators.odin b/core/os/os2/allocators.odin index ddfe230be..864532850 100644 --- a/core/os/os2/allocators.odin +++ b/core/os/os2/allocators.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:runtime" @@ -22,9 +22,14 @@ global_default_temp_allocator_index: uint @(require_results) temp_allocator :: proc() -> runtime.Allocator { + arena := &global_default_temp_allocator_arenas[global_default_temp_allocator_index] + if arena.backing_allocator.procedure == nil { + arena.backing_allocator = heap_allocator() + } + return runtime.Allocator{ procedure = temp_allocator_proc, - data = &global_default_temp_allocator_arenas[global_default_temp_allocator_index], + data = arena, } } @@ -57,8 +62,8 @@ TEMP_ALLOCATOR_GUARD_END :: proc(temp: runtime.Arena_Temp, loc := #caller_locati @(deferred_out=TEMP_ALLOCATOR_GUARD_END) TEMP_ALLOCATOR_GUARD :: #force_inline proc(loc := #caller_location) -> (runtime.Arena_Temp, runtime.Source_Code_Location) { - tmp := temp_allocator_temp_begin(loc) global_default_temp_allocator_index = (global_default_temp_allocator_index+1)%MAX_TEMP_ARENA_COUNT + tmp := temp_allocator_temp_begin(loc) return tmp, loc } diff --git a/core/os/os2/dir_linux.odin b/core/os/os2/dir_linux.odin index d4f62e213..6a097e192 100644 --- a/core/os/os2/dir_linux.odin +++ b/core/os/os2/dir_linux.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 Read_Directory_Iterator_Impl :: struct { diff --git a/core/os/os2/dir_posix.odin b/core/os/os2/dir_posix.odin index 75f620d90..14fddde50 100644 --- a/core/os/os2/dir_posix.odin +++ b/core/os/os2/dir_posix.odin @@ -1,5 +1,5 @@ -//+private -//+build darwin, netbsd, freebsd, openbsd +#+private +#+build darwin, netbsd, freebsd, openbsd package os2 import "core:sys/posix" diff --git a/core/os/os2/dir_windows.odin b/core/os/os2/dir_windows.odin index 1b9675064..09990aeec 100644 --- a/core/os/os2/dir_windows.odin +++ b/core/os/os2/dir_windows.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:runtime" diff --git a/core/os/os2/env_linux.odin b/core/os/os2/env_linux.odin index 99dd00d90..c248a323c 100644 --- a/core/os/os2/env_linux.odin +++ b/core/os/os2/env_linux.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:runtime" diff --git a/core/os/os2/env_posix.odin b/core/os/os2/env_posix.odin index 93524fb0c..e2080485d 100644 --- a/core/os/os2/env_posix.odin +++ b/core/os/os2/env_posix.odin @@ -1,5 +1,5 @@ -//+private -//+build darwin, netbsd, freebsd, openbsd +#+private +#+build darwin, netbsd, freebsd, openbsd package os2 import "base:runtime" diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin index ac30eb1d4..a1e8c969d 100644 --- a/core/os/os2/env_windows.odin +++ b/core/os/os2/env_windows.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import win32 "core:sys/windows" diff --git a/core/os/os2/errors_linux.odin b/core/os/os2/errors_linux.odin index ed55ea15e..a7556c306 100644 --- a/core/os/os2/errors_linux.odin +++ b/core/os/os2/errors_linux.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "core:sys/linux" @@ -162,6 +162,8 @@ _get_platform_error :: proc(errno: linux.Errno) -> Error { return .Invalid_File case .ENOMEM: return .Out_Of_Memory + case .ENOSYS: + return .Unsupported } return Platform_Error(i32(errno)) diff --git a/core/os/os2/errors_posix.odin b/core/os/os2/errors_posix.odin index 9e3424a4a..0b5876c0b 100644 --- a/core/os/os2/errors_posix.odin +++ b/core/os/os2/errors_posix.odin @@ -1,5 +1,5 @@ -//+private -//+build darwin, netbsd, freebsd, openbsd +#+private +#+build darwin, netbsd, freebsd, openbsd package os2 import "core:sys/posix" @@ -26,6 +26,8 @@ _get_platform_error :: proc() -> Error { return .Invalid_File case .ENOMEM: return .Out_Of_Memory + case .ENOSYS: + return .Unsupported case: return Platform_Error(errno) } diff --git a/core/os/os2/errors_windows.odin b/core/os/os2/errors_windows.odin index 8a9a47ca6..404560f98 100644 --- a/core/os/os2/errors_windows.odin +++ b/core/os/os2/errors_windows.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:runtime" @@ -55,13 +55,15 @@ _get_platform_error :: proc() -> Error { case win32.ERROR_NEGATIVE_SEEK: return .Invalid_Offset + case win32.ERROR_BROKEN_PIPE: + return .Broken_Pipe + case win32.ERROR_BAD_ARGUMENTS, win32.ERROR_INVALID_PARAMETER, win32.ERROR_NOT_ENOUGH_MEMORY, win32.ERROR_NO_MORE_FILES, win32.ERROR_LOCK_VIOLATION, - win32.ERROR_BROKEN_PIPE, win32.ERROR_CALL_NOT_IMPLEMENTED, win32.ERROR_INSUFFICIENT_BUFFER, win32.ERROR_INVALID_NAME, diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index ad6ddbf17..e9ce13447 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:runtime" diff --git a/core/os/os2/file_posix.odin b/core/os/os2/file_posix.odin index dae85d224..b7dc43287 100644 --- a/core/os/os2/file_posix.odin +++ b/core/os/os2/file_posix.odin @@ -1,5 +1,5 @@ -//+private -//+build darwin, netbsd, freebsd, openbsd +#+private +#+build darwin, netbsd, freebsd, openbsd package os2 import "base:runtime" diff --git a/core/os/os2/file_posix_darwin.odin b/core/os/os2/file_posix_darwin.odin index 056d775e6..920a63a71 100644 --- a/core/os/os2/file_posix_darwin.odin +++ b/core/os/os2/file_posix_darwin.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:runtime" diff --git a/core/os/os2/file_posix_freebsd.odin b/core/os/os2/file_posix_freebsd.odin index e5007f8fe..05d031930 100644 --- a/core/os/os2/file_posix_freebsd.odin +++ b/core/os/os2/file_posix_freebsd.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:runtime" diff --git a/core/os/os2/file_posix_netbsd.odin b/core/os/os2/file_posix_netbsd.odin index a9cc77a41..f96c227ba 100644 --- a/core/os/os2/file_posix_netbsd.odin +++ b/core/os/os2/file_posix_netbsd.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:runtime" diff --git a/core/os/os2/file_posix_other.odin b/core/os/os2/file_posix_other.odin index 929622578..74b6374ec 100644 --- a/core/os/os2/file_posix_other.odin +++ b/core/os/os2/file_posix_other.odin @@ -1,5 +1,5 @@ -//+private -//+build openbsd +#+private +#+build openbsd package os2 import "base:runtime" diff --git a/core/os/os2/file_util.odin b/core/os/os2/file_util.odin index 963544985..8af46fab3 100644 --- a/core/os/os2/file_util.odin +++ b/core/os/os2/file_util.odin @@ -164,7 +164,7 @@ read_entire_file_from_file :: proc(f: ^File, allocator: runtime.Allocator) -> (d } @(require_results) -write_entire_file :: proc(name: string, data: []byte, perm: int, truncate := true) -> Error { +write_entire_file :: proc(name: string, data: []byte, perm: int = 0o644, truncate := true) -> Error { flags := O_WRONLY|O_CREATE if truncate { flags |= O_TRUNC diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 382156420..511935d74 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:runtime" diff --git a/core/os/os2/heap_linux.odin b/core/os/os2/heap_linux.odin index e765c320b..ede5eb2ac 100644 --- a/core/os/os2/heap_linux.odin +++ b/core/os/os2/heap_linux.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "core:sys/linux" diff --git a/core/os/os2/heap_posix.odin b/core/os/os2/heap_posix.odin index fcae267fa..1b52aed75 100644 --- a/core/os/os2/heap_posix.odin +++ b/core/os/os2/heap_posix.odin @@ -1,5 +1,5 @@ -//+private -//+build darwin, netbsd, freebsd, openbsd +#+private +#+build darwin, netbsd, freebsd, openbsd package os2 import "base:runtime" diff --git a/core/os/os2/heap_windows.odin b/core/os/os2/heap_windows.odin index 4afc016a0..7fd4529a0 100644 --- a/core/os/os2/heap_windows.odin +++ b/core/os/os2/heap_windows.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "core:mem" diff --git a/core/os/os2/internal_util.odin b/core/os/os2/internal_util.odin index 041cd531b..164e1e1be 100644 --- a/core/os/os2/internal_util.odin +++ b/core/os/os2/internal_util.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:intrinsics" diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index ba2f7235c..7be4121ae 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "core:strings" diff --git a/core/os/os2/path_posix.odin b/core/os/os2/path_posix.odin index 0b9a52532..5ffdac28e 100644 --- a/core/os/os2/path_posix.odin +++ b/core/os/os2/path_posix.odin @@ -1,5 +1,5 @@ -//+private -//+build darwin, netbsd, freebsd, openbsd +#+private +#+build darwin, netbsd, freebsd, openbsd package os2 import "base:runtime" @@ -40,7 +40,7 @@ _mkdir_all :: proc(path: string, perm: int) -> Error { internal_mkdir_all :: proc(path: string, perm: int) -> Error { dir, file := filepath.split(path) - if file != path { + if file != path && dir != "/" { if len(dir) > 1 && dir[len(dir) - 1] == '/' { dir = dir[:len(dir) - 1] } diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin index 4aa695ee2..3e92cb6f3 100644 --- a/core/os/os2/path_windows.odin +++ b/core/os/os2/path_windows.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import win32 "core:sys/windows" diff --git a/core/os/os2/pipe.odin b/core/os/os2/pipe.odin index 9254d6f8e..5d3e8368e 100644 --- a/core/os/os2/pipe.odin +++ b/core/os/os2/pipe.odin @@ -1,6 +1,43 @@ package os2 +/* +Create an anonymous pipe. + +This procedure creates an anonymous pipe, returning two ends of the pipe, `r` +and `w`. The file `r` is the readable end of the pipe. The file `w` is a +writeable end of the pipe. + +Pipes are used as an inter-process communication mechanism, to communicate +between a parent and a child process. The child uses one end of the pipe to +write data, and the parent uses the other end to read from the pipe +(or vice-versa). When a parent passes one of the ends of the pipe to the child +process, that end of the pipe needs to be closed by the parent, before any data +is attempted to be read. + +Although pipes look like files and is compatible with most file APIs in package +os2, the way it's meant to be read is different. Due to asynchronous nature of +the communication channel, the data may not be present at the time of a read +request. The other scenario is when a pipe has no data because the other end +of the pipe was closed by the child process. +*/ @(require_results) pipe :: proc() -> (r, w: ^File, err: Error) { return _pipe() } + +/* +Check if the pipe has any data. + +This procedure checks whether a read-end of the pipe has data that can be +read, and returns `true`, if the pipe has readable data, and `false` if the +pipe is empty. This procedure does not block the execution of the current +thread. + +**Note**: If the other end of the pipe was closed by the child process, the +`.Broken_Pipe` +can be returned by this procedure. Handle these errors accordingly. +*/ +@(require_results) +pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) { + return _pipe_has_data(r) +} diff --git a/core/os/os2/pipe_linux.odin b/core/os/os2/pipe_linux.odin index 42315cf4e..852674c69 100644 --- a/core/os/os2/pipe_linux.odin +++ b/core/os/os2/pipe_linux.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "core:sys/linux" @@ -15,3 +15,29 @@ _pipe :: proc() -> (r, w: ^File, err: Error) { return } + +@(require_results) +_pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) { + if r == nil || r.impl == nil { + return false, nil + } + fd := linux.Fd((^File_Impl)(r.impl).fd) + poll_fds := []linux.Poll_Fd { + linux.Poll_Fd { + fd = fd, + events = {.IN, .HUP}, + }, + } + n, errno := linux.poll(poll_fds, 0) + if n != 1 || errno != nil { + return false, _get_platform_error(errno) + } + pipe_events := poll_fds[0].revents + if pipe_events >= {.IN} { + return true, nil + } + if pipe_events >= {.HUP} { + return false, .Broken_Pipe + } + return false, nil +} \ No newline at end of file diff --git a/core/os/os2/pipe_posix.odin b/core/os/os2/pipe_posix.odin index 13c1f8aec..df9425339 100644 --- a/core/os/os2/pipe_posix.odin +++ b/core/os/os2/pipe_posix.odin @@ -1,5 +1,5 @@ -//+private -//+build darwin, netbsd, freebsd, openbsd +#+private +#+build darwin, netbsd, freebsd, openbsd package os2 import "core:sys/posix" @@ -44,3 +44,30 @@ _pipe :: proc() -> (r, w: ^File, err: Error) { return } +@(require_results) +_pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) { + if r == nil || r.impl == nil { + return false, nil + } + fd := __fd(r) + poll_fds := []posix.pollfd { + posix.pollfd { + fd = fd, + events = {.IN, .HUP}, + }, + } + n := posix.poll(raw_data(poll_fds), u32(len(poll_fds)), 0) + if n < 0 { + return false, _get_platform_error() + } else if n != 1 { + return false, nil + } + pipe_events := poll_fds[0].revents + if pipe_events >= {.IN} { + return true, nil + } + if pipe_events >= {.HUP} { + return false, .Broken_Pipe + } + return false, nil +} diff --git a/core/os/os2/pipe_windows.odin b/core/os/os2/pipe_windows.odin index 59615e306..d6dc47c9c 100644 --- a/core/os/os2/pipe_windows.odin +++ b/core/os/os2/pipe_windows.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import win32 "core:sys/windows" @@ -15,3 +15,15 @@ _pipe :: proc() -> (r, w: ^File, err: Error) { return new_file(uintptr(p[0]), ""), new_file(uintptr(p[1]), ""), nil } +@(require_results) +_pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) { + if r == nil || r.impl == nil { + return false, nil + } + handle := win32.HANDLE((^File_Impl)(r.impl).fd) + bytes_available: u32 + if !win32.PeekNamedPipe(handle, nil, 0, nil, &bytes_available, nil) { + return false, _get_platform_error() + } + return bytes_available > 0, nil +} \ No newline at end of file diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index ce65987b0..5b5a6e844 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -1,16 +1,17 @@ package os2 import "base:runtime" + import "core:time" /* - In procedures that explicitly state this as one of the allowed values, - specifies an infinite timeout. +In procedures that explicitly state this as one of the allowed values, +specifies an infinite timeout. */ TIMEOUT_INFINITE :: time.MIN_DURATION // Note(flysand): Any negative duration will be treated as infinity /* - Arguments to the current process. +Arguments to the current process. */ args := get_args() @@ -24,17 +25,17 @@ get_args :: proc() -> []string { } /* - Exit the current process. +Exit the current process. */ exit :: proc "contextless" (code: int) -> ! { _exit(code) } /* - Obtain the UID of the current process. +Obtain the UID of the current process. - **Note(windows)**: Windows doesn't follow the posix permissions model, so - the function simply returns -1. +**Note(windows)**: Windows doesn't follow the posix permissions model, so +the function simply returns -1. */ @(require_results) get_uid :: proc() -> int { @@ -42,15 +43,15 @@ get_uid :: proc() -> int { } /* - Obtain the effective UID of the current process. +Obtain the effective UID of the current process. - The effective UID is typically the same as the UID of the process. In case - the process was run by a user with elevated permissions, the process may - lower the privilege to perform some tasks without privilege. In these cases - the real UID of the process and the effective UID are different. - - **Note(windows)**: Windows doesn't follow the posix permissions model, so - the function simply returns -1. +The effective UID is typically the same as the UID of the process. In case +the process was run by a user with elevated permissions, the process may +lower the privilege to perform some tasks without privilege. In these cases +the real UID of the process and the effective UID are different. + +**Note(windows)**: Windows doesn't follow the posix permissions model, so +the function simply returns -1. */ @(require_results) get_euid :: proc() -> int { @@ -58,10 +59,10 @@ get_euid :: proc() -> int { } /* - Obtain the GID of the current process. - - **Note(windows)**: Windows doesn't follow the posix permissions model, so - the function simply returns -1. +Obtain the GID of the current process. + +**Note(windows)**: Windows doesn't follow the posix permissions model, so +the function simply returns -1. */ @(require_results) get_gid :: proc() -> int { @@ -69,15 +70,15 @@ get_gid :: proc() -> int { } /* - Obtain the effective GID of the current process. - - The effective GID is typically the same as the GID of the process. In case - the process was run by a user with elevated permissions, the process may - lower the privilege to perform some tasks without privilege. In these cases - the real GID of the process and the effective GID are different. +Obtain the effective GID of the current process. - **Note(windows)**: Windows doesn't follow the posix permissions model, so - the function simply returns -1. +The effective GID is typically the same as the GID of the process. In case +the process was run by a user with elevated permissions, the process may +lower the privilege to perform some tasks without privilege. In these cases +the real GID of the process and the effective GID are different. + +**Note(windows)**: Windows doesn't follow the posix permissions model, so +the function simply returns -1. */ @(require_results) get_egid :: proc() -> int { @@ -85,7 +86,7 @@ get_egid :: proc() -> int { } /* - Obtain the ID of the current process. +Obtain the ID of the current process. */ @(require_results) get_pid :: proc() -> int { @@ -93,13 +94,13 @@ get_pid :: proc() -> int { } /* - Obtain the ID of the parent process. +Obtain the ID of the parent process. - **Note(windows)**: Windows does not mantain strong relationships between - parent and child processes. This function returns the ID of the process - that has created the current process. In case the parent has died, the ID - returned by this function can identify a non-existent or a different - process. +**Note(windows)**: Windows does not mantain strong relationships between +parent and child processes. This function returns the ID of the process +that has created the current process. In case the parent has died, the ID +returned by this function can identify a non-existent or a different +process. */ @(require_results) get_ppid :: proc() -> int { @@ -107,7 +108,7 @@ get_ppid :: proc() -> int { } /* - Obtain ID's of all processes running in the system. +Obtain ID's of all processes running in the system. */ @(require_results) process_list :: proc(allocator: runtime.Allocator) -> ([]int, Error) { @@ -115,9 +116,9 @@ process_list :: proc(allocator: runtime.Allocator) -> ([]int, Error) { } /* - Bit set specifying which fields of the `Process_Info` struct need to be - obtained by the `process_info()` procedure. Each bit corresponds to a - field in the `Process_Info` struct. +Bit set specifying which fields of the `Process_Info` struct need to be +obtained by the `process_info()` procedure. Each bit corresponds to a +field in the `Process_Info` struct. */ Process_Info_Fields :: bit_set[Process_Info_Field] Process_Info_Field :: enum { @@ -134,8 +135,8 @@ Process_Info_Field :: enum { ALL_INFO :: Process_Info_Fields{.Executable_Path, .PPid, .Priority, .Command_Line, .Command_Args, .Environment, .Username, .Working_Dir} /* - Contains information about the process as obtained by the `process_info()` - procedure. +Contains information about the process as obtained by the `process_info()` +procedure. */ Process_Info :: struct { // The information about a process the struct contains. `pid` is always @@ -162,19 +163,19 @@ Process_Info :: struct { } /* - Obtain information about a process. +Obtain information about a process. - This procedure obtains an information, specified by `selection` parameter of - a process given by `pid`. +This procedure obtains an information, specified by `selection` parameter of +a process given by `pid`. - Use `free_process_info` to free the memory allocated by this procedure. The - `free_process_info` procedure needs to be called, even if this procedure - returned an error, as some of the fields may have been allocated. +Use `free_process_info` to free the memory allocated by this procedure. The +`free_process_info` procedure needs to be called, even if this procedure +returned an error, as some of the fields may have been allocated. - **Note**: The resulting information may or may contain the fields specified - by the `selection` parameter. Always check whether the returned - `Process_Info` struct has the required fields before checking the error code - returned by this procedure. +**Note**: The resulting information may or may contain the fields specified +by the `selection` parameter. Always check whether the returned +`Process_Info` struct has the required fields before checking the error code +returned by this procedure. */ @(require_results) process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { @@ -182,20 +183,20 @@ process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: } /* - Obtain information about a process. +Obtain information about a process. - This procedure obtains information, specified by `selection` parameter - about a process that has been opened by the application, specified in - the `process` parameter. +This procedure obtains information, specified by `selection` parameter +about a process that has been opened by the application, specified in +the `process` parameter. - Use `free_process_info` to free the memory allocated by this procedure. The - `free_process_info` procedure needs to be called, even if this procedure - returned an error, as some of the fields may have been allocated. +Use `free_process_info` to free the memory allocated by this procedure. The +`free_process_info` procedure needs to be called, even if this procedure +returned an error, as some of the fields may have been allocated. - **Note**: The resulting information may or may contain the fields specified - by the `selection` parameter. Always check whether the returned - `Process_Info` struct has the required fields before checking the error code - returned by this procedure. +**Note**: The resulting information may or may contain the fields specified +by the `selection` parameter. Always check whether the returned +`Process_Info` struct has the required fields before checking the error code +returned by this procedure. */ @(require_results) process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { @@ -203,19 +204,19 @@ process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, } /* - Obtain information about the current process. +Obtain information about the current process. - This procedure obtains the information, specified by `selection` parameter - about the currently running process. +This procedure obtains the information, specified by `selection` parameter +about the currently running process. - Use `free_process_info` to free the memory allocated by this procedure. The - `free_process_info` procedure needs to be called, even if this procedure - returned an error, as some of the fields may have been allocated. +Use `free_process_info` to free the memory allocated by this procedure. The +`free_process_info` procedure needs to be called, even if this procedure +returned an error, as some of the fields may have been allocated. - **Note**: The resulting information may or may contain the fields specified - by the `selection` parameter. Always check whether the returned - `Process_Info` struct has the required fields before checking the error code - returned by this procedure. +**Note**: The resulting information may or may contain the fields specified +by the `selection` parameter. Always check whether the returned +`Process_Info` struct has the required fields before checking the error code +returned by this procedure. */ @(require_results) current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { @@ -223,7 +224,7 @@ current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime. } /* - Obtain information about the specified process. +Obtain information about the specified process. */ process_info :: proc { process_info_by_pid, @@ -232,11 +233,11 @@ process_info :: proc { } /* - Free the information about the process. +Free the information about the process. - This procedure frees the memory occupied by process info using the provided - allocator. The allocator needs to be the same allocator that was supplied - to the `process_info` function. +This procedure frees the memory occupied by process info using the provided +allocator. The allocator needs to be the same allocator that was supplied +to the `process_info` function. */ free_process_info :: proc(pi: Process_Info, allocator: runtime.Allocator) { delete(pi.executable_path, allocator) @@ -254,13 +255,13 @@ free_process_info :: proc(pi: Process_Info, allocator: runtime.Allocator) { } /* - Represents a process handle. +Represents a process handle. - When a process dies, the OS is free to re-use the pid of that process. The - `Process` struct represents a handle to the process that will refer to a - specific process, even after it has died. +When a process dies, the OS is free to re-use the pid of that process. The +`Process` struct represents a handle to the process that will refer to a +specific process, even after it has died. - **Note(linux)**: The `handle` will be referring to pidfd. +**Note(linux)**: The `handle` will be referring to pidfd. */ Process :: struct { pid: int, @@ -276,13 +277,13 @@ Process_Open_Flag :: enum { } /* - Open a process handle using it's pid. +Open a process handle using it's pid. - This procedure obtains a process handle of a process specified by `pid`. - This procedure can be subject to race conditions. See the description of - `Process`. +This procedure obtains a process handle of a process specified by `pid`. +This procedure can be subject to race conditions. See the description of +`Process`. - Use `process_close()` function to close the process handle. +Use `process_close()` function to close the process handle. */ @(require_results) process_open :: proc(pid: int, flags := Process_Open_Flags {}) -> (Process, Error) { @@ -322,31 +323,139 @@ Process_Desc :: struct { } /* - Create a new process and obtain its handle. +Create a new process and obtain its handle. - This procedure creates a new process, with a given command and environment - strings as parameters. Use `environ()` to inherit the environment of the - current process. +This procedure creates a new process, with a given command and environment +strings as parameters. Use `environ()` to inherit the environment of the +current process. - The `desc` parameter specifies the description of how the process should - be created. It contains information such as the command line, the - environment of the process, the starting directory and many other options. - Most of the fields in the struct can be set to `nil` or an empty value. - - Use `process_close` to close the handle to the process. Note, that this - is not the same as terminating the process. One can terminate the process - and not close the handle, in which case the handle would be leaked. In case - the function returns an error, an invalid handle is returned. +The `desc` parameter specifies the description of how the process should +be created. It contains information such as the command line, the +environment of the process, the starting directory and many other options. +Most of the fields in the struct can be set to `nil` or an empty value. - This procedure is not thread-safe. It may alter the inheritance properties - of file handles in an unpredictable manner. In case multiple threads change - handle inheritance properties, make sure to serialize all those calls. +Use `process_close` to close the handle to the process. Note, that this +is not the same as terminating the process. One can terminate the process +and not close the handle, in which case the handle would be leaked. In case +the function returns an error, an invalid handle is returned. + +This procedure is not thread-safe. It may alter the inheritance properties +of file handles in an unpredictable manner. In case multiple threads change +handle inheritance properties, make sure to serialize all those calls. */ @(require_results) -process_start :: proc(desc := Process_Desc {}) -> (Process, Error) { +process_start :: proc(desc: Process_Desc) -> (Process, Error) { return _process_start(desc) } +/* +Execute the process and capture stdout and stderr streams. + +This procedure creates a new process, with a given command and environment +strings as parameters, and waits until the process finishes execution. While +the process is running, this procedure accumulates the output of its stdout +and stderr streams and returns byte slices containing the captured data from +the streams. + +This procedure expects that `stdout` and `stderr` fields of the `desc` parameter +are left at default, i.e. a `nil` value. You can not capture stdout/stderr and +redirect it to a file at the same time. + +This procedure does not free `stdout` and `stderr` slices before an error is +returned. Make sure to call `delete` on these slices. +*/ +@(require_results) +process_exec :: proc( + desc: Process_Desc, + allocator: runtime.Allocator, + loc := #caller_location, +) -> ( + state: Process_State, + stdout: []byte, + stderr: []byte, + err: Error, +) { + assert(desc.stdout == nil, "Cannot redirect stdout when it's being captured", loc) + assert(desc.stderr == nil, "Cannot redirect stderr when it's being captured", loc) + + stdout_r, stdout_w := pipe() or_return + defer close(stdout_r) + stderr_r, stderr_w := pipe() or_return + defer close(stderr_r) + + process: Process + { + // NOTE(flysand): Make sure the write-ends are closed, regardless + // of the outcome. This makes read-ends readable on our side. + defer close(stdout_w) + defer close(stderr_w) + desc := desc + desc.stdout = stdout_w + desc.stderr = stderr_w + process = process_start(desc) or_return + } + + { + stdout_b: [dynamic]byte + stdout_b.allocator = allocator + defer stdout = stdout_b[:] + + stderr_b: [dynamic]byte + stderr_b.allocator = allocator + defer stderr = stderr_b[:] + + buf: [1024]u8 = --- + + stdout_done, stderr_done, has_data: bool + for err == nil && (!stdout_done || !stderr_done) { + n := 0 + + if !stdout_done { + has_data, err = pipe_has_data(stdout_r) + if has_data { + n, err = read(stdout_r, buf[:]) + } + + switch err { + case nil: + _, err = append(&stdout_b, ..buf[:n]) + case .EOF, .Broken_Pipe: + stdout_done = true + err = nil + } + } + + if err == nil && !stderr_done { + n = 0 + has_data, err = pipe_has_data(stderr_r) + if has_data { + n, err = read(stderr_r, buf[:]) + } + + switch err { + case nil: + _, err = append(&stderr_b, ..buf[:n]) + case .EOF, .Broken_Pipe: + stderr_done = true + err = nil + } + } + } + } + + if err != nil { + state, _ = process_wait(process, timeout=0) + if !state.exited { + _ = process_kill(process) + state, _ = process_wait(process) + } + return + } + + state, err = process_wait(process) + return +} + /* The state of the process after it has finished execution. */ @@ -371,17 +480,17 @@ Process_State :: struct { } /* - Wait for a process event. +Wait for a process event. - This procedure blocks the execution until the process has exited or the - timeout (if specified) has reached zero. If the timeout is `TIMEOUT_INFINITE`, - no timeout restriction is imposed and the procedure can block indefinately. +This procedure blocks the execution until the process has exited or the +timeout (if specified) has reached zero. If the timeout is `TIMEOUT_INFINITE`, +no timeout restriction is imposed and the procedure can block indefinately. - If the timeout has expired, the `General_Error.Timeout` is returned as - the error. +If the timeout has expired, the `General_Error.Timeout` is returned as +the error. - If an error is returned for any other reason, other than timeout, the - process state is considered undetermined. +If an error is returned for any other reason, other than timeout, the +process state is considered undetermined. */ @(require_results) process_wait :: proc(process: Process, timeout := TIMEOUT_INFINITE) -> (Process_State, Error) { @@ -389,12 +498,12 @@ process_wait :: proc(process: Process, timeout := TIMEOUT_INFINITE) -> (Process_ } /* - Close the handle to a process. +Close the handle to a process. - This procedure closes the handle associated with a process. It **does not** - terminate a process, in case it was running. In case a termination is - desired, kill the process first, wait for the process to finish, - then close the handle. +This procedure closes the handle associated with a process. It **does not** +terminate a process, in case it was running. In case a termination is +desired, kill the process first, wait for the process to finish, +then close the handle. */ @(require_results) process_close :: proc(process: Process) -> (Error) { @@ -402,10 +511,9 @@ process_close :: proc(process: Process) -> (Error) { } /* - Terminate a process. - - This procedure terminates a process, specified by it's handle, `process`. +Terminate a process. +This procedure terminates a process, specified by it's handle, `process`. */ @(require_results) process_kill :: proc(process: Process) -> (Error) { diff --git a/core/os/os2/process_linux.odin b/core/os/os2/process_linux.odin index b6db46423..7eb4dfa44 100644 --- a/core/os/os2/process_linux.odin +++ b/core/os/os2/process_linux.odin @@ -1,5 +1,5 @@ -//+build linux -//+private file +#+build linux +#+private file package os2 import "base:runtime" @@ -523,7 +523,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { write_errno_to_parent_and_abort :: proc(parent_fd: linux.Fd, errno: linux.Errno) -> ! { error_byte: [1]u8 = { u8(errno) } linux.write(parent_fd, error_byte[:]) - intrinsics.trap() + linux.exit(126) } stdin_fd: linux.Fd diff --git a/core/os/os2/process_posix.odin b/core/os/os2/process_posix.odin index ea4ada81e..b54374cec 100644 --- a/core/os/os2/process_posix.odin +++ b/core/os/os2/process_posix.odin @@ -1,5 +1,5 @@ -//+private -//+build darwin, netbsd, freebsd, openbsd +#+private +#+build darwin, netbsd, freebsd, openbsd package os2 import "base:runtime" @@ -163,7 +163,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { #assert(len(posix.Errno) < max(u8)) errno := u8(posix.errno()) posix.write(parent_fd, &errno, 1) - runtime.trap() + posix.exit(126) } null := posix.open("/dev/null", {.RDWR}) @@ -223,7 +223,6 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { return } - process.pid = int(pid) process, _ = _process_open(int(pid), {}) return } diff --git a/core/os/os2/process_posix_darwin.odin b/core/os/os2/process_posix_darwin.odin index 648c4d389..0ea1f643c 100644 --- a/core/os/os2/process_posix_darwin.odin +++ b/core/os/os2/process_posix_darwin.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:runtime" diff --git a/core/os/os2/process_posix_other.odin b/core/os/os2/process_posix_other.odin index 02f78b9ac..65da3e9e2 100644 --- a/core/os/os2/process_posix_other.odin +++ b/core/os/os2/process_posix_other.odin @@ -1,5 +1,5 @@ -//+private -//+build netbsd, openbsd, freebsd +#+private +#+build netbsd, openbsd, freebsd package os2 import "base:runtime" @@ -15,6 +15,7 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) } _process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) { + process.pid = pid err = .Unsupported return } diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin index edb321509..201fa28e7 100644 --- a/core/os/os2/process_windows.odin +++ b/core/os/os2/process_windows.odin @@ -1,4 +1,4 @@ -//+private file +#+private file package os2 import "base:runtime" @@ -650,26 +650,30 @@ _build_command_line :: proc(command: []string, allocator: runtime.Allocator) -> strings.write_byte(&builder, ' ') } j := 0 - strings.write_byte(&builder, '"') - for j < len(arg) { - backslashes := 0 - for j < len(arg) && arg[j] == '\\' { - backslashes += 1 + if strings.contains_any(arg, "()[]{}^=;!'+,`~\" ") { + strings.write_byte(&builder, '"') + for j < len(arg) { + backslashes := 0 + for j < len(arg) && arg[j] == '\\' { + backslashes += 1 + j += 1 + } + if j == len(arg) { + _write_byte_n_times(&builder, '\\', 2*backslashes) + break + } else if arg[j] == '"' { + _write_byte_n_times(&builder, '\\', 2*backslashes+1) + strings.write_byte(&builder, arg[j]) + } else { + _write_byte_n_times(&builder, '\\', backslashes) + strings.write_byte(&builder, arg[j]) + } j += 1 } - if j == len(arg) { - _write_byte_n_times(&builder, '\\', 2*backslashes) - break - } else if arg[j] == '"' { - _write_byte_n_times(&builder, '\\', 2*backslashes+1) - strings.write_byte(&builder, '"') - } else { - _write_byte_n_times(&builder, '\\', backslashes) - strings.write_byte(&builder, arg[j]) - } - j += 1 + strings.write_byte(&builder, '"') + } else { + strings.write_string(&builder, arg) } - strings.write_byte(&builder, '"') } return strings.to_string(builder) } diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index 6ccac1be0..0433c1a61 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "core:time" diff --git a/core/os/os2/stat_posix.odin b/core/os/os2/stat_posix.odin index a817a862b..88029c1f5 100644 --- a/core/os/os2/stat_posix.odin +++ b/core/os/os2/stat_posix.odin @@ -1,5 +1,5 @@ -//+private -//+build darwin, netbsd, freebsd, openbsd +#+private +#+build darwin, netbsd, freebsd, openbsd package os2 import "base:runtime" diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index 566417c84..8ed2a6fed 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:runtime" diff --git a/core/os/os2/temp_file_linux.odin b/core/os/os2/temp_file_linux.odin index d6f90fbaf..4eacbc54a 100644 --- a/core/os/os2/temp_file_linux.odin +++ b/core/os/os2/temp_file_linux.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:runtime" diff --git a/core/os/os2/temp_file_posix.odin b/core/os/os2/temp_file_posix.odin index 67ec4d3e8..b44ea13a7 100644 --- a/core/os/os2/temp_file_posix.odin +++ b/core/os/os2/temp_file_posix.odin @@ -1,5 +1,5 @@ -//+private -//+build darwin, netbsd, freebsd, openbsd +#+private +#+build darwin, netbsd, freebsd, openbsd package os2 import "base:runtime" diff --git a/core/os/os2/temp_file_windows.odin b/core/os/os2/temp_file_windows.odin index d888eda52..3e3e1285c 100644 --- a/core/os/os2/temp_file_windows.odin +++ b/core/os/os2/temp_file_windows.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:runtime" diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin index 2bb6c0c59..c7e1750d6 100644 --- a/core/os/os_darwin.odin +++ b/core/os/os_darwin.odin @@ -204,7 +204,7 @@ ENOPROTOOPT :: _Platform_Error.ENOPROTOOPT EPROTONOSUPPORT :: _Platform_Error.EPROTONOSUPPORT ESOCKTNOSUPPORT :: _Platform_Error.ESOCKTNOSUPPORT ENOTSUP :: _Platform_Error.ENOTSUP -EOPNOTSUPP :: _Platform_Error.EOPNOTSUPP +EOPNOTSUPP :: _Platform_Error.EOPNOTSUPP EPFNOSUPPORT :: _Platform_Error.EPFNOSUPPORT EAFNOSUPPORT :: _Platform_Error.EAFNOSUPPORT EADDRINUSE :: _Platform_Error.EADDRINUSE @@ -1095,7 +1095,8 @@ unset_env :: proc(key: string) -> Error { } @(require_results) -get_current_directory :: proc() -> string { +get_current_directory :: proc(allocator := context.allocator) -> string { + context.allocator = allocator page_size := get_page_size() // NOTE(tetra): See note in os_linux.odin/get_current_directory. buf := make([dynamic]u8, page_size) for { diff --git a/core/os/os_freebsd.odin b/core/os/os_freebsd.odin index c05a06129..f617cf973 100644 --- a/core/os/os_freebsd.odin +++ b/core/os/os_freebsd.odin @@ -840,7 +840,8 @@ get_env :: proc(key: string, allocator := context.allocator) -> (value: string) } @(require_results) -get_current_directory :: proc() -> string { +get_current_directory :: proc(allocator := context.allocator) -> string { + context.allocator = allocator // NOTE(tetra): I would use PATH_MAX here, but I was not able to find // an authoritative value for it across all systems. // The largest value I could find was 4096, so might as well use the page size. @@ -920,7 +921,7 @@ get_page_size :: proc() -> int { _processor_core_count :: proc() -> int { count : int = 0 count_size := size_of(count) - if _sysctlbyname("hw.logicalcpu", &count, &count_size, nil, 0) == 0 { + if _sysctlbyname("hw.ncpu", &count, &count_size, nil, 0) == 0 { if count > 0 { return count } diff --git a/core/os/os_freestanding.odin b/core/os/os_freestanding.odin index c908e3738..c22a6d7d5 100644 --- a/core/os/os_freestanding.odin +++ b/core/os/os_freestanding.odin @@ -1,4 +1,4 @@ -//+build freestanding +#+build freestanding package os #panic("package os does not support a freestanding target") diff --git a/core/os/os_js.odin b/core/os/os_js.odin index eb434c727..348554728 100644 --- a/core/os/os_js.odin +++ b/core/os/os_js.odin @@ -1,35 +1,38 @@ -//+build js +#+build js package os -import "base:runtime" +foreign import "odin_env" @(require_results) is_path_separator :: proc(c: byte) -> bool { return c == '/' || c == '\\' } +Handle :: distinct u32 + +stdout: Handle = 1 +stderr: Handle = 2 + @(require_results) open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Error) { unimplemented("core:os procedure not supported on JS target") } close :: proc(fd: Handle) -> Error { - unimplemented("core:os procedure not supported on JS target") + return nil } flush :: proc(fd: Handle) -> (err: Error) { - unimplemented("core:os procedure not supported on JS target") + return nil } - - write :: proc(fd: Handle, data: []byte) -> (int, Error) { - unimplemented("core:os procedure not supported on JS target") -} - -@(private="file") -read_console :: proc(handle: Handle, b: []byte) -> (n: int, err: Error) { - unimplemented("core:os procedure not supported on JS target") + foreign odin_env { + @(link_name="write") + _write :: proc "contextless" (fd: Handle, p: []byte) --- + } + _write(fd, data) + return len(data), nil } read :: proc(fd: Handle, data: []byte) -> (int, Error) { @@ -45,19 +48,6 @@ file_size :: proc(fd: Handle) -> (i64, Error) { unimplemented("core:os procedure not supported on JS target") } - -@(private) -MAX_RW :: 1<<30 - -@(private) -pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { - unimplemented("core:os procedure not supported on JS target") -} -@(private) -pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { - unimplemented("core:os procedure not supported on JS target") -} - read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { unimplemented("core:os procedure not supported on JS target") } @@ -65,16 +55,6 @@ write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) unimplemented("core:os procedure not supported on JS target") } -stdout: Handle = 1 -stderr: Handle = 2 - -@(require_results) -get_std_handle :: proc "contextless" (h: uint) -> Handle { - context = runtime.default_context() - unimplemented("core:os procedure not supported on JS target") -} - - @(require_results) exists :: proc(path: string) -> bool { unimplemented("core:os procedure not supported on JS target") @@ -90,9 +70,6 @@ is_dir :: proc(path: string) -> bool { unimplemented("core:os procedure not supported on JS target") } -// NOTE(tetra): GetCurrentDirectory is not thread safe with SetCurrentDirectory and GetFullPathName -//@private cwd_lock := win32.SRWLOCK{} // zero is initialized - @(require_results) get_current_directory :: proc(allocator := context.allocator) -> string { unimplemented("core:os procedure not supported on JS target") @@ -118,18 +95,6 @@ remove_directory :: proc(path: string) -> (err: Error) { } - -@(private, require_results) -is_abs :: proc(path: string) -> bool { - unimplemented("core:os procedure not supported on JS target") -} - -@(private, require_results) -fix_long_path :: proc(path: string) -> string { - unimplemented("core:os procedure not supported on JS target") -} - - link :: proc(old_name, new_name: string) -> (err: Error) { unimplemented("core:os procedure not supported on JS target") } @@ -169,7 +134,6 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F unimplemented("core:os procedure not supported on JS target") } -Handle :: distinct uintptr File_Time :: distinct u64 _Platform_Error :: enum i32 { @@ -254,12 +218,7 @@ WSAECONNRESET :: Platform_Error.WSAECONNRESET ERROR_FILE_IS_PIPE :: General_Error.File_Is_Pipe ERROR_FILE_IS_NOT_DIR :: General_Error.Not_Dir -// "Argv" arguments converted to Odin strings -args := _alloc_command_line_arguments() - - - - +args: []string @(require_results) last_write_time :: proc(fd: Handle) -> (File_Time, Error) { @@ -279,26 +238,14 @@ get_page_size :: proc() -> int { @(private, require_results) _processor_core_count :: proc() -> int { - unimplemented("core:os procedure not supported on JS target") + return 1 } exit :: proc "contextless" (code: int) -> ! { - context = runtime.default_context() - unimplemented("core:os procedure not supported on JS target") + unimplemented_contextless("core:os procedure not supported on JS target") } - - @(require_results) current_thread_id :: proc "contextless" () -> int { - context = runtime.default_context() - unimplemented("core:os procedure not supported on JS target") + return 0 } - - - -@(require_results) -_alloc_command_line_arguments :: proc() -> []string { - return nil -} - diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index 9132edbfe..e9039ba20 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -242,10 +242,13 @@ F_SETFL: int : 4 /* Set file flags */ // NOTE(zangent): These are OS specific! // Do not mix these up! -RTLD_LAZY :: 0x001 -RTLD_NOW :: 0x002 -RTLD_BINDING_MASK :: 0x3 -RTLD_GLOBAL :: 0x100 +RTLD_LAZY :: 0x0001 +RTLD_NOW :: 0x0002 +RTLD_BINDING_MASK :: 0x0003 +RTLD_GLOBAL :: 0x0100 +RTLD_NOLOAD :: 0x0004 +RTLD_DEEPBIND :: 0x0008 +RTLD_NODELETE :: 0x1000 socklen_t :: c.int @@ -985,7 +988,8 @@ unset_env :: proc(key: string) -> Error { } @(require_results) -get_current_directory :: proc() -> string { +get_current_directory :: proc(allocator := context.allocator) -> string { + context.allocator = allocator // NOTE(tetra): I would use PATH_MAX here, but I was not able to find // an authoritative value for it across all systems. // The largest value I could find was 4096, so might as well use the page size. diff --git a/core/os/os_netbsd.odin b/core/os/os_netbsd.odin index a56c0b784..493527803 100644 --- a/core/os/os_netbsd.odin +++ b/core/os/os_netbsd.odin @@ -894,7 +894,8 @@ get_env :: proc(key: string, allocator := context.allocator) -> (value: string) } @(require_results) -get_current_directory :: proc() -> string { +get_current_directory :: proc(allocator := context.allocator) -> string { + context.allocator = allocator // NOTE(tetra): I would use PATH_MAX here, but I was not able to find // an authoritative value for it across all systems. // The largest value I could find was 4096, so might as well use the page size. @@ -978,7 +979,7 @@ get_page_size :: proc() -> int { _processor_core_count :: proc() -> int { count : int = 0 count_size := size_of(count) - if _sysctlbyname("hw.logicalcpu", &count, &count_size, nil, 0) == 0 { + if _sysctlbyname("hw.ncpu", &count, &count_size, nil, 0) == 0 { if count > 0 { return count } diff --git a/core/os/os_openbsd.odin b/core/os/os_openbsd.odin index aff78dc60..62872d9dc 100644 --- a/core/os/os_openbsd.odin +++ b/core/os/os_openbsd.odin @@ -806,7 +806,8 @@ get_env :: proc(key: string, allocator := context.allocator) -> (value: string) } @(require_results) -get_current_directory :: proc() -> string { +get_current_directory :: proc(allocator := context.allocator) -> string { + context.allocator = allocator buf := make([dynamic]u8, MAX_PATH) for { cwd := _unix_getcwd(cstring(raw_data(buf)), c.size_t(len(buf))) diff --git a/core/os/os_windows.odin b/core/os/os_windows.odin index 6fb0631cd..552508f3b 100644 --- a/core/os/os_windows.odin +++ b/core/os/os_windows.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package os import win32 "core:sys/windows" diff --git a/core/os/stat_unix.odin b/core/os/stat_unix.odin index 8e89bee4f..7f7985e83 100644 --- a/core/os/stat_unix.odin +++ b/core/os/stat_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd, openbsd, netbsd, haiku +#+build linux, darwin, freebsd, openbsd, netbsd, haiku package os import "core:time" diff --git a/core/path/filepath/path_unix.odin b/core/path/filepath/path_unix.odin index 7137ad844..a18dc739e 100644 --- a/core/path/filepath/path_unix.odin +++ b/core/path/filepath/path_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd, openbsd, netbsd +#+build linux, darwin, freebsd, openbsd, netbsd package filepath when ODIN_OS == .Darwin { diff --git a/core/prof/spall/spall_linux.odin b/core/prof/spall/spall_linux.odin index b25d2b336..8060af448 100644 --- a/core/prof/spall/spall_linux.odin +++ b/core/prof/spall/spall_linux.odin @@ -1,10 +1,10 @@ -//+private +#+private package spall // Only for types and constants. import "core:os" -// Package is `//+no-instrumentation`, safe to use. +// Package is `#+no-instrumentation`, safe to use. import "core:sys/linux" MAX_RW :: 0x7fffffff diff --git a/core/prof/spall/spall_unix.odin b/core/prof/spall/spall_unix.odin index fc05b8525..455245aad 100644 --- a/core/prof/spall/spall_unix.odin +++ b/core/prof/spall/spall_unix.odin @@ -1,5 +1,5 @@ -//+private -//+build darwin, freebsd, openbsd, netbsd +#+private +#+build darwin, freebsd, openbsd, netbsd package spall // Only for types. diff --git a/core/prof/spall/spall_windows.odin b/core/prof/spall/spall_windows.odin index c8b044963..11e216b63 100644 --- a/core/prof/spall/spall_windows.odin +++ b/core/prof/spall/spall_windows.odin @@ -1,10 +1,10 @@ -//+private +#+private package spall // Only for types. import "core:os" -// Package is `//+no-instrumentation`, safe to use. +// Package is `#+no-instrumentation`, safe to use. import win32 "core:sys/windows" MAX_RW :: 1<<30 diff --git a/core/simd/x86/abm.odin b/core/simd/x86/abm.odin index 9018a835a..4b07086ce 100644 --- a/core/simd/x86/abm.odin +++ b/core/simd/x86/abm.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 import "base:intrinsics" diff --git a/core/simd/x86/adx.odin b/core/simd/x86/adx.odin index 5750ae627..9c6ae063a 100644 --- a/core/simd/x86/adx.odin +++ b/core/simd/x86/adx.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 @(require_results) diff --git a/core/simd/x86/aes.odin b/core/simd/x86/aes.odin index a2cd2e4d3..338381422 100644 --- a/core/simd/x86/aes.odin +++ b/core/simd/x86/aes.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 @(require_results, enable_target_feature = "aes") diff --git a/core/simd/x86/cmpxchg16b.odin b/core/simd/x86/cmpxchg16b.odin index 1307a9cf2..78ebd182f 100644 --- a/core/simd/x86/cmpxchg16b.odin +++ b/core/simd/x86/cmpxchg16b.odin @@ -1,4 +1,4 @@ -//+build amd64 +#+build amd64 package simd_x86 import "base:intrinsics" diff --git a/core/simd/x86/fxsr.odin b/core/simd/x86/fxsr.odin index a9213fed2..ab8cdca7d 100644 --- a/core/simd/x86/fxsr.odin +++ b/core/simd/x86/fxsr.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 @(enable_target_feature="fxsr") diff --git a/core/simd/x86/pclmulqdq.odin b/core/simd/x86/pclmulqdq.odin index e827bf6b9..14e633c06 100644 --- a/core/simd/x86/pclmulqdq.odin +++ b/core/simd/x86/pclmulqdq.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 @(require_results, enable_target_feature="pclmul") diff --git a/core/simd/x86/rdtsc.odin b/core/simd/x86/rdtsc.odin index 8a8b13c4b..84c762274 100644 --- a/core/simd/x86/rdtsc.odin +++ b/core/simd/x86/rdtsc.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 @(require_results) diff --git a/core/simd/x86/sha.odin b/core/simd/x86/sha.odin index bc58e8504..8caa3a268 100644 --- a/core/simd/x86/sha.odin +++ b/core/simd/x86/sha.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 @(require_results, enable_target_feature="sha") diff --git a/core/simd/x86/sse.odin b/core/simd/x86/sse.odin index 4dac50234..1b4a863b6 100644 --- a/core/simd/x86/sse.odin +++ b/core/simd/x86/sse.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 import "base:intrinsics" diff --git a/core/simd/x86/sse2.odin b/core/simd/x86/sse2.odin index 2e3eb8523..4c231c377 100644 --- a/core/simd/x86/sse2.odin +++ b/core/simd/x86/sse2.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 import "base:intrinsics" @@ -240,7 +240,7 @@ _mm_sll_epi64 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { } @(require_results, enable_target_feature="sse2") _mm_srai_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { - return transmute(__m128i)psraiw(transmute(i16x8)a. IMM8) + return transmute(__m128i)psraiw(transmute(i16x8)a, IMM8) } @(require_results, enable_target_feature="sse2") _mm_sra_epi16 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { @@ -262,7 +262,7 @@ _mm_srli_si128 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { } @(require_results, enable_target_feature="sse2") _mm_srli_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { - return transmute(__m128i)psrliw(transmute(i16x8)a. IMM8) + return transmute(__m128i)psrliw(transmute(i16x8)a, IMM8) } @(require_results, enable_target_feature="sse2") _mm_srl_epi16 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { diff --git a/core/simd/x86/sse3.odin b/core/simd/x86/sse3.odin index a905a7726..0e074c946 100644 --- a/core/simd/x86/sse3.odin +++ b/core/simd/x86/sse3.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 import "base:intrinsics" diff --git a/core/simd/x86/sse41.odin b/core/simd/x86/sse41.odin index c2c1abc2d..81089ed63 100644 --- a/core/simd/x86/sse41.odin +++ b/core/simd/x86/sse41.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 import "core:simd" diff --git a/core/simd/x86/sse42.odin b/core/simd/x86/sse42.odin index 7a674176b..1a5cb3f50 100644 --- a/core/simd/x86/sse42.odin +++ b/core/simd/x86/sse42.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 import "core:simd" diff --git a/core/simd/x86/ssse3.odin b/core/simd/x86/ssse3.odin index 2026c7f53..07c846e7b 100644 --- a/core/simd/x86/ssse3.odin +++ b/core/simd/x86/ssse3.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 import "base:intrinsics" diff --git a/core/simd/x86/types.odin b/core/simd/x86/types.odin index 06a2cd41e..ea0eff534 100644 --- a/core/simd/x86/types.odin +++ b/core/simd/x86/types.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 import "core:simd" diff --git a/core/slice/map.odin b/core/slice/map.odin index 545ba8305..c68488d26 100644 --- a/core/slice/map.odin +++ b/core/slice/map.odin @@ -48,11 +48,11 @@ map_entries :: proc(m: $M/map[$K]$V, allocator := context.allocator) -> (entries return } -map_entry_infos :: proc(m: $M/map[$K]$V, allocator := context.allocator) -> (entries: []Map_Entry_Info(K, V)) #no_bounds_check { +map_entry_infos :: proc(m: $M/map[$K]$V, allocator := context.allocator) -> (entries: []Map_Entry_Info(K, V), err: runtime.Allocator_Error) #no_bounds_check { m := m rm := (^runtime.Raw_Map)(&m) - info := type_info_base(type_info_of(M)).variant.(Type_Info_Map) + info := runtime.type_info_base(type_info_of(M)).variant.(runtime.Type_Info_Map) if info.map_info != nil { entries = make(type_of(entries), len(m), allocator) or_return @@ -61,8 +61,8 @@ map_entry_infos :: proc(m: $M/map[$K]$V, allocator := context.allocator) -> (ent entry_index := 0 for bucket_index in 0.. []byte { return ([^]byte)(raw_data(s))[:len(s) * size_of(T)] } +/* + Turns a byte slice into a type. +*/ +@(require_results) +to_type :: proc(buf: []u8, $T: typeid) -> (T, bool) #optional_ok { + if len(buf) < size_of(T) { + return {}, false + } + return intrinsics.unaligned_load((^T)(raw_data(buf))), true +} + /* Turn a slice of one type, into a slice of another type. @@ -96,9 +107,37 @@ contains :: proc(array: $T/[]$E, value: E) -> bool where intrinsics.type_is_comp return found } +/* +Searches the given slice for the given element in O(n) time. + +If you need a custom search condition, see `linear_search_proc` + +Inputs: +- array: The slice to search in. +- key: The element to search for. + +Returns: +- index: The index `i`, such that `array[i]` is the first occurrence of `key` in `array`, or -1 if `key` is not present in `array`. + +Example: + index: int + found: bool + + a := []i32{10, 10, 10, 20} + + index, found = linear_search_reverse(a, 10) + assert(index == 0 && found == true) + + index, found = linear_search_reverse(a, 30) + assert(index == -1 && found == false) + + // Note that `index == 1`, since it is relative to `a[2:]` + index, found = linear_search_reverse(a[2:], 20) + assert(index == 1 && found == true) +*/ @(require_results) linear_search :: proc(array: $A/[]$T, key: T) -> (index: int, found: bool) - where intrinsics.type_is_comparable(T) #no_bounds_check { + where intrinsics.type_is_comparable(T) { for x, i in array { if x == key { return i, true @@ -107,8 +146,18 @@ linear_search :: proc(array: $A/[]$T, key: T) -> (index: int, found: bool) return -1, false } +/* +Searches the given slice for the first element satisfying predicate `f` in O(n) time. + +Inputs: +- array: The slice to search in. +- f: The search condition. + +Returns: +- index: The index `i`, such that `array[i]` is the first `x` in `array` for which `f(x) == true`, or -1 if such `x` does not exist. +*/ @(require_results) -linear_search_proc :: proc(array: $A/[]$T, f: proc(T) -> bool) -> (index: int, found: bool) #no_bounds_check { +linear_search_proc :: proc(array: $A/[]$T, f: proc(T) -> bool) -> (index: int, found: bool) { for x, i in array { if f(x) { return i, true @@ -118,22 +167,88 @@ linear_search_proc :: proc(array: $A/[]$T, f: proc(T) -> bool) -> (index: int, f } /* - Binary search searches the given slice for the given element. - If the slice is not sorted, the returned index is unspecified and meaningless. +Searches the given slice for the given element in O(n) time, starting from the +slice end. - If the value is found then the returned int is the index of the matching element. - If there are multiple matches, then any one of the matches could be returned. +If you need a custom search condition, see `linear_search_reverse_proc` - If the value is not found then the returned int is the index where a matching - element could be inserted while maintaining sorted order. +Inputs: +- array: The slice to search in. +- key: The element to search for. - # Examples +Returns: +- index: The index `i`, such that `array[i]` is the last occurrence of `key` in `array`, or -1 if `key` is not present in `array`. +Example: + index: int + found: bool + + a := []i32{10, 10, 10, 20} + + index, found = linear_search_reverse(a, 20) + assert(index == 3 && found == true) + + index, found = linear_search_reverse(a, 10) + assert(index == 2 && found == true) + + index, found = linear_search_reverse(a, 30) + assert(index == -1 && found == false) + + // Note that `index == 1`, since it is relative to `a[2:]` + index, found = linear_search_reverse(a[2:], 20) + assert(index == 1 && found == true) +*/ +@(require_results) +linear_search_reverse :: proc(array: $A/[]$T, key: T) -> (index: int, found: bool) + where intrinsics.type_is_comparable(T) { + #reverse for x, i in array { + if x == key { + return i, true + } + } + return -1, false +} + +/* +Searches the given slice for the last element satisfying predicate `f` in O(n) +time, starting from the slice end. + +Inputs: +- array: The slice to search in. +- f: The search condition. + +Returns: +- index: The index `i`, such that `array[i]` is the last `x` in `array` for which `f(x) == true`, or -1 if such `x` does not exist. +*/ +@(require_results) +linear_search_reverse_proc :: proc(array: $A/[]$T, f: proc(T) -> bool) -> (index: int, found: bool) { + #reverse for x, i in array { + if f(x) { + return i, true + } + } + return -1, false +} + +/* +Searches the given slice for the given element. +If the slice is not sorted, the returned index is unspecified and meaningless. + +If the value is found then the returned int is the index of the matching element. +If there are multiple matches, then any one of the matches could be returned. + +If the value is not found then the returned int is the index where a matching +element could be inserted while maintaining sorted order. + +For slices of more complex types see: `binary_search_by` + +Example: + /* Looks up a series of four elements. The first is found, with a uniquely determined position; the second and third are not found; the fourth could match any position in `[1, 4]`. + */ - ``` index: int found: bool @@ -150,9 +265,6 @@ linear_search_proc :: proc(array: $A/[]$T, f: proc(T) -> bool) -> (index: int, f index, found = slice.binary_search(s, 1) assert(index >= 1 && index <= 4 && found == true) - ``` - - For slices of more complex types see: binary_search_by */ @(require_results) binary_search :: proc(array: $A/[]$T, key: T) -> (index: int, found: bool) @@ -161,21 +273,21 @@ binary_search :: proc(array: $A/[]$T, key: T) -> (index: int, found: bool) } /* - Binary search searches the given slice for the given element. - If the slice is not sorted, the returned index is unspecified and meaningless. +Searches the given slice for the given element. +If the slice is not sorted, the returned index is unspecified and meaningless. - If the value is found then the returned int is the index of the matching element. - If there are multiple matches, then any one of the matches could be returned. +If the value is found then the returned int is the index of the matching element. +If there are multiple matches, then any one of the matches could be returned. - If the value is not found then the returned int is the index where a matching - element could be inserted while maintaining sorted order. +If the value is not found then the returned int is the index where a matching +element could be inserted while maintaining sorted order. - The array elements and key may be different types. This allows the filter procedure - to compare keys against a slice of structs, one struct value at a time. +The array elements and key may be different types. This allows the filter procedure +to compare keys against a slice of structs, one struct value at a time. - Returns: - index: int - found: bool +Returns: +- index: int +- found: bool */ @(require_results) diff --git a/core/slice/sort_private.odin b/core/slice/sort_private.odin index 487b51907..36637c4cd 100644 --- a/core/slice/sort_private.odin +++ b/core/slice/sort_private.odin @@ -1,4 +1,4 @@ -//+private +#+private package slice import "base:intrinsics" diff --git a/core/strings/strings.odin b/core/strings/strings.odin index b69c4a0e0..af93ff33c 100644 --- a/core/strings/strings.odin +++ b/core/strings/strings.odin @@ -93,7 +93,7 @@ Inputs: Returns: - res: A string created from the null-terminated byte pointer and length */ -string_from_null_terminated_ptr :: proc(ptr: [^]byte, len: int) -> (res: string) { +string_from_null_terminated_ptr :: proc "contextless" (ptr: [^]byte, len: int) -> (res: string) { s := string(ptr[:len]) s = truncate_to_byte(s, 0) return s @@ -139,7 +139,7 @@ NOTE: Failure to find the byte results in returning the entire string. Returns: - res: The truncated string */ -truncate_to_byte :: proc(str: string, b: byte) -> (res: string) { +truncate_to_byte :: proc "contextless" (str: string, b: byte) -> (res: string) { n := index_byte(str, b) if n < 0 { n = len(str) @@ -261,7 +261,7 @@ Inputs: Returns: - result: `-1` if `lhs` comes first, `1` if `rhs` comes first, or `0` if they are equal */ -compare :: proc(lhs, rhs: string) -> (result: int) { +compare :: proc "contextless" (lhs, rhs: string) -> (result: int) { return mem.compare(transmute([]byte)lhs, transmute([]byte)rhs) } /* @@ -752,7 +752,7 @@ cut :: proc(s: string, rune_offset := int(0), rune_length := int(0)) -> (res: st count += 1 } - if rune_length <= 1 { + if rune_length < 1 { return s } @@ -1447,7 +1447,7 @@ Output: -1 */ -index_byte :: proc(s: string, c: byte) -> (res: int) { +index_byte :: proc "contextless" (s: string, c: byte) -> (res: int) { return #force_inline bytes.index_byte(transmute([]u8)s, c) } /* @@ -1482,7 +1482,7 @@ Output: -1 */ -last_index_byte :: proc(s: string, c: byte) -> (res: int) { +last_index_byte :: proc "contextless" (s: string, c: byte) -> (res: int) { return #force_inline bytes.last_index_byte(transmute([]u8)s, c) } /* @@ -1576,8 +1576,8 @@ Output: -1 */ -index :: proc(s, substr: string) -> (res: int) { - hash_str_rabin_karp :: proc(s: string) -> (hash: u32 = 0, pow: u32 = 1) { +index :: proc "contextless" (s, substr: string) -> (res: int) { + hash_str_rabin_karp :: proc "contextless" (s: string) -> (hash: u32 = 0, pow: u32 = 1) { for i := 0; i < len(s); i += 1 { hash = hash*PRIME_RABIN_KARP + u32(s[i]) } diff --git a/core/sync/chan/chan.odin b/core/sync/chan/chan.odin index 53a3bff4b..c470d15f3 100644 --- a/core/sync/chan/chan.odin +++ b/core/sync/chan/chan.odin @@ -22,19 +22,17 @@ Raw_Chan :: struct { allocator: runtime.Allocator, allocation_size: int, msg_size: u16, - closed: b16, // atomic + closed: b16, // guarded by `mutex` mutex: sync.Mutex, r_cond: sync.Cond, w_cond: sync.Cond, - r_waiting: int, // atomic - w_waiting: int, // atomic + r_waiting: int, // guarded by `mutex` + w_waiting: int, // guarded by `mutex` // Buffered queue: ^Raw_Queue, // Unbuffered - r_mutex: sync.Mutex, - w_mutex: sync.Mutex, unbuffered_data: rawptr, } @@ -164,27 +162,30 @@ send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool) { } if c.queue != nil { // buffered sync.guard(&c.mutex) - for c.queue.len == c.queue.cap { - sync.atomic_add(&c.w_waiting, 1) + for !c.closed && c.queue.len == c.queue.cap { + c.w_waiting += 1 sync.wait(&c.w_cond, &c.mutex) - sync.atomic_sub(&c.w_waiting, 1) + c.w_waiting -= 1 + } + + if c.closed { + return false } ok = raw_queue_push(c.queue, msg_in) - if sync.atomic_load(&c.r_waiting) > 0 { + if c.r_waiting > 0 { sync.signal(&c.r_cond) } } else if c.unbuffered_data != nil { // unbuffered - sync.guard(&c.w_mutex) sync.guard(&c.mutex) - if sync.atomic_load(&c.closed) { + if c.closed { return false } mem.copy(c.unbuffered_data, msg_in, int(c.msg_size)) - sync.atomic_add(&c.w_waiting, 1) - if sync.atomic_load(&c.r_waiting) > 0 { + c.w_waiting += 1 + if c.r_waiting > 0 { sync.signal(&c.r_cond) } sync.wait(&c.w_cond, &c.mutex) @@ -201,13 +202,13 @@ recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> (ok: bool) { if c.queue != nil { // buffered sync.guard(&c.mutex) for c.queue.len == 0 { - if sync.atomic_load(&c.closed) { + if c.closed { return } - sync.atomic_add(&c.r_waiting, 1) + c.r_waiting += 1 sync.wait(&c.r_cond, &c.mutex) - sync.atomic_sub(&c.r_waiting, 1) + c.r_waiting -= 1 } msg := raw_queue_pop(c.queue) @@ -215,27 +216,26 @@ recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> (ok: bool) { mem.copy(msg_out, msg, int(c.msg_size)) } - if sync.atomic_load(&c.w_waiting) > 0 { + if c.w_waiting > 0 { sync.signal(&c.w_cond) } ok = true } else if c.unbuffered_data != nil { // unbuffered - sync.guard(&c.r_mutex) sync.guard(&c.mutex) - for !sync.atomic_load(&c.closed) && - sync.atomic_load(&c.w_waiting) == 0 { - sync.atomic_add(&c.r_waiting, 1) + for !c.closed && + c.w_waiting == 0 { + c.r_waiting += 1 sync.wait(&c.r_cond, &c.mutex) - sync.atomic_sub(&c.r_waiting, 1) + c.r_waiting -= 1 } - if sync.atomic_load(&c.closed) { + if c.closed { return } mem.copy(msg_out, c.unbuffered_data, int(c.msg_size)) - sync.atomic_sub(&c.w_waiting, 1) + c.w_waiting -= 1 sync.signal(&c.w_cond) ok = true @@ -255,21 +255,24 @@ try_send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool) return false } + if c.closed { + return false + } + ok = raw_queue_push(c.queue, msg_in) - if sync.atomic_load(&c.r_waiting) > 0 { + if c.r_waiting > 0 { sync.signal(&c.r_cond) } } else if c.unbuffered_data != nil { // unbuffered - sync.guard(&c.w_mutex) sync.guard(&c.mutex) - if sync.atomic_load(&c.closed) { + if c.closed { return false } mem.copy(c.unbuffered_data, msg_in, int(c.msg_size)) - sync.atomic_add(&c.w_waiting, 1) - if sync.atomic_load(&c.r_waiting) > 0 { + c.w_waiting += 1 + if c.r_waiting > 0 { sync.signal(&c.r_cond) } sync.wait(&c.w_cond, &c.mutex) @@ -294,21 +297,19 @@ try_recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> bool { mem.copy(msg_out, msg, int(c.msg_size)) } - if sync.atomic_load(&c.w_waiting) > 0 { + if c.w_waiting > 0 { sync.signal(&c.w_cond) } return true } else if c.unbuffered_data != nil { // unbuffered - sync.guard(&c.r_mutex) sync.guard(&c.mutex) - if sync.atomic_load(&c.closed) || - sync.atomic_load(&c.w_waiting) == 0 { + if c.closed || c.w_waiting == 0 { return false } mem.copy(msg_out, c.unbuffered_data, int(c.msg_size)) - sync.atomic_sub(&c.w_waiting, 1) + c.w_waiting -= 1 sync.signal(&c.w_cond) return true @@ -351,10 +352,10 @@ close :: proc "contextless" (c: ^Raw_Chan) -> bool { return false } sync.guard(&c.mutex) - if sync.atomic_load(&c.closed) { + if c.closed { return false } - sync.atomic_store(&c.closed, true) + c.closed = true sync.broadcast(&c.r_cond) sync.broadcast(&c.w_cond) return true @@ -366,7 +367,7 @@ is_closed :: proc "contextless" (c: ^Raw_Chan) -> bool { return true } sync.guard(&c.mutex) - return bool(sync.atomic_load(&c.closed)) + return bool(c.closed) } @@ -423,9 +424,9 @@ raw_queue_pop :: proc "contextless" (q: ^Raw_Queue) -> (data: rawptr) { can_recv :: proc "contextless" (c: ^Raw_Chan) -> bool { sync.guard(&c.mutex) if is_buffered(c) { - return len(c) > 0 + return c.queue.len > 0 } - return sync.atomic_load(&c.w_waiting) > 0 + return c.w_waiting > 0 } @@ -435,7 +436,7 @@ can_send :: proc "contextless" (c: ^Raw_Chan) -> bool { if is_buffered(c) { return c.queue.len < c.queue.cap } - return sync.atomic_load(&c.r_waiting) > 0 + return c.w_waiting == 0 } @@ -484,4 +485,4 @@ select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs: [] ok = send_raw(sends[sel.idx], send_msgs[sel.idx]) } return -} \ No newline at end of file +} diff --git a/core/sync/extended.odin b/core/sync/extended.odin index b446fefa0..30b1b2770 100644 --- a/core/sync/extended.odin +++ b/core/sync/extended.odin @@ -8,7 +8,7 @@ _ :: vg Wait group. Wait group is a synchronization primitive used by the waiting thread to wait, -until a all working threads finish work. +until all working threads finish work. The waiting thread first sets the number of working threads it will expect to wait for using `wait_group_add` call, and start waiting using `wait_group_wait` @@ -35,7 +35,7 @@ Wait_Group :: struct #no_copy { /* Increment an internal counter of a wait group. -This procedure atomicaly increments a number to the specified wait group's +This procedure atomically increments a number to the specified wait group's internal counter by a specified amount. This operation can be done on any thread. */ @@ -48,12 +48,12 @@ wait_group_add :: proc "contextless" (wg: ^Wait_Group, delta: int) { atomic_add(&wg.counter, delta) if wg.counter < 0 { - _panic("sync.Wait_Group negative counter") + panic_contextless("sync.Wait_Group negative counter") } if wg.counter == 0 { cond_broadcast(&wg.cond) if wg.counter != 0 { - _panic("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait") + panic_contextless("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait") } } } @@ -81,7 +81,7 @@ wait_group_wait :: proc "contextless" (wg: ^Wait_Group) { if wg.counter != 0 { cond_wait(&wg.cond, &wg.mutex) if wg.counter != 0 { - _panic("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait") + panic_contextless("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait") } } } @@ -105,7 +105,7 @@ wait_group_wait_with_timeout :: proc "contextless" (wg: ^Wait_Group, duration: t return false } if wg.counter != 0 { - _panic("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait") + panic_contextless("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait") } } return true @@ -121,7 +121,7 @@ When `barrier_wait` procedure is called by any thread, that thread will block the execution, until all threads associated with the barrier reach the same point of execution and also call `barrier_wait`. -when barrier is initialized, a `thread_count` parameter is passed, signifying +When a barrier is initialized, a `thread_count` parameter is passed, signifying the amount of participant threads of the barrier. The barrier also keeps track of an internal atomic counter. When a thread calls `barrier_wait`, the internal counter is incremented. When the internal counter reaches `thread_count`, it is @@ -208,7 +208,7 @@ Represents a thread synchronization primitive that, when signalled, releases one single waiting thread and then resets automatically to a state where it can be signalled again. -When a thread calls `auto_reset_event_wait`, it's execution will be blocked, +When a thread calls `auto_reset_event_wait`, its execution will be blocked, until the event is signalled by another thread. The call to `auto_reset_event_signal` wakes up exactly one thread waiting for the event. */ @@ -228,15 +228,15 @@ thread. */ auto_reset_event_signal :: proc "contextless" (e: ^Auto_Reset_Event) { old_status := atomic_load_explicit(&e.status, .Relaxed) + new_status := old_status + 1 if old_status < 1 else 1 for { - new_status := old_status + 1 if old_status < 1 else 1 if _, ok := atomic_compare_exchange_weak_explicit(&e.status, old_status, new_status, .Release, .Relaxed); ok { break } - - if old_status < 0 { - sema_post(&e.sema) - } + cpu_relax() + } + if old_status < 0 { + sema_post(&e.sema) } } @@ -297,7 +297,7 @@ waiting to acquire the lock, exactly one of those threads is unblocked and allowed into the critical section. */ ticket_mutex_unlock :: #force_inline proc "contextless" (m: ^Ticket_Mutex) { - atomic_add_explicit(&m.serving, 1, .Relaxed) + atomic_add_explicit(&m.serving, 1, .Release) } /* @@ -331,8 +331,8 @@ Benaphore. A benaphore is a combination of an atomic variable and a semaphore that can improve locking efficiency in a no-contention system. Acquiring a benaphore -lock doesn't call into an internal semaphore, if no other thread in a middle of -a critical section. +lock doesn't call into an internal semaphore, if no other thread is in the +middle of a critical section. Once a lock on a benaphore is acquired by a thread, no other thread is allowed into any critical sections, associted with the same benaphore, until the lock @@ -355,7 +355,7 @@ from entering any critical sections associated with the same benaphore, until until the lock is released. */ benaphore_lock :: proc "contextless" (b: ^Benaphore) { - if atomic_add_explicit(&b.counter, 1, .Acquire) > 1 { + if atomic_add_explicit(&b.counter, 1, .Acquire) > 0 { sema_wait(&b.sema) } } @@ -381,10 +381,10 @@ Release a lock on a benaphore. This procedure releases a lock on the specified benaphore. If any of the threads are waiting on the lock, exactly one thread is allowed into a critical section -associated with the same banaphore. +associated with the same benaphore. */ benaphore_unlock :: proc "contextless" (b: ^Benaphore) { - if atomic_sub_explicit(&b.counter, 1, .Release) > 0 { + if atomic_sub_explicit(&b.counter, 1, .Release) > 1 { sema_post(&b.sema) } } @@ -418,8 +418,8 @@ benaphore_guard :: proc "contextless" (m: ^Benaphore) -> bool { /* Recursive benaphore. -Recurisve benaphore is just like a plain benaphore, except it allows reentrancy -into the critical section. +A recursive benaphore is just like a plain benaphore, except it allows +reentrancy into the critical section. When a lock is acquired on a benaphore, all other threads attempting to acquire a lock on the same benaphore will be blocked from any critical sections, @@ -449,13 +449,15 @@ recursive benaphore, until the lock is released. */ recursive_benaphore_lock :: proc "contextless" (b: ^Recursive_Benaphore) { tid := current_thread_id() - if atomic_add_explicit(&b.counter, 1, .Acquire) > 1 { - if tid != b.owner { - sema_wait(&b.sema) + check_owner: if tid != atomic_load_explicit(&b.owner, .Acquire) { + atomic_add_explicit(&b.counter, 1, .Relaxed) + if _, ok := atomic_compare_exchange_strong_explicit(&b.owner, 0, tid, .Release, .Relaxed); ok { + break check_owner } + sema_wait(&b.sema) + atomic_store_explicit(&b.owner, tid, .Release) } // inside the lock - b.owner = tid b.recursion += 1 } @@ -472,15 +474,14 @@ benaphore, until the lock is released. */ recursive_benaphore_try_lock :: proc "contextless" (b: ^Recursive_Benaphore) -> bool { tid := current_thread_id() - if b.owner == tid { - atomic_add_explicit(&b.counter, 1, .Acquire) - } - - if v, _ := atomic_compare_exchange_strong_explicit(&b.counter, 0, 1, .Acquire, .Acquire); v != 0 { + check_owner: if tid != atomic_load_explicit(&b.owner, .Acquire) { + if _, ok := atomic_compare_exchange_strong_explicit(&b.owner, 0, tid, .Release, .Relaxed); ok { + atomic_add_explicit(&b.counter, 1, .Relaxed) + break check_owner + } return false } // inside the lock - b.owner = tid b.recursion += 1 return true } @@ -494,14 +495,14 @@ for other threads for entering. */ recursive_benaphore_unlock :: proc "contextless" (b: ^Recursive_Benaphore) { tid := current_thread_id() - _assert(tid == b.owner, "tid != b.owner") + assert_contextless(tid == atomic_load_explicit(&b.owner, .Relaxed), "tid != b.owner") b.recursion -= 1 recursion := b.recursion + if recursion == 0 { - b.owner = 0 - } - if atomic_sub_explicit(&b.counter, 1, .Release) > 0 { - if recursion == 0 { + if atomic_sub_explicit(&b.counter, 1, .Relaxed) == 1 { + atomic_store_explicit(&b.owner, 0, .Release) + } else { sema_post(&b.sema) } } @@ -740,4 +741,4 @@ Make event available. one_shot_event_signal :: proc "contextless" (e: ^One_Shot_Event) { atomic_store_explicit(&e.state, 1, .Release) futex_broadcast(&e.state) -} \ No newline at end of file +} diff --git a/core/sync/futex_darwin.odin b/core/sync/futex_darwin.odin index fca9aadfe..10ff7bfbb 100644 --- a/core/sync/futex_darwin.odin +++ b/core/sync/futex_darwin.odin @@ -1,5 +1,5 @@ -//+private -//+build darwin +#+private +#+build darwin package sync import "core:c" @@ -12,6 +12,8 @@ foreign System { // __ulock_wait is not available on 10.15 // See https://github.com/odin-lang/Odin/issues/1959 __ulock_wait :: proc "c" (operation: u32, addr: rawptr, value: u64, timeout_us: u32) -> c.int --- + // >= MacOS 11. + __ulock_wait2 :: proc "c" (operation: u32, addr: rawptr, value: u64, timeout_ns: u64, value2: u64) -> c.int --- __ulock_wake :: proc "c" (operation: u32, addr: rawptr, wake_value: u64) -> c.int --- } @@ -48,22 +50,29 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, durati case -ETIMEDOUT: return false case: - _panic("darwin.os_sync_wait_on_address_with_timeout failure") + panic_contextless("darwin.os_sync_wait_on_address_with_timeout failure") } } else { - timeout_ns := u32(duration) - s := __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, u64(expected), timeout_ns) + when darwin.ULOCK_WAIT_2_AVAILABLE { + timeout_ns := u64(duration) + s := __ulock_wait2(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, u64(expected), timeout_ns, 0) + } else { + timeout_us := u32(duration / time.Microsecond) + s := __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, u64(expected), timeout_us) + } + if s >= 0 { return true } + switch s { case EINTR, EFAULT: return true case ETIMEDOUT: return false case: - _panic("futex_wait failure") + panic_contextless("futex_wait failure") } return true @@ -83,7 +92,7 @@ _futex_signal :: proc "contextless" (f: ^Futex) { case -ENOENT: return case: - _panic("darwin.os_sync_wake_by_address_any failure") + panic_contextless("darwin.os_sync_wake_by_address_any failure") } } } else { @@ -99,7 +108,7 @@ _futex_signal :: proc "contextless" (f: ^Futex) { case ENOENT: return case: - _panic("futex_wake_single failure") + panic_contextless("futex_wake_single failure") } } @@ -119,7 +128,7 @@ _futex_broadcast :: proc "contextless" (f: ^Futex) { case -ENOENT: return case: - _panic("darwin.os_sync_wake_by_address_all failure") + panic_contextless("darwin.os_sync_wake_by_address_all failure") } } } else { @@ -135,7 +144,7 @@ _futex_broadcast :: proc "contextless" (f: ^Futex) { case ENOENT: return case: - _panic("futex_wake_all failure") + panic_contextless("futex_wake_all failure") } } diff --git a/core/sync/futex_freebsd.odin b/core/sync/futex_freebsd.odin index ac6e2400a..e3f95b146 100644 --- a/core/sync/futex_freebsd.odin +++ b/core/sync/futex_freebsd.odin @@ -1,5 +1,5 @@ -//+private -//+build freebsd +#+private +#+build freebsd package sync import "core:c" @@ -21,7 +21,7 @@ _futex_wait :: proc "contextless" (f: ^Futex, expected: u32) -> bool { continue } - _panic("_futex_wait failure") + panic_contextless("_futex_wait failure") } unreachable() @@ -44,14 +44,14 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, durati return false } - _panic("_futex_wait_with_timeout failure") + panic_contextless("_futex_wait_with_timeout failure") } _futex_signal :: proc "contextless" (f: ^Futex) { errno := freebsd._umtx_op(f, .WAKE, 1, nil, nil) if errno != nil { - _panic("_futex_signal failure") + panic_contextless("_futex_signal failure") } } @@ -59,6 +59,6 @@ _futex_broadcast :: proc "contextless" (f: ^Futex) { errno := freebsd._umtx_op(f, .WAKE, cast(c.ulong)max(i32), nil, nil) if errno != nil { - _panic("_futex_broadcast failure") + panic_contextless("_futex_broadcast failure") } } diff --git a/core/sync/futex_haiku.odin b/core/sync/futex_haiku.odin index 6fe5894a0..21d07b801 100644 --- a/core/sync/futex_haiku.odin +++ b/core/sync/futex_haiku.odin @@ -1,4 +1,4 @@ -//+private +#+private package sync import "core:c" diff --git a/core/sync/futex_linux.odin b/core/sync/futex_linux.odin index fe57c12ed..52143880b 100644 --- a/core/sync/futex_linux.odin +++ b/core/sync/futex_linux.odin @@ -1,5 +1,5 @@ -//+private -//+build linux +#+private +#+build linux package sync import "core:time" @@ -15,7 +15,7 @@ _futex_wait :: proc "contextless" (futex: ^Futex, expected: u32) -> bool { return true case: // TODO(flysand): More descriptive panic messages based on the vlaue of `errno` - _panic("futex_wait failure") + panic_contextless("futex_wait failure") } } @@ -34,7 +34,7 @@ _futex_wait_with_timeout :: proc "contextless" (futex: ^Futex, expected: u32, du case .NONE, .EINTR, .EAGAIN: return true case: - _panic("futex_wait_with_timeout failure") + panic_contextless("futex_wait_with_timeout failure") } } @@ -44,7 +44,7 @@ _futex_signal :: proc "contextless" (futex: ^Futex) { case .NONE: return case: - _panic("futex_wake_single failure") + panic_contextless("futex_wake_single failure") } } @@ -57,6 +57,6 @@ _futex_broadcast :: proc "contextless" (futex: ^Futex) { case .NONE: return case: - _panic("_futex_wake_all failure") + panic_contextless("_futex_wake_all failure") } } diff --git a/core/sync/futex_netbsd.odin b/core/sync/futex_netbsd.odin index d12409f32..e49b25b02 100644 --- a/core/sync/futex_netbsd.odin +++ b/core/sync/futex_netbsd.odin @@ -1,4 +1,4 @@ -//+private +#+private package sync import "base:intrinsics" @@ -35,7 +35,7 @@ _futex_wait :: proc "contextless" (futex: ^Futex, expected: u32) -> bool { case EINTR, EAGAIN: return true case: - _panic("futex_wait failure") + panic_contextless("futex_wait failure") } } return true @@ -55,7 +55,7 @@ _futex_wait_with_timeout :: proc "contextless" (futex: ^Futex, expected: u32, du case ETIMEDOUT: return false case: - _panic("futex_wait_with_timeout failure") + panic_contextless("futex_wait_with_timeout failure") } } return true @@ -63,12 +63,12 @@ _futex_wait_with_timeout :: proc "contextless" (futex: ^Futex, expected: u32, du _futex_signal :: proc "contextless" (futex: ^Futex) { if _, ok := intrinsics.syscall_bsd(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, 1, 0, 0, 0); !ok { - _panic("futex_wake_single failure") + panic_contextless("futex_wake_single failure") } } _futex_broadcast :: proc "contextless" (futex: ^Futex) { if _, ok := intrinsics.syscall_bsd(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, uintptr(max(i32)), 0, 0, 0); !ok { - _panic("_futex_wake_all failure") + panic_contextless("_futex_wake_all failure") } } diff --git a/core/sync/futex_openbsd.odin b/core/sync/futex_openbsd.odin index 4883a0841..7d3cc8578 100644 --- a/core/sync/futex_openbsd.odin +++ b/core/sync/futex_openbsd.odin @@ -1,5 +1,5 @@ -//+private -//+build openbsd +#+private +#+build openbsd package sync import "core:c" @@ -36,7 +36,7 @@ _futex_wait :: proc "contextless" (f: ^Futex, expected: u32) -> bool { return false } - _panic("futex_wait failure") + panic_contextless("futex_wait failure") } _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, duration: time.Duration) -> bool { @@ -62,14 +62,14 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, durati return false } - _panic("futex_wait_with_timeout failure") + panic_contextless("futex_wait_with_timeout failure") } _futex_signal :: proc "contextless" (f: ^Futex) { res := _unix_futex(f, FUTEX_WAKE_PRIVATE, 1, nil) if res == -1 { - _panic("futex_wake_single failure") + panic_contextless("futex_wake_single failure") } } @@ -77,6 +77,6 @@ _futex_broadcast :: proc "contextless" (f: ^Futex) { res := _unix_futex(f, FUTEX_WAKE_PRIVATE, u32(max(i32)), nil) if res == -1 { - _panic("_futex_wake_all failure") + panic_contextless("_futex_wake_all failure") } } diff --git a/core/sync/futex_wasm.odin b/core/sync/futex_wasm.odin index de88e8198..0f9659a02 100644 --- a/core/sync/futex_wasm.odin +++ b/core/sync/futex_wasm.odin @@ -1,5 +1,5 @@ -//+private -//+build wasm32, wasm64p32 +#+private +#+build wasm32, wasm64p32 package sync import "base:intrinsics" @@ -10,7 +10,7 @@ import "core:time" _futex_wait :: proc "contextless" (f: ^Futex, expected: u32) -> bool { when !intrinsics.has_target_feature("atomics") { - _panic("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it") + panic_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it") } else { s := intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, -1) return s != 0 @@ -19,7 +19,7 @@ _futex_wait :: proc "contextless" (f: ^Futex, expected: u32) -> bool { _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, duration: time.Duration) -> bool { when !intrinsics.has_target_feature("atomics") { - _panic("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it") + panic_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it") } else { s := intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, i64(duration)) return s != 0 @@ -28,7 +28,7 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, durati _futex_signal :: proc "contextless" (f: ^Futex) { when !intrinsics.has_target_feature("atomics") { - _panic("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it") + panic_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it") } else { loop: for { s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), 1) @@ -41,7 +41,7 @@ _futex_signal :: proc "contextless" (f: ^Futex) { _futex_broadcast :: proc "contextless" (f: ^Futex) { when !intrinsics.has_target_feature("atomics") { - _panic("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it") + panic_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it") } else { loop: for { s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), ~u32(0)) diff --git a/core/sync/futex_windows.odin b/core/sync/futex_windows.odin index 6a26baf5b..bb9686a1a 100644 --- a/core/sync/futex_windows.odin +++ b/core/sync/futex_windows.odin @@ -1,5 +1,5 @@ -//+private -//+build windows +#+private +#+build windows package sync import "core:time" diff --git a/core/sync/primitives.odin b/core/sync/primitives.odin index a22824481..f091de045 100644 --- a/core/sync/primitives.odin +++ b/core/sync/primitives.odin @@ -1,6 +1,5 @@ package sync -import "base:runtime" import "core:time" /* @@ -390,7 +389,7 @@ recursive_mutex_guard :: proc "contextless" (m: ^Recursive_Mutex) -> bool { A condition variable. `Cond` implements a condition variable, a rendezvous point for threads waiting -for signalling the occurence of an event. Condition variables are used on +for signalling the occurence of an event. Condition variables are used in conjuction with mutexes to provide a shared access to one or more shared variable. @@ -560,7 +559,7 @@ futex_wait :: proc "contextless" (f: ^Futex, expected: u32) { return } ok := _futex_wait(f, expected) - _assert(ok, "futex_wait failure") + assert_contextless(ok, "futex_wait failure") } /* @@ -597,18 +596,3 @@ Wake up multiple threads waiting on a futex. futex_broadcast :: proc "contextless" (f: ^Futex) { _futex_broadcast(f) } - - -@(private) -_assert :: proc "contextless" (cond: bool, msg: string) { - if !cond { - _panic(msg) - } -} - -@(private) -_panic :: proc "contextless" (msg: string) -> ! { - runtime.print_string(msg) - runtime.print_byte('\n') - runtime.trap() -} diff --git a/core/sync/primitives_atomic.odin b/core/sync/primitives_atomic.odin index 1d8e423db..3c4324eb7 100644 --- a/core/sync/primitives_atomic.odin +++ b/core/sync/primitives_atomic.odin @@ -240,7 +240,7 @@ atomic_recursive_mutex_lock :: proc "contextless" (m: ^Atomic_Recursive_Mutex) { atomic_recursive_mutex_unlock :: proc "contextless" (m: ^Atomic_Recursive_Mutex) { tid := current_thread_id() - _assert(tid == m.owner, "tid != m.owner") + assert_contextless(tid == m.owner, "tid != m.owner") m.recursion -= 1 recursion := m.recursion if recursion == 0 { @@ -361,7 +361,7 @@ atomic_sema_wait_with_timeout :: proc "contextless" (s: ^Atomic_Sema, duration: if !futex_wait_with_timeout(&s.count, u32(original_count), remaining) { return false } - original_count = s.count + original_count = atomic_load_explicit(&s.count, .Relaxed) } if original_count == atomic_compare_exchange_strong_explicit(&s.count, original_count, original_count-1, .Acquire, .Acquire) { return true diff --git a/core/sync/primitives_darwin.odin b/core/sync/primitives_darwin.odin index 146f69e86..141cea744 100644 --- a/core/sync/primitives_darwin.odin +++ b/core/sync/primitives_darwin.odin @@ -1,5 +1,5 @@ -//+build darwin -//+private +#+build darwin +#+private package sync import "core:c" diff --git a/core/sync/primitives_freebsd.odin b/core/sync/primitives_freebsd.odin index 2d7cbf18d..fe6b11e72 100644 --- a/core/sync/primitives_freebsd.odin +++ b/core/sync/primitives_freebsd.odin @@ -1,5 +1,5 @@ -//+build freebsd -//+private +#+build freebsd +#+private package sync import "core:c" diff --git a/core/sync/primitives_haiku.odin b/core/sync/primitives_haiku.odin index 4b8f6b02d..69d005206 100644 --- a/core/sync/primitives_haiku.odin +++ b/core/sync/primitives_haiku.odin @@ -1,4 +1,4 @@ -//+private +#+private package sync import "core:sys/haiku" diff --git a/core/sync/primitives_internal.odin b/core/sync/primitives_internal.odin index 23483aef5..4478a77d2 100644 --- a/core/sync/primitives_internal.odin +++ b/core/sync/primitives_internal.odin @@ -1,4 +1,4 @@ -//+private +#+private package sync import "core:time" diff --git a/core/sync/primitives_linux.odin b/core/sync/primitives_linux.odin index aa7a8b4b2..bf04f8d99 100644 --- a/core/sync/primitives_linux.odin +++ b/core/sync/primitives_linux.odin @@ -1,5 +1,5 @@ -//+build linux -//+private +#+build linux +#+private package sync import "core:sys/linux" diff --git a/core/sync/primitives_netbsd.odin b/core/sync/primitives_netbsd.odin index 594f2ff5c..66da0745a 100644 --- a/core/sync/primitives_netbsd.odin +++ b/core/sync/primitives_netbsd.odin @@ -1,4 +1,4 @@ -//+private +#+private package sync foreign import libc "system:c" diff --git a/core/sync/primitives_openbsd.odin b/core/sync/primitives_openbsd.odin index ff3ff837f..1f6efd8f7 100644 --- a/core/sync/primitives_openbsd.odin +++ b/core/sync/primitives_openbsd.odin @@ -1,5 +1,5 @@ -//+build openbsd -//+private +#+build openbsd +#+private package sync foreign import libc "system:c" diff --git a/core/sync/primitives_wasm.odin b/core/sync/primitives_wasm.odin index f8d9ab657..8906d96be 100644 --- a/core/sync/primitives_wasm.odin +++ b/core/sync/primitives_wasm.odin @@ -1,5 +1,5 @@ -//+private -//+build wasm32, wasm64p32 +#+private +#+build wasm32, wasm64p32 package sync _current_thread_id :: proc "contextless" () -> int { diff --git a/core/sync/primitives_windows.odin b/core/sync/primitives_windows.odin index 9f5bfc280..744bc248b 100644 --- a/core/sync/primitives_windows.odin +++ b/core/sync/primitives_windows.odin @@ -1,5 +1,5 @@ -//+build windows -//+private +#+build windows +#+private package sync import "core:time" diff --git a/core/sys/darwin/Foundation/NSEvent.odin b/core/sys/darwin/Foundation/NSEvent.odin index f20afd3ab..548c5c172 100644 --- a/core/sys/darwin/Foundation/NSEvent.odin +++ b/core/sys/darwin/Foundation/NSEvent.odin @@ -5,8 +5,8 @@ Event :: struct {using _: Object} -EventMask :: distinct bit_set[EventType; UInteger] -EventMaskAny :: ~EventMask{} +EventMask :: distinct bit_set[EventType; UInteger] +EventMaskAny :: transmute(EventMask)(max(UInteger)) when size_of(UInteger) == 4 { // We don't support a 32-bit darwin system but this is mostly to shut up the type checker for the time being diff --git a/core/sys/darwin/darwin.odin b/core/sys/darwin/darwin.odin index ddd25a76c..d109f5544 100644 --- a/core/sys/darwin/darwin.odin +++ b/core/sys/darwin/darwin.odin @@ -1,4 +1,4 @@ -//+build darwin +#+build darwin package darwin import "core:c" diff --git a/core/sys/darwin/mach_darwin.odin b/core/sys/darwin/mach_darwin.odin index ac33ebb62..2cc823e69 100644 --- a/core/sys/darwin/mach_darwin.odin +++ b/core/sys/darwin/mach_darwin.odin @@ -1,29 +1,524 @@ package darwin -foreign import pthread "system:System.framework" +foreign import mach "system:System.framework" import "core:c" +import "base:intrinsics" -// NOTE(tetra): Unclear whether these should be aligned 16 or not. -// However all other sync primitives are aligned for robustness. -// I cannot currently align these though. -// See core/sys/unix/pthread_linux.odin/pthread_t. -task_t :: distinct u64 -semaphore_t :: distinct u64 +kern_return_t :: distinct c.int -kern_return_t :: distinct u64 -thread_act_t :: distinct u64 +mach_port_t :: distinct c.uint +vm_map_t :: mach_port_t +mem_entry_name_port_t :: mach_port_t +ipc_space_t :: mach_port_t +thread_t :: mach_port_t +task_t :: mach_port_t +semaphore_t :: mach_port_t + +vm_size_t :: distinct c.uintptr_t + +vm_address_t :: vm_offset_t +vm_offset_t :: distinct c.uintptr_t + +// NOTE(beau): typedefed to int in the original headers +boolean_t :: b32 + +vm_prot_t :: distinct c.int + +vm_inherit_t :: distinct c.uint + +mach_port_name_t :: distinct c.uint + +sync_policy_t :: distinct c.int @(default_calling_convention="c") -foreign pthread { - mach_task_self :: proc() -> task_t --- +foreign mach { + mach_task_self :: proc() -> mach_port_t --- - semaphore_create :: proc(task: task_t, semaphore: ^semaphore_t, policy, value: c.int) -> kern_return_t --- - semaphore_destroy :: proc(task: task_t, semaphore: semaphore_t) -> kern_return_t --- + semaphore_create :: proc(task: task_t, semaphore: ^semaphore_t, policy: Sync_Policy, value: c.int) -> Kern_Return --- + semaphore_destroy :: proc(task: task_t, semaphore: semaphore_t) -> Kern_Return --- - semaphore_signal :: proc(semaphore: semaphore_t) -> kern_return_t --- - semaphore_signal_all :: proc(semaphore: semaphore_t) -> kern_return_t --- - semaphore_signal_thread :: proc(semaphore: semaphore_t, thread: thread_act_t) -> kern_return_t --- - - semaphore_wait :: proc(semaphore: semaphore_t) -> kern_return_t --- + semaphore_signal :: proc(semaphore: semaphore_t) -> Kern_Return --- + semaphore_signal_all :: proc(semaphore: semaphore_t) -> Kern_Return --- + semaphore_signal_thread :: proc(semaphore: semaphore_t, thread: thread_t) -> Kern_Return --- + + semaphore_wait :: proc(semaphore: semaphore_t) -> Kern_Return --- + + vm_allocate :: proc (target_task : vm_map_t, address: ^vm_address_t, size: vm_size_t, flags: VM_Flags) -> Kern_Return --- + + vm_deallocate :: proc(target_task: vm_map_t, address: vm_address_t, size: vm_size_t) -> Kern_Return --- + + vm_map :: proc( + target_task: vm_map_t, + address: ^vm_address_t, + size: vm_size_t, + mask: vm_address_t, + flags: VM_Flags, + object: mem_entry_name_port_t, + offset: vm_offset_t, + copy: boolean_t, + cur_protection, + max_protection: VM_Prot_Flags, + inheritance: VM_Inherit, + ) -> Kern_Return --- + + mach_make_memory_entry :: proc( + target_task: vm_map_t, + size: ^vm_size_t, + offset: vm_offset_t, + permission: VM_Prot_Flags, + object_handle: ^mem_entry_name_port_t, + parent_entry: mem_entry_name_port_t, + ) -> Kern_Return --- + + mach_port_deallocate :: proc( + task: ipc_space_t, + name: mach_port_name_t, + ) -> Kern_Return --- + + vm_page_size: vm_size_t +} + +Kern_Return :: enum kern_return_t { + Success, + + /* Specified address is not currently valid. + */ + Invalid_Address, + + /* Specified memory is valid, but does not permit the + * required forms of access. + */ + Protection_Failure, + + /* The address range specified is already in use, or + * no address range of the size specified could be + * found. + */ + No_Space, + + /* The function requested was not applicable to this + * type of argument, or an argument is invalid + */ + Invalid_Argument, + + /* The function could not be performed. A catch-all. + */ + Failure, + + /* A system resource could not be allocated to fulfill + * this request. This failure may not be permanent. + */ + Resource_Shortage, + + /* The task in question does not hold receive rights + * for the port argument. + */ + Not_Receiver, + + /* Bogus access restriction. + */ + No_Access, + + /* During a page fault, the target address refers to a + * memory object that has been destroyed. This + * failure is permanent. + */ + Memory_Failure, + + /* During a page fault, the memory object indicated + * that the data could not be returned. This failure + * may be temporary; future attempts to access this + * same data may succeed, as defined by the memory + * object. + */ + Memory_Error, + + /* The receive right is already a member of the portset. + */ + Already_In_Set, + + /* The receive right is not a member of a port set. + */ + Not_In_Set, + + /* The name already denotes a right in the task. + */ + Name_Exists, + + /* The operation was aborted. Ipc code will + * catch this and reflect it as a message error. + */ + Aborted, + + /* The name doesn't denote a right in the task. + */ + Invalid_Name, + + /* Target task isn't an active task. + */ + Invalid_Task, + + /* The name denotes a right, but not an appropriate right. + */ + Invalid_Right, + + /* A blatant range error. + */ + Invalid_Value, + + /* Operation would overflow limit on user-references. + */ + URefs_Overflow, + + /* The supplied (port) capability is improper. + */ + Invalid_Capability, + + /* The task already has send or receive rights + * for the port under another name. + */ + Right_Exists, + + /* Target host isn't actually a host. + */ + Invalid_Host, + + /* An attempt was made to supply "precious" data + * for memory that is already present in a + * memory object. + */ + Memory_Present, + + /* A page was requested of a memory manager via + * memory_object_data_request for an object using + * a MEMORY_OBJECT_COPY_CALL strategy, with the + * VM_PROT_WANTS_COPY flag being used to specify + * that the page desired is for a copy of the + * object, and the memory manager has detected + * the page was pushed into a copy of the object + * while the kernel was walking the shadow chain + * from the copy to the object. This error code + * is delivered via memory_object_data_error + * and is handled by the kernel (it forces the + * kernel to restart the fault). It will not be + * seen by users. + */ + Memory_Data_Moved, + + /* A strategic copy was attempted of an object + * upon which a quicker copy is now possible. + * The caller should retry the copy using + * vm_object_copy_quickly. This error code + * is seen only by the kernel. + */ + Memory_Restart_Copy, + + /* An argument applied to assert processor set privilege + * was not a processor set control port. + */ + Invalid_Processor_Set, + + /* The specified scheduling attributes exceed the thread's + * limits. + */ + Policy_Limit, + + /* The specified scheduling policy is not currently + * enabled for the processor set. + */ + Invalid_Policy, + + /* The external memory manager failed to initialize the + * memory object. + */ + Invalid_Object, + + /* A thread is attempting to wait for an event for which + * there is already a waiting thread. + */ + Already_Waiting, + + /* An attempt was made to destroy the default processor + * set. + */ + Default_Set, + + /* An attempt was made to fetch an exception port that is + * protected, or to abort a thread while processing a + * protected exception. + */ + Exception_Protected, + + /* A ledger was required but not supplied. + */ + Invalid_Ledger, + + /* The port was not a memory cache control port. + */ + Invalid_Memory_Control, + + /* An argument supplied to assert security privilege + * was not a host security port. + */ + Invalid_Security, + + /* thread_depress_abort was called on a thread which + * was not currently depressed. + */ + Not_Depressed, + + /* Object has been terminated and is no longer available + */ + Terminated, + + /* Lock set has been destroyed and is no longer available. + */ + Lock_Set_Destroyed, + + /* The thread holding the lock terminated before releasing + * the lock + */ + Lock_Unstable, + + /* The lock is already owned by another thread + */ + Lock_Owned, + + /* The lock is already owned by the calling thread + */ + Lock_Owned_Self, + + /* Semaphore has been destroyed and is no longer available. + */ + Semaphore_Destroyed, + + /* Return from RPC indicating the target server was + * terminated before it successfully replied + */ + Rpc_Server_Terminated, + + /* Terminate an orphaned activation. + */ + RPC_Terminate_Orphan, + + /* Allow an orphaned activation to continue executing. + */ + RPC_Continue_Orphan, + + /* Empty thread activation (No thread linked to it) + */ + Not_Supported, + + /* Remote node down or inaccessible. + */ + Node_Down, + + /* A signalled thread was not actually waiting. */ + Not_Waiting, + + /* Some thread-oriented operation (semaphore_wait) timed out + */ + Operation_Timed_Out, + + /* During a page fault, indicates that the page was rejected + * as a result of a signature check. + */ + Codesign_Error, + + /* The requested property cannot be changed at this time. + */ + Policy_Static, + + /* The provided buffer is of insufficient size for the requested data. + */ + Insufficient_Buffer_Size, + + /* Denied by security policy + */ + Denied, + + /* The KC on which the function is operating is missing + */ + Missing_KC, + + /* The KC on which the function is operating is invalid + */ + Invalid_KC, + + /* A search or query operation did not return a result + */ + Not_Found, + + /* Maximum return value allowable + */ + Return_Max = 0x100, +} + +/* + * VM allocation flags: + * + * VM_FLAGS_FIXED + * (really the absence of VM_FLAGS_ANYWHERE) + * Allocate new VM region at the specified virtual address, if possible. + * + * VM_FLAGS_ANYWHERE + * Allocate new VM region anywhere it would fit in the address space. + * + * VM_FLAGS_PURGABLE + * Create a purgable VM object for that new VM region. + * + * VM_FLAGS_4GB_CHUNK + * The new VM region will be chunked up into 4GB sized pieces. + * + * VM_FLAGS_NO_PMAP_CHECK + * (for DEBUG kernel config only, ignored for other configs) + * Do not check that there is no stale pmap mapping for the new VM region. + * This is useful for kernel memory allocations at bootstrap when building + * the initial kernel address space while some memory is already in use. + * + * VM_FLAGS_OVERWRITE + * The new VM region can replace existing VM regions if necessary + * (to be used in combination with VM_FLAGS_FIXED). + * + * VM_FLAGS_NO_CACHE + * Pages brought in to this VM region are placed on the speculative + * queue instead of the active queue. In other words, they are not + * cached so that they will be stolen first if memory runs low. + */ + +@(private="file") +LOG2 :: intrinsics.constant_log2 + +VM_Flag :: enum c.int { + Anywhere, + Purgable, + _4GB_Chunk, + Random_Addr, + No_Cache, + Resilient_Codesign, + Resilient_Media, + Permanent, + + // NOTE(beau): log 2 of the bit we want in the bit set so we get that bit in + // the bit set + + TPRO = LOG2(0x1000), + Overwrite = LOG2(0x4000),/* delete any existing mappings first */ + + Superpage_Size_Any = LOG2(0x10000), + Superpage_Size_2MB = LOG2(0x20000), + __Superpage3 = LOG2(0x40000), + + Return_Data_Addr = LOG2(0x100000), + Return_4K_Data_Addr = LOG2(0x800000), + + Alias_Mask1 = 24, + Alias_Mask2, + Alias_Mask3, + Alias_Mask4, + Alias_Mask5, + Alias_Mask6, + Alias_Mask7, + Alias_Mask8, + + HW = TPRO, +} + +VM_Flags :: distinct bit_set[VM_Flag; c.int] +VM_FLAGS_FIXED :: VM_Flags{} + +/* + * VM_FLAGS_SUPERPAGE_MASK + * 3 bits that specify whether large pages should be used instead of + * base pages (!=0), as well as the requested page size. + */ +VM_FLAGS_SUPERPAGE_MASK :: VM_Flags { + .Superpage_Size_Any, + .Superpage_Size_2MB, + .__Superpage3, +} + +// 0xFF000000 +VM_FLAGS_ALIAS_MASK :: VM_Flags { + .Alias_Mask1, + .Alias_Mask2, + .Alias_Mask3, + .Alias_Mask4, + .Alias_Mask5, + .Alias_Mask6, + .Alias_Mask7, + .Alias_Mask8, +} + +VM_GET_FLAGS_ALIAS :: proc(flags: VM_Flags) -> c.int { + return transmute(c.int)(flags & VM_FLAGS_ALIAS_MASK) >> 24 +} +// NOTE(beau): no need for VM_SET_FLAGS_ALIAS, just mask in things from +// VM_Flag.Alias_Mask* + +/* These are the flags that we accept from user-space */ +VM_FLAGS_USER_ALLOCATE :: VM_Flags { + .Anywhere, + .Purgable, + ._4GB_Chunk, + .Random_Addr, + .No_Cache, + .Permanent, + .Overwrite, +} | VM_FLAGS_FIXED | VM_FLAGS_SUPERPAGE_MASK | VM_FLAGS_ALIAS_MASK + +VM_FLAGS_USER_MAP :: VM_Flags { + .Return_4K_Data_Addr, + .Return_Data_Addr, +} | VM_FLAGS_USER_ALLOCATE + +VM_FLAGS_USER_REMAP :: VM_Flags { + .Anywhere, + .Random_Addr, + .Overwrite, + .Return_Data_Addr, + .Resilient_Codesign, + .Resilient_Media, +} | VM_FLAGS_FIXED + +VM_FLAGS_SUPERPAGE_NONE :: VM_Flags{} /* no superpages, if all bits are 0 */ + +/* + * Protection values, defined as bits within the vm_prot_t type + */ + +VM_Prot :: enum vm_prot_t { + Read, + Write, + Execute, +} + +VM_Prot_Flags :: distinct bit_set[VM_Prot; vm_prot_t] + +VM_PROT_NONE :: VM_Prot_Flags{} +VM_PROT_DEFAULT :: VM_Prot_Flags{.Read, .Write} +VM_PROT_ALL :: VM_Prot_Flags{.Read, .Write, .Execute} + +/* + * Enumeration of valid values for vm_inherit_t. + */ + +VM_Inherit :: enum vm_inherit_t { + Share, + Copy, + None, + Donate_Copy, + + Default = Copy, + Last_Valid = None, +} + +Sync_Policy :: enum sync_policy_t { + Fifo, + Fixed_Priority, + Reversed, + Order_Mask, + + Lifo = Fifo | Reversed, } diff --git a/core/sys/darwin/sync.odin b/core/sys/darwin/sync.odin index 121d3edef..58fc7c9e4 100644 --- a/core/sys/darwin/sync.odin +++ b/core/sys/darwin/sync.odin @@ -5,6 +5,7 @@ foreign import system "system:System.framework" // #define OS_WAIT_ON_ADDR_AVAILABILITY \ // __API_AVAILABLE(macos(14.4), ios(17.4), tvos(17.4), watchos(10.4)) when ODIN_OS == .Darwin { + when ODIN_PLATFORM_SUBTARGET == .iOS && ODIN_MINIMUM_OS_VERSION >= 17_04_00 { WAIT_ON_ADDRESS_AVAILABLE :: true } else when ODIN_MINIMUM_OS_VERSION >= 14_04_00 { @@ -12,8 +13,18 @@ when ODIN_OS == .Darwin { } else { WAIT_ON_ADDRESS_AVAILABLE :: false } + + when ODIN_PLATFORM_SUBTARGET == .iOS && ODIN_MINIMUM_OS_VERSION >= 14_00_00 { + ULOCK_WAIT_2_AVAILABLE :: true + } else when ODIN_MINIMUM_OS_VERSION >= 11_00_00 { + ULOCK_WAIT_2_AVAILABLE :: true + } else { + ULOCK_WAIT_2_AVAILABLE :: false + } + } else { WAIT_ON_ADDRESS_AVAILABLE :: false + ULOCK_WAIT_2_AVAILABLE :: false } os_sync_wait_on_address_flag :: enum u32 { diff --git a/core/sys/freebsd/syscalls.odin b/core/sys/freebsd/syscalls.odin index 8590df46e..83b51138a 100644 --- a/core/sys/freebsd/syscalls.odin +++ b/core/sys/freebsd/syscalls.odin @@ -21,6 +21,7 @@ SYS_close : uintptr : 6 SYS_getpid : uintptr : 20 SYS_recvfrom : uintptr : 29 SYS_accept : uintptr : 30 +SYS_getsockname: uintptr : 32 SYS_fcntl : uintptr : 92 SYS_fsync : uintptr : 95 SYS_socket : uintptr : 97 @@ -201,6 +202,26 @@ accept_nil :: proc "contextless" (s: Fd) -> (Fd, Errno) { accept :: proc { accept_T, accept_nil } +// Get socket name. +// +// The getsockname() system call appeared in 4.2BSD. +getsockname :: proc "contextless" (s: Fd, sockaddr: ^$T) -> Errno { + // sockaddr must contain a valid pointer, or this will segfault because + // we're telling the syscall that there's memory available to write to. + addrlen: socklen_t = size_of(T) + + result, ok := intrinsics.syscall_bsd(SYS_getsockname, + cast(uintptr)s, + cast(uintptr)sockaddr, + cast(uintptr)&addrlen) + + if !ok { + return cast(Errno)result + } + + return nil +} + // Synchronize changes to a file. // // The fsync() system call appeared in 4.2BSD. diff --git a/core/sys/haiku/errors.odin b/core/sys/haiku/errors.odin index 023045001..febe647ea 100644 --- a/core/sys/haiku/errors.odin +++ b/core/sys/haiku/errors.odin @@ -1,4 +1,4 @@ -//+build haiku +#+build haiku package sys_haiku import "core:c" diff --git a/core/sys/haiku/find_directory.odin b/core/sys/haiku/find_directory.odin index 103e677d7..758c4dff4 100644 --- a/core/sys/haiku/find_directory.odin +++ b/core/sys/haiku/find_directory.odin @@ -1,4 +1,4 @@ -//+build haiku +#+build haiku package sys_haiku import "core:c" diff --git a/core/sys/haiku/os.odin b/core/sys/haiku/os.odin index 883072c2d..6ab3ef573 100644 --- a/core/sys/haiku/os.odin +++ b/core/sys/haiku/os.odin @@ -1,4 +1,4 @@ -//+build haiku +#+build haiku package sys_haiku import "core:c" diff --git a/core/sys/haiku/types.odin b/core/sys/haiku/types.odin index 0440d5a98..47755b0b7 100644 --- a/core/sys/haiku/types.odin +++ b/core/sys/haiku/types.odin @@ -1,4 +1,4 @@ -//+build haiku +#+build haiku package sys_haiku import "core:c" diff --git a/core/sys/info/cpu_arm.odin b/core/sys/info/cpu_arm.odin index aa4bb368a..960e55a56 100644 --- a/core/sys/info/cpu_arm.odin +++ b/core/sys/info/cpu_arm.odin @@ -1,4 +1,4 @@ -//+build arm32, arm64 +#+build arm32, arm64 package sysinfo import "core:sys/unix" diff --git a/core/sys/info/cpu_intel.odin b/core/sys/info/cpu_intel.odin index 73d4c15e7..95b53dda0 100644 --- a/core/sys/info/cpu_intel.odin +++ b/core/sys/info/cpu_intel.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package sysinfo import "base:intrinsics" @@ -28,6 +28,23 @@ CPU_Feature :: enum u64 { ssse3, // Supplemental streaming SIMD extension 3 sse41, // Streaming SIMD extension 4 and 4.1 sse42, // Streaming SIMD extension 4 and 4.2 + + avx512bf16, // Vector Neural Network Instructions supporting bfloat16 + avx512bitalg, // Bit Algorithms + avx512bw, // Byte and Word instructions + avx512cd, // Conflict Detection instructions + avx512dq, // Doubleword and Quadword instructions + avx512er, // Exponential and Reciprocal instructions + avx512f, // Foundation + avx512fp16, // Vector 16-bit float instructions + avx512ifma, // Integer Fused Multiply Add + avx512pf, // Prefetch instructions + avx512vbmi, // Vector Byte Manipulation Instructions + avx512vbmi2, // Vector Byte Manipulation Instructions 2 + avx512vl, // Vector Length extensions + avx512vnni, // Vector Neural Network Instructions + avx512vp2intersect, // Vector Pair Intersection to a Pair of Mask Registers + avx512vpopcntdq, // Vector Population Count for Doubleword and Quadword } CPU_Features :: distinct bit_set[CPU_Feature; u64] @@ -82,9 +99,11 @@ init_cpu_features :: proc "c" () { // // See: crbug.com/375968 os_supports_avx := false + os_supports_avx512 := false if .os_xsave in set && is_set(26, ecx1) { eax, _ := xgetbv(0) os_supports_avx = is_set(1, eax) && is_set(2, eax) + os_supports_avx512 = is_set(5, eax) && is_set(6, eax) && is_set(7, eax) } if os_supports_avx { try_set(&set, .avx, 28, ecx1) @@ -94,11 +113,37 @@ init_cpu_features :: proc "c" () { return } - _, ebx7, _, _ := cpuid(7, 0) + _, ebx7, ecx7, edx7 := cpuid(7, 0) try_set(&set, .bmi1, 3, ebx7) if os_supports_avx { try_set(&set, .avx2, 5, ebx7) } + if os_supports_avx512 { + try_set(&set, .avx512f, 16, ebx7) + try_set(&set, .avx512dq, 17, ebx7) + try_set(&set, .avx512ifma, 21, ebx7) + try_set(&set, .avx512pf, 26, ebx7) + try_set(&set, .avx512er, 27, ebx7) + try_set(&set, .avx512cd, 28, ebx7) + try_set(&set, .avx512bw, 30, ebx7) + + // XMM/YMM are also required for 128/256-bit instructions + if os_supports_avx { + try_set(&set, .avx512vl, 31, ebx7) + } + + try_set(&set, .avx512vbmi, 1, ecx7) + try_set(&set, .avx512vbmi2, 6, ecx7) + try_set(&set, .avx512vnni, 11, ecx7) + try_set(&set, .avx512bitalg, 12, ecx7) + try_set(&set, .avx512vpopcntdq, 14, ecx7) + + try_set(&set, .avx512vp2intersect, 8, edx7) + try_set(&set, .avx512fp16, 23, edx7) + + eax7_1, _, _, _ := cpuid(7, 1) + try_set(&set, .avx512bf16, 5, eax7_1) + } try_set(&set, .bmi2, 8, ebx7) try_set(&set, .erms, 9, ebx7) try_set(&set, .rdseed, 18, ebx7) diff --git a/core/sys/info/cpu_linux_arm.odin b/core/sys/info/cpu_linux_arm.odin index dcc252971..6408decb7 100644 --- a/core/sys/info/cpu_linux_arm.odin +++ b/core/sys/info/cpu_linux_arm.odin @@ -1,5 +1,5 @@ -//+build arm32, arm64 -//+build linux +#+build arm32, arm64 +#+build linux package sysinfo import "core:sys/linux" diff --git a/core/sys/info/cpu_linux_riscv64.odin b/core/sys/info/cpu_linux_riscv64.odin index 0b64c3725..84f6134d4 100644 --- a/core/sys/info/cpu_linux_riscv64.odin +++ b/core/sys/info/cpu_linux_riscv64.odin @@ -1,5 +1,5 @@ -//+build riscv64 -//+build linux +#+build riscv64 +#+build linux package sysinfo import "base:intrinsics" diff --git a/core/sys/info/platform_bsd.odin b/core/sys/info/platform_bsd.odin index e2273d253..6bb32cd3d 100644 --- a/core/sys/info/platform_bsd.odin +++ b/core/sys/info/platform_bsd.odin @@ -1,4 +1,4 @@ -//+build openbsd, netbsd +#+build openbsd, netbsd package sysinfo import sys "core:sys/unix" diff --git a/core/sys/info/platform_darwin.odin b/core/sys/info/platform_darwin.odin index 3fd857bfe..56cd883d3 100644 --- a/core/sys/info/platform_darwin.odin +++ b/core/sys/info/platform_darwin.odin @@ -530,6 +530,11 @@ macos_release_map: map[string]Darwin_To_Release = { "23F79" = {{23, 5, 0}, "macOS", {"Sonoma", {14, 5, 0}}}, "23G80" = {{23, 6, 0}, "macOS", {"Sonoma", {14, 6, 0}}}, "23G93" = {{23, 6, 0}, "macOS", {"Sonoma", {14, 6, 1}}}, + "23H124" = {{23, 6, 0}, "macOS", {"Sonoma", {14, 7, 0}}}, + + // MacOS Sequoia + "24A335" = {{24, 0, 0}, "macOS", {"Sequoia", {15, 0, 0}}}, + "24A348" = {{24, 0, 0}, "macOS", {"Sequoia", {15, 0, 1}}}, } @(private) diff --git a/core/sys/kqueue/kqueue.odin b/core/sys/kqueue/kqueue.odin index 27d1ecaae..56be1cf7a 100644 --- a/core/sys/kqueue/kqueue.odin +++ b/core/sys/kqueue/kqueue.odin @@ -1,4 +1,4 @@ -//+build darwin, netbsd, openbsd, freebsd +#+build darwin, netbsd, openbsd, freebsd package kqueue when ODIN_OS == .Darwin { diff --git a/core/sys/linux/bits.odin b/core/sys/linux/bits.odin index 8a4a6dd7a..9f2c7a5d8 100644 --- a/core/sys/linux/bits.odin +++ b/core/sys/linux/bits.odin @@ -152,66 +152,43 @@ Errno :: enum i32 { RDONLY flag is not present, because it has the value of 0, i.e. it is the default, unless WRONLY or RDWR is specified. */ -when ODIN_ARCH != .arm64 && ODIN_ARCH != .arm32 { - Open_Flags_Bits :: enum { - WRONLY = 0, - RDWR = 1, - CREAT = 6, - EXCL = 7, - NOCTTY = 8, - TRUNC = 9, - APPEND = 10, - NONBLOCK = 11, - DSYNC = 12, - ASYNC = 13, - DIRECT = 14, - LARGEFILE = 15, - DIRECTORY = 16, - NOFOLLOW = 17, - NOATIME = 18, - CLOEXEC = 19, - PATH = 21, - } - // https://github.com/torvalds/linux/blob/7367539ad4b0f8f9b396baf02110962333719a48/include/uapi/asm-generic/fcntl.h#L19 - #assert(1 << uint(Open_Flags_Bits.WRONLY) == 0o0000000_1) - #assert(1 << uint(Open_Flags_Bits.RDWR) == 0o0000000_2) - #assert(1 << uint(Open_Flags_Bits.CREAT) == 0o00000_100) - #assert(1 << uint(Open_Flags_Bits.EXCL) == 0o00000_200) - #assert(1 << uint(Open_Flags_Bits.NOCTTY) == 0o00000_400) - #assert(1 << uint(Open_Flags_Bits.TRUNC) == 0o0000_1000) - #assert(1 << uint(Open_Flags_Bits.APPEND) == 0o0000_2000) - #assert(1 << uint(Open_Flags_Bits.NONBLOCK) == 0o0000_4000) - #assert(1 << uint(Open_Flags_Bits.DSYNC) == 0o000_10000) - #assert(1 << uint(Open_Flags_Bits.ASYNC) == 0o000_20000) - #assert(1 << uint(Open_Flags_Bits.DIRECT) == 0o000_40000) - #assert(1 << uint(Open_Flags_Bits.LARGEFILE) == 0o00_100000) - #assert(1 << uint(Open_Flags_Bits.DIRECTORY) == 0o00_200000) - #assert(1 << uint(Open_Flags_Bits.NOFOLLOW) == 0o00_400000) - #assert(1 << uint(Open_Flags_Bits.NOATIME) == 0o0_1000000) - #assert(1 << uint(Open_Flags_Bits.CLOEXEC) == 0o0_2000000) - #assert(1 << uint(Open_Flags_Bits.PATH) == 0o_10000000) - -} else { - Open_Flags_Bits :: enum { - WRONLY = 0, - RDWR = 1, - CREAT = 6, - EXCL = 7, - NOCTTY = 8, - TRUNC = 9, - APPEND = 10, - NONBLOCK = 11, - DSYNC = 12, - ASYNC = 13, - DIRECTORY = 14, - NOFOLLOW = 15, - DIRECT = 16, - LARGEFILE = 17, - NOATIME = 18, - CLOEXEC = 19, - PATH = 21, - } +Open_Flags_Bits :: enum { + WRONLY = 0, + RDWR = 1, + CREAT = 6, + EXCL = 7, + NOCTTY = 8, + TRUNC = 9, + APPEND = 10, + NONBLOCK = 11, + DSYNC = 12, + ASYNC = 13, + DIRECT = 14, + LARGEFILE = 15, + DIRECTORY = 16, + NOFOLLOW = 17, + NOATIME = 18, + CLOEXEC = 19, + PATH = 21, } +// https://github.com/torvalds/linux/blob/7367539ad4b0f8f9b396baf02110962333719a48/include/uapi/asm-generic/fcntl.h#L19 +#assert(1 << uint(Open_Flags_Bits.WRONLY) == 0o0000000_1) +#assert(1 << uint(Open_Flags_Bits.RDWR) == 0o0000000_2) +#assert(1 << uint(Open_Flags_Bits.CREAT) == 0o00000_100) +#assert(1 << uint(Open_Flags_Bits.EXCL) == 0o00000_200) +#assert(1 << uint(Open_Flags_Bits.NOCTTY) == 0o00000_400) +#assert(1 << uint(Open_Flags_Bits.TRUNC) == 0o0000_1000) +#assert(1 << uint(Open_Flags_Bits.APPEND) == 0o0000_2000) +#assert(1 << uint(Open_Flags_Bits.NONBLOCK) == 0o0000_4000) +#assert(1 << uint(Open_Flags_Bits.DSYNC) == 0o000_10000) +#assert(1 << uint(Open_Flags_Bits.ASYNC) == 0o000_20000) +#assert(1 << uint(Open_Flags_Bits.DIRECT) == 0o000_40000) +#assert(1 << uint(Open_Flags_Bits.LARGEFILE) == 0o00_100000) +#assert(1 << uint(Open_Flags_Bits.DIRECTORY) == 0o00_200000) +#assert(1 << uint(Open_Flags_Bits.NOFOLLOW) == 0o00_400000) +#assert(1 << uint(Open_Flags_Bits.NOATIME) == 0o0_1000000) +#assert(1 << uint(Open_Flags_Bits.CLOEXEC) == 0o0_2000000) +#assert(1 << uint(Open_Flags_Bits.PATH) == 0o_10000000) /* Bits for FD_Flags bitset diff --git a/core/sys/linux/helpers.odin b/core/sys/linux/helpers.odin index f1abbbf61..aefc1179e 100644 --- a/core/sys/linux/helpers.odin +++ b/core/sys/linux/helpers.odin @@ -1,5 +1,5 @@ -//+build linux -//+no-instrumentation +#+build linux +#+no-instrumentation package linux import "base:intrinsics" diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index 5ee07a93d..c5894d78b 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -1,4 +1,4 @@ -//+no-instrumentation +#+no-instrumentation package linux import "base:intrinsics" @@ -1443,8 +1443,9 @@ ptrace_traceme :: proc "contextless" (rq: PTrace_Traceme_Type) -> (Errno) { } ptrace_peek :: proc "contextless" (rq: PTrace_Peek_Type, pid: Pid, addr: uintptr) -> (uint, Errno) { - ret := syscall(SYS_ptrace, rq, pid, addr) - return errno_unwrap(ret, uint) + res: uint = --- + ret := syscall(SYS_ptrace, rq, pid, addr, &res) + return res, Errno(-ret) } ptrace_poke :: proc "contextless" (rq: PTrace_Poke_Type, pid: Pid, addr: uintptr, data: uint) -> (Errno) { @@ -1493,12 +1494,12 @@ ptrace_setregset :: proc "contextless" (rq: PTrace_Setregset_Type, pid: Pid, not } ptrace_getsiginfo :: proc "contextless" (rq: PTrace_Getsiginfo_Type, pid: Pid, si: ^Sig_Info) -> (Errno) { - ret := syscall(SYS_ptrace, rq, pid, si) + ret := syscall(SYS_ptrace, rq, pid, si, rawptr(nil)) return Errno(-ret) } ptrace_peeksiginfo :: proc "contextless" (rq: PTrace_Peeksiginfo_Type, pid: Pid, si: ^Sig_Info) -> (Errno) { - ret := syscall(SYS_ptrace, rq, pid, si) + ret := syscall(SYS_ptrace, rq, pid, si, rawptr(nil)) return Errno(-ret) } @@ -1518,52 +1519,52 @@ ptrace_setoptions :: proc "contextless" (rq: PTrace_Setoptions_Type, pid: Pid, o } ptrace_geteventmsg :: proc "contextless" (rq: PTrace_Geteventmsg_Type, pid: Pid, msg: ^uint) -> (Errno) { - ret := syscall(SYS_ptrace, rq, pid, msg) + ret := syscall(SYS_ptrace, rq, pid, msg, rawptr(nil)) return Errno(-ret) } ptrace_cont :: proc "contextless" (rq: PTrace_Cont_Type, pid: Pid, sig: Signal) -> (Errno) { - ret := syscall(SYS_ptrace, rq, pid, sig) + ret := syscall(SYS_ptrace, rq, pid, rawptr(nil), sig) return Errno(-ret) } ptrace_singlestep :: proc "contextless" (rq: PTrace_Singlestep_Type, pid: Pid, sig: Signal) -> (Errno) { - ret := syscall(SYS_ptrace, rq, pid, sig) + ret := syscall(SYS_ptrace, rq, pid, rawptr(nil), sig) return Errno(-ret) } ptrace_syscall :: proc "contextless" (rq: PTrace_Syscall_Type, pid: Pid, sig: Signal) -> (Errno) { - ret := syscall(SYS_ptrace, rq, pid, sig) + ret := syscall(SYS_ptrace, rq, pid, rawptr(nil), sig) return Errno(-ret) } ptrace_sysemu :: proc "contextless" (rq: PTrace_Sysemu_Type, pid: Pid, sig: Signal) -> (Errno) { - ret := syscall(SYS_ptrace, rq, pid, sig) + ret := syscall(SYS_ptrace, rq, pid, rawptr(nil), sig) return Errno(-ret) } ptrace_sysemu_singlestep :: proc "contextless" (rq: PTrace_Sysemu_Singlestep_Type, pid: Pid, sig: Signal) -> (Errno) { - ret := syscall(SYS_ptrace, rq, pid, sig) + ret := syscall(SYS_ptrace, rq, pid, rawptr(nil), sig) return Errno(-ret) } ptrace_listen :: proc "contextless" (rq: PTrace_Listen_Type, pid: Pid) -> (Errno) { - ret := syscall(SYS_ptrace, rq, pid) + ret := syscall(SYS_ptrace, rq, pid, 0, rawptr(nil)) return Errno(-ret) } ptrace_interrupt :: proc "contextless" (rq: PTrace_Interrupt_Type, pid: Pid) -> (Errno) { - ret := syscall(SYS_ptrace, rq, pid) + ret := syscall(SYS_ptrace, rq, pid, 0, rawptr(nil)) return Errno(-ret) } ptrace_attach :: proc "contextless" (rq: PTrace_Attach_Type, pid: Pid) -> (Errno) { - ret := syscall(SYS_ptrace, rq, pid) + ret := syscall(SYS_ptrace, rq, pid, 0, rawptr(nil)) return Errno(-ret) } ptrace_seize :: proc "contextless" (rq: PTrace_Seize_Type, pid: Pid, opt: PTrace_Options) -> (Errno) { - ret := syscall(SYS_ptrace, rq, pid, 0, transmute(u32) opt) + ret := syscall(SYS_ptrace, rq, pid, 0, transmute(u32) opt, rawptr(nil)) return Errno(-ret) } diff --git a/core/sys/linux/syscall_amd64.odin b/core/sys/linux/syscall_amd64.odin index ee4e16280..31c8ed61c 100644 --- a/core/sys/linux/syscall_amd64.odin +++ b/core/sys/linux/syscall_amd64.odin @@ -1,4 +1,4 @@ -//+build amd64 +#+build amd64 package linux // AMD64 uses the new way to define syscalls, i.e. one that diff --git a/core/sys/linux/syscall_arm32.odin b/core/sys/linux/syscall_arm32.odin index 74640a1a3..731ce36a5 100644 --- a/core/sys/linux/syscall_arm32.odin +++ b/core/sys/linux/syscall_arm32.odin @@ -1,4 +1,4 @@ -//+build arm32 +#+build arm32 package linux // This file was taken and transformed from diff --git a/core/sys/linux/syscall_arm64.odin b/core/sys/linux/syscall_arm64.odin index 61b5a31b7..da8eb45da 100644 --- a/core/sys/linux/syscall_arm64.odin +++ b/core/sys/linux/syscall_arm64.odin @@ -1,4 +1,4 @@ -//+build arm64 +#+build arm64 package linux // Syscalls for arm64 are defined using the new way, i.e. differently from diff --git a/core/sys/linux/syscall_i386.odin b/core/sys/linux/syscall_i386.odin index 4609fc99c..affdff02c 100644 --- a/core/sys/linux/syscall_i386.odin +++ b/core/sys/linux/syscall_i386.odin @@ -1,4 +1,4 @@ -//+build i386 +#+build i386 package linux // The numbers are taken from diff --git a/core/sys/linux/syscall_riscv64.odin b/core/sys/linux/syscall_riscv64.odin index d2fd0c2ff..17845c5ed 100644 --- a/core/sys/linux/syscall_riscv64.odin +++ b/core/sys/linux/syscall_riscv64.odin @@ -1,4 +1,4 @@ -//+build riscv64 +#+build riscv64 package linux // https://github.com/riscv-collab/riscv-gnu-toolchain/blob/master/linux-headers/include/asm-generic/unistd.h diff --git a/core/sys/linux/types.odin b/core/sys/linux/types.odin index 0e5b8218b..07c654749 100644 --- a/core/sys/linux/types.odin +++ b/core/sys/linux/types.odin @@ -1136,6 +1136,12 @@ when ODIN_ARCH == .arm32 { eflags: uint, rsp: uint, ss: uint, + fs_base: uint, + gs_base: uint, + ds: uint, + es: uint, + fs: uint, + gs: uint, } // All floating point state _Arch_User_FP_Regs :: struct { diff --git a/core/sys/linux/wrappers.odin b/core/sys/linux/wrappers.odin index 7a30c3bde..4f6118c80 100644 --- a/core/sys/linux/wrappers.odin +++ b/core/sys/linux/wrappers.odin @@ -1,4 +1,4 @@ -//+build linux +#+build linux package linux /// Low 8 bits of the exit code diff --git a/core/sys/posix/dirent.odin b/core/sys/posix/dirent.odin index bbb5416c5..48830b030 100644 --- a/core/sys/posix/dirent.odin +++ b/core/sys/posix/dirent.odin @@ -193,13 +193,23 @@ when ODIN_OS == .Darwin { } else when ODIN_OS == .NetBSD { dirent :: struct { - d_ino: ino_t, /* [PSX] file number of entry */ - d_reclen: c.uint16_t, /* length of this record */ - d_namelen: c.uint16_t, /* length of string in d_name */ - d_type: D_Type, /* file type */ - d_name: [512]c.char `fmt:"s,0"`, /* [PSX] entry name */ + d_ino: ino_t, /* [PSX] file number of entry */ + d_reclen: c.uint16_t, /* length of this record */ + d_namelen: c.uint16_t, /* length of string in d_name */ + d_type: D_Type, /* file type */ + d_name: [512]c.char `fmt:"s,0"`, /* [PSX] entry name */ } +} else when ODIN_OS == .Linux { + + dirent :: struct { + d_ino: u64, /* [PSX] file number of entry */ + d_off: i64, /* directory offset of the next entry */ + d_reclen: u16, /* length of this record */ + d_type: D_Type, /* file type */ + d_name: [256]c.char `fmt:"s,0"`, /* [PSX] entry name */ + } + } else { #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/dlfcn.odin b/core/sys/posix/dlfcn.odin index 0ddc41337..6a467a2bd 100644 --- a/core/sys/posix/dlfcn.odin +++ b/core/sys/posix/dlfcn.odin @@ -111,6 +111,15 @@ when ODIN_OS == .Darwin { RTLD_LOCAL :: RTLD_Flags{RTLD_Flag_Bits(log2(_RTLD_LOCAL))} +} else when ODIN_OS == .Linux { + + RTLD_LAZY :: 0x001 + RTLD_NOW :: 0x002 + RTLD_GLOBAL :: 0x100 + + _RTLD_LOCAL :: 0 + RTLD_LOCAL :: RTLD_Flags{} + } else { #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/errno.odin b/core/sys/posix/errno.odin index 4ef10aadf..e670ca889 100644 --- a/core/sys/posix/errno.odin +++ b/core/sys/posix/errno.odin @@ -141,7 +141,7 @@ when ODIN_OS == .Darwin { EMLINK :: 31 EPIPE :: 32 EAGAIN :: 35 - EWOULDBLOCK :: 35 + EWOULDBLOCK :: EAGAIN EINPROGRESS :: 36 EALREADY :: 37 ENOTSOCK :: 38 @@ -151,7 +151,7 @@ when ODIN_OS == .Darwin { ENOPROTOOPT :: 42 EPROTONOSUPPORT :: 43 ENOTSUP :: 45 - EOPNOTSUPP :: 45 + EOPNOTSUPP :: ENOTSUP EAFNOSUPPORT :: 47 EADDRINUSE :: 48 EADDRNOTAVAIL :: 49 @@ -220,7 +220,7 @@ when ODIN_OS == .Darwin { EMLINK :: 31 EPIPE :: 32 EAGAIN :: 35 - EWOULDBLOCK :: 35 + EWOULDBLOCK :: EAGAIN EINPROGRESS :: 36 EALREADY :: 37 ENOTSOCK :: 38 @@ -230,7 +230,7 @@ when ODIN_OS == .Darwin { ENOPROTOOPT :: 42 EPROTONOSUPPORT :: 43 ENOTSUP :: 45 - EOPNOTSUPP :: 45 + EOPNOTSUPP :: ENOTSUP EAFNOSUPPORT :: 47 EADDRINUSE :: 48 EADDRNOTAVAIL :: 49 @@ -301,7 +301,7 @@ when ODIN_OS == .Darwin { EMLINK :: 31 EPIPE :: 32 EAGAIN :: 35 - EWOULDBLOCK :: 35 + EWOULDBLOCK :: EAGAIN EINPROGRESS :: 36 EALREADY :: 37 ENOTSOCK :: 38 @@ -311,7 +311,7 @@ when ODIN_OS == .Darwin { ENOPROTOOPT :: 42 EPROTONOSUPPORT :: 43 ENOTSUP :: 45 - EOPNOTSUPP :: 45 + EOPNOTSUPP :: ENOTSUP EAFNOSUPPORT :: 47 EADDRINUSE :: 48 EADDRNOTAVAIL :: 49 @@ -367,6 +367,95 @@ when ODIN_OS == .Darwin { ETIME :: -1 } +} else when ODIN_OS == .Linux { + EPERM :: 1 + ENOENT :: 2 + ESRCH :: 3 + EINTR :: 4 + EIO :: 5 + ENXIO :: 6 + E2BIG :: 7 + ENOEXEC :: 8 + EBADF :: 9 + ECHILD :: 10 + EAGAIN :: 11 + EWOULDBLOCK :: EAGAIN + ENOMEM :: 12 + EACCES :: 13 + EFAULT :: 14 + EBUSY :: 16 + EEXIST :: 17 + EXDEV :: 18 + ENODEV :: 19 + ENOTDIR :: 20 + EISDIR :: 21 + EINVAL :: 22 + ENFILE :: 23 + EMFILE :: 24 + ENOTTY :: 25 + ETXTBSY :: 26 + EFBIG :: 27 + ENOSPC :: 28 + ESPIPE :: 29 + EROFS :: 30 + EMLINK :: 31 + EPIPE :: 32 + + EDEADLK :: 35 + ENAMETOOLONG :: 36 + ENOLCK :: 37 + ENOSYS :: 38 + ENOTEMPTY :: 39 + ELOOP :: 40 + ENOMSG :: 42 + EIDRM :: 43 + + ENOSTR :: 60 + ENODATA :: 61 + ETIME :: 62 + ENOSR :: 63 + + ENOLINK :: 67 + + EPROTO :: 71 + EMULTIHOP :: 72 + EBADMSG :: 74 + EOVERFLOW :: 75 + + ENOTSOCK :: 88 + EDESTADDRREQ :: 89 + EMSGSIZE :: 90 + EPROTOTYPE :: 91 + ENOPROTOOPT :: 92 + EPROTONOSUPPORT :: 93 + + EOPNOTSUPP :: 95 + ENOTSUP :: EOPNOTSUPP + EAFNOSUPPORT :: 97 + EADDRINUSE :: 98 + EADDRNOTAVAIL :: 99 + ENETDOWN :: 100 + ENETUNREACH :: 101 + ENETRESET :: 102 + ECONNABORTED :: 103 + ECONNRESET :: 104 + ENOBUFS :: 105 + EISCONN :: 106 + ENOTCONN :: 107 + + ETIMEDOUT :: 110 + ECONNREFUSED :: 111 + + EHOSTUNREACH :: 113 + EALREADY :: 114 + EINPROGRESS :: 115 + ESTALE :: 116 + + EDQUOT :: 122 + ECANCELED :: 125 + + EOWNERDEAD :: 130 + ENOTRECOVERABLE :: 131 } else { #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/fcntl.odin b/core/sys/posix/fcntl.odin index ca030a9a5..1ccc07c54 100644 --- a/core/sys/posix/fcntl.odin +++ b/core/sys/posix/fcntl.odin @@ -92,9 +92,6 @@ Lock_Type :: enum c.short { WRLCK = F_WRLCK, } -// Assertions made to unify this bit set. -#assert(O_RDONLY == 0) - O_Flag_Bits :: enum c.int { // Sets FD_CLOEXEC on the file descriptor. CLOEXEC = log2(O_CLOEXEC), @@ -107,11 +104,11 @@ O_Flag_Bits :: enum c.int { // If terminal device, do not make it the controlling terminal for the process. NOCTTY = log2(O_NOCTTY), // Don't follow symbolic links, fail with errno ELOOP. - NOFOLLOW = log2(O_NOFOLOW), + NOFOLLOW = log2(O_NOFOLLOW), // If exists and regular, truncate the length to 0. TRUNC = log2(O_TRUNC), - // NOTE: use with `posix.O_TTY_INIT + { .OTHER_FLAG, .OTHER_FLAG }`, unfortunately can't be in + // NOTE: use with `posix.O_TTY_INIT + { .OTHER_FLAG, .OTHER_FLAG }`, unfortunately can't be in // this bit set enum because it is 0 on some platforms and a value on others. // TTY_INIT = O_TTY_INIT, @@ -123,7 +120,8 @@ O_Flag_Bits :: enum c.int { NONBLOCK = log2(O_NONBLOCK), // Write I/O shall complete as defined by synchronized I/O file integrity completion. SYNC = log2(O_SYNC), - // NOTE: use with `posix.O_RSYNC + { .OTHER_FLAG, .OTHER_FLAG }`, unfortunately can't be in + + // NOTE: use with `posix.O_RSYNC + { .OTHER_FLAG, .OTHER_FLAG }`, unfortunately can't be in // this bit set enum because it is 0 on some platforms and a value on others. // RSYNC = O_RSYNC, @@ -135,11 +133,10 @@ O_Flag_Bits :: enum c.int { WRONLY = log2(O_WRONLY), // Reading only. // RDONLY = 0, // Default - } + O_Flags :: bit_set[O_Flag_Bits; c.int] -// A mask of all the access mode bits. O_ACCMODE :: O_Flags{ .EXEC, .RDWR, .WRONLY } AT_Flag_Bits :: enum c.int { @@ -152,8 +149,8 @@ AT_Flags :: bit_set[AT_Flag_Bits; c.int] when ODIN_OS == .Darwin { - off_t :: distinct c.int64_t - pid_t :: distinct c.int32_t + off_t :: distinct c.int64_t + pid_t :: distinct c.int32_t F_DUPFD :: 0 F_DUPFD_CLOEXEC :: 67 @@ -178,7 +175,7 @@ when ODIN_OS == .Darwin { O_DIRECTORY :: 0x00100000 O_EXCL :: 0x00000800 O_NOCTTY :: 0x00020000 - O_NOFOLOW :: 0x00000100 + O_NOFOLLOW :: 0x00000100 O_TRUNC :: 0x00000400 _O_TTY_INIT :: 0 @@ -189,16 +186,16 @@ when ODIN_OS == .Darwin { O_NONBLOCK :: 0x00000004 O_SYNC :: 0x0080 - _O_RSYNC :: 0 - O_RSYNC :: O_Flags{} + _O_RSYNC :: 0 + O_RSYNC :: O_Flags{} - O_EXEC :: 0x40000000 - O_RDONLY :: 0 - O_RDWR :: 0x0002 - O_WRONLY :: 0x0001 + O_EXEC :: 0x40000000 + O_RDONLY :: 0 + O_RDWR :: 0x0002 + O_WRONLY :: 0x0001 _O_SEARCH :: O_EXEC | O_DIRECTORY - O_SEARCH :: O_Flags{ .EXEC, .DIRECTORY } + O_SEARCH :: O_Flags{.EXEC, .DIRECTORY} AT_FDCWD: FD: -2 @@ -217,8 +214,8 @@ when ODIN_OS == .Darwin { } else when ODIN_OS == .FreeBSD { - off_t :: distinct c.int64_t - pid_t :: distinct c.int32_t + off_t :: distinct c.int64_t + pid_t :: distinct c.int32_t F_DUPFD :: 0 F_DUPFD_CLOEXEC :: 17 @@ -243,7 +240,7 @@ when ODIN_OS == .Darwin { O_DIRECTORY :: 0x00020000 O_EXCL :: 0x0800 O_NOCTTY :: 0x8000 - O_NOFOLOW :: 0x0100 + O_NOFOLLOW :: 0x0100 O_TRUNC :: 0x0400 _O_TTY_INIT :: 0x00080000 @@ -256,10 +253,10 @@ when ODIN_OS == .Darwin { _O_RSYNC :: 0 O_RSYNC :: O_Flags{} // NOTE: not defined in headers - O_EXEC :: 0x00040000 - O_RDONLY :: 0 - O_RDWR :: 0x0002 - O_WRONLY :: 0x0001 + O_EXEC :: 0x00040000 + O_RDONLY :: 0 + O_RDWR :: 0x0002 + O_WRONLY :: 0x0001 _O_SEARCH :: O_EXEC O_SEARCH :: O_Flags{ .EXEC } @@ -282,8 +279,8 @@ when ODIN_OS == .Darwin { } else when ODIN_OS == .NetBSD { - off_t :: distinct c.int64_t - pid_t :: distinct c.int32_t + off_t :: distinct c.int64_t + pid_t :: distinct c.int32_t F_DUPFD :: 0 F_DUPFD_CLOEXEC :: 12 @@ -308,7 +305,7 @@ when ODIN_OS == .Darwin { O_DIRECTORY :: 0x0020000 O_EXCL :: 0x0800 O_NOCTTY :: 0x8000 - O_NOFOLOW :: 0x0100 + O_NOFOLLOW :: 0x0100 O_TRUNC :: 0x0400 _O_TTY_INIT :: 0 @@ -319,14 +316,14 @@ when ODIN_OS == .Darwin { O_NONBLOCK :: 0x0004 O_SYNC :: 0x0080 - _O_RSYNC :: 0x0002 - O_RSYNC :: O_Flags{O_Flag_Bits(log2(_O_RSYNC))} + _O_RSYNC :: 0x0002 + O_RSYNC :: O_Flags{O_Flag_Bits(log2(_O_RSYNC))} - O_EXEC :: 0x04000000 - O_RDONLY :: 0 - O_RDWR :: 0x0002 - O_WRONLY :: 0x0001 + O_EXEC :: 0x04000000 + O_RDONLY :: 0 + O_RDWR :: 0x0002 + O_WRONLY :: 0x0001 _O_SEARCH :: 0x00800000 O_SEARCH :: O_Flags{O_Flag_Bits(log2(_O_SEARCH))} @@ -347,8 +344,8 @@ when ODIN_OS == .Darwin { } } else when ODIN_OS == .OpenBSD { - off_t :: distinct c.int64_t - pid_t :: distinct c.int32_t + off_t :: distinct c.int64_t + pid_t :: distinct c.int32_t F_DUPFD :: 0 F_DUPFD_CLOEXEC :: 10 @@ -373,7 +370,7 @@ when ODIN_OS == .Darwin { O_DIRECTORY :: 0x20000 O_EXCL :: 0x0800 O_NOCTTY :: 0x8000 - O_NOFOLOW :: 0x0100 + O_NOFOLLOW :: 0x0100 O_TRUNC :: 0x0400 _O_TTY_INIT :: 0 @@ -384,13 +381,13 @@ when ODIN_OS == .Darwin { O_NONBLOCK :: 0x0004 O_SYNC :: 0x0080 - _O_RSYNC :: O_SYNC - O_RSYNC :: O_Flags{ .SYNC } + _O_RSYNC :: O_SYNC + O_RSYNC :: O_Flags{.SYNC} - O_EXEC :: 0x04000000 // NOTE: not defined in the headers - O_RDONLY :: 0 - O_RDWR :: 0x0002 - O_WRONLY :: 0x0001 + O_EXEC :: 0x04000000 // NOTE: not defined in the headers + O_RDONLY :: 0 + O_RDWR :: 0x0002 + O_WRONLY :: 0x0001 _O_SEARCH :: 0 O_SEARCH :: O_Flags{} // NOTE: not defined in the headers @@ -410,6 +407,72 @@ when ODIN_OS == .Darwin { l_whence: c.short, /* [PSX] flag (Whence) of starting offset */ } +} else when ODIN_OS == .Linux { + + off_t :: distinct c.int64_t + pid_t :: distinct c.int + + F_DUPFD :: 0 + F_GETFD :: 1 + F_SETFD :: 2 + F_GETFL :: 3 + F_SETFL :: 4 + F_GETLK :: 5 + F_SETLK :: 6 + F_SETLKW :: 7 + F_SETOWN :: 8 + F_GETOWN :: 9 + F_RDLCK :: 0 + F_UNLCK :: 2 + F_WRLCK :: 1 + + F_DUPFD_CLOEXEC :: 1030 + + FD_CLOEXEC :: 1 + + O_CREAT :: 0o0_000_100 + O_EXCL :: 0o0_000_200 + O_NOCTTY :: 0o0_000_400 + O_TRUNC :: 0o0_001_000 + O_DIRECTORY :: 0o0_200_000 + O_NOFOLLOW :: 0o0_400_000 + O_CLOEXEC :: 0o2_000_000 + + _O_TTY_INIT :: 0 + O_TTY_INIT :: O_Flags{} + + O_APPEND :: 0o0_002_000 + O_NONBLOCK :: 0o0_004_000 + O_DSYNC :: 0o0_010_000 + O_SYNC :: 0o4_010_000 + + _O_RSYNC :: 0 + O_RSYNC :: O_Flags{} + + O_EXEC :: 0x04000000 // NOTE: not defined in the headers + + O_RDONLY :: 0 + O_WRONLY :: 0o1 + O_RDWR :: 0o2 + + _O_SEARCH :: 0 + O_SEARCH :: O_Flags{} + + AT_FDCWD: FD: -100 + + AT_EACCESS :: 0x200 + AT_SYMLINK_NOFOLLOW :: 0x100 + AT_SYMLINK_FOLLOW :: 0x400 + AT_REMOVEDIR :: 0x200 + + flock :: struct { + l_start: off_t, /* [PSX] relative offset in bytes. */ + l_len: off_t, /* [PSX] size; if 0 then until EOF. */ + l_pid: pid_t, /* [PSX] process ID of the process holding the lock. */ + l_type: Lock_Type, /* [PSX] type of lock. */ + l_whence: c.short, /* [PSX] flag (Whence) of starting offset. */ + } + } else { #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/fnmatch.odin b/core/sys/posix/fnmatch.odin index 9e54972e7..1326ce9e4 100644 --- a/core/sys/posix/fnmatch.odin +++ b/core/sys/posix/fnmatch.odin @@ -53,6 +53,14 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS FNM_PERIOD :: 0x04 FNM_NOESCAPE :: 0x01 +} else when ODIN_OS == .Linux { + + FNM_NOMATCH :: 1 + + FNM_PATHNAME :: 0x01 + FNM_NOESCAPE :: 0x02 + FNM_PERIOD :: 0x04 + } else { #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/glob.odin b/core/sys/posix/glob.odin index 4f41d83db..c17f8b2c3 100644 --- a/core/sys/posix/glob.odin +++ b/core/sys/posix/glob.odin @@ -112,7 +112,7 @@ when ODIN_OS == .Darwin { glob_t :: struct { gl_pathc: c.size_t, /* [PSX] count of paths matched by pattern */ - gl_matchc: c.size_t, /* count of paths matching pattern */ + gl_matchc: c.size_t, /* count of paths matching pattern */ gl_offs: c.size_t, /* [PSX] slots to reserve at the beginning of gl_pathv */ gl_flags: Glob_Flags, /* copy of flags parameter to glob */ gl_pathv: [^]cstring `fmt:"v,gl_pathc"`, /* [PSX] pointer to list of matched pathnames */ @@ -144,7 +144,7 @@ when ODIN_OS == .Darwin { glob_t :: struct { gl_pathc: c.size_t, /* [PSX] count of paths matched by pattern */ - gl_matchc: c.size_t, /* count of paths matching pattern */ + gl_matchc: c.size_t, /* count of paths matching pattern */ gl_offs: c.size_t, /* [PSX] slots to reserve at the beginning of gl_pathv */ gl_flags: Glob_Flags, /* copy of flags parameter to glob */ gl_pathv: [^]cstring `fmt:"v,gl_pathc"`, /* [PSX] pointer to list of matched pathnames */ @@ -174,6 +174,35 @@ when ODIN_OS == .Darwin { GLOB_NOMATCH :: -3 GLOB_NOSPACE :: -1 +} else when ODIN_OS == .Linux { + + glob_t :: struct { + gl_pathc: c.size_t, /* [PSX] count of paths matched by pattern */ + gl_pathv: [^]cstring `fmt:"v,gl_pathc"`, /* [PSX] pointer to list of matched pathnames */ + gl_offs: c.size_t, /* [PSX] slots to reserve at the beginning of gl_pathv */ + gl_flags: Glob_Flags, /* copy of flags parameter to glob */ + + // Non-standard alternate file system access functions: + + gl_closedir: proc "c" (dirp: DIR), + gl_readdir: proc "c" (dirp: DIR) -> ^dirent, + gl_opendir: proc "c" (path: cstring) -> DIR, + gl_lstat: proc "c" (path: cstring, buf: ^stat_t) -> result, + gl_stat: proc "c" (path: cstring, buf: ^stat_t) -> result, + } + + GLOB_ERR :: 1 << 0 + GLOB_MARK :: 1 << 1 + GLOB_NOSORT :: 1 << 2 + GLOB_DOOFFS :: 1 << 3 + GLOB_NOCHECK :: 1 << 4 + GLOB_APPEND :: 1 << 5 + GLOB_NOESCAPE :: 1 << 6 + + GLOB_NOSPACE :: 1 + GLOB_ABORTED :: 2 + GLOB_NOMATCH :: 3 + } else { #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/grp.odin b/core/sys/posix/grp.odin index c8a39de6a..3a78e2c4c 100644 --- a/core/sys/posix/grp.odin +++ b/core/sys/posix/grp.odin @@ -114,7 +114,7 @@ foreign lib { getgrnam_r :: proc(name: cstring, grp: ^group, buffer: [^]byte, bufsize: c.size_t, result: ^^group) -> Errno --- } -when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { gid_t :: distinct c.uint32_t diff --git a/core/sys/posix/langinfo.odin b/core/sys/posix/langinfo.odin index 24ecc917a..04b46921c 100644 --- a/core/sys/posix/langinfo.odin +++ b/core/sys/posix/langinfo.odin @@ -238,7 +238,7 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD { ABDAY_4 :: 16 ABDAY_5 :: 17 ABDAY_6 :: 18 - ABDAY_7 :: 19 + ABDAY_7 :: 19 MON_1 :: 20 MON_2 :: 21 @@ -278,7 +278,91 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD { YESEXPR :: 47 NOEXPR :: 49 - CRNCYSTR :: 50 + CRNCYSTR :: 50 + +} else when ODIN_OS == .Linux { + + // NOTE: declared with `_t` so we can enumerate the real `nl_info`. + nl_item_t :: distinct c.int + + // NOTE: All these values are set in an enum on the Linux implementation. + // Some depend on locale.h contants (bits/locale.h to be precise). + + // NOTE: ABDAY_1 is set to LC_TIME << 16 (LC_TIME is 2) on the enum group of + // the Linux implementation. + ABDAY_1 :: 0x20_000 + ABDAY_2 :: 0x20_001 + ABDAY_3 :: 0x20_002 + ABDAY_4 :: 0x20_003 + ABDAY_5 :: 0x20_004 + ABDAY_6 :: 0x20_005 + ABDAY_7 :: 0x20_006 + + DAY_1 :: 0x20_007 + DAY_2 :: 0x20_008 + DAY_3 :: 0x20_009 + DAY_4 :: 0x20_00A + DAY_5 :: 0x20_00B + DAY_6 :: 0x20_00C + DAY_7 :: 0x20_00D + + ABMON_1 :: 0x20_00E + ABMON_2 :: 0x20_010 + ABMON_3 :: 0x20_011 + ABMON_4 :: 0x20_012 + ABMON_5 :: 0x20_013 + ABMON_6 :: 0x20_014 + ABMON_7 :: 0x20_015 + ABMON_8 :: 0x20_016 + ABMON_9 :: 0x20_017 + ABMON_10 :: 0x20_018 + ABMON_11 :: 0x20_019 + ABMON_12 :: 0x20_01A + + MON_1 :: 0x20_01B + MON_2 :: 0x20_01C + MON_3 :: 0x20_01D + MON_4 :: 0x20_01E + MON_5 :: 0x20_020 + MON_6 :: 0x20_021 + MON_7 :: 0x20_022 + MON_8 :: 0x20_023 + MON_9 :: 0x20_024 + MON_10 :: 0x20_025 + MON_11 :: 0x20_026 + MON_12 :: 0x20_027 + + AM_STR :: 0x20_028 + PM_STR :: 0x20_029 + + D_T_FMT :: 0x20_02A + D_FMT :: 0x20_02B + T_FMT :: 0x20_02C + T_FMT_AMPM :: 0x20_02D + + ERA :: 0x20_02E + ERA_D_FMT :: 0x20_030 + ALT_DIGITS :: 0x20_031 + ERA_D_T_FMT :: 0x20_032 + ERA_T_FMT :: 0x20_033 + + // NOTE: CODESET is the 16th member of the enum group starting with value + // LC_CTYPE << 16, LC_CTYPE is 0. + CODESET :: 0x0F + + // NOTE: CRNCYSTR is the 16th member of the enum group starting with value + // LC_MONETARY << 16, LC_MONETARY is 4. + CRNCYSTR :: 0x40_00F + + // NOTE: RADIXCHAR is the 1st member of the enum group starting with value + // LC_NUMERIC << 16, LC_NUMERIC is 1. + RADIXCHAR :: 0x10_000 + THOUSEP :: 0x10_001 + + // NOTE: YESEXPR is the 1st member of the enum group starting with value + // LC_MESSAGES << 16, LC_MESSAGES is 5. + YESEXPR :: 0x50_000 + NOEXPR :: 0x50_001 } else { #panic("posix is unimplemented for the current target") diff --git a/core/sys/posix/limits.odin b/core/sys/posix/limits.odin index 7bb561215..58adce0a1 100644 --- a/core/sys/posix/limits.odin +++ b/core/sys/posix/limits.odin @@ -454,6 +454,101 @@ when ODIN_OS == .Darwin { NL_TEXTMAX :: 255 NZERO :: 20 +} else when ODIN_OS == .Linux { + + // A definition of one of the symbolic constants in the following list shall be omitted from + // on specific implementations where the corresponding value is equal to or greater + // than the stated minimum, but is unspecified. + // + // This indetermination might depend on the amount of available memory space on a specific + // instance of a specific implementation. The actual value supported by a specific instance shall + // be provided by the sysconf() function. + + // AIO_LISTIO_MAX :: sysconf(._AIO_LISTIO_MAX) + // AIO_MAX :: sysconf(._AIO_MAX) + // AIO_PRIO_DELTA_MAX :: sysconf(._AIO_PRIO_DELTA_MAX) + ARG_MAX :: 131_072 + // ATEXIT_MAX :: sysconf(._ATEXIT_MAX) + // CHILD_MAX :: sysconf(._POSIX_ARG_MAX) + // DELAYTIMER_MAX :: sysconf(._DELAYTIMER_MAX) + // HOST_NAME_MAX :: sysconf(._HOST_NAME_MAX) + // IOV_MAX :: sysconf(._XOPEN_IOV_MAX) + // LOGIN_NAME_MAX :: sysconf(._LOGIN_NAME_MAX) + // MQ_OPEN_MAX :: sysconf(._MQ_OPEN_MAX) + // MQ_PRIO_MAX :: sysconf(._MQ_PRIO_MAX) + // PAGESIZE :: PAGE_SIZE + // PAGE_SIZE :: sysconf(._PAGE_SIZE) + PTHREAD_DESTRUCTOR_ITERATIONS :: 4 + // PTHREAD_KEYS_MAX :: sysconf(._PTHREAD_KEYS_MAX) + // PTHREAD_STACK_MIN :: sysconf(._PTHREAD_STACK_MIN) + // RTSIG_MAX :: sysconf(._RTSIG_MAX) + // SEM_NSEMS_MAX :: sysconf(._SEM_NSEMS_MAX) + // SEM_VALUE_MAX :: sysconf(._SEM_VALUE_MAX) + // SIGQUEUE_MAX :: sysconf(._SIGQUEUE_MAX) + // SS_REPL_MAX :: sysconf(._SS_REPL_MAX) + // STREAM_MAX :: sysconf(._STREAM_MAX) + // SYMLOOP_MAX :: sysconf(._SYSLOOP_MAX) + // TIMER_MAX :: sysconf(._TIMER_MAX) + // TRACE_EVENT_NAME_MAX :: sysconf(._TRACE_EVENT_NAME_MAX) + // TRACE_NAME_MAX :: sysconf(._TRACE_NAME_MAX) + // TRACE_SYS_MAX :: sysconf(._TRACE_SYS_MAX) + // TRACE_USER_EVENT_MAX :: sysconf(._TRACE_USER_EVENT_MAX) + // TTY_NAME_MAX :: sysconf(._TTY_NAME_MAX) + // TZNAME_MAX :: sysconf(._TZNAME_MAX) + + // The values in the following list may be constants within an implementation or may vary from + // one pathname to another. + // For example, file systems or directories may have different characteristics. + // + // A definition of one of the symbolic constants in the following list shall be omitted from the + // header on specific implementations where the corresponding value is equal to or + // greater than the stated minimum, but where the value can vary depending on the file to which + // it is applied. + // The actual value supported for a specific pathname shall be provided by the pathconf() function. + + // FILESIZEBITS :: pathconf(".", ._FILESIZEBITS) + LINK_MAX :: 127 + MAX_CANON :: 255 + MAX_INPUT :: 255 + NAME_MAX :: 255 + PATH_MAX :: 4096 + PIPE_BUF :: 4096 + // POSIX_ALLOC_SIZE_MIN :: sysconf(._POSIX_ALLOC_SIZE_MIN) + // POSIX_REC_INCR_XFER_SIZE :: sysconf(._POSIX_REC_INCR_XFER_SIZE) + // POSIX_REC_MAX_XFER_SIZE :: sysconf(._POSIX_REC_MAX_XFER_SIZE) + // POSIX_REC_MIN_XFER_SIZE :: sysconf(._POSIX_REC_MIN_XFER_SIZE) + // POSIX_REC_XFER_ALIGN :: sysconf(._POSIX_REC_XFER_ALIGN) + // SYMLINK_MAX :: pathconf(".", ._SYMLINK_MAX) + + + // The magnitude limitations in the following list shall be fixed by specific implementations. + // An application should assume that the value of the symbolic constant defined by + // in a specific implementation is the minimum that pertains whenever the application is run + // under that implementation. + // A specific instance of a specific implementation may increase the value relative to that + // supplied by for that implementation. + // The actual value supported by a specific instance shall be provided by the sysconf() function. + + BC_BASE_MAX :: 99 + BC_DIM_MAX :: 2048 + BC_SCALE_MAX :: 99 + BC_STRING_MAX :: 1000 + CHARCLASS_NAME_MAX :: 14 + COLL_WEIGHTS_MAX :: 2 + EXPR_NEST_MAX :: 32 + // LINE_MAX :: sysconf(._LINE_MAX) + // NGROUPS_MAX :: sysconf(._NGROUPS_MAX) + RE_DUP_MAX :: 255 + + // Other limits. + + NL_ARGMAX :: 9 + NL_LANGMAX :: 32 // 14 on glibc, 32 on musl + NL_MSGMAX :: 32_767 + NL_SETMAX :: 255 + NL_TEXTMAX :: 2048 // 255 on glibc, 2048 on musl + NZERO :: 20 + } else { #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/locale.odin b/core/sys/posix/locale.odin index 1f2a336b5..fae692bcb 100644 --- a/core/sys/posix/locale.odin +++ b/core/sys/posix/locale.odin @@ -88,6 +88,44 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS LC_NUMERIC :: 4 LC_TIME :: 5 +} else when ODIN_OS == .Linux { + + // NOTE: All of these fields are standard ([PSX]). + lconv :: struct { + decimal_point: cstring, + thousand_sep: cstring, + grouping: cstring, + int_curr_symbol: cstring, + currency_symbol: cstring, + mon_decimal_points: cstring, + mon_thousands_sep: cstring, + mon_grouping: cstring, + positive_sign: cstring, + negative_sign: cstring, + int_frac_digits: c.char, + frac_digits: c.char, + p_cs_precedes: c.char, + p_sep_by_space: c.char, + n_cs_precedes: c.char, + n_sep_by_space: c.char, + p_sign_posn: c.char, + n_sign_posn: c.char, + int_p_cs_precedes: c.char, + int_n_cs_precedes: c.char, + int_p_sep_by_space: c.char, + int_n_sep_by_space: c.char, + int_p_sign_posn: c.char, + int_n_sign_posn: c.char, + } + + LC_CTYPE :: 0 + LC_NUMERIC :: 1 + LC_TIME :: 2 + LC_COLLATE :: 3 + LC_MONETARY :: 4 + LC_MESSAGES :: 5 + LC_ALL :: 6 + } else { #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/net_if.odin b/core/sys/posix/net_if.odin index aaeb5088a..75c1a863e 100644 --- a/core/sys/posix/net_if.odin +++ b/core/sys/posix/net_if.odin @@ -44,7 +44,7 @@ foreign lib { if_freenameindex :: proc(ptr: ^if_nameindex_t) --- } -when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { // NOTE: `_t` suffix added due to name conflict. diff --git a/core/sys/posix/netdb.odin b/core/sys/posix/netdb.odin index 7570f9a22..f31de2c2b 100644 --- a/core/sys/posix/netdb.odin +++ b/core/sys/posix/netdb.odin @@ -318,7 +318,7 @@ Info_Errno :: enum c.int { OVERFLOW = EAI_OVERFLOW, } -when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { hostent :: struct { h_name: cstring, /* [PSX] official name of host */ @@ -412,9 +412,28 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS NI_NUMERICSERV :: 2 NI_NUMERICSCOPE :: 32 NI_DGRAM :: 16 + + } else when ODIN_OS == .Linux { + + AI_PASSIVE :: 0x001 + AI_CANONNAME :: 0x002 + AI_NUMERICHOST :: 0x004 + AI_NUMERICSERV :: 0x400 + AI_V4MAPPED :: 0x008 + AI_ALL :: 0x010 + AI_ADDRCONFIG :: 0x020 + + NI_NOFQDN :: 4 + NI_NUMERICHOST :: 1 + NI_NAMEREQD :: 8 + NI_NUMERICSERV :: 2 + NI_NUMERICSCOPE :: 0x100 + NI_DGRAM :: 16 + } when ODIN_OS == .OpenBSD { + EAI_AGAIN :: -3 EAI_BADFLAGS :: -1 EAI_FAIL :: -4 @@ -425,7 +444,22 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS EAI_SOCKTYPE :: -7 EAI_SYSTEM :: -11 EAI_OVERFLOW :: -14 + + } else when ODIN_OS == .Linux { + + EAI_AGAIN :: -3 + EAI_BADFLAGS :: -1 + EAI_FAIL :: -4 + EAI_FAMILY :: -6 + EAI_MEMORY :: -10 + EAI_NONAME :: -2 + EAI_SERVICE :: -8 + EAI_SOCKTYPE :: -7 + EAI_SYSTEM :: -11 + EAI_OVERFLOW :: -12 + } else { + EAI_AGAIN :: 2 EAI_BADFLAGS :: 3 EAI_FAIL :: 4 @@ -438,6 +472,6 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS EAI_OVERFLOW :: 14 } -}else { +} else { #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/netinet_in.odin b/core/sys/posix/netinet_in.odin index 3926c5288..22bfde9bc 100644 --- a/core/sys/posix/netinet_in.odin +++ b/core/sys/posix/netinet_in.odin @@ -30,7 +30,7 @@ Protocol :: enum c.int { UDP = IPPROTO_UDP, } -when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { in_addr :: struct { s_addr: in_addr_t, /* [PSX] big endian address */ @@ -44,26 +44,63 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS }, } - sockaddr_in :: struct { - sin_len: c.uint8_t, - sin_family: sa_family_t, /* [PSX] AF_INET (but a smaller size) */ - sin_port: in_port_t, /* [PSX] port number */ - sin_addr: in_addr, /* [PSX] IP address */ - sin_zero: [8]c.char, - } + when ODIN_OS == .Linux { - sockaddr_in6 :: struct { - sin6_len: c.uint8_t, - sin6_family: sa_family_t, /* [PSX] AF_INET6 (but a smaller size) */ - sin6_port: in_port_t, /* [PSX] port number */ - sin6_flowinfo: c.uint32_t, /* [PSX] IPv6 traffic class and flow information */ - sin6_addr: in6_addr, /* [PSX] IPv6 address */ - sin6_scope_id: c.uint32_t, /* [PSX] set of interfaces for a scope */ - } + sockaddr_in :: struct { + sin_family: sa_family_t, /* [PSX] AF_INET (but a smaller size) */ + sin_port: in_port_t, /* [PSX] port number */ + sin_addr: in_addr, /* [PSX] IP address */ + sin_zero: [8]c.char, + } + + sockaddr_in6 :: struct { + sin6_family: sa_family_t, /* [PSX] AF_INET6 (but a smaller size) */ + sin6_port: in_port_t, /* [PSX] port number */ + sin6_flowinfo: u32be, /* [PSX] IPv6 traffic class and flow information */ + sin6_addr: in6_addr, /* [PSX] IPv6 address */ + sin6_scope_id: c.uint32_t, /* [PSX] set of interfaces for a scope */ + } + + IPV6_MULTICAST_IF :: 17 + IPV6_UNICAST_HOPS :: 16 + IPV6_MULTICAST_HOPS :: 18 + IPV6_MULTICAST_LOOP :: 19 + IPV6_JOIN_GROUP :: 20 + IPV6_LEAVE_GROUP :: 21 + IPV6_V6ONLY :: 26 + + } else { + + sockaddr_in :: struct { + sin_len: c.uint8_t, + sin_family: sa_family_t, /* [PSX] AF_INET (but a smaller size) */ + sin_port: in_port_t, /* [PSX] port number */ + sin_addr: in_addr, /* [PSX] IP address */ + sin_zero: [8]c.char, + } + + sockaddr_in6 :: struct { + sin6_len: c.uint8_t, + sin6_family: sa_family_t, /* [PSX] AF_INET6 (but a smaller size) */ + sin6_port: in_port_t, /* [PSX] port number */ + sin6_flowinfo: c.uint32_t, /* [PSX] IPv6 traffic class and flow information */ + sin6_addr: in6_addr, /* [PSX] IPv6 address */ + sin6_scope_id: c.uint32_t, /* [PSX] set of interfaces for a scope */ + } + + ipv6_mreq :: struct { + ipv6mr_multiaddr: in6_addr, /* [PSX] IPv6 multicast address */ + ipv6mr_interface: c.uint, /* [PSX] interface index */ + } + + IPV6_JOIN_GROUP :: 12 + IPV6_LEAVE_GROUP :: 13 + IPV6_MULTICAST_HOPS :: 10 + IPV6_MULTICAST_IF :: 9 + IPV6_MULTICAST_LOOP :: 11 + IPV6_UNICAST_HOPS :: 4 + IPV6_V6ONLY :: 27 - ipv6_mreq :: struct { - ipv6mr_multiaddr: in6_addr, /* [PSX] IPv6 multicast address */ - ipv6mr_interface: c.uint, /* [PSX] interface index */ } IPPROTO_IP :: 0 @@ -76,14 +113,6 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS INADDR_ANY :: 0x00000000 INADDR_BROADCAST :: 0xFFFFFFFF - IPV6_JOIN_GROUP :: 12 - IPV6_LEAVE_GROUP :: 13 - IPV6_MULTICAST_HOPS :: 10 - IPV6_MULTICAST_IF :: 9 - IPV6_MULTICAST_LOOP :: 11 - IPV6_UNICAST_HOPS :: 4 - IPV6_V6ONLY :: 27 - IN6_IS_ADDR_UNSPECIFIED :: #force_inline proc "contextless" (a: in6_addr) -> b32 { return a.s6_addr == 0 } diff --git a/core/sys/posix/netinet_tcp.odin b/core/sys/posix/netinet_tcp.odin index ecd084b38..284351732 100644 --- a/core/sys/posix/netinet_tcp.odin +++ b/core/sys/posix/netinet_tcp.odin @@ -2,7 +2,7 @@ package posix // netinet/tcp.h - definitions for the Internet Transmission Control Protocol (TCP) -when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { TCP_NODELAY :: 0x01 diff --git a/core/sys/posix/poll.odin b/core/sys/posix/poll.odin index 3e825e009..9e38afe12 100644 --- a/core/sys/posix/poll.odin +++ b/core/sys/posix/poll.odin @@ -72,6 +72,26 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS POLLHUP :: 0x0010 POLLNVAL :: 0x0020 +} else when ODIN_OS == .Linux { + + pollfd :: struct { + fd: FD, /* [PSX] the following descriptor being polled */ + events: Poll_Event, /* [PSX] the input event flags */ + revents: Poll_Event, /* [PSX] the output event flags */ + } + + POLLIN :: 0x0001 + POLLRDNORM :: 0x0040 + POLLRDBAND :: 0x0080 + POLLPRI :: 0x0002 + POLLOUT :: 0x0004 + POLLWRNORM :: 0x0100 + POLLWRBAND :: 0x0200 + + POLLERR :: 0x0008 + POLLHUP :: 0x0010 + POLLNVAL :: 0x0020 + } else { #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/posix.odin b/core/sys/posix/posix.odin index 5cb4122a6..44486e424 100644 --- a/core/sys/posix/posix.odin +++ b/core/sys/posix/posix.odin @@ -1,5 +1,7 @@ /* -Bindings for most POSIX APIs. +Raw bindings for most POSIX APIs. + +Targets glibc and musl compatibility. APIs that have been left out are due to not being useful, being fully replaced (and better) by other Odin packages, diff --git a/core/sys/posix/pthread.odin b/core/sys/posix/pthread.odin index e264f6f6c..76acb1a3d 100644 --- a/core/sys/posix/pthread.odin +++ b/core/sys/posix/pthread.odin @@ -513,6 +513,50 @@ when ODIN_OS == .Darwin { sched_priority: c.int, /* [PSX] process or thread execution scheduling priority */ } +} else when ODIN_OS == .Linux { + + PTHREAD_CANCEL_DEFERRED :: 0 + PTHREAD_CANCEL_ASYNCHRONOUS :: 1 + + PTHREAD_CANCEL_ENABLE :: 0 + PTHREAD_CANCEL_DISABLE :: 1 + + PTHREAD_CANCELED :: rawptr(~uintptr(0)) + + PTHREAD_CREATE_JOINABLE :: 0 + PTHREAD_CREATE_DETACHED :: 1 + + PTHREAD_INHERIT_SCHED :: 0 + PTHREAD_EXPLICIT_SCHED :: 1 + + PTHREAD_PRIO_NONE :: 0 + PTHREAD_PRIO_INHERIT :: 1 + PTHREAD_PRIO_PROTECT :: 2 + + PTHREAD_PROCESS_PRIVATE :: 0 + PTHREAD_PROCESS_SHARED :: 1 + + PTHREAD_SCOPE_SYSTEM :: 0 + PTHREAD_SCOPE_PROCESS :: 1 + + pthread_t :: distinct c.ulong + + pthread_attr_t :: struct #raw_union { + __size: [56]c.char, // NOTE: may be smaller depending on libc or arch, but never larger. + __align: c.long, + } + + pthread_key_t :: distinct c.uint + + sched_param :: struct { + sched_priority: c.int, /* [PSX] process or thread execution scheduling priority */ + + // NOTE: may be smaller depending on libc or arch, but never larger. + __reserved1: c.int, + __reserved2: [4]c.long, + __reserved3: c.int, + } + } else { #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/sched.odin b/core/sys/posix/sched.odin index 6623ba6e6..3923257aa 100644 --- a/core/sys/posix/sched.odin +++ b/core/sys/posix/sched.odin @@ -94,7 +94,7 @@ when ODIN_OS == .Darwin { SCHED_RR :: 3 SCHED_OTHER :: 2 -} else when ODIN_OS == .NetBSD { +} else when ODIN_OS == .NetBSD || ODIN_OS == .Linux { SCHED_OTHER :: 0 SCHED_FIFO :: 1 diff --git a/core/sys/posix/signal.odin b/core/sys/posix/signal.odin index c35494185..1e3f05104 100644 --- a/core/sys/posix/signal.odin +++ b/core/sys/posix/signal.odin @@ -480,11 +480,6 @@ when ODIN_OS == .Darwin { uid_t :: distinct c.uint32_t sigset_t :: distinct c.uint32_t - // MOTE: unimplemented on darwin. - // - // SIGRTMIN :: - // SIGRTMAX :: - SIGHUP :: 1 SIGQUIT :: 3 SIGTRAP :: 5 @@ -625,11 +620,6 @@ when ODIN_OS == .Darwin { __bits: [4]c.uint32_t, } - // MOTE: unimplemented on darwin. - // - // SIGRTMIN :: 65 - // SIGRTMAX :: 126 - SIGHUP :: 1 SIGQUIT :: 3 SIGTRAP :: 5 @@ -794,11 +784,6 @@ when ODIN_OS == .Darwin { __bits: [4]c.uint32_t, } - // MOTE: unimplemented on darwin. - // - // SIGRTMIN :: 33 - // SIGRTMAX :: 63 - SIGHUP :: 1 SIGQUIT :: 3 SIGTRAP :: 5 @@ -1126,6 +1111,180 @@ when ODIN_OS == .Darwin { SI_ASYNCIO :: -4 // NOTE: not implemented SI_MESGQ :: -5 // NOTE: not implemented +} else when ODIN_OS == .Linux { + + // Request that signal be held + SIG_HOLD :: rawptr(uintptr(2)) + + uid_t :: distinct c.uint32_t + sigset_t :: struct { + [1024/(8 * size_of(c.ulong))]val, + } + + SIGHUP :: 1 + SIGQUIT :: 3 + SIGTRAP :: 5 + SIGBUS :: 7 + SIGKILL :: 9 + SIGUSR1 :: 10 + SIGUSR2 :: 12 + SIGPIPE :: 13 + SIGALRM :: 14 + SIGCHLD :: 17 + SIGCONT :: 18 + SIGSTOP :: 19 + SIGTSTP :: 20 + SIGTTIN :: 21 + SIGTTOU :: 22 + SIGURG :: 23 + SIGXCPU :: 24 + SIGXFSZ :: 25 + SIGVTALRM :: 26 + SIGPROF :: 27 + SIGPOLL :: 29 + SIGSYS :: 31 + + // NOTE: this is actually defined as `sigaction`, but due to the function with the same name + // `_t` has been added. + + sigaction_t :: struct { + using _: struct #raw_union { + sa_handler: proc "c" (Signal), /* [PSX] signal-catching function or one of the SIG_IGN or SIG_DFL */ + sa_sigaction: proc "c" (Signal, ^siginfo_t, rawptr), /* [PSX] signal-catching function */ + }, + sa_mask: sigset_t, /* [PSX] set of signals to be blocked during execution of the signal handling function */ + sa_flags: SA_Flags, /* [PSX] special flags */ + sa_restorer: proc "c" (), + } + + SIG_BLOCK :: 0 + SIG_UNBLOCK :: 1 + SIG_SETMASK :: 2 + + SA_NOCLDSTOP :: 1 + SA_NOCLDWAIT :: 2 + SA_SIGINFO :: 4 + SA_ONSTACK :: 0x08000000 + SA_RESTART :: 0x10000000 + SA_NODEFER :: 0x40000000 + SA_RESETHAND :: 0x80000000 + + SS_ONSTACK :: 1 + SS_DISABLE :: 2 + + when ODIN_ARCH == .arm64 { + MINSIGSTKSZ :: 6144 + SIGSTKSZ :: 12288 + } else { + MINSIGSTKSZ :: 2048 + SIGSTKSZ :: 8192 + } + + stack_t :: struct { + ss_sp: rawptr, /* [PSX] stack base or pointer */ + ss_flags: SS_Flags, /* [PSX] flags */ + ss_size: c.size_t, /* [PSX] stack size */ + } + + @(private) + __SI_MAX_SIZE :: 128 + + when size_of(int) == 8 { + @(private) + _pad0 :: struct { + _pad0: c.int, + } + @(private) + __SI_PAD_SIZE :: (__SI_MAX_SIZE / size_of(c.int)) - 4 + + } else { + @(private) + _pad0 :: struct {} + @(private) + __SI_PAD_SIZE :: (__SI_MAX_SIZE / size_of(c.int)) - 3 + } + + siginfo_t :: struct #align(8) { + si_signo: Signal, /* [PSX] signal number */ + si_errno: Errno, /* [PSX] errno value associated with this signal */ + si_code: struct #raw_union { /* [PSX] specific more detailed codes per signal */ + ill: ILL_Code, + fpe: FPE_Code, + segv: SEGV_Code, + bus: BUS_Code, + trap: TRAP_Code, + chld: CLD_Code, + poll: POLL_Code, + any: Any_Code, + }, + __pad0: _pad0, + using _sifields: struct #raw_union { + _pad: [__SI_PAD_SIZE]c.int, + + using _: struct { + si_pid: pid_t, /* [PSX] sending process ID */ + si_uid: uid_t, /* [PSX] real user ID of sending process */ + using _: struct #raw_union { + si_status: c.int, /* [PSX] exit value or signal */ + si_value: sigval, /* [PSX] signal value */ + }, + }, + using _: struct { + si_addr: rawptr, /* [PSX] address of faulting instruction */ + }, + using _: struct { + si_band: c.long, /* [PSX] band event for SIGPOLL */ + }, + }, + } + + ILL_ILLOPC :: 1 + ILL_ILLOPN :: 2 + ILL_ILLADR :: 3 + ILL_ILLTRP :: 4 + ILL_PRVOPC :: 5 + ILL_PRVREG :: 6 + ILL_COPROC :: 7 + ILL_BADSTK :: 8 + + FPE_INTDIV :: 1 + FPE_INTOVF :: 2 + FPE_FLTDIV :: 3 + FPE_FLTOVF :: 4 + FPE_FLTUND :: 5 + FPE_FLTRES :: 6 + FPE_FLTINV :: 7 + FPE_FLTSUB :: 8 + + SEGV_MAPERR :: 1 + SEGV_ACCERR :: 2 + + BUS_ADRALN :: 1 + BUS_ADRERR :: 2 + BUS_OBJERR :: 3 + + TRAP_BRKPT :: 1 + TRAP_TRACE :: 2 + + CLD_EXITED :: 1 + CLD_KILLED :: 2 + CLD_DUMPED :: 3 + CLD_TRAPPED :: 4 + CLD_STOPPED :: 5 + CLD_CONTINUED :: 6 + + POLL_IN :: 1 + POLL_OUT :: 2 + POLL_MSG :: 3 + POLL_ERR :: 4 + POLL_PRI :: 5 + POLL_HUP :: 6 + + SI_USER :: 0 + SI_QUEUE :: -1 + SI_TIMER :: -2 + SI_MESGQ :: -3 + SI_ASYNCIO :: -4 } else { #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/sys_ipc.odin b/core/sys/posix/sys_ipc.odin index f8778ee15..33f8fa259 100644 --- a/core/sys/posix/sys_ipc.odin +++ b/core/sys/posix/sys_ipc.odin @@ -84,6 +84,32 @@ when ODIN_OS == .Darwin { IPC_SET :: 1 IPC_STAT :: 2 +} else when ODIN_OS == .Linux { + + key_t :: distinct c.int32_t + + ipc_perm :: struct { + __ipc_perm_key: key_t, + uid: uid_t, /* [PSX] owner's user ID */ + gid: gid_t, /* [PSX] owner's group ID */ + cuid: uid_t, /* [PSX] creator's user ID */ + cgid: gid_t, /* [PSX] creator's group ID */ + mode: mode_t, /* [PSX] read/write perms */ + __ipc_perm_seq: c.int, + __pad1: c.long, + __pad2: c.long, + } + + IPC_CREAT :: 0o01000 + IPC_EXCL :: 0o02000 + IPC_NOWAIT :: 0o04000 + + IPC_PRIVATE :: key_t(0) + + IPC_RMID :: 0 + IPC_SET :: 1 + IPC_STAT :: 2 + } else { #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/sys_mman.odin b/core/sys/posix/sys_mman.odin index 217d321ac..2f4eb566b 100644 --- a/core/sys/posix/sys_mman.odin +++ b/core/sys/posix/sys_mman.odin @@ -163,7 +163,7 @@ when ODIN_OS == .NetBSD { @(private) LMSYNC :: "msync" } -when ODIN_OS == .Darwin || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { +when ODIN_OS == .Darwin || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { PROT_EXEC :: 0x04 _PROT_NONE :: 0x00 @@ -174,7 +174,7 @@ when ODIN_OS == .Darwin || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { MAP_PRIVATE :: 0x0002 MAP_SHARED :: 0x0001 - when ODIN_OS == .Darwin { + when ODIN_OS == .Darwin || ODIN_OS == .Linux { MS_INVALIDATE :: 0x0002 _MS_SYNC :: 0x0010 } else when ODIN_OS == .NetBSD { @@ -184,6 +184,7 @@ when ODIN_OS == .Darwin || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { MS_INVALIDATE :: 0x0004 _MS_SYNC :: 0x0002 } + MS_ASYNC :: 0x0001 MS_SYNC :: Sync_Flags{Sync_Flags_Bits(log2(_MS_SYNC))} diff --git a/core/sys/posix/sys_msg.odin b/core/sys/posix/sys_msg.odin index a8b86e501..98b76c3a5 100644 --- a/core/sys/posix/sys_msg.odin +++ b/core/sys/posix/sys_msg.odin @@ -64,28 +64,22 @@ when ODIN_OS == .Darwin { MSG_NOERROR :: 0o10000 - // NOTE: this is #pragma pack(4) - - msqid_ds :: struct #align(4) { - msg_perm: ipc_perm, /* [PSX] operation permission structure */ + msqid_ds :: struct #max_field_align(4) { + msg_perm: ipc_perm, /* [PSX] operation permission structure */ msg_first: c.int32_t, msg_last: c.int32_t, msg_cbytes: msglen_t, - msg_qnum: msgqnum_t, /* [PSX] number of messages currently on queue */ - msg_qbytes: msglen_t, /* [PSX] maximum number of bytes allowed on queue */ - msg_lspid: pid_t, /* [PSX] process ID of last msgsnd() */ - msg_lrpid: pid_t, /* [PSX] process ID of last msgrcv() */ - msg_stime: time_t, /* [PSX] time of last msgsnd() */ + msg_qnum: msgqnum_t, /* [PSX] number of messages currently on queue */ + msg_qbytes: msglen_t, /* [PSX] maximum number of bytes allowed on queue */ + msg_lspid: pid_t, /* [PSX] process ID of last msgsnd() */ + msg_lrpid: pid_t, /* [PSX] process ID of last msgrcv() */ + msg_stime: time_t, /* [PSX] time of last msgsnd() */ msg_pad1: c.int32_t, - using _: struct #align(4) { - msg_rtime: time_t, /* [PSX] time of last msgrcv() */ - msg_pad2: c.int32_t, - using _: struct #align(4) { - msg_ctime: time_t, /* [PSX] time of last change */ - msg_pad3: c.int32_t, - msg_pad4: [4]c.int32_t, - }, - }, + msg_rtime: time_t, /* [PSX] time of last msgrcv() */ + msg_pad2: c.int32_t, + msg_ctime: time_t, /* [PSX] time of last change */ + msg_pad3: c.int32_t, + msg_pad4: [4]c.int32_t, } } else when ODIN_OS == .FreeBSD { diff --git a/core/sys/posix/sys_resource.odin b/core/sys/posix/sys_resource.odin index 6716d60c3..55789ee95 100644 --- a/core/sys/posix/sys_resource.odin +++ b/core/sys/posix/sys_resource.odin @@ -95,7 +95,7 @@ when ODIN_OS == .NetBSD { @(private) LGETRUSAGE :: "getrusage" } -when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { PRIO_PROCESS :: 0 PRIO_PGRP :: 1 @@ -103,7 +103,7 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS rlim_t :: distinct c.uint64_t - RLIM_INFINITY :: (rlim_t(1) << 63) - 1 + RLIM_INFINITY :: ~rlim_t(0) when ODIN_OS == .Linux else (rlim_t(1) << 63) - 1 RLIM_SAVED_MAX :: RLIM_INFINITY RLIM_SAVED_CUR :: RLIM_INFINITY @@ -143,9 +143,16 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS RLIMIT_CPU :: 0 RLIMIT_DATA :: 2 RLIMIT_FSIZE :: 1 - RLIMIT_NOFILE :: 8 + RLIMIT_NOFILE :: 7 when ODIN_OS == .Linux else 8 RLIMIT_STACK :: 3 - RLIMIT_AS :: 5 when ODIN_OS == .Darwin || ODIN_OS == .OpenBSD else 10 + + when ODIN_OS == .Linux { + RLIMIT_AS :: 9 + } else when ODIN_OS == .Darwin || ODIN_OS == .OpenBSD { + RLIMIT_AS :: 5 + } else { + RLIMIT_AS :: 10 + } } else { #panic("posix is unimplemented for the current target") diff --git a/core/sys/posix/sys_select.odin b/core/sys/posix/sys_select.odin index 3392e02bc..c20636b21 100644 --- a/core/sys/posix/sys_select.odin +++ b/core/sys/posix/sys_select.odin @@ -55,7 +55,7 @@ when ODIN_OS == .NetBSD { LSELECT :: "select" } -when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { suseconds_t :: distinct (c.int32_t when ODIN_OS == .Darwin || ODIN_OS == .NetBSD else c.long) diff --git a/core/sys/posix/sys_sem.odin b/core/sys/posix/sys_sem.odin index 3fcde325b..2d7bd1b1e 100644 --- a/core/sys/posix/sys_sem.odin +++ b/core/sys/posix/sys_sem.odin @@ -79,19 +79,15 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS SETALL :: 9 when ODIN_OS == .Darwin { - // NOTE: this is #pragma pack(4) - - semid_ds :: struct #align(4) { - sem_perm: ipc_perm, /* [PSX] operation permission structure */ - sem_base: c.int32_t, /* 32 bit base ptr for semaphore set */ - sem_nsems: c.ushort, /* [PSX] number of semaphores in set */ - sem_otime: time_t, /* [PSX] last semop() */ + semid_ds :: struct #max_field_align(4) { + sem_perm: ipc_perm, /* [PSX] operation permission structure */ + sem_base: c.int32_t, /* 32 bit base ptr for semaphore set */ + sem_nsems: c.ushort, /* [PSX] number of semaphores in set */ + sem_otime: time_t, /* [PSX] last semop() */ sem_pad1: c.int32_t, - using _: struct #align(4) { - sem_ctime: time_t, /* [PSX] last time changed by semctl() */ - sem_pad2: c.int32_t, - sem_pad3: [4]c.int32_t, - }, + sem_ctime: time_t, /* [PSX] last time changed by semctl() */ + sem_pad2: c.int32_t, + sem_pad3: [4]c.int32_t, } } else when ODIN_OS == .FreeBSD { semid_ds :: struct { @@ -127,6 +123,36 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS sem_flg: c.short, /* [PSX] operation flags */ } +} else when ODIN_OS == .Linux { + + SEM_UNDO :: 0x1000 // undo the operation on exit + + // Commands for `semctl'. + GETPID :: 11 + GETVAL :: 12 + GETALL :: 13 + GETNCNT :: 14 + GETZCNT :: 15 + SETVAL :: 16 + SETALL :: 17 + + semid_ds :: struct { + sem_perm: ipc_perm, // [PSX] operation permission structure + sem_otime: time_t, // [PSX] last semop() + __sem_otime_high: c.ulong, + sem_ctime: time_t, // [PSX] last time changed by semctl() + __sem_ctime_high: c.ulong, + sem_nsems: c.ulong, // [PSX] number of semaphores in set + __glibc_reserved3: c.ulong, + __glibc_reserved4: c.ulong, + } + + sembuf :: struct { + sem_num: c.ushort, /* [PSX] semaphore number */ + sem_op: c.short, /* [PSX] semaphore operation */ + sem_flg: c.short, /* [PSX] operation flags */ + } + } else { #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/sys_shm.odin b/core/sys/posix/sys_shm.odin index 3bc883ce4..ff56ba441 100644 --- a/core/sys/posix/sys_shm.odin +++ b/core/sys/posix/sys_shm.odin @@ -67,20 +67,16 @@ when ODIN_OS == .Darwin { shmatt_t :: distinct c.ushort - // NOTE: this is #pragma pack(4) - - shmid_ds :: struct #align(4) { - shm_perm: ipc_perm, /* [PSX] operation permission structure */ - shm_segsz: c.size_t, /* [PSX] size of segment in bytes */ - shm_lpid: pid_t, /* [PSX] process ID of last shared memory operation */ - shm_cpid: pid_t, /* [PSX] process ID of creator */ - shm_nattch: shmatt_t, /* [PSX] number of current attaches */ - using _: struct #align(4) { - shm_atime: time_t, /* [PSX] time of last shmat() */ - shm_dtime: time_t, /* [PSX] time of last shmdt() */ - shm_ctime: time_t, /* [PSX] time of last change by shmctl() */ - shm_internal: rawptr, - }, + shmid_ds :: struct #max_field_align(4) { + shm_perm: ipc_perm, /* [PSX] operation permission structure */ + shm_segsz: c.size_t, /* [PSX] size of segment in bytes */ + shm_lpid: pid_t, /* [PSX] process ID of last shared memory operation */ + shm_cpid: pid_t, /* [PSX] process ID of creator */ + shm_nattch: shmatt_t, /* [PSX] number of current attaches */ + shm_atime: time_t, /* [PSX] time of last shmat() */ + shm_dtime: time_t, /* [PSX] time of last shmdt() */ + shm_ctime: time_t, /* [PSX] time of last change by shmctl() */ + shm_internal: rawptr, } } else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD { diff --git a/core/sys/posix/sys_socket.odin b/core/sys/posix/sys_socket.odin index 36c3c1467..e613f0a10 100644 --- a/core/sys/posix/sys_socket.odin +++ b/core/sys/posix/sys_socket.odin @@ -321,16 +321,23 @@ when ODIN_OS == .NetBSD { @(private) LSOCKET :: "socket" } -when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { socklen_t :: distinct c.uint _sa_family_t :: distinct c.uint8_t - sockaddr :: struct { - sa_len: c.uint8_t, /* total length */ - sa_family: sa_family_t, /* [PSX] address family */ - sa_data: [14]c.char, /* [PSX] socket address */ + when ODIN_OS == .Linux { + sockaddr :: struct { + sa_family: sa_family_t, /* [PSX] address family */ + sa_data: [14]c.char, /* [PSX] socket address */ + } + } else { + sockaddr :: struct { + sa_len: c.uint8_t, /* total length */ + sa_family: sa_family_t, /* [PSX] address family */ + sa_data: [14]c.char, /* [PSX] socket address */ + } } @@ -339,6 +346,11 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS _SS_PAD1SIZE :: 6 @(private) _SS_PAD2SIZE :: 240 + } else when ODIN_OS == .Linux { + @(private) + _SS_SIZE :: 128 + @(private) + _SS_PADSIZE :: _SS_SIZE - size_of(c.uint16_t) - size_of(c.uint64_t) } else { @(private) _SS_MAXSIZE :: 128 @@ -350,28 +362,52 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS _SS_PAD2SIZE :: _SS_MAXSIZE - size_of(c.uint8_t) - size_of(sa_family_t) - _SS_PAD1SIZE - _SS_ALIGNSIZE } - sockaddr_storage :: struct { - ss_len: c.uint8_t, /* address length */ - ss_family: sa_family_t, /* [PSX] address family */ - __ss_pad1: [_SS_PAD1SIZE]c.char, - __ss_align: c.int64_t, /* force structure storage alignment */ - __ss_pad2: [_SS_PAD2SIZE]c.char, - } + when ODIN_OS == .Linux { + sockaddr_storage :: struct { + ss_family: sa_family_t, /* [PSX] address family */ + __ss_padding: [_SS_PADSIZE]c.char, + __ss_align: c.uint64_t, /* force structure storage alignment */ + } - msghdr :: struct { - msg_name: rawptr, /* [PSX] optional address */ - msg_namelen: socklen_t, /* [PSX] size of address */ - msg_iov: [^]iovec, /* [PSX] scatter/gather array */ - msg_iovlen: c.int, /* [PSX] members in msg_iov */ - msg_control: rawptr, /* [PSX] ancillary data */ - msg_controllen: socklen_t, /* [PSX] ancillary data buffer length */ - msg_flags: Msg_Flags, /* [PSX] flags on received message */ - } + msghdr :: struct { + msg_name: rawptr, /* [PSX] optional address */ + msg_namelen: socklen_t, /* [PSX] size of address */ + msg_iov: [^]iovec, /* [PSX] scatter/gather array */ + msg_iovlen: c.size_t, /* [PSX] members in msg_iov */ + msg_control: rawptr, /* [PSX] ancillary data */ + msg_controllen: c.size_t, /* [PSX] ancillary data buffer length */ + msg_flags: Msg_Flags, /* [PSX] flags on received message */ + } - cmsghdr :: struct { - cmsg_len: socklen_t, /* [PSX] data byte count, including cmsghdr */ - cmsg_level: c.int, /* [PSX] originating protocol */ - cmsg_type: c.int, /* [PSX] protocol-specific type */ + cmsghdr :: struct { + cmsg_len: c.size_t, /* [PSX] data byte count, including cmsghdr */ + cmsg_level: c.int, /* [PSX] originating protocol */ + cmsg_type: c.int, /* [PSX] protocol-specific type */ + } + } else { + sockaddr_storage :: struct { + ss_len: c.uint8_t, /* address length */ + ss_family: sa_family_t, /* [PSX] address family */ + __ss_pad1: [_SS_PAD1SIZE]c.char, + __ss_align: c.int64_t, /* force structure storage alignment */ + __ss_pad2: [_SS_PAD2SIZE]c.char, + } + + msghdr :: struct { + msg_name: rawptr, /* [PSX] optional address */ + msg_namelen: socklen_t, /* [PSX] size of address */ + msg_iov: [^]iovec, /* [PSX] scatter/gather array */ + msg_iovlen: c.int, /* [PSX] members in msg_iov */ + msg_control: rawptr, /* [PSX] ancillary data */ + msg_controllen: socklen_t, /* [PSX] ancillary data buffer length */ + msg_flags: Msg_Flags, /* [PSX] flags on received message */ + } + + cmsghdr :: struct { + cmsg_len: socklen_t, /* [PSX] data byte count, including cmsghdr */ + cmsg_level: c.int, /* [PSX] originating protocol */ + cmsg_type: c.int, /* [PSX] protocol-specific type */ + } } SCM_RIGHTS :: 0x01 @@ -421,57 +457,90 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS SOCK_STREAM :: 1 // Options to be accessed at socket level, not protocol level. - SOL_SOCKET :: 0xffff + when ODIN_OS == .Linux { + SOL_SOCKET :: 1 - SO_ACCEPTCONN :: 0x0002 - SO_BROADCAST :: 0x0020 - SO_DEBUG :: 0x0001 - SO_DONTROUTE :: 0x0010 - SO_ERROR :: 0x1007 - SO_KEEPALIVE :: 0x0008 - SO_OOBINLINE :: 0x0100 - SO_RCVBUF :: 0x1002 - SO_RCVLOWAT :: 0x1004 - SO_REUSEADDR :: 0x0004 - SO_SNDBUF :: 0x1001 - SO_SNDLOWAT :: 0x1003 - SO_TYPE :: 0x1008 + SO_ACCEPTCONN :: 30 + SO_BROADCAST :: 6 + SO_DEBUG :: 1 + SO_DONTROUTE :: 5 + SO_ERROR :: 4 + SO_KEEPALIVE :: 9 + SO_OOBINLINE :: 10 + SO_RCVBUF :: 8 + SO_RCVLOWAT :: 18 + SO_REUSEADDR :: 2 + SO_SNDBUF :: 7 + SO_SNDLOWAT :: 19 + SO_TYPE :: 3 + SO_LINGER :: 13 - when ODIN_OS == .Darwin { - SO_LINGER :: 0x1080 - SO_RCVTIMEO :: 0x1006 - SO_SNDTIMEO :: 0x1005 - } else when ODIN_OS == .FreeBSD { - SO_LINGER :: 0x0080 - SO_RCVTIMEO :: 0x1006 - SO_SNDTIMEO :: 0x1005 - } else when ODIN_OS == .NetBSD { - SO_LINGER :: 0x0080 - SO_RCVTIMEO :: 0x100c - SO_SNDTIMEO :: 0x100b - } else when ODIN_OS == .OpenBSD { - SO_LINGER :: 0x0080 - SO_RCVTIMEO :: 0x1006 - SO_SNDTIMEO :: 0x1005 + SO_RCVTIMEO :: 66 + SO_SNDTIMEO :: 67 + } else { + SOL_SOCKET :: 0xffff + + SO_ACCEPTCONN :: 0x0002 + SO_BROADCAST :: 0x0020 + SO_DEBUG :: 0x0001 + SO_DONTROUTE :: 0x0010 + SO_ERROR :: 0x1007 + SO_KEEPALIVE :: 0x0008 + SO_OOBINLINE :: 0x0100 + SO_RCVBUF :: 0x1002 + SO_RCVLOWAT :: 0x1004 + SO_REUSEADDR :: 0x0004 + SO_SNDBUF :: 0x1001 + SO_SNDLOWAT :: 0x1003 + SO_TYPE :: 0x1008 + + when ODIN_OS == .Darwin { + SO_LINGER :: 0x1080 + SO_RCVTIMEO :: 0x1006 + SO_SNDTIMEO :: 0x1005 + } else when ODIN_OS == .FreeBSD { + SO_LINGER :: 0x0080 + SO_RCVTIMEO :: 0x1006 + SO_SNDTIMEO :: 0x1005 + } else when ODIN_OS == .NetBSD { + SO_LINGER :: 0x0080 + SO_RCVTIMEO :: 0x100c + SO_SNDTIMEO :: 0x100b + } else when ODIN_OS == .OpenBSD { + SO_LINGER :: 0x0080 + SO_RCVTIMEO :: 0x1006 + SO_SNDTIMEO :: 0x1005 + } } // The maximum backlog queue length for listen(). SOMAXCONN :: 128 - MSG_CTRUNC :: 0x20 - MSG_DONTROUTE :: 0x4 - MSG_EOR :: 0x8 - MSG_OOB :: 0x1 - MSG_PEEK :: 0x2 - MSG_TRUNC :: 0x10 - MSG_WAITALL :: 0x40 + when ODIN_OS == .Linux { + MSG_CTRUNC :: 0x008 + MSG_DONTROUTE :: 0x004 + MSG_EOR :: 0x080 + MSG_OOB :: 0x001 + MSG_PEEK :: 0x002 + MSG_TRUNC :: 0x020 + MSG_WAITALL :: 0x100 + MSG_NOSIGNAL :: 0x4000 + } else { + MSG_CTRUNC :: 0x20 + MSG_DONTROUTE :: 0x4 + MSG_EOR :: 0x8 + MSG_OOB :: 0x1 + MSG_PEEK :: 0x2 + MSG_TRUNC :: 0x10 + MSG_WAITALL :: 0x40 - when ODIN_OS == .Darwin { - MSG_NOSIGNAL :: 0x80000 - } else when ODIN_OS == .FreeBSD { - MSG_NOSIGNAL :: 0x00020000 - } else when ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { - MSG_NOSIGNAL :: 0x0400 + when ODIN_OS == .Darwin { + MSG_NOSIGNAL :: 0x80000 + } else when ODIN_OS == .FreeBSD { + MSG_NOSIGNAL :: 0x00020000 + } else when ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + MSG_NOSIGNAL :: 0x0400 + } } AF_INET :: 2 @@ -483,6 +552,8 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS AF_INET6 :: 28 } else when ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { AF_INET6 :: 24 + } else when ODIN_OS == .Linux { + AF_INET6 :: 10 } SHUT_RD :: 0 diff --git a/core/sys/posix/sys_time.odin b/core/sys/posix/sys_time.odin index 093fdd688..fd2e58121 100644 --- a/core/sys/posix/sys_time.odin +++ b/core/sys/posix/sys_time.odin @@ -66,7 +66,7 @@ when ODIN_OS == .NetBSD { @(private) LUTIMES :: "utimes" } -when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { itimerval :: struct { it_interval: timeval, /* [PSX] timer interval */ diff --git a/core/sys/posix/sys_times.odin b/core/sys/posix/sys_times.odin index 685ced515..d38f3efc4 100644 --- a/core/sys/posix/sys_times.odin +++ b/core/sys/posix/sys_times.odin @@ -24,7 +24,7 @@ when ODIN_OS == .NetBSD { @(private) LTIMES :: "times" } -when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { tms :: struct { tms_utime: clock_t, /* [PSX] user CPU time */ diff --git a/core/sys/posix/sys_uio.odin b/core/sys/posix/sys_uio.odin index 01664e576..6755fe2ae 100644 --- a/core/sys/posix/sys_uio.odin +++ b/core/sys/posix/sys_uio.odin @@ -30,7 +30,7 @@ foreign libc { writev :: proc(fildes: FD, iov: [^]iovec, iovcnt: c.int) -> c.ssize_t --- } -when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { iovec :: struct { iov_base: rawptr, /* [PSX] base address of I/O memory region */ diff --git a/core/sys/posix/sys_un.odin b/core/sys/posix/sys_un.odin index 146882051..15eb7b5fc 100644 --- a/core/sys/posix/sys_un.odin +++ b/core/sys/posix/sys_un.odin @@ -12,6 +12,13 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS sun_path: [104]c.char, /* [PSX] socket pathname */ } +} else when ODIN_OS == .Linux { + + sockaddr_un :: struct { + sun_family: sa_family_t, /* [PSX] address family */ + sun_path: [108]c.char, /* [PSX] socket pathname */ + } + } else { #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/sys_utsname.odin b/core/sys/posix/sys_utsname.odin index 803f40ffd..4786eb4fd 100644 --- a/core/sys/posix/sys_utsname.odin +++ b/core/sys/posix/sys_utsname.odin @@ -37,10 +37,15 @@ foreign lib { uname :: proc(uname: ^utsname) -> c.int --- } -when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { - @(private) - _SYS_NAMELEN :: 256 + when ODIN_OS == .Linux { + @(private) + _SYS_NAMELEN :: 65 + } else { + @(private) + _SYS_NAMELEN :: 256 + } utsname :: struct { sysname: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] name of OS */ diff --git a/core/sys/posix/sys_wait.odin b/core/sys/posix/sys_wait.odin index 8421c8bfd..e0e2ae21b 100644 --- a/core/sys/posix/sys_wait.odin +++ b/core/sys/posix/sys_wait.odin @@ -124,11 +124,11 @@ WIFCONTINUED :: #force_inline proc "contextless" (x: c.int) -> bool { idtype_t :: enum c.int { // Wait for any children and `id` is ignored. - P_ALL, + P_ALL = _P_ALL, // Wait for any child wiith a process group ID equal to `id`. - P_PID, + P_PID = _P_PID, // Wait for any child with a process group ID equal to `id`. - P_PGID, + P_PGID = _P_PGID, } Wait_Flag_Bits :: enum c.int { @@ -166,6 +166,10 @@ when ODIN_OS == .Darwin { WNOWAIT :: 0x00000020 WSTOPPED :: 0x00000008 + _P_ALL :: 0 + _P_PID :: 1 + _P_PGID :: 2 + @(private) _WSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { return x & 0o177 @@ -221,6 +225,10 @@ when ODIN_OS == .Darwin { WNOWAIT :: 8 WSTOPPED :: 2 + _P_ALL :: 7 + _P_PID :: 0 + _P_PGID :: 2 + @(private) _WSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { return x & 0o177 @@ -275,6 +283,10 @@ when ODIN_OS == .Darwin { WNOWAIT :: 0x00010000 WSTOPPED :: 0x00000002 + _P_ALL :: 0 + _P_PID :: 1 + _P_PGID :: 2 + @(private) _WSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { return x & 0o177 @@ -330,6 +342,10 @@ when ODIN_OS == .Darwin { WNOWAIT :: 0x00010000 WSTOPPED :: 0x00000002 + _P_ALL :: 0 + _P_PID :: 2 + _P_PGID :: 1 + @(private) _WSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { return x & 0o177 diff --git a/core/sys/posix/ulimit.odin b/core/sys/posix/ulimit.odin index 067b83271..782756f6e 100644 --- a/core/sys/posix/ulimit.odin +++ b/core/sys/posix/ulimit.odin @@ -31,7 +31,7 @@ Ulimit_Cmd :: enum c.int { SETFSIZE = UL_SETFSIZE, } -when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { UL_GETFSIZE :: 1 UL_SETFSIZE :: 2 diff --git a/core/sys/posix/unistd.odin b/core/sys/posix/unistd.odin index 15dbb576f..5b428ef6f 100644 --- a/core/sys/posix/unistd.odin +++ b/core/sys/posix/unistd.odin @@ -1181,20 +1181,20 @@ when ODIN_OS == .Darwin { F_TLOCK :: 2 F_ULOCK :: 0 - _CS_PATH :: 1 - _CS_POSIX_V6_ILP32_OFF32_CFLAGS :: 2 - _CS_POSIX_V6_ILP32_OFF32_LDFLAGS :: 3 - _CS_POSIX_V6_ILP32_OFF32_LIBS :: 4 - _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS :: 5 - _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS :: 6 - _CS_POSIX_V6_ILP32_OFFBIG_LIBS :: 7 - _CS_POSIX_V6_LP64_OFF64_CFLAGS :: 8 - _CS_POSIX_V6_LP64_OFF64_LDFLAGS :: 9 - _CS_POSIX_V6_LP64_OFF64_LIBS :: 10 - _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS :: 11 - _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS :: 12 - _CS_POSIX_V6_LPBIG_OFFBIG_LIBS :: 13 - _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS :: 14 + _CS_PATH :: 1 + _CS_POSIX_V6_ILP32_OFF32_CFLAGS :: 2 + _CS_POSIX_V6_ILP32_OFF32_LDFLAGS :: 3 + _CS_POSIX_V6_ILP32_OFF32_LIBS :: 4 + _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS :: 5 + _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS :: 6 + _CS_POSIX_V6_ILP32_OFFBIG_LIBS :: 7 + _CS_POSIX_V6_LP64_OFF64_CFLAGS :: 8 + _CS_POSIX_V6_LP64_OFF64_LDFLAGS :: 9 + _CS_POSIX_V6_LP64_OFF64_LIBS :: 10 + _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS :: 11 + _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS :: 12 + _CS_POSIX_V6_LPBIG_OFFBIG_LIBS :: 13 + _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS :: 14 _PC_LINK_MAX :: 1 _PC_MAX_CANON :: 2 @@ -1362,20 +1362,20 @@ when ODIN_OS == .Darwin { F_TLOCK :: 2 F_ULOCK :: 0 - _CS_PATH :: 1 - _CS_POSIX_V6_ILP32_OFF32_CFLAGS :: 2 - _CS_POSIX_V6_ILP32_OFF32_LDFLAGS :: 3 - _CS_POSIX_V6_ILP32_OFF32_LIBS :: 4 - _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS :: 5 - _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS :: 6 - _CS_POSIX_V6_ILP32_OFFBIG_LIBS :: 7 - _CS_POSIX_V6_LP64_OFF64_CFLAGS :: 8 - _CS_POSIX_V6_LP64_OFF64_LDFLAGS :: 9 - _CS_POSIX_V6_LP64_OFF64_LIBS :: 10 - _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS :: 11 - _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS :: 12 - _CS_POSIX_V6_LPBIG_OFFBIG_LIBS :: 13 - _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS :: 14 + _CS_PATH :: 1 + _CS_POSIX_V6_ILP32_OFF32_CFLAGS :: 2 + _CS_POSIX_V6_ILP32_OFF32_LDFLAGS :: 3 + _CS_POSIX_V6_ILP32_OFF32_LIBS :: 4 + _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS :: 5 + _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS :: 6 + _CS_POSIX_V6_ILP32_OFFBIG_LIBS :: 7 + _CS_POSIX_V6_LP64_OFF64_CFLAGS :: 8 + _CS_POSIX_V6_LP64_OFF64_LDFLAGS :: 9 + _CS_POSIX_V6_LP64_OFF64_LIBS :: 10 + _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS :: 11 + _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS :: 12 + _CS_POSIX_V6_LPBIG_OFFBIG_LIBS :: 13 + _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS :: 14 _PC_LINK_MAX :: 1 _PC_MAX_CANON :: 2 @@ -1543,20 +1543,20 @@ when ODIN_OS == .Darwin { F_TLOCK :: 2 F_ULOCK :: 0 - _CS_PATH :: 1 - _CS_POSIX_V6_ILP32_OFF32_CFLAGS :: 2 - _CS_POSIX_V6_ILP32_OFF32_LDFLAGS :: 3 - _CS_POSIX_V6_ILP32_OFF32_LIBS :: 4 - _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS :: 5 - _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS :: 6 - _CS_POSIX_V6_ILP32_OFFBIG_LIBS :: 7 - _CS_POSIX_V6_LP64_OFF64_CFLAGS :: 8 - _CS_POSIX_V6_LP64_OFF64_LDFLAGS :: 9 - _CS_POSIX_V6_LP64_OFF64_LIBS :: 10 - _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS :: 11 - _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS :: 12 - _CS_POSIX_V6_LPBIG_OFFBIG_LIBS :: 13 - _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS :: 14 + _CS_PATH :: 1 + _CS_POSIX_V6_ILP32_OFF32_CFLAGS :: 2 + _CS_POSIX_V6_ILP32_OFF32_LDFLAGS :: 3 + _CS_POSIX_V6_ILP32_OFF32_LIBS :: 4 + _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS :: 5 + _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS :: 6 + _CS_POSIX_V6_ILP32_OFFBIG_LIBS :: 7 + _CS_POSIX_V6_LP64_OFF64_CFLAGS :: 8 + _CS_POSIX_V6_LP64_OFF64_LDFLAGS :: 9 + _CS_POSIX_V6_LP64_OFF64_LIBS :: 10 + _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS :: 11 + _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS :: 12 + _CS_POSIX_V6_LPBIG_OFFBIG_LIBS :: 13 + _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS :: 14 _PC_LINK_MAX :: 1 _PC_MAX_CANON :: 2 @@ -1655,7 +1655,6 @@ when ODIN_OS == .Darwin { _SC_TTY_NAME_MAX :: 68 _SC_HOST_NAME_MAX :: 69 - _SC_PASS_MAX :: 70 _SC_REGEXP :: 71 _SC_SHELL :: 72 _SC_SYMLOOP_MAX :: 73 @@ -1729,20 +1728,20 @@ when ODIN_OS == .Darwin { F_TLOCK :: 2 F_ULOCK :: 0 - _CS_PATH :: 1 - _CS_POSIX_V6_ILP32_OFF32_CFLAGS :: 2 - _CS_POSIX_V6_ILP32_OFF32_LDFLAGS :: 3 - _CS_POSIX_V6_ILP32_OFF32_LIBS :: 4 - _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS :: 5 - _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS :: 6 - _CS_POSIX_V6_ILP32_OFFBIG_LIBS :: 7 - _CS_POSIX_V6_LP64_OFF64_CFLAGS :: 8 - _CS_POSIX_V6_LP64_OFF64_LDFLAGS :: 9 - _CS_POSIX_V6_LP64_OFF64_LIBS :: 10 - _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS :: 11 - _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS :: 12 - _CS_POSIX_V6_LPBIG_OFFBIG_LIBS :: 13 - _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS :: 14 + _CS_PATH :: 1 + _CS_POSIX_V6_ILP32_OFF32_CFLAGS :: 2 + _CS_POSIX_V6_ILP32_OFF32_LDFLAGS :: 3 + _CS_POSIX_V6_ILP32_OFF32_LIBS :: 4 + _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS :: 5 + _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS :: 6 + _CS_POSIX_V6_ILP32_OFFBIG_LIBS :: 7 + _CS_POSIX_V6_LP64_OFF64_CFLAGS :: 8 + _CS_POSIX_V6_LP64_OFF64_LDFLAGS :: 9 + _CS_POSIX_V6_LP64_OFF64_LIBS :: 10 + _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS :: 11 + _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS :: 12 + _CS_POSIX_V6_LPBIG_OFFBIG_LIBS :: 13 + _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS :: 14 _PC_LINK_MAX :: 1 _PC_MAX_CANON :: 2 @@ -1911,6 +1910,193 @@ when ODIN_OS == .Darwin { _POSIX_VDISABLE :: '\377' +} else when ODIN_OS == .Linux { + + _F_OK :: 0 + X_OK :: 1 + W_OK :: 2 + R_OK :: 4 + + F_LOCK :: 1 + F_TEST :: 3 + F_TLOCK :: 2 + F_ULOCK :: 0 + + _CS_PATH :: 1 + _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS :: 2 + + _CS_POSIX_V6_ILP32_OFF32_CFLAGS :: 1116 + _CS_POSIX_V6_ILP32_OFF32_LDFLAGS :: 1117 + _CS_POSIX_V6_ILP32_OFF32_LIBS :: 1118 + _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS :: 1120 + _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS :: 1121 + _CS_POSIX_V6_ILP32_OFFBIG_LIBS :: 1122 + _CS_POSIX_V6_LP64_OFF64_CFLAGS :: 1124 + _CS_POSIX_V6_LP64_OFF64_LDFLAGS :: 1125 + _CS_POSIX_V6_LP64_OFF64_LIBS :: 1126 + _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS :: 1128 + _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS :: 1129 + _CS_POSIX_V6_LPBIG_OFFBIG_LIBS :: 1130 + + _PC_LINK_MAX :: 1 + _PC_MAX_CANON :: 2 + _PC_MAX_INPUT :: 3 + _PC_NAME_MAX :: 4 + _PC_PATH_MAX :: 5 + _PC_PIPE_BUF :: 6 + _PC_CHOWN_RESTRICTED :: 7 + _PC_NO_TRUNC :: 8 + _PC_VDISABLE :: 9 + _PC_SYNC_IO :: 10 + _PC_ASYNC_IO :: 11 + _PC_PRIO_IO :: 12 + _PC_FILESIZEBITS :: 14 + _PC_REC_INCR_XFER_SIZE :: 15 + _PC_REC_MAX_XFER_SIZE :: 16 + _PC_REC_MIN_XFER_SIZE :: 17 + _PC_REC_XFER_ALIGN :: 18 + _PC_ALLOC_SIZE_MIN :: 19 + _PC_SYMLINK_MAX :: 20 + _PC_2_SYMLINK :: 21 + + _SC_ARG_MAX :: 1 + _SC_CHILD_MAX :: 2 + _SC_CLK_TCK :: 3 + _SC_NGROUPS_MAX :: 4 + _SC_OPEN_MAX :: 5 + _SC_STREAM_MAX :: 6 + _SC_TZNAME_MAX :: 7 + _SC_JOB_CONTROL :: 8 + _SC_SAVED_IDS :: 9 + _SC_REALTIME_SIGNALS :: 10 + _SC_PRIORITY_SCHEDULING :: 11 + _SC_TIMERS :: 12 + _SC_ASYNCHRONOUS_IO :: 13 + _SC_PRIORITIZED_IO :: 14 + _SC_SYNCHRONIZED_IO :: 15 + _SC_FSYNC :: 16 + _SC_MAPPED_FILES :: 17 + _SC_MEMLOCK :: 18 + _SC_MEMLOCK_RANGE :: 19 + _SC_MEMORY_PROTECTION :: 20 + _SC_MESSAGE_PASSING :: 21 + _SC_SEMAPHORES :: 22 + _SC_SHARED_MEMORY_OBJECTS :: 23 + _SC_AIO_LISTIO_MAX :: 24 + _SC_AIO_MAX :: 25 + _SC_AIO_PRIO_DELTA_MAX :: 26 + _SC_DELAYTIMER_MAX :: 27 + _SC_MQ_OPEN_MAX :: 28 + _SC_MQ_PRIO_MAX :: 29 + _SC_VERSION :: 30 + _SC_PAGESIZE :: 31 + _SC_PAGE_SIZE :: _SC_PAGESIZE + _SC_RTSIG_MAX :: 32 + _SC_SEM_NSEMS_MAX :: 33 + _SC_SEM_VALUE_MAX :: 34 + _SC_SIGQUEUE_MAX :: 35 + _SC_TIMER_MAX :: 36 + _SC_BC_BASE_MAX :: 37 + _SC_BC_DIM_MAX :: 38 + _SC_BC_SCALE_MAX :: 39 + _SC_BC_STRING_MAX :: 40 + _SC_COLL_WEIGHTS_MAX :: 41 + _SC_EXPR_NEST_MAX :: 43 + _SC_LINE_MAX :: 44 + _SC_RE_DUP_MAX :: 45 + _SC_2_VERSION :: 47 + _SC_2_C_BIND :: 48 + _SC_2_C_DEV :: 49 + _SC_2_FORT_DEV :: 50 + _SC_2_FORT_RUN :: 51 + _SC_2_SW_DEV :: 52 + _SC_2_LOCALEDEF :: 53 + + _SC_IOV_MAX :: 62 + _SC_THREADS :: 69 + _SC_THREAD_SAFE_FUNCTIONS :: 70 + _SC_GETGR_R_SIZE_MAX :: 71 + _SC_GETPW_R_SIZE_MAX :: 72 + _SC_LOGIN_NAME_MAX :: 73 + _SC_TTY_NAME_MAX :: 74 + _SC_THREAD_DESTRUCTOR_ITERATIONS :: 75 + _SC_THREAD_KEYS_MAX :: 76 + _SC_THREAD_STACK_MIN :: 77 + _SC_THREAD_THREADS_MAX :: 78 + _SC_THREAD_ATTR_STACKADDR :: 79 + _SC_THREAD_ATTR_STACKSIZE :: 80 + _SC_THREAD_PRIORITY_SCHEDULING :: 81 + _SC_THREAD_PRIO_INHERIT :: 82 + _SC_THREAD_PRIO_PROTECT :: 83 + _SC_THREAD_PROCESS_SHARED :: 84 + _SC_NPROCESSORS_CONF :: 85 + _SC_NPROCESSORS_ONLN :: 86 + _SC_PHYS_PAGES :: 87 + _SC_AVPHYS_PAGES :: 88 + _SC_ATEXIT_MAX :: 89 + _SC_PASS_MAX :: 90 + _SC_XOPEN_VERSION :: 91 + _SC_XOPEN_UNIX :: 92 + _SC_XOPEN_CRYPT :: 93 + _SC_XOPEN_ENH_I18N :: 94 + _SC_XOPEN_SHM :: 95 + _SC_2_CHAR_TERM :: 96 + _SC_2_UPE :: 97 + + _SC_XOPEN_LEGACY :: 129 + _SC_XOPEN_REALTIME :: 130 + _SC_XOPEN_REALTIME_THREADS :: 131 + _SC_ADVISORY_INFO :: 132 + _SC_BARRIERS :: 133 + _SC_CLOCK_SELECTION :: 137 + _SC_CPUTIME :: 138 + _SC_THREAD_CPUTIME :: 139 + _SC_MONOTONIC_CLOCK :: 149 + _SC_READER_WRITER_LOCKS :: 153 + _SC_SPIN_LOCKS :: 154 + _SC_REGEXP :: 155 + _SC_SHELL :: 157 + _SC_SPAWN :: 159 + _SC_SPORADIC_SERVER :: 160 + _SC_THREAD_SPORADIC_SERVER :: 161 + _SC_TIMEOUTS :: 164 + _SC_TYPED_MEMORY_OBJECTS :: 165 + _SC_2_PBS :: 168 + _SC_2_PBS_ACCOUNTING :: 169 + _SC_2_PBS_MESSAGE :: 171 + _SC_2_PBS_TRACK :: 172 + _SC_SYMLOOP_MAX :: 173 + _SC_2_PBS_CHECKPOINT :: 174 + _SC_V6_ILP32_OFF32 :: 175 + _SC_V6_ILP32_OFFBIG :: 176 + _SC_V6_LP64_OFF64 :: 177 + _SC_V6_LPBIG_OFFBIG :: 178 + _SC_HOST_NAME_MAX :: 179 + _SC_TRACE :: 180 + _SC_TRACE_EVENT_FILTER :: 181 + _SC_TRACE_INHERIT :: 182 + _SC_TRACE_LOG :: 183 + + _SC_IPV6 :: 234 + _SC_RAW_SOCKETS :: 235 + _SC_V7_ILP32_OFF32 :: 236 + _SC_V7_ILP32_OFFBIG :: 237 + _SC_V7_LP64_OFF64 :: 238 + _SC_V7_LPBIG_OFFBIG :: 239 + _SC_SS_REPL_MAX :: 240 + _SC_TRACE_EVENT_NAME_MAX :: 241 + _SC_TRACE_NAME_MAX :: 242 + _SC_TRACE_SYS_MAX :: 243 + _SC_TRACE_USER_EVENT_MAX :: 244 + _SC_XOPEN_STREAMS :: 245 + _SC_THREAD_ROBUST_PRIO_INHERIT :: 246 + _SC_THREAD_ROBUST_PRIO_PROTECT :: 247 + + // NOTE: Not implemented. + _SC_XOPEN_UUCP :: 0 + // NOTE: Not implemented. + _POSIX_VDISABLE :: 0 + } else { #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/utime.odin b/core/sys/posix/utime.odin index 591a6db06..1207cb402 100644 --- a/core/sys/posix/utime.odin +++ b/core/sys/posix/utime.odin @@ -24,7 +24,7 @@ when ODIN_OS == .NetBSD { @(private) LUTIME :: "utime" } -when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { utimbuf :: struct { actime: time_t, /* [PSX] access time (seconds since epoch) */ diff --git a/core/sys/posix/wordexp.odin b/core/sys/posix/wordexp.odin index d730db0f7..6bb362625 100644 --- a/core/sys/posix/wordexp.odin +++ b/core/sys/posix/wordexp.odin @@ -102,6 +102,27 @@ when ODIN_OS == .Darwin { WRDE_NOSPACE :: 4 WRDE_SYNTAX :: 6 +} else when ODIN_OS == .Linux { + + wordexp_t :: struct { + we_wordc: c.size_t, /* [PSX] count of words matched by words */ + we_wordv: [^]cstring, /* [PSX] pointer to list of expanded words */ + we_offs: c.size_t, /* [PSX] slots to reserve at the beginning of we_wordv */ + } + + WRDE_DOOFFS :: 1 << 0 /* Insert PWORDEXP->we_offs NULLs. */ + WRDE_APPEND :: 1 << 1 /* Append to results of a previous call. */ + WRDE_NOCMD :: 1 << 2 /* Don't do command substitution. */ + WRDE_REUSE :: 1 << 3 /* Reuse storage in PWORDEXP. */ + WRDE_SHOWERR :: 1 << 4 /* Don't redirect stderr to /dev/null. */ + WRDE_UNDEF :: 1 << 5 /* Error for expanding undefined variables. */ + + WRDE_NOSPACE :: 1 + WRDE_BADCHAR :: 2 + WRDE_BADVAL :: 3 + WRDE_CMDSUB :: 4 + WRDE_SYNTAX :: 5 + } else { #panic("posix is unimplemented for the current target") } diff --git a/core/sys/unix/pthread_darwin.odin b/core/sys/unix/pthread_darwin.odin index 378fa9309..eb2cc4c9f 100644 --- a/core/sys/unix/pthread_darwin.odin +++ b/core/sys/unix/pthread_darwin.odin @@ -1,4 +1,4 @@ -//+build darwin +#+build darwin package unix import "core:c" diff --git a/core/sys/unix/pthread_freebsd.odin b/core/sys/unix/pthread_freebsd.odin index 5f4dac289..38fe7db55 100644 --- a/core/sys/unix/pthread_freebsd.odin +++ b/core/sys/unix/pthread_freebsd.odin @@ -1,4 +1,4 @@ -//+build freebsd +#+build freebsd package unix import "core:c" diff --git a/core/sys/unix/pthread_linux.odin b/core/sys/unix/pthread_linux.odin index f4ded7464..d67add24b 100644 --- a/core/sys/unix/pthread_linux.odin +++ b/core/sys/unix/pthread_linux.odin @@ -1,4 +1,4 @@ -//+build linux +#+build linux package unix import "core:c" diff --git a/core/sys/unix/pthread_openbsd.odin b/core/sys/unix/pthread_openbsd.odin index 855e7d99c..2c6d9e598 100644 --- a/core/sys/unix/pthread_openbsd.odin +++ b/core/sys/unix/pthread_openbsd.odin @@ -1,4 +1,4 @@ -//+build openbsd +#+build openbsd package unix import "core:c" diff --git a/core/sys/unix/pthread_unix.odin b/core/sys/unix/pthread_unix.odin index 26a21e629..43c4866ed 100644 --- a/core/sys/unix/pthread_unix.odin +++ b/core/sys/unix/pthread_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd, openbsd, netbsd, haiku +#+build linux, darwin, freebsd, openbsd, netbsd, haiku package unix foreign import "system:pthread" diff --git a/core/sys/unix/sysctl_darwin.odin b/core/sys/unix/sysctl_darwin.odin index 92222bdfe..32dd720b0 100644 --- a/core/sys/unix/sysctl_darwin.odin +++ b/core/sys/unix/sysctl_darwin.odin @@ -1,4 +1,4 @@ -//+build darwin +#+build darwin package unix import "base:intrinsics" diff --git a/core/sys/unix/sysctl_freebsd.odin b/core/sys/unix/sysctl_freebsd.odin index 8ca40ef1b..f5fee6c6c 100644 --- a/core/sys/unix/sysctl_freebsd.odin +++ b/core/sys/unix/sysctl_freebsd.odin @@ -1,4 +1,4 @@ -//+build freebsd +#+build freebsd package unix import "base:intrinsics" diff --git a/core/sys/unix/sysctl_openbsd.odin b/core/sys/unix/sysctl_openbsd.odin index b93e8f9bd..49c9b6336 100644 --- a/core/sys/unix/sysctl_openbsd.odin +++ b/core/sys/unix/sysctl_openbsd.odin @@ -1,4 +1,4 @@ -//+build openbsd +#+build openbsd package unix import "core:c" diff --git a/core/sys/valgrind/callgrind.odin b/core/sys/valgrind/callgrind.odin index b1ba8c6e9..5cd58753a 100644 --- a/core/sys/valgrind/callgrind.odin +++ b/core/sys/valgrind/callgrind.odin @@ -1,4 +1,4 @@ -//+build amd64 +#+build amd64 package sys_valgrind import "base:intrinsics" diff --git a/core/sys/valgrind/helgrind.odin b/core/sys/valgrind/helgrind.odin index 2f0114522..3f5e7a531 100644 --- a/core/sys/valgrind/helgrind.odin +++ b/core/sys/valgrind/helgrind.odin @@ -1,4 +1,4 @@ -//+build amd64 +#+build amd64 package sys_valgrind import "base:intrinsics" diff --git a/core/sys/valgrind/memcheck.odin b/core/sys/valgrind/memcheck.odin index dfbe4c3be..bc77444be 100644 --- a/core/sys/valgrind/memcheck.odin +++ b/core/sys/valgrind/memcheck.odin @@ -1,4 +1,4 @@ -//+build amd64 +#+build amd64 package sys_valgrind import "base:intrinsics" diff --git a/core/sys/valgrind/valgrind.odin b/core/sys/valgrind/valgrind.odin index d0c46af53..b5c71664f 100644 --- a/core/sys/valgrind/valgrind.odin +++ b/core/sys/valgrind/valgrind.odin @@ -1,4 +1,4 @@ -//+build amd64 +#+build amd64 package sys_valgrind import "base:intrinsics" diff --git a/core/sys/wasm/README.md b/core/sys/wasm/README.md new file mode 100644 index 000000000..1aaeaa429 --- /dev/null +++ b/core/sys/wasm/README.md @@ -0,0 +1,15 @@ +# WASM on the Web + +This directory is for use when targeting the `js_wasm32` target and the packages that rely on it. + +The `js_wasm32` target assumes that the WASM output will be ran within a web browser rather than a standalone VM. In the VM cases, either `wasi_wasm32` or `freestanding_wasm32` should be used accordingly. + +## Example for `js_wasm32` + +```html + + + +``` diff --git a/vendor/wasm/js/dom.odin b/core/sys/wasm/js/dom.odin similarity index 74% rename from vendor/wasm/js/dom.odin rename to core/sys/wasm/js/dom.odin index 3a8bd0ac4..ffc58a9a3 100644 --- a/vendor/wasm/js/dom.odin +++ b/core/sys/wasm/js/dom.odin @@ -1,4 +1,4 @@ -//+build js wasm32, js wasm64p32 +#+build js wasm32, js wasm64p32 package wasm_js_interface foreign import dom_lib "odin_dom" @@ -8,9 +8,15 @@ foreign dom_lib { get_element_value_f64 :: proc(id: string) -> f64 --- set_element_value_f64 :: proc(id: string, value: f64) --- + get_element_key_f64 :: proc(id: string, key: string) -> f64 --- + set_element_key_f64 :: proc(id: string, key: string, value: f64) --- + set_element_value_string :: proc(id: string, value: string) --- get_element_value_string_length :: proc(id: string) -> int --- + set_element_key_string :: proc(id: string, key: string, value: string) --- + get_element_key_string_length :: proc(id: string, key: string, ) -> int --- + device_pixel_ratio :: proc() -> f64 --- window_set_scroll :: proc(x, y: f64) --- @@ -24,10 +30,21 @@ get_element_value_string :: proc "contextless" (id: string, buf: []byte) -> stri } n := _get_element_value_string(id, buf) return string(buf[:n]) +} + +get_element_key_string :: proc "contextless" (id: string, key: string, buf: []byte) -> string { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="get_element_key_string") + _get_element_key_string :: proc(id: string, key: string, buf: []byte) -> int --- + } + n := _get_element_key_string(id, key, buf) + return string(buf[:n]) } + get_element_min_max :: proc "contextless" (id: string) -> (min, max: f64) { @(default_calling_convention="contextless") foreign dom_lib { @@ -73,4 +90,4 @@ window_get_scroll :: proc "contextless" () -> (x, y: f64) { scroll: [2]f64 _window_get_scroll(&scroll) return scroll.x, scroll.y -} +} \ No newline at end of file diff --git a/vendor/wasm/js/dom_all_targets.odin b/core/sys/wasm/js/dom_all_targets.odin similarity index 98% rename from vendor/wasm/js/dom_all_targets.odin rename to core/sys/wasm/js/dom_all_targets.odin index ef629b347..171deed2f 100644 --- a/vendor/wasm/js/dom_all_targets.odin +++ b/core/sys/wasm/js/dom_all_targets.odin @@ -1,4 +1,4 @@ -//+build !js +#+build !js package wasm_js_interface import "base:runtime" diff --git a/vendor/wasm/js/events.odin b/core/sys/wasm/js/events.odin similarity index 81% rename from vendor/wasm/js/events.odin rename to core/sys/wasm/js/events.odin index 4e786e2be..905b3eba9 100644 --- a/vendor/wasm/js/events.odin +++ b/core/sys/wasm/js/events.odin @@ -1,4 +1,4 @@ -//+build js wasm32, js wasm64p32 +#+build js wasm32, js wasm64p32 package wasm_js_interface foreign import dom_lib "odin_dom" @@ -30,10 +30,12 @@ Event_Kind :: enum u32 { Wheel, Focus, + Focus_In, + Focus_Out, Submit, Blur, Change, - HashChange, + Hash_Change, Select, Animation_Start, @@ -80,6 +82,9 @@ Event_Kind :: enum u32 { Context_Menu, + Gamepad_Connected, + Gamepad_Disconnected, + Custom, } @@ -110,10 +115,12 @@ event_kind_string := [Event_Kind]string{ .Wheel = "wheel", .Focus = "focus", + .Focus_In = "focusin", + .Focus_Out = "focusout", .Submit = "submit", .Blur = "blur", .Change = "change", - .HashChange = "hashchange", + .Hash_Change = "hashchange", .Select = "select", .Animation_Start = "animationstart", @@ -160,6 +167,9 @@ event_kind_string := [Event_Kind]string{ .Context_Menu = "contextmenu", + .Gamepad_Connected = "gamepadconnected", + .Gamepad_Disconnected = "gamepaddisconnected", + .Custom = "?custom?", } @@ -176,9 +186,15 @@ Key_Location :: enum u8 { Numpad = 3, } -KEYBOARD_MAX_KEY_SIZE :: 16 +KEYBOARD_MAX_KEY_SIZE :: 16 KEYBOARD_MAX_CODE_SIZE :: 16 +GAMEPAD_MAX_ID_SIZE :: 64 +GAMEPAD_MAX_MAPPING_SIZE :: 64 + +GAMEPAD_MAX_BUTTONS :: 64 +GAMEPAD_MAX_AXES :: 16 + Event_Target_Kind :: enum u32 { Element = 0, Document = 1, @@ -199,6 +215,30 @@ Event_Option :: enum u8 { } Event_Options :: distinct bit_set[Event_Option; u8] +Gamepad_Button :: struct { + value: f64, + pressed: bool, + touched: bool, +} + +Gamepad_State :: struct { + id: string, + mapping: string, + index: int, + connected: bool, + timestamp: f64, + + button_count: int, + axis_count: int, + buttons: [GAMEPAD_MAX_BUTTONS]Gamepad_Button `fmt:"v,button_count"`, + axes: [GAMEPAD_MAX_AXES]f64 `fmt:"v,axes_count"`, + + _id_len: int `fmt:"-"`, + _mapping_len: int `fmt:"-"`, + _id_buf: [GAMEPAD_MAX_ID_SIZE]byte `fmt:"-"`, + _mapping_buf: [GAMEPAD_MAX_MAPPING_SIZE]byte `fmt:"-"`, +} + Event :: struct { kind: Event_Kind, target_kind: Event_Target_Kind, @@ -235,10 +275,10 @@ Event :: struct { repeat: bool, - _key_len: int, - _code_len: int, - _key_buf: [KEYBOARD_MAX_KEY_SIZE]byte, - _code_buf: [KEYBOARD_MAX_KEY_SIZE]byte, + _key_len: int `fmt:"-"`, + _code_len: int `fmt:"-"`, + _key_buf: [KEYBOARD_MAX_KEY_SIZE]byte `fmt:"-"`, + _code_buf: [KEYBOARD_MAX_KEY_SIZE]byte `fmt:"-"`, }, mouse: struct { @@ -256,6 +296,8 @@ Event :: struct { button: i16, buttons: bit_set[0..<16; u16], }, + + gamepad: Gamepad_State, }, @@ -332,7 +374,18 @@ remove_custom_event_listener :: proc(id: string, name: string, user_data: rawptr return _remove_event_listener(id, name, user_data, callback) } +get_gamepad_state :: proc "contextless" (index: int, s: ^Gamepad_State) -> bool { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="get_gamepad_state") + _get_gamepad_state :: proc(index: int, s: ^Gamepad_State) -> bool --- + } + if s == nil { + return false + } + return _get_gamepad_state(index, s) +} @(export, link_name="odin_dom_do_event_callback") @@ -351,9 +404,13 @@ do_event_callback :: proc(user_data: rawptr, callback: proc(e: Event)) { init_event_raw(&event) - if event.kind == .Key_Up || event.kind == .Key_Down || event.kind == .Key_Press { + #partial switch event.kind { + case .Key_Up, .Key_Down, .Key_Press: event.key.key = string(event.key._key_buf[:event.key._key_len]) event.key.code = string(event.key._code_buf[:event.key._code_len]) + case .Gamepad_Connected, .Gamepad_Disconnected: + event.gamepad.id = string(event.gamepad._id_buf[:event.gamepad._id_len]) + event.gamepad.mapping = string(event.gamepad._mapping_buf[:event.gamepad._mapping_len]) } callback(event) diff --git a/vendor/wasm/js/events_all_targets.odin b/core/sys/wasm/js/events_all_targets.odin similarity index 99% rename from vendor/wasm/js/events_all_targets.odin rename to core/sys/wasm/js/events_all_targets.odin index 19a004250..b7e01ca10 100644 --- a/vendor/wasm/js/events_all_targets.odin +++ b/core/sys/wasm/js/events_all_targets.odin @@ -1,4 +1,4 @@ -//+build !js +#+build !js package wasm_js_interface @@ -284,5 +284,4 @@ add_custom_event_listener :: proc(id: string, name: string, user_data: rawptr, c } remove_custom_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc(e: Event)) -> bool { panic("vendor:wasm/js not supported on non JS targets") -} - +} \ No newline at end of file diff --git a/vendor/wasm/js/general.odin b/core/sys/wasm/js/general.odin similarity index 88% rename from vendor/wasm/js/general.odin rename to core/sys/wasm/js/general.odin index 513c60a6f..4ed2ae298 100644 --- a/vendor/wasm/js/general.odin +++ b/core/sys/wasm/js/general.odin @@ -1,4 +1,4 @@ -//+build js wasm32, js wasm64p32 +#+build js wasm32, js wasm64p32 package wasm_js_interface foreign import "odin_env" diff --git a/vendor/wasm/js/memory_all_targets.odin b/core/sys/wasm/js/memory_all_targets.odin similarity index 96% rename from vendor/wasm/js/memory_all_targets.odin rename to core/sys/wasm/js/memory_all_targets.odin index e1de6a696..e80d13c0b 100644 --- a/vendor/wasm/js/memory_all_targets.odin +++ b/core/sys/wasm/js/memory_all_targets.odin @@ -1,4 +1,4 @@ -//+build !js +#+build !js package wasm_js_interface import "core:mem" diff --git a/vendor/wasm/js/memory_js.odin b/core/sys/wasm/js/memory_js.odin similarity index 97% rename from vendor/wasm/js/memory_js.odin rename to core/sys/wasm/js/memory_js.odin index c513cc4a1..8232cd0c9 100644 --- a/vendor/wasm/js/memory_js.odin +++ b/core/sys/wasm/js/memory_js.odin @@ -1,4 +1,4 @@ -//+build js wasm32, js wasm64p32 +#+build js wasm32, js wasm64p32 package wasm_js_interface import "core:mem" diff --git a/vendor/wasm/js/runtime.js b/core/sys/wasm/js/odin.js similarity index 89% rename from vendor/wasm/js/runtime.js rename to core/sys/wasm/js/odin.js index 74ad7568e..6f086e595 100644 --- a/vendor/wasm/js/runtime.js +++ b/core/sys/wasm/js/odin.js @@ -108,7 +108,7 @@ class WasmMemoryInterface { const bytes = this.loadBytes(ptr, Number(len)); return new TextDecoder().decode(bytes); } - + loadCstring(ptr) { const start = this.loadPtr(ptr); if (start == 0) { @@ -1259,7 +1259,7 @@ class WebGLInterface { }; -function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { +function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory, eventQueue, event_temp) { const MAX_INFO_CONSOLE_LINES = 512; let infoConsoleLines = new Array(); let currentLine = {}; @@ -1377,10 +1377,8 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { info.scrollTop = info.scrollHeight; }; - let event_temp_data = {}; - let webglContext = new WebGLInterface(wasmMemoryInterface); - + const env = {}; if (memory) { @@ -1455,9 +1453,13 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { let wmi = wasmMemoryInterface; - let e = event_temp_data.event; + if (!event_temp.data) { + return; + } - wmi.storeU32(off(4), event_temp_data.name_code); + let e = event_temp.data.event; + + wmi.storeU32(off(4), event_temp.data.name_code); if (e.target == document) { wmi.storeU32(off(4), 1); } else if (e.target == window) { @@ -1475,8 +1477,8 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { align(W); - wmi.storeI32(off(W), event_temp_data.id_ptr); - wmi.storeUint(off(W), event_temp_data.id_len); + wmi.storeI32(off(W), event_temp.data.id_ptr); + wmi.storeUint(off(W), event_temp.data.id_len); align(8); wmi.storeF64(off(8), e.timeStamp*1e-3); @@ -1518,7 +1520,7 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { } else if (e instanceof KeyboardEvent) { // Note: those strings are constructed // on the native side from buffers that - // are filled later, so skip them + // are filled later, so skip them const keyPtr = off(W*2, W); const codePtr = off(W*2, W); @@ -1531,15 +1533,49 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { wmi.storeU8(off(1), !!e.repeat); - wmi.storeI32(off(W), e.key.length) - wmi.storeI32(off(W), e.code.length) + wmi.storeInt(off(W, W), e.key.length) + wmi.storeInt(off(W, W), e.code.length) wmi.storeString(off(16, 1), e.key); wmi.storeString(off(16, 1), e.code); } else if (e.type === 'scroll') { - wmi.storeF64(off(8), window.scrollX); - wmi.storeF64(off(8), window.scrollY); + wmi.storeF64(off(8, 8), window.scrollX); + wmi.storeF64(off(8, 8), window.scrollY); } else if (e.type === 'visibilitychange') { wmi.storeU8(off(1), !document.hidden); + } else if (e instanceof GamepadEvent) { + const idPtr = off(W*2, W); + const mappingPtr = off(W*2, W); + + wmi.storeI32(off(W, W), e.gamepad.index); + wmi.storeU8(off(1), !!e.gamepad.connected); + wmi.storeF64(off(8, 8), e.gamepad.timestamp); + + wmi.storeInt(off(W, W), e.gamepad.buttons.length); + wmi.storeInt(off(W, W), e.gamepad.axes.length); + + for (let i = 0; i < 64; i++) { + if (i < e.gamepad.buttons.length) { + let b = e.gamepad.buttons[i]; + wmi.storeF64(off(8, 8), b.value); + wmi.storeU8(off(1), !!b.pressed); + wmi.storeU8(off(1), !!b.touched); + } else { + off(16, 8); + } + } + for (let i = 0; i < 16; i++) { + if (i < e.gamepad.axes.length) { + let a = e.gamepad.axes[i]; + wmi.storeF64(off(8, 8), a); + } else { + off(8, 8); + } + } + + wmi.storeInt(off(W, W), e.gamepad.id.length) + wmi.storeInt(off(W, W), e.gamepad.mapping.length) + wmi.storeString(off(64, 1), e.gamepad.id); + wmi.storeString(off(64, 1), e.gamepad.mapping); } }, @@ -1552,12 +1588,30 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { } let listener = (e) => { - const odin_ctx = wasmMemoryInterface.exports.default_context_ptr(); - event_temp_data.id_ptr = id_ptr; - event_temp_data.id_len = id_len; - event_temp_data.event = e; - event_temp_data.name_code = name_code; - wasmMemoryInterface.exports.odin_dom_do_event_callback(data, callback, odin_ctx); + let event_data = {}; + event_data.id_ptr = id_ptr; + event_data.id_len = id_len; + event_data.event = e; + event_data.name_code = name_code; + + eventQueue.push({event_data: event_data, data: data, callback: callback}); + }; + wasmMemoryInterface.listenerMap[{data: data, callback: callback}] = listener; + element.addEventListener(name, listener, !!use_capture); + return true; + }, + + add_window_event_listener: (name_ptr, name_len, name_code, data, callback, use_capture) => { + let name = wasmMemoryInterface.loadString(name_ptr, name_len); + let element = window; + let listener = (e) => { + let event_data = {}; + event_data.id_ptr = 0; + event_data.id_len = 0; + event_data.event = e; + event_data.name_code = name_code; + + eventQueue.push({event_data: event_data, data: data, callback: callback}); }; wasmMemoryInterface.listenerMap[{data: data, callback: callback}] = listener; element.addEventListener(name, listener, !!use_capture); @@ -1579,24 +1633,6 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { element.removeEventListener(name, listener); return true; }, - - - add_window_event_listener: (name_ptr, name_len, name_code, data, callback, use_capture) => { - let name = wasmMemoryInterface.loadString(name_ptr, name_len); - let element = window; - let listener = (e) => { - const odin_ctx = wasmMemoryInterface.exports.default_context_ptr(); - event_temp_data.id_ptr = 0; - event_temp_data.id_len = 0; - event_temp_data.event = e; - event_temp_data.name_code = name_code; - wasmMemoryInterface.exports.odin_dom_do_event_callback(data, callback, odin_ctx); - }; - wasmMemoryInterface.listenerMap[{data: data, callback: callback}] = listener; - element.addEventListener(name, listener, !!use_capture); - return true; - }, - remove_window_event_listener: (name_ptr, name_len, data, callback) => { let name = wasmMemoryInterface.loadString(name_ptr, name_len); let element = window; @@ -1612,18 +1648,18 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { }, event_stop_propagation: () => { - if (event_temp_data && event_temp_data.event) { - event_temp_data.event.stopPropagation(); + if (event_temp.data && event_temp.data.event) { + event_temp.data.event.stopPropagation(); } }, event_stop_immediate_propagation: () => { - if (event_temp_data && event_temp_data.event) { - event_temp_data.event.stopImmediatePropagation(); + if (event_temp.data && event_temp.data.event) { + event_temp.data.event.stopImmediatePropagation(); } }, event_prevent_default: () => { - if (event_temp_data && event_temp_data.event) { - event_temp_data.event.preventDefault(); + if (event_temp.data && event_temp.data.event) { + event_temp.data.event.preventDefault(); } }, @@ -1644,6 +1680,76 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { return false; }, + get_gamepad_state: (gamepad_id, ep) => { + let index = gamepad_id; + let gps = navigator.getGamepads(); + if (0 <= index && index < gps.length) { + let gamepad = gps[index]; + if (!gamepad) { + return false; + } + + const W = wasmMemoryInterface.intSize; + let offset = ep; + let off = (amount, alignment) => { + if (alignment === undefined) { + alignment = Math.min(amount, W); + } + if (offset % alignment != 0) { + offset += alignment - (offset%alignment); + } + let x = offset; + offset += amount; + return x; + }; + + let align = (alignment) => { + const modulo = offset & (alignment-1); + if (modulo != 0) { + offset += alignment - modulo + } + }; + + let wmi = wasmMemoryInterface; + + const idPtr = off(W*2, W); + const mappingPtr = off(W*2, W); + + wmi.storeI32(off(W), gamepad.index); + wmi.storeU8(off(1), !!gamepad.connected); + wmi.storeF64(off(8), gamepad.timestamp); + + wmi.storeInt(off(W), gamepad.buttons.length); + wmi.storeInt(off(W), gamepad.axes.length); + + for (let i = 0; i < 64; i++) { + if (i < gamepad.buttons.length) { + let b = gamepad.buttons[i]; + wmi.storeF64(off(8, 8), b.value); + wmi.storeU8(off(1), !!b.pressed); + wmi.storeU8(off(1), !!b.touched); + } else { + off(16, 8); + } + } + for (let i = 0; i < 16; i++) { + if (i < gamepad.axes.length) { + wmi.storeF64(off(8, 8), gamepad.axes[i]); + } else { + off(8, 8); + } + } + + wmi.storeInt(off(W, W), gamepad.id.length) + wmi.storeInt(off(W, W), gamepad.mapping.length) + wmi.storeString(off(64, 1), gamepad.id); + wmi.storeString(off(64, 1), gamepad.mapping); + + return true; + } + return false; + }, + get_element_value_f64: (id_ptr, id_len) => { let id = wasmMemoryInterface.loadString(id_ptr, id_len); let element = getElement(id); @@ -1696,6 +1802,55 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { } }, + get_element_key_f64: (id_ptr, id_len, key_ptr, key_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let key = wasmMemoryInterface.loadString(key_ptr, key_len); + let element = getElement(id); + return element ? element[key] : 0; + }, + get_element_key_string: (id_ptr, id_len, key_ptr, key_len, buf_ptr, buf_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let key = wasmMemoryInterface.loadString(key_ptr, key_len); + let element = getElement(id); + if (element) { + let str = element[key]; + if (buf_len > 0 && buf_ptr) { + let n = Math.min(buf_len, str.length); + str = str.substring(0, n); + this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder().encode(str)) + return n; + } + } + return 0; + }, + get_element_key_string_length: (id_ptr, id_len, key_ptr, key_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let key = wasmMemoryInterface.loadString(key_ptr, key_len); + let element = getElement(id); + if (element && element[key]) { + return element[key].length; + } + return 0; + }, + + set_element_key_f64: (id_ptr, id_len, key_ptr, key_len, value) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let key = wasmMemoryInterface.loadString(key_ptr, key_len); + let element = getElement(id); + if (element) { + element[key] = value; + } + }, + set_element_key_string: (id_ptr, id_len, key_ptr, key_len, value_ptr, value_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let key = wasmMemoryInterface.loadString(key_ptr, key_len); + let value = wasmMemoryInterface.loadString(value_ptr, value_len); + let element = getElement(id); + if (element) { + element[key] = value; + } + }, + get_bounding_client_rect: (rect_ptr, id_ptr, id_len) => { let id = wasmMemoryInterface.loadString(id_ptr, id_len); @@ -1750,7 +1905,10 @@ async function runWasm(wasmPath, consoleElement, extraForeignImports, wasmMemory } wasmMemoryInterface.setIntSize(intSize); - let imports = odinSetupDefaultImports(wasmMemoryInterface, consoleElement, wasmMemoryInterface.memory); + let eventQueue = new Array(); + let event_temp = {}; + + let imports = odinSetupDefaultImports(wasmMemoryInterface, consoleElement, wasmMemoryInterface.memory, eventQueue, event_temp); let exports = {}; if (extraForeignImports !== undefined) { @@ -1775,14 +1933,14 @@ async function runWasm(wasmPath, consoleElement, extraForeignImports, wasmMemory exports._start(); - // Define a `@export step :: proc(dt: f32) -> (keep_going: bool) {` + // Define a `@export step :: proc(delta_time: f64) -> (keep_going: bool) {` // in your app and it will get called every frame. // return `false` to stop the execution of the module. if (exports.step) { const odin_ctx = exports.default_context_ptr(); let prevTimeStamp = undefined; - const step = (currTimeStamp) => { + function step(currTimeStamp) { if (prevTimeStamp == undefined) { prevTimeStamp = currTimeStamp; } @@ -1790,13 +1948,20 @@ async function runWasm(wasmPath, consoleElement, extraForeignImports, wasmMemory const dt = (currTimeStamp - prevTimeStamp)*0.001; prevTimeStamp = currTimeStamp; + while (eventQueue.length > 0) { + let e = eventQueue.shift() + event_temp.data = e.event_data; + exports.odin_dom_do_event_callback(e.data, e.callback, odin_ctx); + } + event_temp.data = null; + if (!exports.step(dt, odin_ctx)) { exports._end(); return; } window.requestAnimationFrame(step); - }; + } window.requestAnimationFrame(step); } else { diff --git a/core/sys/wasm/wasi/wasi_api.odin b/core/sys/wasm/wasi/wasi_api.odin index 38d95e754..8d50f1690 100644 --- a/core/sys/wasm/wasi/wasi_api.odin +++ b/core/sys/wasm/wasi/wasi_api.odin @@ -1,4 +1,4 @@ -//+build wasm32 +#+build wasm32 package sys_wasi foreign import wasi "wasi_snapshot_preview1" diff --git a/core/sys/windows/advapi32.odin b/core/sys/windows/advapi32.odin index 1e34f1fd6..f834511d4 100644 --- a/core/sys/windows/advapi32.odin +++ b/core/sys/windows/advapi32.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import advapi32 "system:Advapi32.lib" diff --git a/core/sys/windows/bcrypt.odin b/core/sys/windows/bcrypt.odin index d891aa92b..f15f1e305 100644 --- a/core/sys/windows/bcrypt.odin +++ b/core/sys/windows/bcrypt.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import bcrypt "system:Bcrypt.lib" diff --git a/core/sys/windows/bluetooth.odin b/core/sys/windows/bluetooth.odin index 7bfb7ea96..86c66b9a1 100644 --- a/core/sys/windows/bluetooth.odin +++ b/core/sys/windows/bluetooth.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import "system:bthprops.lib" diff --git a/core/sys/windows/codepage.odin b/core/sys/windows/codepage.odin index 90040f1ee..527289f03 100644 --- a/core/sys/windows/codepage.odin +++ b/core/sys/windows/codepage.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows // https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers diff --git a/core/sys/windows/comctl32.odin b/core/sys/windows/comctl32.odin index 9c4404a9d..477800413 100644 --- a/core/sys/windows/comctl32.odin +++ b/core/sys/windows/comctl32.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import "system:Comctl32.lib" diff --git a/core/sys/windows/comdlg32.odin b/core/sys/windows/comdlg32.odin index 30d9b169c..a9800b47a 100644 --- a/core/sys/windows/comdlg32.odin +++ b/core/sys/windows/comdlg32.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import "system:Comdlg32.lib" diff --git a/core/sys/windows/dbghelp.odin b/core/sys/windows/dbghelp.odin index cb5458248..336992b4a 100644 --- a/core/sys/windows/dbghelp.odin +++ b/core/sys/windows/dbghelp.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import "system:Dbghelp.lib" diff --git a/core/sys/windows/dnsapi.odin b/core/sys/windows/dnsapi.odin index dd2d1acee..4fd9f7a19 100644 --- a/core/sys/windows/dnsapi.odin +++ b/core/sys/windows/dnsapi.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import "system:Dnsapi.lib" diff --git a/core/sys/windows/dwmapi.odin b/core/sys/windows/dwmapi.odin index d0ffc6b46..11a46f53a 100644 --- a/core/sys/windows/dwmapi.odin +++ b/core/sys/windows/dwmapi.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import dwmapi "system:Dwmapi.lib" diff --git a/core/sys/windows/gdi32.odin b/core/sys/windows/gdi32.odin index 5cbafddba..1d7a93d85 100644 --- a/core/sys/windows/gdi32.odin +++ b/core/sys/windows/gdi32.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows import "core:math/fixed" diff --git a/core/sys/windows/hidpi.odin b/core/sys/windows/hidpi.odin index bea03694e..5e9787527 100644 --- a/core/sys/windows/hidpi.odin +++ b/core/sys/windows/hidpi.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows import "core:c" diff --git a/core/sys/windows/hidusage.odin b/core/sys/windows/hidusage.odin index a32aa7b9f..eb2a85f2e 100644 --- a/core/sys/windows/hidusage.odin +++ b/core/sys/windows/hidusage.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows USAGE :: distinct USHORT diff --git a/core/sys/windows/ip_helper.odin b/core/sys/windows/ip_helper.odin index 4c2534c10..7a6e545ac 100644 --- a/core/sys/windows/ip_helper.odin +++ b/core/sys/windows/ip_helper.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import "system:iphlpapi.lib" diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index 81db67185..8be50bceb 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import kernel32 "system:Kernel32.lib" @@ -381,6 +381,14 @@ foreign kernel32 { nDefaultTimeOut: DWORD, lpSecurityAttributes: LPSECURITY_ATTRIBUTES, ) -> HANDLE --- + PeekNamedPipe :: proc( + hNamedPipe: HANDLE, + lpBuffer: rawptr, + nBufferSize: u32, + lpBytesRead: ^u32, + lpTotalBytesAvail: ^u32, + lpBytesLeftThisMessage: ^u32, + ) -> BOOL --- CancelIo :: proc(handle: HANDLE) -> BOOL --- GetOverlappedResult :: proc( hFile: HANDLE, diff --git a/core/sys/windows/key_codes.odin b/core/sys/windows/key_codes.odin index 284b0e437..0991ca4b3 100644 --- a/core/sys/windows/key_codes.odin +++ b/core/sys/windows/key_codes.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows // https://docs.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input diff --git a/core/sys/windows/known_folders.odin b/core/sys/windows/known_folders.odin index 439d65faf..cbaf5eeeb 100644 --- a/core/sys/windows/known_folders.odin +++ b/core/sys/windows/known_folders.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows FOLDERID_NetworkFolder :: GUID {0xD20BEEC4, 0x5CA8, 0x4905, {0xAE, 0x3B, 0xBF, 0x25, 0x1E, 0xA0, 0x9B, 0x53}} diff --git a/core/sys/windows/netapi32.odin b/core/sys/windows/netapi32.odin index d9f75c623..9442193ca 100644 --- a/core/sys/windows/netapi32.odin +++ b/core/sys/windows/netapi32.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import netapi32 "system:Netapi32.lib" diff --git a/core/sys/windows/ntdll.odin b/core/sys/windows/ntdll.odin index 23444ff34..747130749 100644 --- a/core/sys/windows/ntdll.odin +++ b/core/sys/windows/ntdll.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import ntdll_lib "system:ntdll.lib" diff --git a/core/sys/windows/shcore.odin b/core/sys/windows/shcore.odin index 54f67989e..08a76ebe6 100644 --- a/core/sys/windows/shcore.odin +++ b/core/sys/windows/shcore.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows diff --git a/core/sys/windows/shell32.odin b/core/sys/windows/shell32.odin index 7340ae4d4..54cee718c 100644 --- a/core/sys/windows/shell32.odin +++ b/core/sys/windows/shell32.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import shell32 "system:Shell32.lib" diff --git a/core/sys/windows/shlwapi.odin b/core/sys/windows/shlwapi.odin index bf9d2d1e8..095fff304 100644 --- a/core/sys/windows/shlwapi.odin +++ b/core/sys/windows/shlwapi.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import shlwapi "system:shlwapi.lib" diff --git a/core/sys/windows/synchronization.odin b/core/sys/windows/synchronization.odin index 79efaab34..bcaeb3f5f 100644 --- a/core/sys/windows/synchronization.odin +++ b/core/sys/windows/synchronization.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import Synchronization "system:Synchronization.lib" diff --git a/core/sys/windows/system_params.odin b/core/sys/windows/system_params.odin index e94d777bf..e463feb7e 100644 --- a/core/sys/windows/system_params.odin +++ b/core/sys/windows/system_params.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows // Parameter for SystemParametersInfo. diff --git a/core/sys/windows/tlhelp.odin b/core/sys/windows/tlhelp.odin index 45d5a3ff9..006c9c330 100644 --- a/core/sys/windows/tlhelp.odin +++ b/core/sys/windows/tlhelp.odin @@ -1,4 +1,4 @@ -//+build windows +#+build windows package sys_windows foreign import kernel32 "system:Kernel32.lib" diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index 934d103e5..e1ace4133 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -253,8 +253,6 @@ FILE_GENERIC_WRITE: DWORD : STANDARD_RIGHTS_WRITE | FILE_APPEND_DATA | SYNCHRONIZE -FILE_FLAG_OPEN_REPARSE_POINT: DWORD : 0x00200000 -FILE_FLAG_BACKUP_SEMANTICS: DWORD : 0x02000000 SECURITY_SQOS_PRESENT: DWORD : 0x00100000 FIONBIO: c_ulong : 0x8004667e @@ -2222,11 +2220,22 @@ WAIT_OBJECT_0: DWORD : 0x00000000 WAIT_TIMEOUT: DWORD : 258 WAIT_FAILED: DWORD : 0xFFFFFFFF +FILE_FLAG_WRITE_THROUGH: DWORD : 0x80000000 +FILE_FLAG_OVERLAPPED: DWORD : 0x40000000 +FILE_FLAG_NO_BUFFERING: DWORD : 0x20000000 +FILE_FLAG_RANDOM_ACCESS: DWORD : 0x10000000 +FILE_FLAG_SEQUENTIAL_SCAN: DWORD : 0x08000000 +FILE_FLAG_DELETE_ON_CLOSE: DWORD : 0x04000000 +FILE_FLAG_BACKUP_SEMANTICS: DWORD : 0x02000000 +FILE_FLAG_POSIX_SEMANTICS: DWORD : 0x01000000 +FILE_FLAG_SESSION_AWARE: DWORD : 0x00800000 +FILE_FLAG_OPEN_REPARSE_POINT: DWORD : 0x00200000 +FILE_FLAG_OPEN_NO_RECALL: DWORD : 0x00100000 +FILE_FLAG_FIRST_PIPE_INSTANCE: DWORD : 0x00080000 + PIPE_ACCESS_INBOUND: DWORD : 0x00000001 PIPE_ACCESS_OUTBOUND: DWORD : 0x00000002 PIPE_ACCESS_DUPLEX: DWORD : 0x00000003 -FILE_FLAG_FIRST_PIPE_INSTANCE: DWORD : 0x00080000 -FILE_FLAG_OVERLAPPED: DWORD : 0x40000000 PIPE_WAIT: DWORD : 0x00000000 PIPE_TYPE_BYTE: DWORD : 0x00000000 PIPE_TYPE_MESSAGE: DWORD : 0x00000004 diff --git a/core/sys/windows/user32.odin b/core/sys/windows/user32.odin index e13dcd55f..514592e7b 100644 --- a/core/sys/windows/user32.odin +++ b/core/sys/windows/user32.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows import "base:intrinsics" diff --git a/core/sys/windows/userenv.odin b/core/sys/windows/userenv.odin index a31e363e1..2a2209d2c 100644 --- a/core/sys/windows/userenv.odin +++ b/core/sys/windows/userenv.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import userenv "system:Userenv.lib" diff --git a/core/sys/windows/util.odin b/core/sys/windows/util.odin index 929df1765..b3eb800bc 100644 --- a/core/sys/windows/util.odin +++ b/core/sys/windows/util.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows import "base:runtime" diff --git a/core/sys/windows/ux_theme.odin b/core/sys/windows/ux_theme.odin index 7af399361..527abd62f 100644 --- a/core/sys/windows/ux_theme.odin +++ b/core/sys/windows/ux_theme.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import uxtheme "system:UxTheme.lib" diff --git a/core/sys/windows/wgl.odin b/core/sys/windows/wgl.odin index d0d96d90b..8fea55c3d 100644 --- a/core/sys/windows/wgl.odin +++ b/core/sys/windows/wgl.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows import "core:c" diff --git a/core/sys/windows/wglext.odin b/core/sys/windows/wglext.odin index 0c4b51d65..4c76b39ec 100644 --- a/core/sys/windows/wglext.odin +++ b/core/sys/windows/wglext.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows // WGL_ARB_buffer_region diff --git a/core/sys/windows/window_messages.odin b/core/sys/windows/window_messages.odin index 888c5ccf9..d69771bdf 100644 --- a/core/sys/windows/window_messages.odin +++ b/core/sys/windows/window_messages.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows WM_NULL :: 0x0000 diff --git a/core/sys/windows/winerror.odin b/core/sys/windows/winerror.odin index d3df3b815..b3b470619 100644 --- a/core/sys/windows/winerror.odin +++ b/core/sys/windows/winerror.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows // https://learn.microsoft.com/en-us/windows/win32/api/winerror/ diff --git a/core/sys/windows/winmm.odin b/core/sys/windows/winmm.odin index a1786c27a..3c7ec80e7 100644 --- a/core/sys/windows/winmm.odin +++ b/core/sys/windows/winmm.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import winmm "system:Winmm.lib" diff --git a/core/sys/windows/winnls.odin b/core/sys/windows/winnls.odin index 292d2fad2..ffb2638d5 100644 --- a/core/sys/windows/winnls.odin +++ b/core/sys/windows/winnls.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows LCTYPE :: distinct DWORD diff --git a/core/sys/windows/winver.odin b/core/sys/windows/winver.odin index 091d53d3a..47751dab7 100644 --- a/core/sys/windows/winver.odin +++ b/core/sys/windows/winver.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import version "system:version.lib" diff --git a/core/sys/windows/wow64_apiset.odin b/core/sys/windows/wow64_apiset.odin index 28558e9ca..3d29b786e 100644 --- a/core/sys/windows/wow64_apiset.odin +++ b/core/sys/windows/wow64_apiset.odin @@ -1,4 +1,4 @@ -//+build windows +#+build windows package sys_windows foreign import kernel32 "system:Kernel32.lib" diff --git a/core/sys/windows/ws2_32.odin b/core/sys/windows/ws2_32.odin index e9bf8abc9..5b2952495 100644 --- a/core/sys/windows/ws2_32.odin +++ b/core/sys/windows/ws2_32.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows // Define flags to be used with the WSAAsyncSelect() call. diff --git a/core/testing/events.odin b/core/testing/events.odin index c9c4b0271..1a47e2d68 100644 --- a/core/testing/events.odin +++ b/core/testing/events.odin @@ -1,4 +1,4 @@ -//+private +#+private package testing /* diff --git a/core/testing/logging.odin b/core/testing/logging.odin index 1c3fc4603..041489dab 100644 --- a/core/testing/logging.odin +++ b/core/testing/logging.odin @@ -1,4 +1,4 @@ -//+private +#+private package testing /* diff --git a/core/testing/reporting.odin b/core/testing/reporting.odin index 81f1d0646..6752cd79b 100644 --- a/core/testing/reporting.odin +++ b/core/testing/reporting.odin @@ -1,4 +1,4 @@ -//+private +#+private package testing /* diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 386ba8cb5..6b9d610ed 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -1,4 +1,4 @@ -//+private +#+private package testing /* @@ -204,6 +204,10 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { } } + when ODIN_OS == .Windows { + console_ansi_init() + } + stdout := io.to_writer(os.stream_from_handle(os.stdout)) stderr := io.to_writer(os.stream_from_handle(os.stderr)) diff --git a/core/testing/runner_windows.odin b/core/testing/runner_windows.odin new file mode 100644 index 000000000..401804c71 --- /dev/null +++ b/core/testing/runner_windows.odin @@ -0,0 +1,22 @@ +#+private +package testing + +import win32 "core:sys/windows" + +console_ansi_init :: proc() { + stdout := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE) + if stdout != win32.INVALID_HANDLE && stdout != nil { + old_console_mode: u32 + if win32.GetConsoleMode(stdout, &old_console_mode) { + win32.SetConsoleMode(stdout, old_console_mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING) + } + } + + stderr := win32.GetStdHandle(win32.STD_ERROR_HANDLE) + if stderr != win32.INVALID_HANDLE && stderr != nil { + old_console_mode: u32 + if win32.GetConsoleMode(stderr, &old_console_mode) { + win32.SetConsoleMode(stderr, old_console_mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING) + } + } +} diff --git a/core/testing/signal_handler.odin b/core/testing/signal_handler.odin index 047ea0b3a..2f1f7c89a 100644 --- a/core/testing/signal_handler.odin +++ b/core/testing/signal_handler.odin @@ -1,4 +1,4 @@ -//+private +#+private package testing /* diff --git a/core/testing/signal_handler_libc.odin b/core/testing/signal_handler_libc.odin index 27d1a0735..7442c100c 100644 --- a/core/testing/signal_handler_libc.odin +++ b/core/testing/signal_handler_libc.odin @@ -1,5 +1,5 @@ -//+private -//+build windows, linux, darwin, freebsd, openbsd, netbsd, haiku +#+private +#+build windows, linux, darwin, freebsd, openbsd, netbsd, haiku package testing /* @@ -26,6 +26,8 @@ import "core:os" @(private="file", thread_local) local_test_index: libc.sig_atomic_t +@(private="file", thread_local) +local_test_index_set: bool // Windows does not appear to have a SIGTRAP, so this is defined here, instead // of in the libc package, just so there's no confusion about it being @@ -45,6 +47,13 @@ stop_runner_callback :: proc "c" (sig: libc.int) { @(private="file") stop_test_callback :: proc "c" (sig: libc.int) { + if !local_test_index_set { + // We're a thread created by a test thread. + // + // There's nothing we can do to inform the test runner about who + // signalled, so hopefully the test will handle their own sub-threads. + return + } if local_test_index == -1 { // We're the test runner, and we ourselves have caught a signal from // which there is no recovery. @@ -114,6 +123,7 @@ This is a dire bug and should be reported to the Odin developers. _setup_signal_handler :: proc() { local_test_index = -1 + local_test_index_set = true // Catch user interrupt / CTRL-C. libc.signal(libc.SIGINT, stop_runner_callback) @@ -135,6 +145,7 @@ _setup_signal_handler :: proc() { _setup_task_signal_handler :: proc(test_index: int) { local_test_index = cast(libc.sig_atomic_t)test_index + local_test_index_set = true } _should_stop_runner :: proc() -> bool { diff --git a/core/testing/signal_handler_other.odin b/core/testing/signal_handler_other.odin index d6d494fa4..81f575495 100644 --- a/core/testing/signal_handler_other.odin +++ b/core/testing/signal_handler_other.odin @@ -1,11 +1,11 @@ -//+private -//+build !windows -//+build !linux -//+build !darwin -//+build !freebsd -//+build !openbsd -//+build !netbsd -//+build !haiku +#+private +#+build !windows +#+build !linux +#+build !darwin +#+build !freebsd +#+build !openbsd +#+build !netbsd +#+build !haiku package testing /* diff --git a/core/testing/testing.odin b/core/testing/testing.odin index d5e7c6830..09bf6dc0e 100644 --- a/core/testing/testing.odin +++ b/core/testing/testing.odin @@ -105,9 +105,13 @@ cleanup :: proc(t: ^T, procedure: proc(rawptr), user_data: rawptr) { append(&t.cleanups, Internal_Cleanup{procedure, user_data, context}) } -expect :: proc(t: ^T, ok: bool, msg: string = "", loc := #caller_location) -> bool { +expect :: proc(t: ^T, ok: bool, msg := "", expr := #caller_expression(ok), loc := #caller_location) -> bool { if !ok { - log.error(msg, location=loc) + if msg == "" { + log.errorf("expected %v to be true", expr, location=loc) + } else { + log.error(msg, location=loc) + } } return ok } @@ -119,10 +123,10 @@ expectf :: proc(t: ^T, ok: bool, format: string, args: ..any, loc := #caller_loc return ok } -expect_value :: proc(t: ^T, value, expected: $T, loc := #caller_location) -> bool where intrinsics.type_is_comparable(T) { +expect_value :: proc(t: ^T, value, expected: $T, loc := #caller_location, value_expr := #caller_expression(value)) -> bool where intrinsics.type_is_comparable(T) { ok := value == expected || reflect.is_nil(value) && reflect.is_nil(expected) if !ok { - log.errorf("expected %v, got %v", expected, value, location=loc) + log.errorf("expected %v to be %v, got %v", value_expr, expected, value, location=loc) } return ok } diff --git a/core/thread/thread.odin b/core/thread/thread.odin index 17ba1a0a2..c1cbceb42 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -272,7 +272,7 @@ create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, t := create(thread_proc, priority) t.data = rawptr(fn) if self_cleanup { - t.flags += {.Self_Cleanup} + intrinsics.atomic_or(&t.flags, {.Self_Cleanup}) } t.init_context = init_context start(t) @@ -307,7 +307,7 @@ create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_co t.user_index = 1 t.user_args[0] = data if self_cleanup { - t.flags += {.Self_Cleanup} + intrinsics.atomic_or(&t.flags, {.Self_Cleanup}) } t.init_context = init_context start(t) @@ -347,7 +347,7 @@ create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_contex mem.copy(&t.user_args[0], &data, size_of(T)) if self_cleanup { - t.flags += {.Self_Cleanup} + intrinsics.atomic_or(&t.flags, {.Self_Cleanup}) } t.init_context = init_context @@ -394,7 +394,7 @@ create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), _ = copy(user_args[n:], mem.ptr_to_bytes(&arg2)) if self_cleanup { - t.flags += {.Self_Cleanup} + intrinsics.atomic_or(&t.flags, {.Self_Cleanup}) } t.init_context = init_context @@ -443,7 +443,7 @@ create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: pr _ = copy(user_args[n:], mem.ptr_to_bytes(&arg3)) if self_cleanup { - t.flags += {.Self_Cleanup} + intrinsics.atomic_or(&t.flags, {.Self_Cleanup}) } t.init_context = init_context @@ -494,7 +494,7 @@ create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: _ = copy(user_args[n:], mem.ptr_to_bytes(&arg4)) if self_cleanup { - t.flags += {.Self_Cleanup} + intrinsics.atomic_or(&t.flags, {.Self_Cleanup}) } t.init_context = init_context diff --git a/core/thread/thread_other.odin b/core/thread/thread_other.odin index 34bbfda08..dde2a8e48 100644 --- a/core/thread/thread_other.odin +++ b/core/thread/thread_other.odin @@ -1,4 +1,4 @@ -//+build js, wasi, orca +#+build js, wasi, orca package thread import "base:intrinsics" diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin index 9bcc42968..d9166b450 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -60,6 +60,7 @@ pool_thread_runner :: proc(t: ^Thread) { if task, ok := pool_pop_waiting(pool); ok { data.task = task pool_do_work(pool, task) + sync.guard(&pool.mutex) data.task = {} } } diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index ddc47244c..9576a3040 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -1,22 +1,18 @@ -// +build linux, darwin, freebsd, openbsd, netbsd, haiku -// +private +#+build linux, darwin, freebsd, openbsd, netbsd, haiku +#+private package thread import "base:runtime" import "core:sync" import "core:sys/unix" -import "core:time" _IS_SUPPORTED :: true -CAS :: sync.atomic_compare_exchange_strong - // NOTE(tetra): Aligned here because of core/unix/pthread_linux.odin/pthread_t. // Also see core/sys/darwin/mach_darwin.odin/semaphore_t. Thread_Os_Specific :: struct #align(16) { unix_thread: unix.pthread_t, // NOTE: very large on Darwin, small on Linux. - cond: sync.Cond, - mutex: sync.Mutex, + start_ok: sync.Sema, } // // Creates a thread which will run the given procedure. @@ -29,14 +25,10 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { // We need to give the thread a moment to start up before we enable cancellation. can_set_thread_cancel_state := unix.pthread_setcancelstate(unix.PTHREAD_CANCEL_ENABLE, nil) == 0 - sync.lock(&t.mutex) - t.id = sync.current_thread_id() - for (.Started not_in sync.atomic_load(&t.flags)) { - // HACK: use a timeout so in the event that the condition is signalled at THIS comment's exact point - // (after checking flags, before starting the wait) it gets itself out of that deadlock after a ms. - sync.wait_with_timeout(&t.cond, &t.mutex, time.Millisecond) + if .Started not_in sync.atomic_load(&t.flags) { + sync.wait(&t.start_ok) } if .Joined in sync.atomic_load(&t.flags) { @@ -66,8 +58,6 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { sync.atomic_or(&t.flags, { .Done }) - sync.unlock(&t.mutex) - if .Self_Cleanup in sync.atomic_load(&t.flags) { res := unix.pthread_detach(t.unix_thread) assert_contextless(res == 0) @@ -132,7 +122,7 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { _start :: proc(t: ^Thread) { sync.atomic_or(&t.flags, { .Started }) - sync.signal(&t.cond) + sync.post(&t.start_ok) } _is_done :: proc(t: ^Thread) -> bool { @@ -140,24 +130,18 @@ _is_done :: proc(t: ^Thread) -> bool { } _join :: proc(t: ^Thread) { - // sync.guard(&t.mutex) - if unix.pthread_equal(unix.pthread_self(), t.unix_thread) { return } - // Preserve other flags besides `.Joined`, like `.Started`. - unjoined := sync.atomic_load(&t.flags) - {.Joined} - joined := unjoined + {.Joined} - - // Try to set `t.flags` from unjoined to joined. If it returns joined, - // it means the previous value had that flag set and we can return. - if res, ok := CAS(&t.flags, unjoined, joined); res == joined && !ok { + // If the previous value was already `Joined`, then we can return. + if .Joined in sync.atomic_or(&t.flags, {.Joined}) { return } + // Prevent non-started threads from blocking main thread with initial wait // condition. - if .Started not_in unjoined { + if .Started not_in sync.atomic_load(&t.flags) { _start(t) } unix.pthread_join(t.unix_thread, nil) diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index 50a4e5fbc..cc73a2d6a 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -1,5 +1,5 @@ -//+build windows -//+private +#+build windows +#+private package thread import "base:intrinsics" @@ -27,7 +27,7 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { __windows_thread_entry_proc :: proc "system" (t_: rawptr) -> win32.DWORD { t := (^Thread)(t_) - if .Joined in t.flags { + if .Joined in sync.atomic_load(&t.flags) { return 0 } @@ -48,9 +48,9 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { t.procedure(t) } - intrinsics.atomic_store(&t.flags, t.flags + {.Done}) + intrinsics.atomic_or(&t.flags, {.Done}) - if .Self_Cleanup in t.flags { + if .Self_Cleanup in sync.atomic_load(&t.flags) { win32.CloseHandle(t.win32_thread) t.win32_thread = win32.INVALID_HANDLE // NOTE(ftphikari): It doesn't matter which context 'free' received, right? diff --git a/core/time/datetime/internal.odin b/core/time/datetime/internal.odin index e7129548e..3477a47f3 100644 --- a/core/time/datetime/internal.odin +++ b/core/time/datetime/internal.odin @@ -1,4 +1,4 @@ -//+private +#+private package datetime // Internal helper functions for calendrical conversions diff --git a/core/time/rfc3339.odin b/core/time/rfc3339.odin index e4c6565d6..20e8ea0bb 100644 --- a/core/time/rfc3339.odin +++ b/core/time/rfc3339.odin @@ -187,4 +187,110 @@ scan_digits :: proc(s: string, sep: string, count: int) -> (res: int, ok: bool) found_sep |= rune(s[count]) == v } return res, found_sep -} \ No newline at end of file +} + +/* +Serialize the timestamp as a RFC 3339 string. + +The boolean `ok` is false if the `time` is not a valid datetime, or if allocating the result string fails. + +**Inputs**: +- `utc_offset`: offset in minutes wrt UTC (ie. the timezone) +- `include_nanos`: whether to include nanoseconds in the result. +*/ +time_to_rfc3339 :: proc(time: Time, utc_offset : int = 0, include_nanos := true, allocator := context.allocator) -> (res: string, ok: bool) { + utc_offset := utc_offset + + // convert to datetime + datetime := time_to_datetime(time) or_return + + if datetime.year < 0 || datetime.year >= 10_000 { return "", false } + + temp_string := [36]u8{} + offset : uint = 0 + + print_as_fixed_int :: proc(dst: []u8, offset: ^uint, width: i8, i: i64) { + i := i + width := width + for digit_idx in 0.. (res: i64, n_digits: i8) { + res = n + n_digits = 9 + for res % 10 == 0 { + res = res / 10 + n_digits -= 1 + } + return + } + + // pre-epoch times: turn, say, -400ms to +600ms for display + nanos := time._nsec % 1_000_000_000 + if nanos < 0 { + nanos += 1_000_000_000 + } + + if nanos != 0 && include_nanos { + temp_string[offset] = '.' + offset += 1 + + // remove trailing zeroes + nanos_nonzero, n_digits := strip_trailing_zeroes_nanos(nanos) + assert(nanos_nonzero != 0) + + // write digits, right-to-left + for digit_idx : i8 = n_digits-1; digit_idx >= 0; digit_idx -= 1 { + digit := u8(nanos_nonzero % 10) + temp_string[offset + uint(digit_idx)] = '0' + u8(digit) + nanos_nonzero /= 10 + } + offset += uint(n_digits) + } + + if utc_offset == 0 { + temp_string[offset] = 'Z' + offset += 1 + } else { + temp_string[offset] = utc_offset > 0 ? '+' : '-' + offset += 1 + utc_offset = abs(utc_offset) + print_as_fixed_int(temp_string[:], &offset, 2, i64(utc_offset / 60)) + temp_string[offset] = ':' + offset += 1 + print_as_fixed_int(temp_string[:], &offset, 2, i64(utc_offset % 60)) + } + + res_as_slice, res_alloc := make_slice([]u8, len=offset, allocator = allocator) + if res_alloc != nil { + return "", false + } + + copy(res_as_slice, temp_string[:offset]) + + return string(res_as_slice), true +} diff --git a/core/time/time_essence.odin b/core/time/time_essence.odin index b7bc616d8..89883f0b9 100644 --- a/core/time/time_essence.odin +++ b/core/time/time_essence.odin @@ -1,4 +1,4 @@ -//+private +#+private package time import "core:sys/es" diff --git a/core/time/time_js.odin b/core/time/time_js.odin index c5090df90..9175fbfe9 100644 --- a/core/time/time_js.odin +++ b/core/time/time_js.odin @@ -1,5 +1,5 @@ -//+private -//+build js +#+private +#+build js package time foreign import "odin_env" diff --git a/core/time/time_linux.odin b/core/time/time_linux.odin index 649f601dc..4e557766e 100644 --- a/core/time/time_linux.odin +++ b/core/time/time_linux.odin @@ -6,8 +6,8 @@ _IS_SUPPORTED :: true _now :: proc "contextless" () -> Time { time_spec_now, _ := linux.clock_gettime(.REALTIME) - ns := time_spec_now.time_sec * 1e9 + time_spec_now.time_nsec - return Time{_nsec=i64(ns)} + ns := i64(time_spec_now.time_sec) * 1e9 + i64(time_spec_now.time_nsec) + return Time{_nsec=ns} } _sleep :: proc "contextless" (d: Duration) { @@ -29,7 +29,7 @@ _sleep :: proc "contextless" (d: Duration) { _tick_now :: proc "contextless" () -> Tick { t, _ := linux.clock_gettime(.MONOTONIC_RAW) - return Tick{_nsec = i64(t.time_sec*1e9 + t.time_nsec)} + return Tick{_nsec = i64(t.time_sec)*1e9 + i64(t.time_nsec)} } _yield :: proc "contextless" () { diff --git a/core/time/time_orca.odin b/core/time/time_orca.odin index b2598fd6e..f529790a5 100644 --- a/core/time/time_orca.odin +++ b/core/time/time_orca.odin @@ -1,5 +1,5 @@ -//+private -//+build orca +#+private +#+build orca package time import "base:intrinsics" diff --git a/core/time/time_other.odin b/core/time/time_other.odin index 164d23f25..d89bcbd42 100644 --- a/core/time/time_other.odin +++ b/core/time/time_other.odin @@ -1,14 +1,14 @@ -//+private -//+build !essence -//+build !js -//+build !linux -//+build !openbsd -//+build !freebsd -//+build !netbsd -//+build !darwin -//+build !wasi -//+build !windows -//+build !orca +#+private +#+build !essence +#+build !js +#+build !linux +#+build !openbsd +#+build !freebsd +#+build !netbsd +#+build !darwin +#+build !wasi +#+build !windows +#+build !orca package time _IS_SUPPORTED :: false diff --git a/core/time/time_unix.odin b/core/time/time_unix.odin index 0d7a43aba..61c4e91d3 100644 --- a/core/time/time_unix.odin +++ b/core/time/time_unix.odin @@ -1,5 +1,5 @@ -//+private -//+build darwin, freebsd, openbsd, netbsd +#+private +#+build darwin, freebsd, openbsd, netbsd package time import "core:sys/posix" diff --git a/core/time/time_wasi.odin b/core/time/time_wasi.odin index 88bebe2e3..c16c40cce 100644 --- a/core/time/time_wasi.odin +++ b/core/time/time_wasi.odin @@ -1,5 +1,5 @@ -//+private -//+build wasi +#+private +#+build wasi package time import "base:intrinsics" diff --git a/core/time/time_windows.odin b/core/time/time_windows.odin index 378b914b0..553ea6d4e 100644 --- a/core/time/time_windows.odin +++ b/core/time/time_windows.odin @@ -1,4 +1,4 @@ -//+private +#+private package time import win32 "core:sys/windows" diff --git a/core/time/tsc_darwin.odin b/core/time/tsc_darwin.odin index 841c0b692..3726cff49 100644 --- a/core/time/tsc_darwin.odin +++ b/core/time/tsc_darwin.odin @@ -1,4 +1,4 @@ -//+private +#+private package time import "core:sys/unix" diff --git a/core/time/tsc_freebsd.odin b/core/time/tsc_freebsd.odin index f4d6ccc3a..dabcb69cb 100644 --- a/core/time/tsc_freebsd.odin +++ b/core/time/tsc_freebsd.odin @@ -1,5 +1,5 @@ -//+private -//+build freebsd +#+private +#+build freebsd package time import "core:c" diff --git a/core/time/tsc_linux.odin b/core/time/tsc_linux.odin index 77a79fe52..a83634414 100644 --- a/core/time/tsc_linux.odin +++ b/core/time/tsc_linux.odin @@ -1,5 +1,5 @@ -//+private -//+build linux +#+private +#+build linux package time import linux "core:sys/linux" diff --git a/examples/all/all_experimental.odin b/examples/all/all_experimental.odin index cd60c269c..1d4eb4bb9 100644 --- a/examples/all/all_experimental.odin +++ b/examples/all/all_experimental.odin @@ -1,4 +1,4 @@ -//+build windows +#+build windows package all import c_tokenizer "core:c/frontend/tokenizer" diff --git a/examples/all/all_linux.odin b/examples/all/all_linux.odin index 18bba951c..ca51d6562 100644 --- a/examples/all/all_linux.odin +++ b/examples/all/all_linux.odin @@ -1,4 +1,4 @@ -//+build linux +#+build linux package all import linux "core:sys/linux" diff --git a/examples/all/all_posix.odin b/examples/all/all_posix.odin index 819dd6dd3..76fac0b87 100644 --- a/examples/all/all_posix.odin +++ b/examples/all/all_posix.odin @@ -1,4 +1,4 @@ -//+build darwin, openbsd, freebsd, netbsd +#+build darwin, openbsd, freebsd, netbsd package all import posix "core:sys/posix" diff --git a/examples/demo/demo.odin b/examples/demo/demo.odin index d66d1ceb0..38b0f1739 100644 --- a/examples/demo/demo.odin +++ b/examples/demo/demo.odin @@ -1,4 +1,4 @@ -//+vet !using-stmt !using-param +#+vet !using-stmt !using-param package main import "core:fmt" @@ -853,7 +853,7 @@ implicit_context_system :: proc() { what_a_fool_believes :: proc() { c := context // this `context` is the same as the parent procedure that it was called from // From this example, context.user_index == 123 - // An context.allocator is assigned to the return value of `my_custom_allocator()` + // A context.allocator is assigned to the return value of `my_custom_allocator()` assert(context.user_index == 123) // The memory management procedure use the `context.allocator` by @@ -906,7 +906,7 @@ parametric_polymorphism :: proc() { // This is how `new` is implemented alloc_type :: proc($T: typeid) -> ^T { - t := cast(^T)alloc(size_of(T), align_of(T)) + t := cast(^T)mem.alloc(size_of(T), align_of(T)) t^ = T{} // Use default initialization value return t } diff --git a/examples/demo/demo.rc b/examples/demo/demo.rc new file mode 100644 index 000000000..ef3ec2fec --- /dev/null +++ b/examples/demo/demo.rc @@ -0,0 +1,75 @@ + +#define Filename "demo.exe" +#define FileDescription "Odin demo project." +#define ProductName "Odin Programming Language Demo" + +#include "winres.h" + +LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT +#pragma code_page(65001) + +#define IDI_ICON1 101 + +#define Q(x) #x +#define QUOTE(x) Q(x) +#define FMTVER(x,y,z,w) QUOTE(x.y.z.w) + +#ifndef V1 +#define V1 1 +#endif +#ifndef V2 +#define V2 0 +#endif +#ifndef V3 +#define V3 0 +#endif +#ifndef V4 +#define V4 0 +#endif +#ifndef ODIN_VERSION +#define ODIN_VERSION FMTVER(V1,V2,V3,V4) +#endif +#ifndef GIT_SHA +#define GIT_SHA _ +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION V1,V2,V3,V4 + PRODUCTVERSION V1,V2,V3,V4 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "0409FDE9" + BEGIN + VALUE "CompanyName", "https://odin-lang.org/" + VALUE "FileDescription", "Odin Demo" + VALUE "FileVersion", FMTVER(V1,V2,V3,V4) + VALUE "InternalName", "demo.exe" + VALUE "LegalCopyright", "Copyright (c) 2016-2024 Ginger Bill. All rights reserved." + VALUE "OriginalFilename", "demo.exe" + VALUE "ProductName", "Odin Programming Language Demo" + VALUE "ProductVersion", QUOTE(ODIN_VERSION) + VALUE "Comments", QUOTE(ODIN_VERSION) + // PrivateBuild + // SpecialBuild + // custom values + VALUE "GitSha", QUOTE(GIT_SHA) + END + END + BLOCK "VarFileInfo" + BEGIN + //0xFDE9=65001=CP_UTF8 + VALUE "Translation", 0x0409, 0xFDE9 + END +END + +IDI_ICON1 ICON "..\\..\\misc\\sourcefile.ico" diff --git a/misc/emblem.ico b/misc/emblem.ico new file mode 100644 index 000000000..f5644b417 Binary files /dev/null and b/misc/emblem.ico differ diff --git a/misc/odin.manifest b/misc/odin.manifest new file mode 100644 index 000000000..d42403b22 --- /dev/null +++ b/misc/odin.manifest @@ -0,0 +1,8 @@ + + + + + UTF-8 + + + diff --git a/misc/odin.rc b/misc/odin.rc new file mode 100644 index 000000000..9e605f6dc --- /dev/null +++ b/misc/odin.rc @@ -0,0 +1,79 @@ + +#include "winres.h" + +// https://learn.microsoft.com/en-us/windows/win32/menurc/stringfileinfo-block + +LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT +#pragma code_page(65001) // CP_UTF8 + +#define IDI_ICON1 101 +#define IDI_ICON2 102 + +#ifndef V1 +#define V1 1 +#endif +#ifndef V2 +#define V2 0 +#endif +#ifndef V3 +#define V3 0 +#endif +#ifndef V4 +#define V4 0 +#endif +#ifndef VF +#define VF "1.0.0.0" +#endif +#ifndef VP +#define VP "1.0.0.0" +#endif +#ifndef GIT_SHA +#define GIT_SHA 0 +#endif +#ifndef NIGHTLY +#define NIGHTLY 0 +#endif + +#define Q(x) #x +#define QUOTE(x) Q(x) + +VS_VERSION_INFO VERSIONINFO + FILEVERSION V1,V2,V3,V4 + PRODUCTVERSION V1,V2,V3,V4 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "0409FDE9" + BEGIN + VALUE "CompanyName", "https://odin-lang.org/" + VALUE "FileDescription", "Odin general-purpose programming language." // note this is shown in the task manager + VALUE "FileVersion", QUOTE(VF) + VALUE "InternalName", "odin.exe" + VALUE "LegalCopyright", "Copyright (c) 2016-2024 Ginger Bill. All rights reserved." + VALUE "OriginalFilename", "odin.exe" + VALUE "ProductName", "The Odin Programming Language" + VALUE "ProductVersion", QUOTE(VP) + VALUE "Comments", QUOTE(git-sha: GIT_SHA) + // custom values + VALUE "GitSha", QUOTE(GIT_SHA) + VALUE "NightlyBuild", QUOTE(NIGHTLY) + END + END + BLOCK "VarFileInfo" + BEGIN + //0xFDE9=65001=CP_UTF8 + VALUE "Translation", 0x0409, 0xFDE9 + END +END + +IDI_ICON1 ICON "emblem.ico" +IDI_ICON2 ICON "sourcefile.ico" diff --git a/misc/sourcefile.ico b/misc/sourcefile.ico new file mode 100644 index 000000000..5f3772633 Binary files /dev/null and b/misc/sourcefile.ico differ diff --git a/src/bug_report.cpp b/src/bug_report.cpp index 5f768f57f..c5a8adea3 100644 --- a/src/bug_report.cpp +++ b/src/bug_report.cpp @@ -919,6 +919,9 @@ gb_internal void report_os_info() { {"23F79", {23, 5, 0}, "macOS", {"Sonoma", {14, 5, 0}}}, {"23G80", {23, 6, 0}, "macOS", {"Sonoma", {14, 6, 0}}}, {"23G93", {23, 6, 0}, "macOS", {"Sonoma", {14, 6, 1}}}, + {"23H124", {23, 6, 0}, "macOS", {"Sonoma", {14, 7, 0}}}, + {"24A335", {24, 0, 0}, "macOS", {"Sequoia", {15, 0, 0}}}, + {"24A348", {24, 0, 0}, "macOS", {"Sequoia", {15, 0, 1}}}, }; diff --git a/src/build_settings.cpp b/src/build_settings.cpp index e86224665..e365d0324 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -285,6 +285,7 @@ enum VetFlags : u64 { VetFlag_Deprecated = 1u<<7, VetFlag_Cast = 1u<<8, VetFlag_Tabs = 1u<<9, + VetFlag_UnusedProcedures = 1u<<10, VetFlag_Unused = VetFlag_UnusedVariables|VetFlag_UnusedImports, @@ -316,6 +317,8 @@ u64 get_vet_flag_from_name(String const &name) { return VetFlag_Cast; } else if (name == "tabs") { return VetFlag_Tabs; + } else if (name == "unused-procedures") { + return VetFlag_UnusedProcedures; } return VetFlag_NONE; } @@ -383,6 +386,7 @@ struct BuildContext { u64 vet_flags; u32 sanitizer_flags; + StringSet vet_packages; bool has_resource; String link_flags; @@ -411,6 +415,7 @@ struct BuildContext { bool no_dynamic_literals; bool no_output_files; bool no_crt; + bool no_rpath; bool no_entry_point; bool no_thread_local; bool use_lld; @@ -1461,8 +1466,6 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta bc->thread_count = gb_max(bc->affinity.thread_count, 1); } - string_set_init(&bc->custom_attributes); - bc->ODIN_VENDOR = str_lit("odin"); bc->ODIN_VERSION = ODIN_VERSION; bc->ODIN_ROOT = odin_root_dir(); @@ -2046,19 +2049,19 @@ gb_internal bool init_build_paths(String init_filename) { return false; } - gbFile output_file_test; - const char* output_file_name = (const char*)output_file.text; - gbFileError output_test_err = gb_file_open_mode(&output_file_test, gbFileMode_Append | gbFileMode_Rw, output_file_name); + // gbFile output_file_test; + // const char* output_file_name = (const char*)output_file.text; + // gbFileError output_test_err = gb_file_open_mode(&output_file_test, gbFileMode_Append | gbFileMode_Rw, output_file_name); - if (output_test_err == 0) { - gb_file_close(&output_file_test); - gb_file_remove(output_file_name); - } else { - String output_file = path_to_string(ha, bc->build_paths[BuildPath_Output]); - defer (gb_free(ha, output_file.text)); - gb_printf_err("No write permissions for output path: %.*s\n", LIT(output_file)); - return false; - } + // if (output_test_err == 0) { + // gb_file_close(&output_file_test); + // gb_file_remove(output_file_name); + // } else { + // String output_file = path_to_string(ha, bc->build_paths[BuildPath_Output]); + // defer (gb_free(ha, output_file.text)); + // gb_printf_err("No write permissions for output path: %.*s\n", LIT(output_file)); + // return false; + // } if (build_context.sanitizer_flags & SanitizerFlag_Address) { switch (build_context.metrics.os) { diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index aa6b087fe..a4dc80733 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1632,6 +1632,22 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o operand->type = t_source_code_location; operand->mode = Addressing_Value; + } else if (name == "caller_expression") { + if (ce->args.count > 1) { + error(ce->args[0], "'#caller_expression' expects either 0 or 1 arguments, got %td", ce->args.count); + } + if (ce->args.count > 0) { + Ast *arg = ce->args[0]; + Operand o = {}; + Entity *e = check_ident(c, &o, arg, nullptr, nullptr, true); + if (e == nullptr || (e->flags & EntityFlag_Param) == 0) { + error(ce->args[0], "'#caller_expression' expected a valid earlier parameter name"); + } + arg->Ident.entity = e; + } + + operand->type = t_string; + operand->mode = Addressing_Value; } else if (name == "exists") { if (ce->args.count != 1) { error(ce->close, "'#exists' expects 1 argument, got %td", ce->args.count); @@ -3096,7 +3112,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As // Okay } else if (!is_type_ordered(type) || !(is_type_numeric(type) || is_type_string(type))) { gbString type_str = type_to_string(original_type); - error(call, "Expected a ordered numeric type to 'min', got '%s'", type_str); + error(call, "Expected an ordered numeric type to 'min', got '%s'", type_str); gb_string_free(type_str); return false; } @@ -3182,7 +3198,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As if (!is_type_ordered(b.type) || !(is_type_numeric(b.type) || is_type_string(b.type))) { gbString type_str = type_to_string(b.type); error(call, - "Expected a ordered numeric type to 'min', got '%s'", + "Expected an ordered numeric type to 'min', got '%s'", type_str); gb_string_free(type_str); return false; @@ -3265,7 +3281,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As // Okay } else if (!is_type_ordered(type) || !(is_type_numeric(type) || is_type_string(type))) { gbString type_str = type_to_string(original_type); - error(call, "Expected a ordered numeric type to 'max', got '%s'", type_str); + error(call, "Expected an ordered numeric type to 'max', got '%s'", type_str); gb_string_free(type_str); return false; } @@ -3356,7 +3372,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As if (!is_type_ordered(b.type) || !(is_type_numeric(b.type) || is_type_string(b.type))) { gbString type_str = type_to_string(b.type); error(arg, - "Expected a ordered numeric type to 'max', got '%s'", + "Expected an ordered numeric type to 'max', got '%s'", type_str); gb_string_free(type_str); return false; @@ -3486,7 +3502,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As Type *type = operand->type; if (!is_type_ordered(type) || !(is_type_numeric(type) || is_type_string(type))) { gbString type_str = type_to_string(operand->type); - error(call, "Expected a ordered numeric or string type to 'clamp', got '%s'", type_str); + error(call, "Expected an ordered numeric or string type to 'clamp', got '%s'", type_str); gb_string_free(type_str); return false; } @@ -3503,7 +3519,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As } if (!is_type_ordered(y.type) || !(is_type_numeric(y.type) || is_type_string(y.type))) { gbString type_str = type_to_string(y.type); - error(call, "Expected a ordered numeric or string type to 'clamp', got '%s'", type_str); + error(call, "Expected an ordered numeric or string type to 'clamp', got '%s'", type_str); gb_string_free(type_str); return false; } @@ -3514,7 +3530,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As } if (!is_type_ordered(z.type) || !(is_type_numeric(z.type) || is_type_string(z.type))) { gbString type_str = type_to_string(z.type); - error(call, "Expected a ordered numeric or string type to 'clamp', got '%s'", type_str); + error(call, "Expected an ordered numeric or string type to 'clamp', got '%s'", type_str); gb_string_free(type_str); return false; } @@ -4967,16 +4983,14 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As check_assignment(c, &x, elem, builtin_name); Type *t = type_deref(operand->type); - switch (id) { - case BuiltinProc_atomic_add: - case BuiltinProc_atomic_sub: - if (!is_type_numeric(t)) { + if (id != BuiltinProc_atomic_exchange) { + if (!is_type_integer_like(t)) { gbString str = type_to_string(t); - error(operand->expr, "Expected a numeric type for '%.*s', got %s", LIT(builtin_name), str); + error(operand->expr, "Expected an integer type for '%.*s', got %s", LIT(builtin_name), str); gb_string_free(str); } else if (is_type_different_to_arch_endianness(t)) { gbString str = type_to_string(t); - error(operand->expr, "Expected a numeric type of the same platform endianness for '%.*s', got %s", LIT(builtin_name), str); + error(operand->expr, "Expected an integer type of the same platform endianness for '%.*s', got %s", LIT(builtin_name), str); gb_string_free(str); } } @@ -5012,19 +5026,16 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As } Type *t = type_deref(operand->type); - switch (id) { - case BuiltinProc_atomic_add_explicit: - case BuiltinProc_atomic_sub_explicit: - if (!is_type_numeric(t)) { + if (id != BuiltinProc_atomic_exchange_explicit) { + if (!is_type_integer_like(t)) { gbString str = type_to_string(t); - error(operand->expr, "Expected a numeric type for '%.*s', got %s", LIT(builtin_name), str); + error(operand->expr, "Expected an integer type for '%.*s', got %s", LIT(builtin_name), str); gb_string_free(str); } else if (is_type_different_to_arch_endianness(t)) { gbString str = type_to_string(t); - error(operand->expr, "Expected a numeric type of the same platform endianness for '%.*s', got %s", LIT(builtin_name), str); + error(operand->expr, "Expected an integer type of the same platform endianness for '%.*s', got %s", LIT(builtin_name), str); gb_string_free(str); } - break; } operand->type = elem; diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 3b532a727..0cc89435d 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -232,6 +232,10 @@ gb_internal bool check_override_as_type_due_to_aliasing(CheckerContext *ctx, Ent // until there is a proper delaying system to try declaration again if they // have failed. + if (e->type != nullptr && is_type_typed(e->type)) { + return false; + } + e->kind = Entity_TypeName; check_type_decl(ctx, e, init, named_type); return true; diff --git a/src/check_expr.cpp b/src/check_expr.cpp index dfc2ffdea..5efa0ceee 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -285,7 +285,7 @@ gb_internal void error_operand_no_value(Operand *o) { if (o->mode == Addressing_NoValue) { Ast *x = unparen_expr(o->expr); - if (x->kind == Ast_CallExpr) { + if (x != nullptr && x->kind == Ast_CallExpr) { Ast *p = unparen_expr(x->CallExpr.proc); if (p->kind == Ast_BasicDirective) { String tag = p->BasicDirective.name.string; @@ -297,7 +297,7 @@ gb_internal void error_operand_no_value(Operand *o) { } gbString err = expr_to_string(o->expr); - if (x->kind == Ast_CallExpr) { + if (x != nullptr && x->kind == Ast_CallExpr) { error(o->expr, "'%s' call does not return a value and cannot be used as a value", err); } else { error(o->expr, "'%s' used as a value", err); @@ -3612,10 +3612,11 @@ gb_internal bool check_transmute(CheckerContext *c, Ast *node, Operand *o, Type if (are_types_identical(src_bt, dst_bt)) { return true; } - if (is_type_integer(src_t) && is_type_integer(dst_t)) { + if ((is_type_integer(src_t) && is_type_integer(dst_t)) || + is_type_integer(src_t) && is_type_bit_set(dst_t)) { if (types_have_same_internal_endian(src_t, dst_t)) { ExactValue src_v = exact_value_to_integer(o->value); - GB_ASSERT(src_v.kind == ExactValue_Integer); + GB_ASSERT(src_v.kind == ExactValue_Integer || src_v.kind == ExactValue_Invalid); BigInt v = src_v.value_integer; BigInt smax = {}; @@ -3900,6 +3901,12 @@ gb_internal void check_binary_expr(CheckerContext *c, Operand *x, Ast *node, Typ // IMPORTANT NOTE(bill): This uses right-left evaluation in type checking only no in check_expr(c, y, be->right); Type *rhs_type = type_deref(y->type); + if (rhs_type == nullptr) { + error(y->expr, "Cannot use '%.*s' on an expression with no value", LIT(op.string)); + x->mode = Addressing_Invalid; + x->expr = node; + return; + } if (is_type_bit_set(rhs_type)) { Type *elem = base_type(rhs_type)->BitSet.elem; @@ -4602,7 +4609,7 @@ gb_internal void convert_to_typed(CheckerContext *c, Operand *operand, Type *tar (operand->value.kind == ExactValue_Integer || operand->value.kind == ExactValue_Float)) { operand->mode = Addressing_Value; - target_type = t_untyped_nil; + // target_type = t_untyped_nil; operand->value = empty_exact_value; update_untyped_expr_value(c, operand->expr, operand->value); break; @@ -6203,22 +6210,6 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A Entity *vt = pt->params->Tuple.variables[pt->variadic_index]; o.type = vt->type; - - // NOTE(bill, 2024-07-14): minimize the stack usage for variadic parameters with the backing array - if (c->decl) { - bool found = false; - for (auto &vr : c->decl->variadic_reuses) { - if (are_types_identical(vt->type, vr.slice_type)) { - vr.max_count = gb_max(vr.max_count, variadic_operands.count); - found = true; - break; - } - } - if (!found) { - array_add(&c->decl->variadic_reuses, VariadicReuseData{vt->type, variadic_operands.count}); - } - } - } else { dummy_argument_count += 1; o.type = t_untyped_nil; @@ -6412,6 +6403,23 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A } score += eval_param_and_score(c, o, t, err, true, var_entity, show_error); } + + if (!vari_expand && variadic_operands.count != 0) { + // NOTE(bill, 2024-07-14): minimize the stack usage for variadic parameters with the backing array + if (c->decl) { + bool found = false; + for (auto &vr : c->decl->variadic_reuses) { + if (are_types_identical(slice, vr.slice_type)) { + vr.max_count = gb_max(vr.max_count, variadic_operands.count); + found = true; + break; + } + } + if (!found) { + array_add(&c->decl->variadic_reuses, VariadicReuseData{slice, variadic_operands.count}); + } + } + } } if (data) { @@ -7807,7 +7815,8 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c name == "load" || name == "load_directory" || name == "load_hash" || - name == "hash" + name == "hash" || + name == "caller_expression" ) { operand->mode = Addressing_Builtin; operand->builtin_id = BuiltinProc_DIRECTIVE; @@ -8087,7 +8096,10 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c GB_ASSERT(c->curr_proc_decl->entity->type->kind == Type_Proc); String scope_features = c->curr_proc_decl->entity->type->Proc.enable_target_feature; if (!check_target_feature_is_superset_of(scope_features, pt->Proc.enable_target_feature, &invalid)) { + ERROR_BLOCK(); error(call, "Inlined procedure enables target feature '%.*s', this requires the calling procedure to at least enable the same feature", LIT(invalid)); + + error_line("\tSuggested Example: @(enable_target_feature=\"%.*s\")\n", LIT(invalid)); } } } @@ -8722,6 +8734,10 @@ gb_internal ExprKind check_basic_directive_expr(CheckerContext *c, Operand *o, A error(node, "#caller_location may only be used as a default argument parameter"); o->type = t_source_code_location; o->mode = Addressing_Value; + } else if (name == "caller_expression") { + error(node, "#caller_expression may only be used as a default argument parameter"); + o->type = t_string; + o->mode = Addressing_Value; } else { if (name == "location") { init_core_source_code_location(c->checker); @@ -8787,11 +8803,6 @@ gb_internal ExprKind check_ternary_if_expr(CheckerContext *c, Operand *o, Ast *n return kind; } - if (x.type == nullptr || x.type == t_invalid || - y.type == nullptr || y.type == t_invalid) { - return kind; - } - bool use_type_hint = type_hint != nullptr && (is_operand_nil(x) || is_operand_nil(y)); convert_to_typed(c, &x, use_type_hint ? type_hint : y.type); @@ -9123,6 +9134,10 @@ gb_internal ExprKind check_or_branch_expr(CheckerContext *c, Operand *o, Ast *no } if (label != nullptr) { + if (c->in_defer) { + error(label, "A labelled '%.*s' cannot be used within a 'defer'", LIT(name)); + return Expr_Expr; + } if (label->kind != Ast_Ident) { error(label, "A branch statement's label name must be an identifier"); return Expr_Expr; diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index c8717ba98..74a9e8825 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -1641,6 +1641,8 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) Ast *expr = unparen_expr(rs->expr); + Operand rhs_operand = {}; + bool is_range = false; bool is_possibly_addressable = true; isize max_val_count = 2; @@ -1698,7 +1700,7 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) } } } - bool is_ptr = is_type_pointer(type_deref(operand.type)); + bool is_ptr = is_type_pointer(operand.type); Type *t = base_type(type_deref(operand.type)); switch (t->kind) { @@ -1750,16 +1752,19 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) break; case Type_DynamicArray: + is_possibly_addressable = true; array_add(&vals, t->DynamicArray.elem); array_add(&vals, t_int); break; case Type_Slice: + is_possibly_addressable = true; array_add(&vals, t->Slice.elem); array_add(&vals, t_int); break; case Type_Map: + is_possibly_addressable = true; is_map = true; array_add(&vals, t->Map.key); array_add(&vals, t->Map.value); @@ -1781,6 +1786,8 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) case Type_Tuple: { + is_possibly_addressable = false; + isize count = t->Tuple.variables.count; if (count < 1) { ERROR_BLOCK(); @@ -1810,8 +1817,6 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) array_add(&vals, e->type); } - is_possibly_addressable = false; - bool do_break = false; for (isize i = rs->vals.count-1; i >= 0; i--) { if (rs->vals[i] != nullptr && count < i+2) { @@ -1831,6 +1836,11 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) case Type_Struct: if (t->Struct.soa_kind != StructSoa_None) { + if (t->Struct.soa_kind == StructSoa_Fixed) { + is_possibly_addressable = operand.mode == Addressing_Variable || is_ptr; + } else { + is_possibly_addressable = true; + } is_soa = true; array_add(&vals, t->Struct.soa_elem); array_add(&vals, t_int); @@ -1907,7 +1917,7 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) if (is_possibly_addressable && i == addressable_index) { entity->flags &= ~EntityFlag_Value; } else { - char const *idx_name = is_map ? "key" : is_bit_set ? "element" : "index"; + char const *idx_name = is_map ? "key" : (is_bit_set || i == 0) ? "element" : "index"; error(token, "The %s variable '%.*s' cannot be made addressable", idx_name, LIT(str)); } } diff --git a/src/check_type.cpp b/src/check_type.cpp index 3767f7666..bbeff9ca7 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -673,7 +673,7 @@ gb_internal void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast * #define ST_ALIGN(_name) if (st->_name != nullptr) { \ if (st->is_packed) { \ - syntax_error(st->_name, "'#%s' cannot be applied with '#packed'", #_name); \ + error(st->_name, "'#%s' cannot be applied with '#packed'", #_name); \ return; \ } \ i64 align = 1; \ @@ -682,12 +682,31 @@ gb_internal void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast * } \ } - ST_ALIGN(field_align); + ST_ALIGN(min_field_align); + ST_ALIGN(max_field_align); ST_ALIGN(align); - if (struct_type->Struct.custom_align < struct_type->Struct.custom_field_align) { - warning(st->align, "#align(%lld) is defined to be less than #field_name(%lld)", - cast(long long)struct_type->Struct.custom_align, - cast(long long)struct_type->Struct.custom_field_align); + if (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); + } + if (struct_type->Struct.custom_max_field_align != 0 && + struct_type->Struct.custom_align > struct_type->Struct.custom_max_field_align) { + error(st->align, "#align(%lld) is defined to be greater than #max_field_align(%lld)", + cast(long long)struct_type->Struct.custom_align, + cast(long long)struct_type->Struct.custom_max_field_align); + } + if (struct_type->Struct.custom_max_field_align != 0 && + struct_type->Struct.custom_min_field_align > struct_type->Struct.custom_max_field_align) { + error(st->align, "#min_field_align(%lld) is defined to be greater than #max_field_align(%lld)", + cast(long long)struct_type->Struct.custom_min_field_align, + cast(long long)struct_type->Struct.custom_max_field_align); + + i64 a = gb_min(struct_type->Struct.custom_min_field_align, struct_type->Struct.custom_max_field_align); + i64 b = gb_max(struct_type->Struct.custom_min_field_align, struct_type->Struct.custom_max_field_align); + // NOTE(bill): sort them to keep code consistent + struct_type->Struct.custom_min_field_align = a; + struct_type->Struct.custom_max_field_align = b; } #undef ST_ALIGN @@ -1605,6 +1624,25 @@ gb_internal bool is_expr_from_a_parameter(CheckerContext *ctx, Ast *expr) { return false; } +gb_internal bool is_caller_expression(Ast *expr) { + if (expr->kind == Ast_BasicDirective && expr->BasicDirective.name.string == "caller_expression") { + return true; + } + + Ast *call = unparen_expr(expr); + if (call->kind != Ast_CallExpr) { + return false; + } + + ast_node(ce, CallExpr, call); + if (ce->proc->kind != Ast_BasicDirective) { + return false; + } + + ast_node(bd, BasicDirective, ce->proc); + String name = bd->name.string; + return name == "caller_expression"; +} gb_internal ParameterValue handle_parameter_value(CheckerContext *ctx, Type *in_type, Type **out_type_, Ast *expr, bool allow_caller_location) { ParameterValue param_value = {}; @@ -1626,7 +1664,19 @@ gb_internal ParameterValue handle_parameter_value(CheckerContext *ctx, Type *in_ if (in_type) { check_assignment(ctx, &o, in_type, str_lit("parameter value")); } + } else if (is_caller_expression(expr)) { + if (expr->kind != Ast_BasicDirective) { + check_builtin_procedure_directive(ctx, &o, expr, t_string); + } + param_value.kind = ParameterValue_Expression; + o.type = t_string; + o.mode = Addressing_Value; + o.expr = expr; + + if (in_type) { + check_assignment(ctx, &o, in_type, str_lit("parameter value")); + } } else { if (in_type) { check_expr_with_type_hint(ctx, &o, expr, in_type); @@ -1858,6 +1908,7 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para case ParameterValue_Nil: break; case ParameterValue_Location: + case ParameterValue_Expression: case ParameterValue_Value: gbString str = type_to_string(type); error(params[i], "A default value for a parameter must not be a polymorphic constant type, got %s", str); diff --git a/src/checker.cpp b/src/checker.cpp index 64c66c8a6..76f996648 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -533,18 +533,13 @@ gb_internal u64 check_vet_flags(CheckerContext *c) { c->curr_proc_decl->proc_lit) { file = c->curr_proc_decl->proc_lit->file(); } - if (file && file->vet_flags_set) { - return file->vet_flags; - } - return build_context.vet_flags; + + return ast_file_vet_flags(file); } gb_internal u64 check_vet_flags(Ast *node) { AstFile *file = node->file(); - if (file && file->vet_flags_set) { - return file->vet_flags; - } - return build_context.vet_flags; + return ast_file_vet_flags(file); } enum VettedEntityKind { @@ -681,20 +676,48 @@ gb_internal bool check_vet_unused(Checker *c, Entity *e, VettedEntity *ve) { return false; } -gb_internal void check_scope_usage(Checker *c, Scope *scope, u64 vet_flags) { - bool vet_unused = (vet_flags & VetFlag_Unused) != 0; - bool vet_shadowing = (vet_flags & (VetFlag_Shadowing|VetFlag_Using)) != 0; - +gb_internal void check_scope_usage_internal(Checker *c, Scope *scope, u64 vet_flags, bool per_entity) { + u64 original_vet_flags = vet_flags; Array vetted_entities = {}; array_init(&vetted_entities, heap_allocator()); + defer (array_free(&vetted_entities)); rw_mutex_shared_lock(&scope->mutex); for (auto const &entry : scope->elements) { Entity *e = entry.value; if (e == nullptr) continue; + + vet_flags = original_vet_flags; + if (per_entity) { + vet_flags = ast_file_vet_flags(e->file); + } + + bool vet_unused = (vet_flags & VetFlag_Unused) != 0; + bool vet_shadowing = (vet_flags & (VetFlag_Shadowing|VetFlag_Using)) != 0; + bool vet_unused_procedures = (vet_flags & VetFlag_UnusedProcedures) != 0; + if (vet_unused_procedures && e->pkg && e->pkg->kind == Package_Runtime) { + vet_unused_procedures = false; + } + VettedEntity ve_unused = {}; VettedEntity ve_shadowed = {}; - bool is_unused = vet_unused && check_vet_unused(c, e, &ve_unused); + bool is_unused = false; + if (vet_unused && check_vet_unused(c, e, &ve_unused)) { + is_unused = true; + } else if (vet_unused_procedures && + e->kind == Entity_Procedure) { + if (e->flags&EntityFlag_Used) { + is_unused = false; + } else if (e->flags & EntityFlag_Require) { + is_unused = false; + } else if (e->pkg && e->pkg->kind == Package_Init && e->token.string == "main") { + is_unused = false; + } else { + is_unused = true; + ve_unused.kind = VettedEntity_Unused; + ve_unused.entity = e; + } + } bool is_shadowed = vet_shadowing && check_vet_shadowing(c, e, &ve_shadowed); if (is_unused && is_shadowed) { VettedEntity ve_both = ve_shadowed; @@ -717,13 +740,18 @@ gb_internal void check_scope_usage(Checker *c, Scope *scope, u64 vet_flags) { } rw_mutex_shared_unlock(&scope->mutex); - gb_sort(vetted_entities.data, vetted_entities.count, gb_size_of(VettedEntity), vetted_entity_variable_pos_cmp); + array_sort(vetted_entities, vetted_entity_variable_pos_cmp); for (auto const &ve : vetted_entities) { Entity *e = ve.entity; Entity *other = ve.other; String name = e->token.string; + vet_flags = original_vet_flags; + if (per_entity) { + vet_flags = ast_file_vet_flags(e->file); + } + if (ve.kind == VettedEntity_Shadowed_And_Unused) { error(e->token, "'%.*s' declared but not used, possibly shadows declaration at line %d", LIT(name), other->token.pos.line); } else if (vet_flags) { @@ -732,6 +760,9 @@ gb_internal void check_scope_usage(Checker *c, Scope *scope, u64 vet_flags) { if (e->kind == Entity_Variable && (vet_flags & VetFlag_UnusedVariables) != 0) { error(e->token, "'%.*s' declared but not used", LIT(name)); } + if (e->kind == Entity_Procedure && (vet_flags & VetFlag_UnusedProcedures) != 0) { + error(e->token, "'%.*s' declared but not used", LIT(name)); + } if ((e->kind == Entity_ImportName || e->kind == Entity_LibraryName) && (vet_flags & VetFlag_UnusedImports) != 0) { error(e->token, "'%.*s' declared but not used", LIT(name)); } @@ -749,7 +780,11 @@ gb_internal void check_scope_usage(Checker *c, Scope *scope, u64 vet_flags) { } } - array_free(&vetted_entities); +} + + +gb_internal void check_scope_usage(Checker *c, Scope *scope, u64 vet_flags) { + check_scope_usage_internal(c, scope, vet_flags, false); for (Scope *child = scope->head_child; child != nullptr; child = child->next) { if (child->flags & (ScopeFlag_Proc|ScopeFlag_Type|ScopeFlag_File)) { @@ -6174,7 +6209,7 @@ gb_internal void check_deferred_procedures(Checker *c) { } if ((src_params == nullptr && dst_params != nullptr) || (src_params != nullptr && dst_params == nullptr)) { - error(src->token, "Deferred procedure '%.*s' parameters do not match the inputs of initial procedure '%.*s'", LIT(src->token.string), LIT(dst->token.string)); + error(src->token, "Deferred procedure '%.*s' parameters do not match the inputs of initial procedure '%.*s'", LIT(dst->token.string), LIT(src->token.string)); continue; } @@ -6187,8 +6222,8 @@ gb_internal void check_deferred_procedures(Checker *c) { gbString s = type_to_string(src_params); gbString d = type_to_string(dst_params); error(src->token, "Deferred procedure '%.*s' parameters do not match the inputs of initial procedure '%.*s':\n\t(%s) =/= (%s)", - LIT(src->token.string), LIT(dst->token.string), - s, d + LIT(dst->token.string), LIT(src->token.string), + d, s ); gb_string_free(d); gb_string_free(s); @@ -6204,7 +6239,7 @@ gb_internal void check_deferred_procedures(Checker *c) { } if ((src_results == nullptr && dst_params != nullptr) || (src_results != nullptr && dst_params == nullptr)) { - error(src->token, "Deferred procedure '%.*s' parameters do not match the results of initial procedure '%.*s'", LIT(src->token.string), LIT(dst->token.string)); + error(src->token, "Deferred procedure '%.*s' parameters do not match the results of initial procedure '%.*s'", LIT(dst->token.string), LIT(src->token.string)); continue; } @@ -6217,8 +6252,8 @@ gb_internal void check_deferred_procedures(Checker *c) { gbString s = type_to_string(src_results); gbString d = type_to_string(dst_params); error(src->token, "Deferred procedure '%.*s' parameters do not match the results of initial procedure '%.*s':\n\t(%s) =/= (%s)", - LIT(src->token.string), LIT(dst->token.string), - s, d + LIT(dst->token.string), LIT(src->token.string), + d, s ); gb_string_free(d); gb_string_free(s); @@ -6270,8 +6305,8 @@ gb_internal void check_deferred_procedures(Checker *c) { gbString s = type_to_string(tsrc); gbString d = type_to_string(dst_params); error(src->token, "Deferred procedure '%.*s' parameters do not match the results of initial procedure '%.*s':\n\t(%s) =/= (%s)", - LIT(src->token.string), LIT(dst->token.string), - s, d + LIT(dst->token.string), LIT(src->token.string), + d, s ); gb_string_free(d); gb_string_free(s); @@ -6497,12 +6532,13 @@ gb_internal void check_parsed_files(Checker *c) { TIME_SECTION("check scope usage"); for (auto const &entry : c->info.files) { AstFile *f = entry.value; - u64 vet_flags = build_context.vet_flags; - if (f->vet_flags_set) { - vet_flags = f->vet_flags; - } + u64 vet_flags = ast_file_vet_flags(f); check_scope_usage(c, f->scope, vet_flags); } + for (auto const &entry : c->info.packages) { + AstPackage *pkg = entry.value; + check_scope_usage_internal(c, pkg->scope, 0, true); + } TIME_SECTION("add basic type information"); // Add "Basic" type information diff --git a/src/entity.cpp b/src/entity.cpp index db6ffdd52..0c4a20df4 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -104,6 +104,7 @@ enum ParameterValueKind { ParameterValue_Constant, ParameterValue_Nil, ParameterValue_Location, + ParameterValue_Expression, ParameterValue_Value, }; diff --git a/src/gb/gb.h b/src/gb/gb.h index 0e65696e2..1fef4b4f5 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -3195,11 +3195,11 @@ void gb_affinity_init(gbAffinity *a) { a->core_count = 1; a->threads_per_core = 1; - if (sysctlbyname("hw.logicalcpu", &count, &count_size, NULL, 0) == 0) { + if (sysctlbyname("kern.smp.cpus", &count, &count_size, NULL, 0) == 0) { if (count > 0) { a->thread_count = count; // Get # of physical cores - if (sysctlbyname("hw.physicalcpu", &count, &count_size, NULL, 0) == 0) { + if (sysctlbyname("kern.smp.cores", &count, &count_size, NULL, 0) == 0) { if (count > 0) { a->core_count = count; a->threads_per_core = a->thread_count / count; @@ -3210,6 +3210,14 @@ void gb_affinity_init(gbAffinity *a) { } } } + } else if (sysctlbyname("hw.ncpu", &count, &count_size, NULL, 0) == 0) { + // SMP disabled or unavailable. + if (count > 0) { + a->is_accurate = true; + a->thread_count = count; + a->core_count = count; + a->threads_per_core = 1; + } } } diff --git a/src/linker.cpp b/src/linker.cpp index faca28932..500fead69 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -548,14 +548,8 @@ gb_internal i32 linker_stage(LinkerData *gen) { // available at runtime wherever the executable is run, so we make require those to be // local to the executable (unless the system collection is used, in which case we search // the system library paths for the library file). - if (string_ends_with(lib, str_lit(".a")) || string_ends_with(lib, str_lit(".o"))) { - // static libs and object files, absolute full path relative to the file in which the lib was imported from + if (string_ends_with(lib, str_lit(".a")) || string_ends_with(lib, str_lit(".o")) || string_ends_with(lib, str_lit(".so")) || string_contains_string(lib, str_lit(".so."))) { lib_str = gb_string_append_fmt(lib_str, " -l:\"%.*s\" ", LIT(lib)); - } else if (string_ends_with(lib, str_lit(".so")) || string_contains_string(lib, str_lit(".so."))) { - // dynamic lib, relative path to executable - // NOTE(vassvik): it is the user's responsibility to make sure the shared library files are visible - // at runtime to the executable - lib_str = gb_string_append_fmt(lib_str, " -l:\"%s/%.*s\" ", cwd, LIT(lib)); } else { // dynamic or static system lib, just link regularly searching system library paths lib_str = gb_string_append_fmt(lib_str, " -l%.*s ", LIT(lib)); @@ -643,6 +637,16 @@ gb_internal i32 linker_stage(LinkerData *gen) { } } + if (!build_context.no_rpath) { + // Set the rpath to the $ORIGIN/@loader_path (the path of the executable), + // so that dynamic libraries are looked for at that path. + if (build_context.metrics.os == TargetOs_darwin) { + link_settings = gb_string_appendc(link_settings, "-Wl,-rpath,@loader_path "); + } else { + link_settings = gb_string_appendc(link_settings, "-Wl,-rpath,\\$ORIGIN "); + } + } + if (!build_context.no_crt) { platform_lib_str = gb_string_appendc(platform_lib_str, "-lm "); if (build_context.metrics.os == TargetOs_darwin) { diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index aa5c4dc60..0b2bb7956 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -531,6 +531,7 @@ namespace lbAbiAmd64SysV { RegClass_SSEInt16, RegClass_SSEInt32, RegClass_SSEInt64, + RegClass_SSEInt128, RegClass_SSEUp, RegClass_X87, RegClass_X87Up, @@ -572,6 +573,15 @@ namespace lbAbiAmd64SysV { gb_internal Array classify(LLVMTypeRef t); gb_internal LLVMTypeRef llreg(LLVMContextRef c, Array const ®_classes, LLVMTypeRef type); + gb_internal LB_ABI_COMPUTE_RETURN_TYPE(compute_return_type) { + if (!return_is_defined) { + return lb_arg_type_direct(LLVMVoidTypeInContext(c)); + } + LB_ABI_MODIFY_RETURN_IF_TUPLE_MACRO(); + + return amd64_type(c, return_type, Amd64TypeAttribute_StructRect, ft->calling_convention); + } + gb_internal LB_ABI_INFO(abi_info) { LLVMContextRef c = m->ctx; lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); @@ -582,12 +592,7 @@ namespace lbAbiAmd64SysV { for (unsigned i = 0; i < arg_count; i++) { ft->args[i] = amd64_type(c, arg_types[i], Amd64TypeAttribute_ByVal, calling_convention); } - - if (return_is_defined) { - ft->ret = amd64_type(c, return_type, Amd64TypeAttribute_StructRect, calling_convention); - } else { - ft->ret = lb_arg_type_direct(LLVMVoidTypeInContext(c)); - } + ft->ret = compute_return_type(ft, c, return_type, return_is_defined, return_is_tuple); return ft; } @@ -616,6 +621,10 @@ namespace lbAbiAmd64SysV { } switch (kind) { case LLVMIntegerTypeKind: + if (LLVM_VERSION_MAJOR >= 18 && sz >= 16) { + return true; + } + return false; case LLVMHalfTypeKind: case LLVMFloatTypeKind: case LLVMDoubleTypeKind: @@ -1257,11 +1266,12 @@ namespace lbAbiWasm { } gb_internal lbArgType non_struct(LLVMContextRef c, LLVMTypeRef type, bool is_return) { - if (!is_return && type == LLVMIntTypeInContext(c, 128)) { - LLVMTypeRef cast_type = LLVMVectorType(LLVMInt64TypeInContext(c), 2); + if (type == LLVMIntTypeInContext(c, 128)) { + // LLVMTypeRef cast_type = LLVMVectorType(LLVMInt64TypeInContext(c), 2); + LLVMTypeRef cast_type = nullptr; return lb_arg_type_direct(type, cast_type, nullptr, nullptr); } - + if (!is_return && lb_sizeof(type) > 8) { return lb_arg_type_indirect(type, nullptr); } @@ -1282,7 +1292,7 @@ namespace lbAbiWasm { case LLVMPointerTypeKind: return true; case LLVMIntegerTypeKind: - return lb_sizeof(type) <= 8; + return lb_sizeof(type) <= 16; } return false; } diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 29d2ccfe6..68f95cb03 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -528,7 +528,7 @@ gb_internal lbAddr lb_store_range_stmt_val(lbProcedure *p, Ast *stmt_val, lbValu gb_internal lbValue lb_emit_source_code_location_const(lbProcedure *p, String const &procedure, TokenPos const &pos); gb_internal lbValue lb_const_source_code_location_const(lbModule *m, String const &procedure, TokenPos const &pos); -gb_internal lbValue lb_handle_param_value(lbProcedure *p, Type *parameter_type, ParameterValue const ¶m_value, TokenPos const &pos); +gb_internal lbValue lb_handle_param_value(lbProcedure *p, Type *parameter_type, ParameterValue const ¶m_value, TypeProc *procedure_type, Ast *call_expression); gb_internal lbValue lb_equal_proc_for_type(lbModule *m, Type *type); gb_internal lbValue lb_hasher_proc_for_type(lbModule *m, Type *type); diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 6a6b119aa..754bbfca2 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -154,7 +154,7 @@ gb_internal LLVMValueRef llvm_const_named_struct(lbModule *m, Type *t, LLVMValue GB_ASSERT(value_count_ == bt->Struct.fields.count); auto field_remapping = lb_get_struct_remapping(m, t); - unsigned values_with_padding_count = LLVMCountStructElementTypes(struct_type); + unsigned values_with_padding_count = elem_count; LLVMValueRef *values_with_padding = gb_alloc_array(permanent_allocator(), LLVMValueRef, values_with_padding_count); for (unsigned i = 0; i < value_count; i++) { @@ -722,7 +722,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } case ExactValue_Integer: - if (is_type_pointer(type) || is_type_multi_pointer(type)) { + if (is_type_pointer(type) || is_type_multi_pointer(type) || is_type_proc(type)) { LLVMTypeRef t = lb_type(m, original_type); LLVMValueRef i = lb_big_int_to_llvm(m, t_uintptr, &value.value_integer); res.value = LLVMConstIntToPtr(i, t); diff --git a/src/llvm_backend_debug.cpp b/src/llvm_backend_debug.cpp index 68e1efc1c..5cc79dcc8 100644 --- a/src/llvm_backend_debug.cpp +++ b/src/llvm_backend_debug.cpp @@ -552,6 +552,48 @@ gb_internal LLVMMetadataRef lb_debug_bitset(lbModule *m, Type *type, String name return final_decl; } +gb_internal LLVMMetadataRef lb_debug_bitfield(lbModule *m, Type *type, String name, LLVMMetadataRef scope, LLVMMetadataRef file, unsigned line) { + Type *bt = base_type(type); + GB_ASSERT(bt->kind == Type_BitField); + + lb_debug_file_line(m, bt->BitField.node, &file, &line); + + u64 size_in_bits = 8*type_size_of(bt); + u32 align_in_bits = 8*cast(u32)type_align_of(bt); + + unsigned element_count = cast(unsigned)bt->BitField.fields.count; + LLVMMetadataRef *elements = gb_alloc_array(permanent_allocator(), LLVMMetadataRef, element_count); + + u64 offset_in_bits = 0; + for (unsigned i = 0; i < element_count; i++) { + Entity *f = bt->BitField.fields[i]; + u8 bit_size = bt->BitField.bit_sizes[i]; + GB_ASSERT(f->kind == Entity_Variable); + String name = f->token.string; + elements[i] = LLVMDIBuilderCreateBitFieldMemberType(m->debug_builder, scope, cast(char const *)name.text, name.len, file, line, + bit_size, offset_in_bits, 0, + LLVMDIFlagZero, lb_debug_type(m, f->type) + ); + + offset_in_bits += bit_size; + } + + LLVMMetadataRef final_decl = LLVMDIBuilderCreateStructType( + m->debug_builder, scope, + cast(char const *)name.text, cast(size_t)name.len, + file, line, + size_in_bits, align_in_bits, + LLVMDIFlagZero, + nullptr, + elements, element_count, + 0, + nullptr, + "", 0 + ); + lb_set_llvm_metadata(m, type, final_decl); + return final_decl; +} + gb_internal LLVMMetadataRef lb_debug_enum(lbModule *m, Type *type, String name, LLVMMetadataRef scope, LLVMMetadataRef file, unsigned line) { Type *bt = base_type(type); GB_ASSERT(bt->kind == Type_Enum); @@ -816,6 +858,7 @@ gb_internal LLVMMetadataRef lb_debug_type_internal(lbModule *m, Type *type) { case Type_Union: return lb_debug_union( m, type, make_string_c(type_to_string(type, temporary_allocator())), nullptr, nullptr, 0); case Type_BitSet: return lb_debug_bitset( m, type, make_string_c(type_to_string(type, temporary_allocator())), nullptr, nullptr, 0); case Type_Enum: return lb_debug_enum( m, type, make_string_c(type_to_string(type, temporary_allocator())), nullptr, nullptr, 0); + case Type_BitField: return lb_debug_bitfield( m, type, make_string_c(type_to_string(type, temporary_allocator())), nullptr, nullptr, 0); case Type_Tuple: if (type->Tuple.variables.count == 1) { @@ -901,42 +944,6 @@ gb_internal LLVMMetadataRef lb_debug_type_internal(lbModule *m, Type *type) { lb_debug_type(m, type->Matrix.elem), subscripts, gb_count_of(subscripts)); } - - case Type_BitField: { - LLVMMetadataRef parent_scope = nullptr; - LLVMMetadataRef scope = nullptr; - LLVMMetadataRef file = nullptr; - unsigned line = 0; - u64 size_in_bits = 8*cast(u64)type_size_of(type); - u32 align_in_bits = 8*cast(u32)type_align_of(type); - LLVMDIFlags flags = LLVMDIFlagZero; - - unsigned element_count = cast(unsigned)type->BitField.fields.count; - LLVMMetadataRef *elements = gb_alloc_array(permanent_allocator(), LLVMMetadataRef, element_count); - - u64 offset_in_bits = 0; - for (unsigned i = 0; i < element_count; i++) { - Entity *f = type->BitField.fields[i]; - u8 bit_size = type->BitField.bit_sizes[i]; - GB_ASSERT(f->kind == Entity_Variable); - String name = f->token.string; - unsigned field_line = 0; - LLVMDIFlags field_flags = LLVMDIFlagZero; - elements[i] = LLVMDIBuilderCreateBitFieldMemberType(m->debug_builder, scope, cast(char const *)name.text, name.len, file, field_line, - bit_size, offset_in_bits, offset_in_bits, - field_flags, lb_debug_type(m, f->type) - ); - - offset_in_bits += bit_size; - } - - - return LLVMDIBuilderCreateStructType(m->debug_builder, parent_scope, "", 0, file, line, - size_in_bits, align_in_bits, flags, - nullptr, elements, element_count, 0, nullptr, - "", 0 - ); - } } GB_PANIC("Invalid type %s", type_to_string(type)); @@ -1022,6 +1029,7 @@ gb_internal LLVMMetadataRef lb_debug_type(lbModule *m, Type *type) { case Type_Union: return lb_debug_union(m, type, name, scope, file, line); case Type_BitSet: return lb_debug_bitset(m, type, name, scope, file, line); case Type_Enum: return lb_debug_enum(m, type, name, scope, file, line); + case Type_BitField: return lb_debug_bitfield(m, type, name, scope, file, line); } } diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index f20c52e88..8967a4e03 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -1225,10 +1225,10 @@ gb_internal lbValue lb_emit_arith(lbProcedure *p, TokenKind op, lbValue lhs, lbV lbValue d3 = lb_emit_struct_ep(p, res.addr, 3); if (immediate_type != ft) { - d0 = lb_emit_conv(p, d0, ft); - d1 = lb_emit_conv(p, d1, ft); - d2 = lb_emit_conv(p, d2, ft); - d3 = lb_emit_conv(p, d3, ft); + z0 = lb_emit_conv(p, z0, ft); + z1 = lb_emit_conv(p, z1, ft); + z2 = lb_emit_conv(p, z2, ft); + z3 = lb_emit_conv(p, z3, ft); } lb_emit_store(p, d0, z0); @@ -2555,17 +2555,27 @@ gb_internal lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left if (are_types_identical(a, b)) { // NOTE(bill): No need for a conversion - } else if (lb_is_const(left) || lb_is_const_nil(left)) { + } else if ((lb_is_const(left) && !is_type_array(left.type)) || lb_is_const_nil(left)) { + // NOTE(karl): !is_type_array(left.type) is there to avoid lb_emit_conv + // trying to convert a constant array into a non-array. In that case we + // want the `else` branch to happen, so it can try to convert the + // non-array into an array instead. + if (lb_is_const_nil(left)) { + if (internal_check_is_assignable_to(right.type, left.type)) { + right = lb_emit_conv(p, right, left.type); + } return lb_emit_comp_against_nil(p, op_kind, right); } left = lb_emit_conv(p, left, right.type); - } else if (lb_is_const(right) || lb_is_const_nil(right)) { + } else if ((lb_is_const(right) && !is_type_array(right.type)) || lb_is_const_nil(right)) { if (lb_is_const_nil(right)) { + if (internal_check_is_assignable_to(left.type, right.type)) { + left = lb_emit_conv(p, left, right.type); + } return lb_emit_comp_against_nil(p, op_kind, left); } right = lb_emit_conv(p, right, left.type); - } else { Type *lt = left.type; Type *rt = right.type; @@ -3451,8 +3461,14 @@ gb_internal lbValue lb_build_expr_internal(lbProcedure *p, Ast *expr) { switch (expr->kind) { case_ast_node(bl, BasicLit, expr); + if (type != nullptr && type->Named.name == "Error") { + Entity *e = type->Named.type_name; + if (e->pkg && e->pkg->name == "os") { + return lb_const_nil(p->module, type); + } + } TokenPos pos = bl->token.pos; - GB_PANIC("Non-constant basic literal %s - %.*s", token_pos_to_string(pos), LIT(token_strings[bl->token.kind])); + GB_PANIC("Non-constant basic literal %s - %.*s (%s)", token_pos_to_string(pos), LIT(token_strings[bl->token.kind]), type_to_string(type)); case_end; case_ast_node(bd, BasicDirective, expr); diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index e850d3364..d84599eb0 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -699,7 +699,9 @@ gb_internal void lb_begin_procedure_body(lbProcedure *p) { } if (e->Variable.param_value.kind != ParameterValue_Invalid) { - lbValue c = lb_handle_param_value(p, e->type, e->Variable.param_value, e->token.pos); + GB_ASSERT(e->Variable.param_value.kind != ParameterValue_Location); + GB_ASSERT(e->Variable.param_value.kind != ParameterValue_Expression); + lbValue c = lb_handle_param_value(p, e->type, e->Variable.param_value, nullptr, nullptr); lb_addr_store(p, res, c); } @@ -3420,7 +3422,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu } -gb_internal lbValue lb_handle_param_value(lbProcedure *p, Type *parameter_type, ParameterValue const ¶m_value, TokenPos const &pos) { +gb_internal lbValue lb_handle_param_value(lbProcedure *p, Type *parameter_type, ParameterValue const ¶m_value, TypeProc *procedure_type, Ast* call_expression) { switch (param_value.kind) { case ParameterValue_Constant: if (is_type_constant_type(parameter_type)) { @@ -3446,8 +3448,60 @@ gb_internal lbValue lb_handle_param_value(lbProcedure *p, Type *parameter_type, if (p->entity != nullptr) { proc_name = p->entity->token.string; } + + ast_node(ce, CallExpr, call_expression); + TokenPos pos = ast_token(ce->proc).pos; + return lb_emit_source_code_location_as_global(p, proc_name, pos); } + case ParameterValue_Expression: + { + Ast *orig = param_value.original_ast_expr; + if (orig->kind == Ast_BasicDirective) { + gbString expr = expr_to_string(call_expression, temporary_allocator()); + return lb_const_string(p->module, make_string_c(expr)); + } + + isize param_idx = -1; + String param_str = {0}; + { + Ast *call = unparen_expr(orig); + GB_ASSERT(call->kind == Ast_CallExpr); + ast_node(ce, CallExpr, call); + GB_ASSERT(ce->proc->kind == Ast_BasicDirective); + GB_ASSERT(ce->args.count == 1); + Ast *target = ce->args[0]; + GB_ASSERT(target->kind == Ast_Ident); + String target_str = target->Ident.token.string; + + param_idx = lookup_procedure_parameter(procedure_type, target_str); + param_str = target_str; + } + GB_ASSERT(param_idx >= 0); + + + Ast *target_expr = nullptr; + ast_node(ce, CallExpr, call_expression); + + if (ce->split_args->positional.count > param_idx) { + target_expr = ce->split_args->positional[param_idx]; + } + + for_array(i, ce->split_args->named) { + Ast *arg = ce->split_args->named[i]; + ast_node(fv, FieldValue, arg); + GB_ASSERT(fv->field->kind == Ast_Ident); + String name = fv->field->Ident.token.string; + if (name == param_str) { + target_expr = fv->value; + break; + } + } + + gbString expr = expr_to_string(target_expr, temporary_allocator()); + return lb_const_string(p->module, make_string_c(expr)); + } + case ParameterValue_Value: return lb_build_expr(p, param_value.ast_value); } @@ -3739,8 +3793,6 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { } } - TokenPos pos = ast_token(ce->proc).pos; - if (pt->params != nullptr) { isize min_count = pt->params->Tuple.variables.count; @@ -3764,7 +3816,7 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { args[arg_index] = lb_const_nil(p->module, e->type); break; case Entity_Variable: - args[arg_index] = lb_handle_param_value(p, e->type, e->Variable.param_value, pos); + args[arg_index] = lb_handle_param_value(p, e->type, e->Variable.param_value, pt, expr); break; case Entity_Constant: diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index 638170bfc..9d4505bb0 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -826,7 +826,7 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ if (t->Struct.soa_kind != StructSoa_None) { - Type *kind_type = get_struct_field_type(tag_type, 10); + Type *kind_type = get_struct_field_type(tag_type, 7); lbValue soa_kind = lb_const_value(m, kind_type, exact_value_i64(t->Struct.soa_kind)); LLVMValueRef soa_type = get_type_info_ptr(m, t->Struct.soa_elem); diff --git a/src/main.cpp b/src/main.cpp index 0a84b2f97..04d3bdf52 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -325,6 +325,7 @@ enum BuildFlagKind { BuildFlag_NoTypeAssert, BuildFlag_NoDynamicLiterals, BuildFlag_NoCRT, + BuildFlag_NoRPath, BuildFlag_NoEntryPoint, BuildFlag_UseLLD, BuildFlag_UseSeparateModules, @@ -339,12 +340,14 @@ enum BuildFlagKind { BuildFlag_VetUnused, BuildFlag_VetUnusedImports, BuildFlag_VetUnusedVariables, + BuildFlag_VetUnusedProcedures, BuildFlag_VetUsingStmt, BuildFlag_VetUsingParam, BuildFlag_VetStyle, BuildFlag_VetSemicolon, BuildFlag_VetCast, BuildFlag_VetTabs, + BuildFlag_VetPackages, BuildFlag_CustomAttribute, BuildFlag_IgnoreUnknownAttributes, @@ -508,7 +511,7 @@ gb_internal bool parse_build_flags(Array args) { auto build_flags = array_make(heap_allocator(), 0, BuildFlag_COUNT); add_flag(&build_flags, BuildFlag_Help, str_lit("help"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_SingleFile, str_lit("file"), BuildFlagParam_None, Command__does_build | Command__does_check); - add_flag(&build_flags, BuildFlag_OutFile, str_lit("out"), BuildFlagParam_String, Command__does_build | Command_test); + add_flag(&build_flags, BuildFlag_OutFile, str_lit("out"), BuildFlagParam_String, Command__does_build | Command_test | Command_doc); add_flag(&build_flags, BuildFlag_OptimizationMode, str_lit("o"), BuildFlagParam_String, Command__does_build); add_flag(&build_flags, BuildFlag_ShowTimings, str_lit("show-timings"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_ShowMoreTimings, str_lit("show-more-timings"), BuildFlagParam_None, Command__does_check); @@ -533,6 +536,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_NoThreadLocal, str_lit("no-thread-local"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_NoDynamicLiterals, str_lit("no-dynamic-literals"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_NoCRT, str_lit("no-crt"), BuildFlagParam_None, Command__does_build); + add_flag(&build_flags, BuildFlag_NoRPath, str_lit("no-rpath"), BuildFlagParam_None, Command__does_build); add_flag(&build_flags, BuildFlag_NoEntryPoint, str_lit("no-entry-point"), BuildFlagParam_None, Command__does_check &~ Command_test); add_flag(&build_flags, BuildFlag_UseLLD, str_lit("lld"), BuildFlagParam_None, Command__does_build); add_flag(&build_flags, BuildFlag_UseSeparateModules, str_lit("use-separate-modules"), BuildFlagParam_None, Command__does_build); @@ -545,6 +549,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_Vet, str_lit("vet"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_VetUnused, str_lit("vet-unused"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_VetUnusedVariables, str_lit("vet-unused-variables"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_VetUnusedProcedures, str_lit("vet-unused-procedures"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_VetUnusedImports, str_lit("vet-unused-imports"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_VetShadowing, str_lit("vet-shadowing"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_VetUsingStmt, str_lit("vet-using-stmt"), BuildFlagParam_None, Command__does_check); @@ -553,6 +558,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_VetSemicolon, str_lit("vet-semicolon"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_VetCast, str_lit("vet-cast"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_VetTabs, str_lit("vet-tabs"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_VetPackages, str_lit("vet-packages"), BuildFlagParam_String, Command__does_check); add_flag(&build_flags, BuildFlag_CustomAttribute, str_lit("custom-attribute"), BuildFlagParam_String, Command__does_check, true); add_flag(&build_flags, BuildFlag_IgnoreUnknownAttributes, str_lit("ignore-unknown-attributes"), BuildFlagParam_None, Command__does_check); @@ -1183,6 +1189,9 @@ gb_internal bool parse_build_flags(Array args) { case BuildFlag_NoCRT: build_context.no_crt = true; break; + case BuildFlag_NoRPath: + build_context.no_rpath = true; + break; case BuildFlag_NoEntryPoint: build_context.no_entry_point = true; break; @@ -1215,6 +1224,36 @@ gb_internal bool parse_build_flags(Array args) { case BuildFlag_VetSemicolon: build_context.vet_flags |= VetFlag_Semicolon; break; case BuildFlag_VetCast: build_context.vet_flags |= VetFlag_Cast; break; case BuildFlag_VetTabs: build_context.vet_flags |= VetFlag_Tabs; break; + case BuildFlag_VetUnusedProcedures: + build_context.vet_flags |= VetFlag_UnusedProcedures; + if (!set_flags[BuildFlag_VetPackages]) { + gb_printf_err("-%.*s must be used with -vet-packages\n", LIT(name)); + bad_flags = true; + } + break; + + case BuildFlag_VetPackages: + { + GB_ASSERT(value.kind == ExactValue_String); + String val = value.value_string; + String_Iterator it = {val, 0}; + for (;;) { + String pkg = string_split_iterator(&it, ','); + if (pkg.len == 0) { + break; + } + + pkg = string_trim_whitespace(pkg); + if (!string_is_valid_identifier(pkg)) { + gb_printf_err("-%.*s '%.*s' must be a valid identifier\n", LIT(name), LIT(pkg)); + bad_flags = true; + continue; + } + + string_set_add(&build_context.vet_packages, pkg); + } + } + break; case BuildFlag_CustomAttribute: { @@ -1229,7 +1268,7 @@ gb_internal bool parse_build_flags(Array args) { attr = string_trim_whitespace(attr); if (!string_is_valid_identifier(attr)) { - gb_printf_err("-custom-attribute '%.*s' must be a valid identifier\n", LIT(attr)); + gb_printf_err("-%.*s '%.*s' must be a valid identifier\n", LIT(name), LIT(attr)); bad_flags = true; continue; } @@ -1369,7 +1408,7 @@ gb_internal bool parse_build_flags(Array args) { build_context.terse_errors = true; break; case BuildFlag_VerboseErrors: - gb_printf_err("-verbose-errors is not the default, -terse-errors can now disable it\n"); + gb_printf_err("-verbose-errors is now the default, -terse-errors can disable it\n"); build_context.hide_error_line = false; build_context.terse_errors = false; break; @@ -2140,131 +2179,15 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(1, "Flags"); print_usage_line(0, ""); - if (check) { - print_usage_line(1, "-file"); - print_usage_line(2, "Tells `%.*s %.*s` to treat the given file as a self-contained package.", LIT(arg0), LIT(command)); - print_usage_line(2, "This means that `/a.odin` won't have access to `/b.odin`'s contents."); - print_usage_line(0, ""); - } if (doc) { - print_usage_line(1, "-short"); - print_usage_line(2, "Shows shortened documentation for the packages."); - print_usage_line(0, ""); - print_usage_line(1, "-all-packages"); print_usage_line(2, "Generates documentation for all packages used in the current project."); print_usage_line(0, ""); - - print_usage_line(1, "-doc-format"); - print_usage_line(2, "Generates documentation as the .odin-doc format (useful for external tooling)."); - print_usage_line(0, ""); } - - if (run_or_build) { - print_usage_line(1, "-out:"); - print_usage_line(2, "Sets the file name of the outputted executable."); - print_usage_line(2, "Example: -out:foo.exe"); - print_usage_line(0, ""); - - print_usage_line(1, "-o:"); - print_usage_line(2, "Sets the optimization mode for compilation."); - print_usage_line(2, "Available options:"); - print_usage_line(3, "-o:none"); - print_usage_line(3, "-o:minimal"); - print_usage_line(3, "-o:size"); - print_usage_line(3, "-o:speed"); - if (LB_USE_NEW_PASS_SYSTEM) { - print_usage_line(3, "-o:aggressive"); - } - print_usage_line(2, "The default is -o:minimal."); - print_usage_line(0, ""); - } - - if (check) { - print_usage_line(1, "-show-timings"); - print_usage_line(2, "Shows basic overview of the timings of different stages within the compiler in milliseconds."); - print_usage_line(0, ""); - - print_usage_line(1, "-show-more-timings"); - print_usage_line(2, "Shows an advanced overview of the timings of different stages within the compiler in milliseconds."); - print_usage_line(0, ""); - - print_usage_line(1, "-show-system-calls"); - print_usage_line(2, "Prints the whole command and arguments for calls to external tools like linker and assembler."); - print_usage_line(0, ""); - - print_usage_line(1, "-export-timings:"); - print_usage_line(2, "Exports timings to one of a few formats. Requires `-show-timings` or `-show-more-timings`."); - print_usage_line(2, "Available options:"); - print_usage_line(3, "-export-timings:json Exports compile time stats to JSON."); - print_usage_line(3, "-export-timings:csv Exports compile time stats to CSV."); - print_usage_line(0, ""); - - print_usage_line(1, "-export-timings-file:"); - print_usage_line(2, "Specifies the filename for `-export-timings`."); - print_usage_line(2, "Example: -export-timings-file:timings.json"); - print_usage_line(0, ""); - - print_usage_line(1, "-export-dependencies:"); - print_usage_line(2, "Exports dependencies to one of a few formats. Requires `-export-dependencies-file`."); - print_usage_line(2, "Available options:"); - print_usage_line(3, "-export-dependencies:make Exports in Makefile format"); - print_usage_line(3, "-export-dependencies:json Exports in JSON format"); - print_usage_line(0, ""); - - print_usage_line(1, "-export-dependencies-file:"); - print_usage_line(2, "Specifies the filename for `-export-dependencies`."); - print_usage_line(2, "Example: -export-dependencies-file:dependencies.d"); - print_usage_line(0, ""); - - print_usage_line(1, "-thread-count:"); - print_usage_line(2, "Overrides the number of threads the compiler will use to compile with."); - print_usage_line(2, "Example: -thread-count:2"); - print_usage_line(0, ""); - } - - if (check_only) { - print_usage_line(1, "-show-unused"); - print_usage_line(2, "Shows unused package declarations within the current project."); - print_usage_line(0, ""); - print_usage_line(1, "-show-unused-with-location"); - print_usage_line(2, "Shows unused package declarations within the current project with the declarations source location."); - print_usage_line(0, ""); - } - - if (run_or_build) { - print_usage_line(1, "-keep-temp-files"); - print_usage_line(2, "Keeps the temporary files generated during compilation."); - print_usage_line(0, ""); - } else if (strip_semicolon) { - print_usage_line(1, "-keep-temp-files"); - print_usage_line(2, "Keeps the temporary files generated during stripping the unneeded semicolons from files."); - print_usage_line(0, ""); - } - - if (check) { - print_usage_line(1, "-collection:="); - print_usage_line(2, "Defines a library collection used for imports."); - print_usage_line(2, "Example: -collection:shared=dir/to/shared"); - print_usage_line(2, "Usage in Code:"); - print_usage_line(3, "import \"shared:foo\""); - print_usage_line(0, ""); - - print_usage_line(1, "-define:="); - print_usage_line(2, "Defines a scalar boolean, integer or string as global constant."); - print_usage_line(2, "Example: -define:SPAM=123"); - print_usage_line(2, "Usage in code:"); - print_usage_line(3, "#config(SPAM, default_value)"); - print_usage_line(0, ""); - - print_usage_line(1, "-show-defineables"); - print_usage_line(2, "Shows an overview of all the #config/#defined usages in the project."); - print_usage_line(0, ""); - - print_usage_line(1, "-export-defineables:"); - print_usage_line(2, "Exports an overview of all the #config/#defined usages in CSV format to the given file path."); - print_usage_line(2, "Example: -export-defineables:defineables.csv"); + if (test_only) { + print_usage_line(1, "-all-packages"); + print_usage_line(2, "Tests all packages imported into the given initial package."); print_usage_line(0, ""); } @@ -2289,8 +2212,20 @@ gb_internal void print_show_help(String const arg0, String const &command) { } if (check) { - print_usage_line(1, "-target:"); - print_usage_line(2, "Sets the target for the executable to be built in."); + print_usage_line(1, "-collection:="); + print_usage_line(2, "Defines a library collection used for imports."); + print_usage_line(2, "Example: -collection:shared=dir/to/shared"); + print_usage_line(2, "Usage in Code:"); + print_usage_line(3, "import \"shared:foo\""); + print_usage_line(0, ""); + + print_usage_line(1, "-custom-attribute:"); + print_usage_line(2, "Add a custom attribute which will be ignored if it is unknown."); + print_usage_line(2, "This can be used with metaprogramming tools."); + print_usage_line(2, "Examples:"); + print_usage_line(3, "-custom-attribute:my_tag"); + print_usage_line(3, "-custom-attribute:my_tag,the_other_thing"); + print_usage_line(3, "-custom-attribute:my_tag -custom-attribute:the_other_thing"); print_usage_line(0, ""); } @@ -2298,31 +2233,378 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(1, "-debug"); print_usage_line(2, "Enables debug information, and defines the global constant ODIN_DEBUG to be 'true'."); print_usage_line(0, ""); + } + if (check) { + print_usage_line(1, "-default-to-nil-allocator"); + print_usage_line(2, "Sets the default allocator to be the nil_allocator, an allocator which does nothing."); + print_usage_line(0, ""); + + print_usage_line(1, "-define:="); + print_usage_line(2, "Defines a scalar boolean, integer or string as global constant."); + print_usage_line(2, "Example: -define:SPAM=123"); + print_usage_line(2, "Usage in code:"); + print_usage_line(3, "#config(SPAM, default_value)"); + print_usage_line(0, ""); + } + + if (run_or_build) { print_usage_line(1, "-disable-assert"); print_usage_line(2, "Disables the code generation of the built-in run-time 'assert' procedure, and defines the global constant ODIN_DISABLE_ASSERT to be 'true'."); print_usage_line(0, ""); + print_usage_line(1, "-disable-red-zone"); + print_usage_line(2, "Disables red zone on a supported freestanding target."); + print_usage_line(0, ""); + } + + if (check) { + print_usage_line(1, "-disallow-do"); + print_usage_line(2, "Disallows the 'do' keyword in the project."); + print_usage_line(0, ""); + } + + if (doc) { + print_usage_line(1, "-doc-format"); + print_usage_line(2, "Generates documentation as the .odin-doc format (useful for external tooling)."); + print_usage_line(0, ""); + } + + if (run_or_build) { + print_usage_line(1, "-dynamic-map-calls"); + print_usage_line(2, "Uses dynamic map calls to minimize code generation at the cost of runtime execution."); + print_usage_line(0, ""); + } + + if (check) { + print_usage_line(1, "-error-pos-style:"); + print_usage_line(2, "Available options:"); + print_usage_line(3, "-error-pos-style:unix file/path:45:3:"); + print_usage_line(3, "-error-pos-style:odin file/path(45:3)"); + print_usage_line(3, "-error-pos-style:default (Defaults to 'odin'.)"); + print_usage_line(0, ""); + + print_usage_line(1, "-export-defineables:"); + print_usage_line(2, "Exports an overview of all the #config/#defined usages in CSV format to the given file path."); + print_usage_line(2, "Example: -export-defineables:defineables.csv"); + print_usage_line(0, ""); + + print_usage_line(1, "-export-dependencies:"); + print_usage_line(2, "Exports dependencies to one of a few formats. Requires `-export-dependencies-file`."); + print_usage_line(2, "Available options:"); + print_usage_line(3, "-export-dependencies:make Exports in Makefile format"); + print_usage_line(3, "-export-dependencies:json Exports in JSON format"); + print_usage_line(0, ""); + + print_usage_line(1, "-export-dependencies-file:"); + print_usage_line(2, "Specifies the filename for `-export-dependencies`."); + print_usage_line(2, "Example: -export-dependencies-file:dependencies.d"); + print_usage_line(0, ""); + + print_usage_line(1, "-export-timings:"); + print_usage_line(2, "Exports timings to one of a few formats. Requires `-show-timings` or `-show-more-timings`."); + print_usage_line(2, "Available options:"); + print_usage_line(3, "-export-timings:json Exports compile time stats to JSON."); + print_usage_line(3, "-export-timings:csv Exports compile time stats to CSV."); + print_usage_line(0, ""); + + print_usage_line(1, "-export-timings-file:"); + print_usage_line(2, "Specifies the filename for `-export-timings`."); + print_usage_line(2, "Example: -export-timings-file:timings.json"); + print_usage_line(0, ""); + } + + if (run_or_build) { + print_usage_line(1, "-extra-assembler-flags:"); + print_usage_line(2, "Adds extra assembler specific flags in a string."); + print_usage_line(0, ""); + + print_usage_line(1, "-extra-linker-flags:"); + print_usage_line(2, "Adds extra linker specific flags in a string."); + print_usage_line(0, ""); + } + + if (check) { + print_usage_line(1, "-file"); + print_usage_line(2, "Tells `%.*s %.*s` to treat the given file as a self-contained package.", LIT(arg0), LIT(command)); + print_usage_line(2, "This means that `/a.odin` won't have access to `/b.odin`'s contents."); + print_usage_line(0, ""); + + print_usage_line(1, "-foreign-error-procedures"); + print_usage_line(2, "States that the error procedures used in the runtime are defined in a separate translation unit."); + print_usage_line(0, ""); + + print_usage_line(1, "-ignore-unknown-attributes"); + print_usage_line(2, "Ignores unknown attributes."); + print_usage_line(2, "This can be used with metaprogramming tools."); + print_usage_line(0, ""); + } + + if (run_or_build) { + #if defined(GB_SYSTEM_WINDOWS) + print_usage_line(1, "-ignore-vs-search"); + print_usage_line(2, "[Windows only]"); + print_usage_line(2, "Ignores the Visual Studio search for library paths."); + print_usage_line(0, ""); + #endif + } + + if (check) { + print_usage_line(1, "-ignore-warnings"); + print_usage_line(2, "Ignores warning messages."); + print_usage_line(0, ""); + + print_usage_line(1, "-json-errors"); + print_usage_line(2, "Prints the error messages as json to stderr."); + print_usage_line(0, ""); + } + + if (run_or_build) { + print_usage_line(1, "-keep-temp-files"); + print_usage_line(2, "Keeps the temporary files generated during compilation."); + print_usage_line(0, ""); + } else if (strip_semicolon) { + print_usage_line(1, "-keep-temp-files"); + print_usage_line(2, "Keeps the temporary files generated during stripping the unneeded semicolons from files."); + print_usage_line(0, ""); + } + + if (run_or_build) { + print_usage_line(1, "-lld"); + print_usage_line(2, "Uses the LLD linker rather than the default."); + print_usage_line(0, ""); + } + + if (check) { + print_usage_line(1, "-max-error-count:"); + print_usage_line(2, "Sets the maximum number of errors that can be displayed before the compiler terminates."); + print_usage_line(2, "Must be an integer >0."); + print_usage_line(2, "If not set, the default max error count is %d.", DEFAULT_MAX_ERROR_COLLECTOR_COUNT); + print_usage_line(0, ""); + } + + if (run_or_build) { + print_usage_line(1, "-microarch:"); + print_usage_line(2, "Specifies the specific micro-architecture for the build in a string."); + print_usage_line(2, "Examples:"); + print_usage_line(3, "-microarch:sandybridge"); + print_usage_line(3, "-microarch:native"); + print_usage_line(3, "-microarch:\"?\" for a list"); + print_usage_line(0, ""); + } + + if (check) { + print_usage_line(1, "-min-link-libs"); + print_usage_line(2, "If set, the number of linked libraries will be minimized to prevent duplications."); + print_usage_line(2, "This is useful for so called \"dumb\" linkers compared to \"smart\" linkers."); + print_usage_line(0, ""); + } + + if (run_or_build) { + print_usage_line(1, "-minimum-os-version:"); + print_usage_line(2, "Sets the minimum OS version targeted by the application."); + print_usage_line(2, "Default: -minimum-os-version:11.0.0"); + print_usage_line(2, "Only used when target is Darwin, if given, linking mismatched versions will emit a warning."); + print_usage_line(0, ""); + print_usage_line(1, "-no-bounds-check"); print_usage_line(2, "Disables bounds checking program wide."); print_usage_line(0, ""); - print_usage_line(1, "-no-type-assert"); - print_usage_line(2, "Disables type assertion checking program wide."); - print_usage_line(0, ""); - print_usage_line(1, "-no-crt"); print_usage_line(2, "Disables automatic linking with the C Run Time."); print_usage_line(0, ""); + } + + if (check && command != "test") { + print_usage_line(1, "-no-entry-point"); + print_usage_line(2, "Removes default requirement of an entry point (e.g. main procedure)."); + print_usage_line(0, ""); + } + + if (run_or_build) { + print_usage_line(1, "-no-rpath"); + print_usage_line(2, "Disables automatic addition of an rpath linked to the executable directory."); + print_usage_line(0, ""); print_usage_line(1, "-no-thread-local"); print_usage_line(2, "Ignores @thread_local attribute, effectively treating the program as if it is single-threaded."); print_usage_line(0, ""); - print_usage_line(1, "-lld"); - print_usage_line(2, "Uses the LLD linker rather than the default."); + print_usage_line(1, "-no-threaded-checker"); + print_usage_line(2, "Disables multithreading in the semantic checker stage."); print_usage_line(0, ""); + print_usage_line(1, "-no-type-assert"); + print_usage_line(2, "Disables type assertion checking program wide."); + print_usage_line(0, ""); + } + + if (run_or_build) { + print_usage_line(1, "-o:"); + print_usage_line(2, "Sets the optimization mode for compilation."); + print_usage_line(2, "Available options:"); + print_usage_line(3, "-o:none"); + print_usage_line(3, "-o:minimal"); + print_usage_line(3, "-o:size"); + print_usage_line(3, "-o:speed"); + if (LB_USE_NEW_PASS_SYSTEM) { + print_usage_line(3, "-o:aggressive (use this with caution)"); + } + print_usage_line(2, "The default is -o:minimal."); + print_usage_line(0, ""); + + + print_usage_line(1, "-obfuscate-source-code-locations"); + print_usage_line(2, "Obfuscate the file and procedure strings, and line and column numbers, stored with a 'runtime.Source_Code_Location' value."); + print_usage_line(0, ""); + + + print_usage_line(1, "-out:"); + print_usage_line(2, "Sets the file name of the outputted executable."); + print_usage_line(2, "Example: -out:foo.exe"); + print_usage_line(0, ""); + } + + if (doc) { + print_usage_line(1, "-out:"); + print_usage_line(2, "Sets the base name of the resultig .odin-doc file."); + print_usage_line(2, "The extension can be optionally included; the resulting file will always have an extension of '.odin-doc'."); + print_usage_line(2, "Example: -out:foo"); + print_usage_line(0, ""); + } + + if (run_or_build) { + #if defined(GB_SYSTEM_WINDOWS) + print_usage_line(1, "-pdb-name:"); + print_usage_line(2, "[Windows only]"); + print_usage_line(2, "Defines the generated PDB name when -debug is enabled."); + print_usage_line(2, "Example: -pdb-name:different.pdb"); + print_usage_line(0, ""); + #endif + } + + if (build) { + print_usage_line(1, "-print-linker-flags"); + print_usage_line(2, "Prints the all of the flags/arguments that will be passed to the linker."); + print_usage_line(0, ""); + } + + if (run_or_build) { + print_usage_line(1, "-reloc-mode:"); + print_usage_line(2, "Specifies the reloc mode."); + print_usage_line(2, "Available options:"); + print_usage_line(3, "-reloc-mode:default"); + print_usage_line(3, "-reloc-mode:static"); + print_usage_line(3, "-reloc-mode:pic"); + print_usage_line(3, "-reloc-mode:dynamic-no-pic"); + print_usage_line(0, ""); + + #if defined(GB_SYSTEM_WINDOWS) + print_usage_line(1, "-resource:"); + print_usage_line(2, "[Windows only]"); + print_usage_line(2, "Defines the resource file for the executable."); + print_usage_line(2, "Example: -resource:path/to/file.rc"); + print_usage_line(2, "or: -resource:path/to/file.res for a precompiled one."); + print_usage_line(0, ""); + #endif + + print_usage_line(1, "-sanitize:"); + print_usage_line(2, "Enables sanitization analysis."); + print_usage_line(2, "Available options:"); + print_usage_line(3, "-sanitize:address"); + print_usage_line(3, "-sanitize:memory"); + print_usage_line(3, "-sanitize:thread"); + print_usage_line(2, "NOTE: This flag can be used multiple times."); + print_usage_line(0, ""); + } + + if (doc) { + print_usage_line(1, "-short"); + print_usage_line(2, "Shows shortened documentation for the packages."); + print_usage_line(0, ""); + } + + if (check) { + print_usage_line(1, "-show-defineables"); + print_usage_line(2, "Shows an overview of all the #config/#defined usages in the project."); + print_usage_line(0, ""); + + print_usage_line(1, "-show-system-calls"); + print_usage_line(2, "Prints the whole command and arguments for calls to external tools like linker and assembler."); + print_usage_line(0, ""); + + print_usage_line(1, "-show-timings"); + print_usage_line(2, "Shows basic overview of the timings of different stages within the compiler in milliseconds."); + print_usage_line(0, ""); + + print_usage_line(1, "-show-more-timings"); + print_usage_line(2, "Shows an advanced overview of the timings of different stages within the compiler in milliseconds."); + print_usage_line(0, ""); + } + + if (check_only) { + print_usage_line(1, "-show-unused"); + print_usage_line(2, "Shows unused package declarations within the current project."); + print_usage_line(0, ""); + print_usage_line(1, "-show-unused-with-location"); + print_usage_line(2, "Shows unused package declarations within the current project with the declarations source location."); + print_usage_line(0, ""); + } + + if (check) { + print_usage_line(1, "-strict-style"); + print_usage_line(2, "This enforces parts of same style as the Odin compiler, prefer '-vet-style -vet-semicolon' if you do not want to match it exactly."); + print_usage_line(2, ""); + print_usage_line(2, "Errs on unneeded tokens, such as unneeded semicolons."); + print_usage_line(2, "Errs on missing trailing commas followed by a newline."); + print_usage_line(2, "Errs on deprecated syntax."); + print_usage_line(2, "Errs when the attached-brace style in not adhered to (also known as 1TBS)."); + print_usage_line(2, "Errs when 'case' labels are not in the same column as the associated 'switch' token."); + print_usage_line(0, ""); + } + + if (run_or_build) { + print_usage_line(1, "-strict-target-features"); + print_usage_line(2, "Makes @(enable_target_features=\"...\") behave the same way as @(require_target_features=\"...\")."); + print_usage_line(2, "This enforces that all generated code uses features supported by the combination of -target, -microarch, and -target-features."); + print_usage_line(0, ""); + + #if defined(GB_SYSTEM_WINDOWS) + print_usage_line(1, "-subsystem: