Merge branch 'master' into bill/subtype-type-info

This commit is contained in:
gingerBill
2025-06-10 09:52:57 +01:00
committed by GitHub
414 changed files with 26453 additions and 7379 deletions

View File

@@ -32,8 +32,8 @@ jobs:
gmake -C vendor/miniaudio/src
./odin check examples/all -vet -strict-style -disallow-do -target:netbsd_amd64
./odin check examples/all -vet -strict-style -disallow-do -target:netbsd_arm64
./odin check vendor/sdl3 -vet -strict-style -disallow-do -target:netbsd_amd64 -no-entry-point
./odin check vendor/sdl3 -vet -strict-style -disallow-do -target:netbsd_arm64 -no-entry-point
./odin check examples/all/sdl3 -vet -strict-style -disallow-do -target:netbsd_amd64 -no-entry-point
./odin check examples/all/sdl3 -vet -strict-style -disallow-do -target:netbsd_arm64 -no-entry-point
./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
./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
@@ -64,7 +64,7 @@ jobs:
gmake -C vendor/cgltf/src
gmake -C vendor/miniaudio/src
./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64
./odin check vendor/sdl3 -vet -strict-style -disallow-do -target:freebsd_amd64 -no-entry-point
./odin check examples/all/sdl3 -vet -strict-style -disallow-do -target:freebsd_amd64 -no-entry-point
./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
./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
@@ -75,14 +75,29 @@ jobs:
fail-fast: false
matrix:
# MacOS 13 runs on Intel, 14 runs on ARM
os: [ubuntu-latest, macos-13, macos-14]
os: [macos-13, macos-14, ubuntu-latest]
runs-on: ${{ matrix.os }}
name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel' || 'Ubuntu') }} Build, Check, and Test
name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel') || (matrix.os == 'ubuntu-latest' && 'Ubuntu') }} Build, Check, and Test
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- name: Download LLVM (Linux)
- name: Download LLVM (MacOS Intel)
if: matrix.os == 'macos-13'
run: |
brew update
brew install llvm@20 lua@5.4 lld
echo "$(brew --prefix llvm@20)/bin" >> $GITHUB_PATH
- name: Download LLVM (MacOS ARM)
if: matrix.os == 'macos-14'
run: |
brew update
brew install llvm@20 wasmtime lua@5.4 lld
echo "$(brew --prefix llvm@20)/bin" >> $GITHUB_PATH
- name: Download LLVM (Ubuntu)
if: matrix.os == 'ubuntu-latest'
run: |
wget https://apt.llvm.org/llvm.sh
@@ -90,18 +105,6 @@ jobs:
sudo ./llvm.sh 20
echo "/usr/lib/llvm-20/bin" >> $GITHUB_PATH
- name: Download LLVM (MacOS Intel)
if: matrix.os == 'macos-13'
run: |
brew install llvm@18 lua@5.4
echo "/usr/local/opt/llvm@18/bin" >> $GITHUB_PATH
- name: Download LLVM (MacOS ARM)
if: matrix.os == 'macos-14'
run: |
brew install llvm@18 wasmtime lua@5.4
echo "/opt/homebrew/opt/llvm@18/bin" >> $GITHUB_PATH
- name: Build Odin
run: ./build_odin.sh release
- name: Odin version
@@ -121,56 +124,61 @@ jobs:
run: ./odin run examples/demo -debug
- name: Odin check examples/all
run: ./odin check examples/all -strict-style -vet -disallow-do
- name: Odin check vendor/sdl3
run: ./odin check vendor/sdl3 -strict-style -vet -disallow-do -no-entry-point
- name: Odin check examples/all/sdl3
run: ./odin check examples/all/sdl3 -strict-style -vet -disallow-do -no-entry-point
- 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
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 -sanitize:address
- 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
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 -sanitize:address
- name: Vendor library tests
run: ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
run: ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- 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
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 -sanitize:address
- name: GitHub Issue tests
run: |
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'
- name: Odin check examples/all for Linux arm64
run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_arm64
if: matrix.os == 'ubuntu-latest'
- name: Odin check examples/all for FreeBSD amd64
run: ./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64
if: matrix.os == 'ubuntu-latest'
- name: Odin check examples/all for OpenBSD amd64
run: ./odin check examples/all -vet -strict-style -disallow-do -target:openbsd_amd64
if: matrix.os == 'ubuntu-latest'
- name: Odin check vendor/sdl3 for Linux i386
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_i386
if: matrix.os == 'ubuntu-latest'
- name: Odin check vendor/sdl3 for Linux arm64
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_arm64
if: matrix.os == 'ubuntu-latest'
- name: Odin check vendor/sdl3 for FreeBSD amd64
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:freebsd_amd64
if: matrix.os == 'ubuntu-latest'
- name: Odin check vendor/sdl3 for OpenBSD amd64
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:openbsd_amd64
if: matrix.os == 'ubuntu-latest'
- name: Run demo on WASI WASM32
run: |
./odin build examples/demo -target:wasi_wasm32 -vet -strict-style -disallow-do -out:demo
wasmtime ./demo.wasm
if: matrix.os == 'macos-14'
- name: Check benchmarks
run: ./odin check tests/benchmark -vet -strict-style -no-entry-point
- name: Odin check examples/all for Linux i386
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_i386
- name: Odin check examples/all for Linux arm64
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_arm64
- name: Odin check examples/all for FreeBSD amd64
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64
- name: Odin check examples/all for OpenBSD amd64
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -target:openbsd_amd64
- name: Odin check examples/all for js_wasm32
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -no-entry-point -target:js_wasm32
- name: Odin check examples/all for js_wasm64p32
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -no-entry-point -target:js_wasm64p32
- name: Odin check examples/all/sdl3 for Linux i386
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_i386
- name: Odin check examples/all/sdl3 for Linux arm64
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_arm64
- name: Odin check examples/all/sdl3 for FreeBSD amd64
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:freebsd_amd64
- name: Odin check examples/all/sdl3 for OpenBSD amd64
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:openbsd_amd64
build_windows:
name: Windows Build, Check, and Test
runs-on: windows-2022
@@ -206,32 +214,32 @@ jobs:
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin check examples/all -vet -strict-style -disallow-do
- name: Odin check vendor/sdl3
- name: Odin check examples/all/sdl3
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point
odin check examples/all/sdl3 -vet -strict-style -disallow-do -no-entry-point
- name: Core library tests
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
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
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 -sanitize:address
- name: Optimized core library tests
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
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
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 -sanitize:address
- name: Vendor library tests
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
copy vendor\lua\5.4\windows\*.dll .
odin test tests/vendor -all-packages -vet -strict-style -disallow-do -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 -sanitize:address
- name: Odin internals tests
shell: cmd
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
odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Check issues
shell: cmd
run: |
@@ -293,8 +301,8 @@ jobs:
- name: Odin check examples/all
run: ./odin check examples/all -target:linux_riscv64 -vet -strict-style -disallow-do
- name: Odin check vendor/sdl3
run: ./odin check vendor/sdl3 -target:linux_riscv64 -vet -strict-style -disallow-do -no-entry-point
- name: Odin check examples/all/sdl3
run: ./odin check examples/all/sdl3 -target:linux_riscv64 -vet -strict-style -disallow-do -no-entry-point
- name: Install riscv64 toolchain and qemu
run: sudo apt-get install -y qemu-user qemu-user-static gcc-12-riscv64-linux-gnu libc6-riscv64-cross

View File

@@ -49,12 +49,12 @@ jobs:
- uses: actions/checkout@v4
- uses: jirutka/setup-alpine@v1
with:
branch: v3.20
branch: edge
- name: (Linux) Download LLVM
run: |
apk add --no-cache \
musl-dev llvm18-dev clang18 git mold lz4 \
libxml2-static llvm18-static zlib-static zstd-static \
musl-dev llvm20-dev clang20 git mold lz4 \
libxml2-static llvm20-static zlib-static zstd-static \
make
shell: alpine.sh --root {0}
- name: build odin
@@ -93,8 +93,9 @@ jobs:
- uses: actions/checkout@v4
- name: Download LLVM and setup PATH
run: |
brew install llvm@18 dylibbundler
echo "/usr/local/opt/llvm@18/bin" >> $GITHUB_PATH
brew update
brew install llvm@20 dylibbundler lld
- name: build odin
# These -L makes the linker prioritize system libraries over LLVM libraries, this is mainly to
# not link with libunwind bundled with LLVM but link with libunwind on the system.
@@ -130,8 +131,9 @@ jobs:
- uses: actions/checkout@v4
- name: Download LLVM and setup PATH
run: |
brew install llvm@18 dylibbundler
echo "/opt/homebrew/opt/llvm@18/bin" >> $GITHUB_PATH
brew update
brew install llvm@20 dylibbundler lld
- name: build odin
# These -L makes the linker prioritize system libraries over LLVM libraries, this is mainly to
# not link with libunwind bundled with LLVM but link with libunwind on the system.

3
.gitignore vendored
View File

@@ -293,5 +293,6 @@ build.sh
# RAD debugger project file
*.raddbg
*.rdi
tests/issues/build/*
misc/featuregen/featuregen

View File

@@ -15,7 +15,7 @@
<img src="https://img.shields.io/discord/568138951836172421?logo=discord">
</a>
<a href="https://github.com/odin-lang/odin/actions">
<img src="https://github.com/odin-lang/odin/workflows/CI/badge.svg?branch=master&event=push">
<img src="https://github.com/odin-lang/odin/actions/workflows/ci.yml/badge.svg?branch=master&event=push">
</a>
</p>

View File

@@ -7,13 +7,232 @@ nil :: nil
false :: 0!=0
true :: 0==0
ODIN_OS :: ODIN_OS
ODIN_ARCH :: ODIN_ARCH
ODIN_ENDIAN :: ODIN_ENDIAN
ODIN_VENDOR :: ODIN_VENDOR
ODIN_VERSION :: ODIN_VERSION
ODIN_ROOT :: ODIN_ROOT
ODIN_DEBUG :: ODIN_DEBUG
// The following constants are added in `checker.cpp`'s `init_universal` procedure.
/*
An `enum` value indicating the target's CPU architecture.
Possible values are: `.amd64`, `.i386`, `.arm32`, `.arm64`, `.wasm32`, `.wasm64p32`, and `.riscv64`.
*/
ODIN_ARCH :: ODIN_ARCH
/*
A `string` indicating the target's CPU architecture.
Possible values are: "amd64", "i386", "arm32", "arm64", "wasm32", "wasm64p32", "riscv64".
*/
ODIN_ARCH_STRING :: ODIN_ARCH_STRING
/*
An `enum` value indicating the type of compiled output, chosen using `-build-mode`.
Possible values are: `.Executable`, `.Dynamic`, `.Static`, `.Object`, `.Assembly`, and `.LLVM_IR`.
*/
ODIN_BUILD_MODE :: ODIN_BUILD_MODE
/*
A `string` containing the name of the folder that contains the entry point,
e.g. for `%ODIN_ROOT%/examples/demo`, this would contain `demo`.
*/
ODIN_BUILD_PROJECT_NAME :: ODIN_BUILD_PROJECT_NAME
/*
An `i64` containing the time at which the executable was compiled, in nanoseconds.
This is compatible with the `time.Time` type, i.e. `time.Time{_nsec=ODIN_COMPILE_TIMESTAMP}`
*/
ODIN_COMPILE_TIMESTAMP :: ODIN_COMPILE_TIMESTAMP
/*
`true` if the `-debug` command line switch is passed, which enables debug info generation.
*/
ODIN_DEBUG :: ODIN_DEBUG
/*
`true` if the `-default-to-nil-allocator` command line switch is passed,
which sets the initial `context.allocator` to an allocator that does nothing.
*/
ODIN_DEFAULT_TO_NIL_ALLOCATOR :: ODIN_DEFAULT_TO_NIL_ALLOCATOR
/*
`true` if the `-default-to-panic-allocator` command line switch is passed,
which sets the initial `context.allocator` to an allocator that panics if allocated from.
*/
ODIN_DEFAULT_TO_PANIC_ALLOCATOR :: ODIN_DEFAULT_TO_PANIC_ALLOCATOR
/*
`true` if the `-disable-assert` command line switch is passed,
which removes all calls to `assert` from the program.
*/
ODIN_DISABLE_ASSERT :: ODIN_DISABLE_ASSERT
/*
An `enum` value indicating the endianness of the target.
Possible values are: `.Little` and `.Big`.
*/
ODIN_ENDIAN :: ODIN_ENDIAN
/*
An `string` indicating the endianness of the target.
Possible values are: "little" and "big".
*/
ODIN_ENDIAN_STRING :: ODIN_ENDIAN_STRING
/*
An `enum` value set using the `-error-pos-style` switch, indicating the source location style used for compile errors and warnings.
Possible values are: `.Default` (Odin-style) and `.Unix`.
*/
ODIN_ERROR_POS_STYLE :: ODIN_ERROR_POS_STYLE
/*
`true` if the `-foreign-error-procedures` command line switch is passed,
which inhibits generation of runtime error procedures, so that they can be in a separate compilation unit.
*/
ODIN_FOREIGN_ERROR_PROCEDURES :: ODIN_FOREIGN_ERROR_PROCEDURES
/*
A `string` describing the microarchitecture used for code generation.
If not set using the `-microarch` command line switch, the compiler will pick a default.
Possible values include, but are not limited to: "sandybridge", "x86-64-v2".
*/
ODIN_MICROARCH_STRING :: ODIN_MICROARCH_STRING
/*
An `int` value representing the minimum OS version given to the linker, calculated as `major * 10_000 + minor * 100 + revision`.
If not set using the `-minimum-os-version` command line switch, it defaults to `0`, except on Darwin, where it's `11_00_00`.
*/
ODIN_MINIMUM_OS_VERSION :: ODIN_MINIMUM_OS_VERSION
/*
`true` if the `-no-bounds-check` command line switch is passed, which disables bounds checking at runtime.
*/
ODIN_NO_BOUNDS_CHECK :: ODIN_NO_BOUNDS_CHECK
/*
`true` if the `-no-crt` command line switch is passed, which inhibits linking with the C Runtime Library, a.k.a. LibC.
*/
ODIN_NO_CRT :: ODIN_NO_CRT
/*
`true` if the `-no-entry-point` command line switch is passed, which makes the declaration of a `main` procedure optional.
*/
ODIN_NO_ENTRY_POINT :: ODIN_NO_ENTRY_POINT
/*
`true` if the `-no-rtti` command line switch is passed, which inhibits generation of full Runtime Type Information.
*/
ODIN_NO_RTTI :: ODIN_NO_RTTI
/*
`true` if the `-no-type-assert` command line switch is passed, which disables type assertion checking program wide.
*/
ODIN_NO_TYPE_ASSERT :: ODIN_NO_TYPE_ASSERT
/*
An `enum` value indicating the optimization level selected using the `-o` command line switch.
Possible values are: `.None`, `.Minimal`, `.Size`, `.Speed`, and `.Aggressive`.
If `ODIN_OPTIMIZATION_MODE` is anything other than `.None` or `.Minimal`, the compiler will also perform a unity build,
and `ODIN_USE_SEPARATE_MODULES` will be set to `false` as a result.
*/
ODIN_OPTIMIZATION_MODE :: ODIN_OPTIMIZATION_MODE
/*
An `enum` value indicating what the target operating system is.
*/
ODIN_OS :: ODIN_OS
/*
A `string` indicating what the target operating system is.
*/
ODIN_OS_STRING :: ODIN_OS_STRING
/*
An `enum` value indicating the platform subtarget, chosen using the `-subtarget` switch.
Possible values are: `.Default` `.iOS`, and `.Android`.
*/
ODIN_PLATFORM_SUBTARGET :: ODIN_PLATFORM_SUBTARGET
/*
A `string` representing the path of the folder containing the Odin compiler,
relative to which we expect to find the `base` and `core` package collections.
*/
ODIN_ROOT :: ODIN_ROOT
/*
A `bit_set` indicating the sanitizer flags set using the `-sanitize` command line switch.
Supported flags are `.Address`, `.Memory`, and `.Thread`.
*/
ODIN_SANITIZER_FLAGS :: ODIN_SANITIZER_FLAGS
/*
`true` if the code is being compiled via an invocation of `odin test`.
*/
ODIN_TEST :: ODIN_TEST
/*
`true` if built using the experimental Tilde backend.
*/
ODIN_TILDE :: ODIN_TILDE
/*
`true` by default, meaning each each package is built into its own object file, and then linked together.
`false` if the `-use-single-module` command line switch to force a unity build is provided.
If `ODIN_OPTIMIZATION_MODE` is anything other than `.None` or `.Minimal`, the compiler will also perform a unity build,
and this constant will also be set to `false`.
*/
ODIN_USE_SEPARATE_MODULES :: ODIN_USE_SEPARATE_MODULES
/*
`true` if Valgrind integration is supported on the target.
*/
ODIN_VALGRIND_SUPPORT :: ODIN_VALGRIND_SUPPORT
/*
A `string` which identifies the compiler being used. The official compiler sets this to `"odin"`.
*/
ODIN_VENDOR :: ODIN_VENDOR
/*
A `string` containing the version of the Odin compiler, typically in the format `dev-YYYY-MM`.
*/
ODIN_VERSION :: ODIN_VERSION
/*
A `string` containing the Git hash part of the Odin version.
Empty if `.git` could not be detected at the time the compiler was built.
*/
ODIN_VERSION_HASH :: ODIN_VERSION_HASH
/*
An `enum` set by the `-subsystem` flag, specifying which Windows subsystem the PE file was created for.
Possible values are:
`.Unknown` - Default and only value on non-Windows platforms
`.Console` - Default on Windows
`.Windows` - Can be used by graphical applications so Windows doesn't open an empty console
There are some other possible values for e.g. EFI applications, but only Console and Windows are supported.
See also: https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header64
*/
ODIN_WINDOWS_SUBSYSTEM :: ODIN_WINDOWS_SUBSYSTEM
/*
An `string` set by the `-subsystem` flag, specifying which Windows subsystem the PE file was created for.
Possible values are:
"UNKNOWN" - Default and only value on non-Windows platforms
"CONSOLE" - Default on Windows
"WINDOWS" - Can be used by graphical applications so Windows doesn't open an empty console
There are some other possible values for e.g. EFI applications, but only Console and Windows are supported.
See also: https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header64
*/
ODIN_WINDOWS_SUBSYSTEM_STRING :: ODIN_WINDOWS_SUBSYSTEM_STRING
/*
`true` if LLVM supports the f16 type.
*/
__ODIN_LLVM_F16_SUPPORTED :: __ODIN_LLVM_F16_SUPPORTED
byte :: u8 // alias
@@ -119,7 +338,8 @@ jmag :: proc(value: Quaternion) -> Float ---
kmag :: proc(value: Quaternion) -> Float ---
conj :: proc(value: Complex_Or_Quaternion) -> Complex_Or_Quaternion ---
expand_values :: proc(value: Struct_Or_Array) -> (A, B, C, ...) ---
expand_values :: proc(value: Struct_Or_Array) -> (A, B, C, ...) ---
compress_values :: proc(values: ...) -> Struct_Or_Array_Like_Type ---
min :: proc(values: ..T) -> T ---
max :: proc(values: ..T) -> T ---
@@ -130,3 +350,6 @@ soa_zip :: proc(slices: ...) -> #soa[]Struct ---
soa_unzip :: proc(value: $S/#soa[]$E) -> (slices: ...) ---
unreachable :: proc() -> ! ---
// Where T is a string, slice, dynamic array, or pointer to an array type
raw_data :: proc(t: $T) -> rawptr

View File

@@ -169,6 +169,7 @@ type_is_union :: proc($T: typeid) -> bool ---
type_is_enum :: proc($T: typeid) -> bool ---
type_is_proc :: proc($T: typeid) -> bool ---
type_is_bit_set :: proc($T: typeid) -> bool ---
type_is_bit_field :: proc($T: typeid) -> bool ---
type_is_simd_vector :: proc($T: typeid) -> bool ---
type_is_matrix :: proc($T: typeid) -> bool ---
@@ -221,6 +222,9 @@ type_map_cell_info :: proc($T: typeid) -> ^runtime.Map_Cell_Info ---
type_convert_variants_to_pointers :: proc($T: typeid) -> typeid where type_is_union(T) ---
type_merge :: proc($U, $V: typeid) -> typeid where type_is_union(U), type_is_union(V) ---
type_integer_to_unsigned :: proc($T: typeid) -> type where type_is_integer(T), !type_is_unsigned(T) ---
type_integer_to_signed :: proc($T: typeid) -> type where type_is_integer(T), type_is_unsigned(T) ---
type_has_shared_fields :: proc($U, $V: typeid) -> bool where type_is_struct(U), type_is_struct(V) ---
constant_utf16_cstring :: proc($literal: string) -> [^]u16 ---
@@ -274,8 +278,12 @@ simd_lanes_ge :: proc(a, b: #simd[N]T) -> #simd[N]Integer ---
simd_extract :: proc(a: #simd[N]T, idx: uint) -> T ---
simd_replace :: proc(a: #simd[N]T, idx: uint, elem: T) -> #simd[N]T ---
simd_reduce_add_bisect :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_mul_bisect :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_add_ordered :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_mul_ordered :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_add_pairs :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_mul_pairs :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_min :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_max :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_and :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
@@ -298,7 +306,7 @@ simd_masked_store :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U)
simd_masked_expand_load :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U) -> #simd[N]T where type_is_integer(U) || type_is_boolean(U) ---
simd_masked_compress_store :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U) where type_is_integer(U) || type_is_boolean(U) ---
simd_indices :: proc($T: typeid/#simd[$N]$E) -> T where type_is_numeric(T) ---
simd_shuffle :: proc(a, b: #simd[N]T, indices: ..int) -> #simd[len(indices)]T ---
simd_select :: proc(cond: #simd[N]boolean_or_integer, true, false: #simd[N]T) -> #simd[N]T ---
@@ -353,15 +361,18 @@ x86_xgetbv :: proc(cx: u32) -> (eax, edx: u32) ---
objc_object :: struct{}
objc_selector :: struct{}
objc_class :: struct{}
objc_ivar :: struct{}
objc_id :: ^objc_object
objc_SEL :: ^objc_selector
objc_Class :: ^objc_class
objc_Ivar :: ^objc_ivar
objc_find_selector :: proc($name: string) -> objc_SEL ---
objc_register_selector :: proc($name: string) -> objc_SEL ---
objc_find_class :: proc($name: string) -> objc_Class ---
objc_register_class :: proc($name: string) -> objc_Class ---
objc_ivar_get :: proc(self: ^$T) -> ^$U ---
valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2, a3, a4: uintptr) -> uintptr ---

View File

@@ -125,7 +125,6 @@ Type_Info_Parameters :: struct { // Only used for procedures parameters and resu
types: []^Type_Info,
names: []string,
}
Type_Info_Tuple :: Type_Info_Parameters // Will be removed eventually
Type_Info_Struct_Flags :: distinct bit_set[Type_Info_Struct_Flag; u8]
Type_Info_Struct_Flag :: enum u8 {
@@ -587,10 +586,14 @@ ALL_ODIN_OS_TYPES :: Odin_OS_Types{
Odin_Platform_Subtarget_Type :: enum int {
Default,
iOS,
Android,
}
*/
Odin_Platform_Subtarget_Type :: type_of(ODIN_PLATFORM_SUBTARGET)
Odin_Platform_Subtarget_Types :: bit_set[Odin_Platform_Subtarget_Type]
/*
// Defined internally by the compiler
Odin_Sanitizer_Flag :: enum u32 {

View File

@@ -648,6 +648,9 @@ append_nothing :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (n: i
@builtin
inject_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
when !ODIN_NO_BOUNDS_CHECK {
ensure(index >= 0, "Index must be positive.", loc)
}
if array == nil {
return
}
@@ -666,6 +669,9 @@ inject_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcas
@builtin
inject_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
when !ODIN_NO_BOUNDS_CHECK {
ensure(index >= 0, "Index must be positive.", loc)
}
if array == nil {
return
}
@@ -689,6 +695,9 @@ inject_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadca
@builtin
inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, #any_int index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
when !ODIN_NO_BOUNDS_CHECK {
ensure(index >= 0, "Index must be positive.", loc)
}
if array == nil {
return
}

View File

@@ -1,6 +1,7 @@
package runtime
import "base:intrinsics"
import "base:sanitizer"
DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE)
@@ -43,6 +44,8 @@ memory_block_alloc :: proc(allocator: Allocator, capacity: uint, alignment: uint
block.base = ([^]byte)(uintptr(block) + base_offset)
block.capacity = uint(end - uintptr(block.base))
sanitizer.address_poison(block.base, block.capacity)
// Should be zeroed
assert(block.used == 0)
assert(block.prev == nil)
@@ -52,6 +55,7 @@ memory_block_alloc :: proc(allocator: Allocator, capacity: uint, alignment: uint
memory_block_dealloc :: proc(block_to_free: ^Memory_Block, loc := #caller_location) {
if block_to_free != nil {
allocator := block_to_free.allocator
sanitizer.address_unpoison(block_to_free.base, block_to_free.capacity)
mem_free(block_to_free, allocator, loc)
}
}
@@ -83,6 +87,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint)
return
}
data = block.base[block.used+alignment_offset:][:min_size]
sanitizer.address_unpoison(block.base[block.used:block.used+size])
block.used += size
return
}
@@ -162,6 +167,7 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
if arena.curr_block != nil {
intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used)
arena.curr_block.used = 0
sanitizer.address_poison(arena.curr_block.base, arena.curr_block.capacity)
}
arena.total_used = 0
}
@@ -226,6 +232,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
// grow data in-place, adjusting next allocation
block.used = uint(new_end)
data = block.base[start:new_end]
sanitizer.address_unpoison(data)
return
}
}
@@ -299,6 +306,7 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
assert(block.used >= temp.used, "out of order use of arena_temp_end", loc)
amount_to_zero := block.used-temp.used
intrinsics.mem_zero(block.base[temp.used:], amount_to_zero)
sanitizer.address_poison(block.base[temp.used:block.capacity])
block.used = temp.used
arena.total_used -= amount_to_zero
}

View File

@@ -1029,3 +1029,32 @@ default_hasher_cstring :: proc "contextless" (data: rawptr, seed: uintptr) -> ui
h &= HASH_MASK
return uintptr(h) | uintptr(uintptr(h) == 0)
}
default_hasher_f64 :: proc "contextless" (f: f64, seed: uintptr) -> uintptr {
f := f
buf: [size_of(f)]u8
if f == 0 {
return default_hasher(&buf, seed, size_of(buf))
}
if f != f {
// TODO(bill): What should the logic be for NaNs?
return default_hasher(&f, seed, size_of(f))
}
return default_hasher(&f, seed, size_of(f))
}
default_hasher_complex128 :: proc "contextless" (x, y: f64, seed: uintptr) -> uintptr {
seed := seed
seed = default_hasher_f64(x, seed)
seed = default_hasher_f64(y, seed)
return seed
}
default_hasher_quaternion256 :: proc "contextless" (x, y, z, w: f64, seed: uintptr) -> uintptr {
seed := seed
seed = default_hasher_f64(x, seed)
seed = default_hasher_f64(y, seed)
seed = default_hasher_f64(z, seed)
seed = default_hasher_f64(w, seed)
return seed
}

View File

@@ -1,5 +1,7 @@
package runtime
import "../sanitizer"
foreign import kernel32 "system:Kernel32.lib"
@(private="file")
@@ -16,7 +18,10 @@ foreign kernel32 {
_heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
HEAP_ZERO_MEMORY :: 0x00000008
return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size))
ptr := HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size))
// NOTE(lucas): asan not guarunteed to unpoison win32 heap out of the box, do it ourselves
sanitizer.address_unpoison(ptr, size)
return ptr
}
_heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
if new_size == 0 {
@@ -28,7 +33,10 @@ _heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
}
HEAP_ZERO_MEMORY :: 0x00000008
return HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, uint(new_size))
new_ptr := HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, uint(new_size))
// NOTE(lucas): asan not guarunteed to unpoison win32 heap out of the box, do it ourselves
sanitizer.address_unpoison(new_ptr, new_size)
return new_ptr
}
_heap_free :: proc "contextless" (ptr: rawptr) {
if ptr == nil {

View File

@@ -16,6 +16,12 @@ RUNTIME_REQUIRE :: false // !ODIN_TILDE
@(private)
__float16 :: f16 when __ODIN_LLVM_F16_SUPPORTED else u16
HAS_HARDWARE_SIMD :: false when (ODIN_ARCH == .amd64 || ODIN_ARCH == .i386) && !intrinsics.has_target_feature("sse2") else
false when (ODIN_ARCH == .arm64 || ODIN_ARCH == .arm32) && !intrinsics.has_target_feature("neon") else
false when (ODIN_ARCH == .wasm64p32 || ODIN_ARCH == .wasm32) && !intrinsics.has_target_feature("simd128") else
false when (ODIN_ARCH == .riscv64) && !intrinsics.has_target_feature("v") else
true
@(private)
byte_slice :: #force_inline proc "contextless" (data: rawptr, len: int) -> []byte #no_bounds_check {
@@ -229,151 +235,242 @@ memory_equal :: proc "contextless" (x, y: rawptr, n: int) -> bool {
case n == 0: return true
case x == y: return true
}
a, b := ([^]byte)(x), ([^]byte)(y)
length := uint(n)
a, b := cast([^]byte)x, cast([^]byte)y
for i := uint(0); i < length; i += 1 {
n := uint(n)
i := uint(0)
m := uint(0)
if n >= 8 {
when HAS_HARDWARE_SIMD {
// Avoid using 256-bit SIMD on platforms where its emulation is
// likely to be less than ideal.
when ODIN_ARCH == .amd64 && intrinsics.has_target_feature("avx2") {
m = n / 32 * 32
for /**/; i < m; i += 32 {
load_a := intrinsics.unaligned_load(cast(^#simd[32]u8)&a[i])
load_b := intrinsics.unaligned_load(cast(^#simd[32]u8)&b[i])
ne := intrinsics.simd_lanes_ne(load_a, load_b)
if intrinsics.simd_reduce_or(ne) != 0 {
return false
}
}
}
}
m = (n-i) / 16 * 16
for /**/; i < m; i += 16 {
load_a := intrinsics.unaligned_load(cast(^#simd[16]u8)&a[i])
load_b := intrinsics.unaligned_load(cast(^#simd[16]u8)&b[i])
ne := intrinsics.simd_lanes_ne(load_a, load_b)
if intrinsics.simd_reduce_or(ne) != 0 {
return false
}
}
m = (n-i) / 8 * 8
for /**/; i < m; i += 8 {
if intrinsics.unaligned_load(cast(^uintptr)&a[i]) != intrinsics.unaligned_load(cast(^uintptr)&b[i]) {
return false
}
}
}
for /**/; i < n; i += 1 {
if a[i] != b[i] {
return false
}
}
return true
/*
when size_of(uint) == 8 {
if word_length := length >> 3; word_length != 0 {
for _ in 0..<word_length {
if intrinsics.unaligned_load((^u64)(a)) != intrinsics.unaligned_load((^u64)(b)) {
return false
}
a = a[size_of(u64):]
b = b[size_of(u64):]
}
}
if length & 4 != 0 {
if intrinsics.unaligned_load((^u32)(a)) != intrinsics.unaligned_load((^u32)(b)) {
return false
}
a = a[size_of(u32):]
b = b[size_of(u32):]
}
if length & 2 != 0 {
if intrinsics.unaligned_load((^u16)(a)) != intrinsics.unaligned_load((^u16)(b)) {
return false
}
a = a[size_of(u16):]
b = b[size_of(u16):]
}
if length & 1 != 0 && a[0] != b[0] {
return false
}
return true
} else {
if word_length := length >> 2; word_length != 0 {
for _ in 0..<word_length {
if intrinsics.unaligned_load((^u32)(a)) != intrinsics.unaligned_load((^u32)(b)) {
return false
}
a = a[size_of(u32):]
b = b[size_of(u32):]
}
}
length &= 3
if length != 0 {
for i in 0..<length {
if a[i] != b[i] {
return false
}
}
}
return true
}
*/
}
memory_compare :: proc "contextless" (a, b: rawptr, n: int) -> int #no_bounds_check {
memory_compare :: proc "contextless" (x, y: rawptr, n: int) -> int #no_bounds_check {
switch {
case a == b: return 0
case a == nil: return -1
case b == nil: return +1
case x == y: return 0
case x == nil: return -1
case y == nil: return +1
}
a, b := cast([^]byte)x, cast([^]byte)y
n := uint(n)
i := uint(0)
m := uint(0)
x := uintptr(a)
y := uintptr(b)
n := uintptr(n)
SU :: size_of(uintptr)
fast := n/SU + 1
offset := (fast-1)*SU
curr_block := uintptr(0)
if n < SU {
fast = 0
}
for /**/; curr_block < fast; curr_block += 1 {
va := (^uintptr)(x + curr_block * size_of(uintptr))^
vb := (^uintptr)(y + curr_block * size_of(uintptr))^
if va ~ vb != 0 {
for pos := curr_block*SU; pos < n; pos += 1 {
a := (^byte)(x+pos)^
b := (^byte)(y+pos)^
if a ~ b != 0 {
return -1 if (int(a) - int(b)) < 0 else +1
when HAS_HARDWARE_SIMD {
when ODIN_ARCH == .amd64 && intrinsics.has_target_feature("avx2") {
m = n / 32 * 32
for /**/; i < m; i += 32 {
load_a := intrinsics.unaligned_load(cast(^#simd[32]u8)&a[i])
load_b := intrinsics.unaligned_load(cast(^#simd[32]u8)&b[i])
comparison := intrinsics.simd_lanes_ne(load_a, load_b)
if intrinsics.simd_reduce_or(comparison) != 0 {
sentinel: #simd[32]u8 = u8(0xFF)
indices := intrinsics.simd_indices(#simd[32]u8)
index_select := intrinsics.simd_select(comparison, indices, sentinel)
index_reduce := cast(uint)intrinsics.simd_reduce_min(index_select)
return -1 if a[i+index_reduce] < b[i+index_reduce] else +1
}
}
}
}
for /**/; offset < n; offset += 1 {
a := (^byte)(x+offset)^
b := (^byte)(y+offset)^
if a ~ b != 0 {
return -1 if (int(a) - int(b)) < 0 else +1
m = (n-i) / 16 * 16
for /**/; i < m; i += 16 {
load_a := intrinsics.unaligned_load(cast(^#simd[16]u8)&a[i])
load_b := intrinsics.unaligned_load(cast(^#simd[16]u8)&b[i])
comparison := intrinsics.simd_lanes_ne(load_a, load_b)
if intrinsics.simd_reduce_or(comparison) != 0 {
sentinel: #simd[16]u8 = u8(0xFF)
indices := intrinsics.simd_indices(#simd[16]u8)
index_select := intrinsics.simd_select(comparison, indices, sentinel)
index_reduce := cast(uint)intrinsics.simd_reduce_min(index_select)
return -1 if a[i+index_reduce] < b[i+index_reduce] else +1
}
}
// 64-bit SIMD is faster than using a `uintptr` to detect a difference then
// re-iterating with the byte-by-byte loop, at least on AMD64.
m = (n-i) / 8 * 8
for /**/; i < m; i += 8 {
load_a := intrinsics.unaligned_load(cast(^#simd[8]u8)&a[i])
load_b := intrinsics.unaligned_load(cast(^#simd[8]u8)&b[i])
comparison := intrinsics.simd_lanes_ne(load_a, load_b)
if intrinsics.simd_reduce_or(comparison) != 0 {
sentinel: #simd[8]u8 = u8(0xFF)
indices := intrinsics.simd_indices(#simd[8]u8)
index_select := intrinsics.simd_select(comparison, indices, sentinel)
index_reduce := cast(uint)intrinsics.simd_reduce_min(index_select)
return -1 if a[i+index_reduce] < b[i+index_reduce] else +1
}
}
for /**/; i < n; i += 1 {
if a[i] ~ b[i] != 0 {
return -1 if int(a[i]) - int(b[i]) < 0 else +1
}
}
return 0
}
memory_compare_zero :: proc "contextless" (a: rawptr, n: int) -> int #no_bounds_check {
x := uintptr(a)
n := uintptr(n)
n := uint(n)
i := uint(0)
m := uint(0)
SU :: size_of(uintptr)
fast := n/SU + 1
offset := (fast-1)*SU
curr_block := uintptr(0)
if n < SU {
fast = 0
// Because we're comparing against zero, we never return -1, as that would
// indicate the compared value is less than zero.
//
// Note that a zero return value here means equality.
bytes := ([^]u8)(a)
if n >= 8 {
when HAS_HARDWARE_SIMD {
when ODIN_ARCH == .amd64 && intrinsics.has_target_feature("avx2") {
scanner32: #simd[32]u8
m = n / 32 * 32
for /**/; i < m; i += 32 {
load := intrinsics.unaligned_load(cast(^#simd[32]u8)&bytes[i])
ne := intrinsics.simd_lanes_ne(scanner32, load)
if intrinsics.simd_reduce_or(ne) > 0 {
return 1
}
}
}
}
scanner16: #simd[16]u8
m = (n-i) / 16 * 16
for /**/; i < m; i += 16 {
load := intrinsics.unaligned_load(cast(^#simd[16]u8)&bytes[i])
ne := intrinsics.simd_lanes_ne(scanner16, load)
if intrinsics.simd_reduce_or(ne) != 0 {
return 1
}
}
m = (n-i) / 8 * 8
for /**/; i < m; i += 8 {
if intrinsics.unaligned_load(cast(^uintptr)&bytes[i]) != 0 {
return 1
}
}
}
for /**/; curr_block < fast; curr_block += 1 {
va := (^uintptr)(x + curr_block * size_of(uintptr))^
if va ~ 0 != 0 {
for pos := curr_block*SU; pos < n; pos += 1 {
a := (^byte)(x+pos)^
if a ~ 0 != 0 {
return -1 if int(a) < 0 else +1
for /**/; i < n; i += 1 {
if bytes[i] != 0 {
return 1
}
}
return 0
}
memory_prefix_length :: proc "contextless" (x, y: rawptr, n: int) -> (idx: int) #no_bounds_check {
switch {
case x == y: return n
case x == nil: return 0
case y == nil: return 0
}
a, b := cast([^]byte)x, cast([^]byte)y
n := uint(n)
i := uint(0)
m := uint(0)
when HAS_HARDWARE_SIMD {
when ODIN_ARCH == .amd64 && intrinsics.has_target_feature("avx2") {
m = n / 32 * 32
for /**/; i < m; i += 32 {
load_a := intrinsics.unaligned_load(cast(^#simd[32]u8)&a[i])
load_b := intrinsics.unaligned_load(cast(^#simd[32]u8)&b[i])
comparison := intrinsics.simd_lanes_ne(load_a, load_b)
if intrinsics.simd_reduce_or(comparison) != 0 {
sentinel: #simd[32]u8 = u8(0xFF)
indices := intrinsics.simd_indices(#simd[32]u8)
index_select := intrinsics.simd_select(comparison, indices, sentinel)
index_reduce := cast(uint)intrinsics.simd_reduce_min(index_select)
return int(i + index_reduce)
}
}
}
}
for /**/; offset < n; offset += 1 {
a := (^byte)(x+offset)^
if a ~ 0 != 0 {
return -1 if int(a) < 0 else +1
m = (n-i) / 16 * 16
for /**/; i < m; i += 16 {
load_a := intrinsics.unaligned_load(cast(^#simd[16]u8)&a[i])
load_b := intrinsics.unaligned_load(cast(^#simd[16]u8)&b[i])
comparison := intrinsics.simd_lanes_ne(load_a, load_b)
if intrinsics.simd_reduce_or(comparison) != 0 {
sentinel: #simd[16]u8 = u8(0xFF)
indices := intrinsics.simd_indices(#simd[16]u8)
index_select := intrinsics.simd_select(comparison, indices, sentinel)
index_reduce := cast(uint)intrinsics.simd_reduce_min(index_select)
return int(i + index_reduce)
}
}
return 0
// 64-bit SIMD is faster than using a `uintptr` to detect a difference then
// re-iterating with the byte-by-byte loop, at least on AMD64.
m = (n-i) / 8 * 8
for /**/; i < m; i += 8 {
load_a := intrinsics.unaligned_load(cast(^#simd[8]u8)&a[i])
load_b := intrinsics.unaligned_load(cast(^#simd[8]u8)&b[i])
comparison := intrinsics.simd_lanes_ne(load_a, load_b)
if intrinsics.simd_reduce_or(comparison) != 0 {
sentinel: #simd[8]u8 = u8(0xFF)
indices := intrinsics.simd_indices(#simd[8]u8)
index_select := intrinsics.simd_select(comparison, indices, sentinel)
index_reduce := cast(uint)intrinsics.simd_reduce_min(index_select)
return int(i + index_reduce)
}
}
for /**/; i < n; i += 1 {
if a[i] ~ b[i] != 0 {
return int(i)
}
}
return int(n)
}
string_eq :: proc "contextless" (lhs, rhs: string) -> bool {
@@ -1106,3 +1203,11 @@ __read_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uin
dst[j>>3] |= the_bit<<(j&7)
}
}
when .Address in ODIN_SANITIZER_FLAGS {
foreign {
@(require)
__asan_unpoison_memory_region :: proc "system" (address: rawptr, size: uint) ---
}
}

View File

@@ -2,21 +2,34 @@
package runtime
@(priority_index=-1e6)
foreign import "system:Foundation.framework"
foreign import ObjC "system:objc"
import "base:intrinsics"
objc_id :: ^intrinsics.objc_object
objc_id :: ^intrinsics.objc_object
objc_Class :: ^intrinsics.objc_class
objc_SEL :: ^intrinsics.objc_selector
objc_SEL :: ^intrinsics.objc_selector
objc_Ivar :: ^intrinsics.objc_ivar
objc_BOOL :: bool
foreign Foundation {
objc_lookUpClass :: proc "c" (name: cstring) -> objc_Class ---
objc_IMP :: proc "c" (object: objc_id, sel: objc_SEL, #c_vararg args: ..any) -> objc_id
foreign ObjC {
sel_registerName :: proc "c" (name: cstring) -> objc_SEL ---
objc_allocateClassPair :: proc "c" (superclass: objc_Class, name: cstring, extraBytes: uint) -> objc_Class ---
objc_msgSend :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) ---
objc_msgSend_fpret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> f64 ---
objc_msgSend_fp2ret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> complex128 ---
objc_msgSend_stret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) ---
objc_lookUpClass :: proc "c" (name: cstring) -> objc_Class ---
objc_allocateClassPair :: proc "c" (superclass: objc_Class, name: cstring, extraBytes: uint) -> objc_Class ---
objc_registerClassPair :: proc "c" (cls : objc_Class) ---
class_addMethod :: proc "c" (cls: objc_Class, name: objc_SEL, imp: objc_IMP, types: cstring) -> objc_BOOL ---
class_addIvar :: proc "c" (cls: objc_Class, name: cstring, size: uint, alignment: u8, types: cstring) -> objc_BOOL ---
class_getInstanceVariable :: proc "c" (cls : objc_Class, name: cstring) -> objc_Ivar ---
class_getInstanceSize :: proc "c" (cls : objc_Class) -> uint ---
ivar_getOffset :: proc "c" (v: objc_Ivar) -> uintptr ---
}

601
base/sanitizer/address.odin Normal file
View File

@@ -0,0 +1,601 @@
#+no-instrumentation
package sanitizer
Address_Death_Callback :: #type proc "c" (pc: rawptr, bp: rawptr, sp: rawptr, addr: rawptr, is_write: i32, access_size: uint)
@(private="file")
ASAN_ENABLED :: .Address in ODIN_SANITIZER_FLAGS
@(private="file")
@(default_calling_convention="system")
foreign {
__asan_poison_memory_region :: proc(address: rawptr, size: uint) ---
__asan_unpoison_memory_region :: proc(address: rawptr, size: uint) ---
__sanitizer_set_death_callback :: proc(callback: Address_Death_Callback) ---
__asan_region_is_poisoned :: proc(begin: rawptr, size: uint) -> rawptr ---
__asan_address_is_poisoned :: proc(addr: rawptr) -> i32 ---
__asan_describe_address :: proc(addr: rawptr) ---
__asan_report_present :: proc() -> i32 ---
__asan_get_report_pc :: proc() -> rawptr ---
__asan_get_report_bp :: proc() -> rawptr ---
__asan_get_report_sp :: proc() -> rawptr ---
__asan_get_report_address :: proc() -> rawptr ---
__asan_get_report_access_type :: proc() -> i32 ---
__asan_get_report_access_size :: proc() -> uint ---
__asan_get_report_description :: proc() -> cstring ---
__asan_locate_address :: proc(addr: rawptr, name: rawptr, name_size: uint, region_address: ^rawptr, region_size: ^uint) -> cstring ---
__asan_get_alloc_stack :: proc(addr: rawptr, trace: rawptr, size: uint, thread_id: ^i32) -> uint ---
__asan_get_free_stack :: proc(addr: rawptr, trace: rawptr, size: uint, thread_id: ^i32) -> uint ---
__asan_get_shadow_mapping :: proc(shadow_scale: ^uint, shadow_offset: ^uint) ---
__asan_print_accumulated_stats :: proc() ---
__asan_get_current_fake_stack :: proc() -> rawptr ---
__asan_addr_is_in_fake_stack :: proc(fake_stack: rawptr, addr: rawptr, beg: ^rawptr, end: ^rawptr) -> rawptr ---
__asan_handle_no_return :: proc() ---
__asan_update_allocation_context :: proc(addr: rawptr) -> i32 ---
}
Address_Access_Type :: enum {
none,
read,
write,
}
Address_Located_Address :: struct {
category: string,
name: string,
region: []byte,
}
Address_Shadow_Mapping :: struct {
scale: uint,
offset: uint,
}
/*
Marks a slice as unaddressable
Code instrumented with `-sanitize:address` is forbidden from accessing any address
within the slice. This procedure is not thread-safe because no two threads can
poison or unpoison memory in the same memory region region simultaneously.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_poison_slice :: proc "contextless" (region: $T/[]$E) {
when ASAN_ENABLED {
__asan_poison_memory_region(raw_data(region), size_of(E) * len(region))
}
}
/*
Marks a slice as addressable
Code instrumented with `-sanitize:address` is allowed to access any address
within the slice again. This procedure is not thread-safe because no two threads
can poison or unpoison memory in the same memory region region simultaneously.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_unpoison_slice :: proc "contextless" (region: $T/[]$E) {
when ASAN_ENABLED {
__asan_unpoison_memory_region(raw_data(region), size_of(E) * len(region))
}
}
/*
Marks a pointer as unaddressable
Code instrumented with `-sanitize:address` is forbidden from accessing any address
within the region the pointer points to. This procedure is not thread-safe because no
two threads can poison or unpoison memory in the same memory region region simultaneously.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_poison_ptr :: proc "contextless" (ptr: ^$T) {
when ASAN_ENABLED {
__asan_poison_memory_region(ptr, size_of(T))
}
}
/*
Marks a pointer as addressable
Code instrumented with `-sanitize:address` is allowed to access any address
within the region the pointer points to again. This procedure is not thread-safe
because no two threads can poison or unpoison memory in the same memory region
region simultaneously.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_unpoison_ptr :: proc "contextless" (ptr: ^$T) {
when ASAN_ENABLED {
__asan_unpoison_memory_region(ptr, size_of(T))
}
}
/*
Marks the region covering `[ptr, ptr+len)` as unaddressable
Code instrumented with `-sanitize:address` is forbidden from accessing any address
within the region. This procedure is not thread-safe because no two threads can
poison or unpoison memory in the same memory region region simultaneously.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_poison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
when ASAN_ENABLED {
assert_contextless(len >= 0)
__asan_poison_memory_region(ptr, uint(len))
}
}
/*
Marks the region covering `[ptr, ptr+len)` as unaddressable
Code instrumented with `-sanitize:address` is forbidden from accessing any address
within the region. This procedure is not thread-safe because no two threads can
poison or unpoison memory in the same memory region region simultaneously.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_poison_rawptr_uint :: proc "contextless" (ptr: rawptr, len: uint) {
when ASAN_ENABLED {
__asan_poison_memory_region(ptr, len)
}
}
/*
Marks the region covering `[ptr, ptr+len)` as addressable
Code instrumented with `-sanitize:address` is allowed to access any address
within the region again. This procedure is not thread-safe because no two
threads can poison or unpoison memory in the same memory region region simultaneously.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_unpoison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
when ASAN_ENABLED {
assert_contextless(len >= 0)
__asan_unpoison_memory_region(ptr, uint(len))
}
}
/*
Marks the region covering `[ptr, ptr+len)` as addressable
Code instrumented with `-sanitize:address` is allowed to access any address
within the region again. This procedure is not thread-safe because no two
threads can poison or unpoison memory in the same memory region region simultaneously.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_unpoison_rawptr_uint :: proc "contextless" (ptr: rawptr, len: uint) {
when ASAN_ENABLED {
__asan_unpoison_memory_region(ptr, len)
}
}
address_poison :: proc {
address_poison_slice,
address_poison_ptr,
address_poison_rawptr,
address_poison_rawptr_uint,
}
address_unpoison :: proc {
address_unpoison_slice,
address_unpoison_ptr,
address_unpoison_rawptr,
address_unpoison_rawptr_uint,
}
/*
Registers a callback to be run when asan detects a memory error right before terminating
the process.
This can be used for logging and/or debugging purposes.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_set_death_callback :: proc "contextless" (callback: Address_Death_Callback) {
when ASAN_ENABLED {
__sanitizer_set_death_callback(callback)
}
}
/*
Checks if the memory region covered by the slice is poisoned.
If it is poisoned this procedure returns the address which would result
in an asan error.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_region_is_poisoned_slice :: proc "contextless" (region: $T/[]$E) -> rawptr {
when ASAN_ENABLED {
return __asan_region_is_poisoned(raw_data(region), size_of(E) * len(region))
} else {
return nil
}
}
/*
Checks if the memory region pointed to by the pointer is poisoned.
If it is poisoned this procedure returns the address which would result
in an asan error.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_region_is_poisoned_ptr :: proc "contextless" (ptr: ^$T) -> rawptr {
when ASAN_ENABLED {
return __asan_region_is_poisoned(ptr, size_of(T))
} else {
return nil
}
}
/*
Checks if the memory region covered by `[ptr, ptr+len)` is poisoned.
If it is poisoned this procedure returns the address which would result
in an asan error.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_region_is_poisoned_rawptr :: proc "contextless" (region: rawptr, len: int) -> rawptr {
when ASAN_ENABLED {
assert_contextless(len >= 0)
return __asan_region_is_poisoned(region, uint(len))
} else {
return nil
}
}
/*
Checks if the memory region covered by `[ptr, ptr+len)` is poisoned.
If it is poisoned this procedure returns the address which would result
in an asan error.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_region_is_poisoned_rawptr_uint :: proc "contextless" (region: rawptr, len: uint) -> rawptr {
when ASAN_ENABLED {
return __asan_region_is_poisoned(region, len)
} else {
return nil
}
}
address_region_is_poisoned :: proc {
address_region_is_poisoned_slice,
address_region_is_poisoned_ptr,
address_region_is_poisoned_rawptr,
address_region_is_poisoned_rawptr_uint,
}
/*
Checks if the address is poisoned.
If it is poisoned this procedure returns `true`, otherwise it returns
`false`.
When asan is not enabled this procedure returns `false`.
*/
@(no_sanitize_address)
address_is_poisoned :: proc "contextless" (address: rawptr) -> bool {
when ASAN_ENABLED {
return __asan_address_is_poisoned(address) != 0
} else {
return false
}
}
/*
Describes the sanitizer state for an address.
This procedure prints the description out to `stdout`.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_describe_address :: proc "contextless" (address: rawptr) {
when ASAN_ENABLED {
__asan_describe_address(address)
}
}
/*
Returns `true` if an asan error has occured, otherwise it returns
`false`.
When asan is not enabled this procedure returns `false`.
*/
@(no_sanitize_address)
address_report_present :: proc "contextless" () -> bool {
when ASAN_ENABLED {
return __asan_report_present() != 0
} else {
return false
}
}
/*
Returns the program counter register value of an asan error.
If no asan error has occurd `nil` is returned.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_get_report_pc :: proc "contextless" () -> rawptr {
when ASAN_ENABLED {
return __asan_get_report_pc()
} else {
return nil
}
}
/*
Returns the base pointer register value of an asan error.
If no asan error has occurd `nil` is returned.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_get_report_bp :: proc "contextless" () -> rawptr {
when ASAN_ENABLED {
return __asan_get_report_bp()
} else {
return nil
}
}
/*
Returns the stack pointer register value of an asan error.
If no asan error has occurd `nil` is returned.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_get_report_sp :: proc "contextless" () -> rawptr {
when ASAN_ENABLED {
return __asan_get_report_sp()
} else {
return nil
}
}
/*
Returns the report buffer address of an asan error.
If no asan error has occurd `nil` is returned.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_get_report_address :: proc "contextless" () -> rawptr {
when ASAN_ENABLED {
return __asan_get_report_address()
} else {
return nil
}
}
/*
Returns the address access type of an asan error.
If no asan error has occurd `.none` is returned.
When asan is not enabled this procedure returns `.none`.
*/
@(no_sanitize_address)
address_get_report_access_type :: proc "contextless" () -> Address_Access_Type {
when ASAN_ENABLED {
if ! address_report_present() {
return .none
}
return __asan_get_report_access_type() == 0 ? .read : .write
} else {
return .none
}
}
/*
Returns the access size of an asan error.
If no asan error has occurd `0` is returned.
When asan is not enabled this procedure returns `0`.
*/
@(no_sanitize_address)
address_get_report_access_size :: proc "contextless" () -> uint {
when ASAN_ENABLED {
return __asan_get_report_access_size()
} else {
return 0
}
}
/*
Returns the bug description of an asan error.
If no asan error has occurd an empty string is returned.
When asan is not enabled this procedure returns an empty string.
*/
@(no_sanitize_address)
address_get_report_description :: proc "contextless" () -> string {
when ASAN_ENABLED {
return string(__asan_get_report_description())
} else {
return ""
}
}
/*
Returns asan information about the address provided, writing the category into `data`.
The information provided include:
* The category of the address, i.e. stack, global, heap, etc.
* The name of the variable this address belongs to
* The memory region of the address
When asan is not enabled this procedure returns zero initialised values.
*/
@(no_sanitize_address)
address_locate_address :: proc "contextless" (addr: rawptr, data: []byte) -> Address_Located_Address {
when ASAN_ENABLED {
out_addr: rawptr
out_size: uint
str := __asan_locate_address(addr, raw_data(data), len(data), &out_addr, &out_size)
return { string(str), string(cstring(raw_data(data))), (cast([^]byte)out_addr)[:out_size] },
} else {
return { "", "", {} }
}
}
/*
Returns the allocation stack trace and thread id for a heap address.
The stack trace is filled into the `data` slice.
When asan is not enabled this procedure returns a zero initialised value.
*/
@(no_sanitize_address)
address_get_alloc_stack_trace :: proc "contextless" (addr: rawptr, data: []rawptr) -> ([]rawptr, int) {
when ASAN_ENABLED {
out_thread: i32
__asan_get_alloc_stack(addr, raw_data(data), len(data), &out_thread)
return data, int(out_thread)
} else {
return {}, 0
}
}
/*
Returns the free stack trace and thread id for a heap address.
The stack trace is filled into the `data` slice.
When asan is not enabled this procedure returns zero initialised values.
*/
@(no_sanitize_address)
address_get_free_stack_trace :: proc "contextless" (addr: rawptr, data: []rawptr) -> ([]rawptr, int) {
when ASAN_ENABLED {
out_thread: i32
__asan_get_free_stack(addr, raw_data(data), len(data), &out_thread)
return data, int(out_thread)
} else {
return {}, 0
}
}
/*
Returns the current asan shadow memory mapping.
When asan is not enabled this procedure returns a zero initialised value.
*/
@(no_sanitize_address)
address_get_shadow_mapping :: proc "contextless" () -> Address_Shadow_Mapping {
when ASAN_ENABLED {
result: Address_Shadow_Mapping
__asan_get_shadow_mapping(&result.scale, &result.offset)
return result
} else {
return {}
}
}
/*
Prints asan statistics to `stderr`
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_print_accumulated_stats :: proc "contextless" () {
when ASAN_ENABLED {
__asan_print_accumulated_stats()
}
}
/*
Returns the address of the current fake stack used by asan.
This pointer can be then used for `address_is_in_fake_stack`.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_get_current_fake_stack :: proc "contextless" () -> rawptr {
when ASAN_ENABLED {
return __asan_get_current_fake_stack()
} else {
return nil
}
}
/*
Returns if an address belongs to a given fake stack and if so the region of the fake frame.
When asan is not enabled this procedure returns zero initialised values.
*/
@(no_sanitize_address)
address_is_in_fake_stack :: proc "contextless" (fake_stack: rawptr, addr: rawptr) -> ([]byte, bool) {
when ASAN_ENABLED {
begin: rawptr
end: rawptr
if __asan_addr_is_in_fake_stack(fake_stack, addr, &begin, &end) == nil {
return {}, false
}
return ((cast([^]byte)begin)[:uintptr(end)-uintptr(begin)]), true
} else {
return {}, false
}
}
/*
Performs shadow memory cleanup for the current thread before a procedure with no return is called
i.e. a procedure such as `panic` and `os.exit`.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_handle_no_return :: proc "contextless" () {
when ASAN_ENABLED {
__asan_handle_no_return()
}
}
/*
Updates the allocation stack trace for the given address.
Returns `true` if successful, otherwise it returns `false`.
When asan is not enabled this procedure returns `false`.
*/
@(no_sanitize_address)
address_update_allocation_context :: proc "contextless" (addr: rawptr) -> bool {
when ASAN_ENABLED {
return __asan_update_allocation_context(addr) != 0
} else {
return false
}
}

38
base/sanitizer/doc.odin Normal file
View File

@@ -0,0 +1,38 @@
/*
The `sanitizer` package implements various procedures for interacting with sanitizers
from user code.
An odin project can be linked with various sanitizers to help identify various different
bugs. These sanitizers are:
## Address
Enabled with `-sanitize:address` when building an odin project.
The address sanitizer (asan) is a runtime memory error detector used to help find common memory
related bugs. Typically asan interacts with libc but Odin code can be marked up to interact
with the asan runtime to extend the memory error detection outside of libc using this package.
For more information about asan see: https://clang.llvm.org/docs/AddressSanitizer.html
Procedures can be made exempt from asan when marked up with @(no_sanitize_address)
## Memory
Enabled with `-sanitize:memory` when building an odin project.
The memory sanitizer is another runtime memory error detector with the sole purpose to catch the
use of uninitialized memory. This is not a very common bug in Odin as by default everything is
set to zero when initialised (ZII).
For more information about the memory sanitizer see: https://clang.llvm.org/docs/MemorySanitizer.html
## Thread
Enabled with `-sanitize:thread` when building an odin project.
The thread sanitizer is a runtime data race detector. It can be used to detect if multiple threads
are concurrently writing and accessing a memory location without proper syncronisation.
For more information about the thread sanitizer see: https://clang.llvm.org/docs/ThreadSanitizer.html
*/
package sanitizer

View File

@@ -0,0 +1,74 @@
#+no-instrumentation
package sanitizer
@(private="file")
MSAN_ENABLED :: .Memory in ODIN_SANITIZER_FLAGS
@(private="file")
@(default_calling_convention="system")
foreign {
__msan_unpoison :: proc(addr: rawptr, size: uint) ---
}
/*
Marks a slice as fully initialized.
Code instrumented with `-sanitize:memory` will be permitted to access any
address within the slice as if it had already been initialized.
When msan is not enabled this procedure does nothing.
*/
memory_unpoison_slice :: proc "contextless" (region: $T/[]$E) {
when MSAN_ENABLED {
__msan_unpoison(raw_data(region), size_of(E) * len(region))
}
}
/*
Marks a pointer as fully initialized.
Code instrumented with `-sanitize:memory` will be permitted to access memory
within the region the pointer points to as if it had already been initialized.
When msan is not enabled this procedure does nothing.
*/
memory_unpoison_ptr :: proc "contextless" (ptr: ^$T) {
when MSAN_ENABLED {
__msan_unpoison(ptr, size_of(T))
}
}
/*
Marks the region covering `[ptr, ptr+len)` as fully initialized.
Code instrumented with `-sanitize:memory` will be permitted to access memory
within this range as if it had already been initialized.
When msan is not enabled this procedure does nothing.
*/
memory_unpoison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
when MSAN_ENABLED {
__msan_unpoison(ptr, uint(len))
}
}
/*
Marks the region covering `[ptr, ptr+len)` as fully initialized.
Code instrumented with `-sanitize:memory` will be permitted to access memory
within this range as if it had already been initialized.
When msan is not enabled this procedure does nothing.
*/
memory_unpoison_rawptr_uint :: proc "contextless" (ptr: rawptr, len: uint) {
when MSAN_ENABLED {
__msan_unpoison(ptr, len)
}
}
memory_unpoison :: proc {
memory_unpoison_slice,
memory_unpoison_ptr,
memory_unpoison_rawptr,
memory_unpoison_rawptr_uint,
}

View File

@@ -19,16 +19,27 @@ if "%VSCMD_ARG_TGT_ARCH%" neq "x64" (
)
)
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-%%d" --no-patch --no-notes HEAD') do (
set CURR_DATE_TIME=%%i
set GIT_SHA=%%j
)
if %ERRORLEVEL% equ 0 (
goto have_git_hash_and_date
)
:skip_git_hash
pushd misc
cl /nologo get-date.c
popd
for /f %%i in ('misc\get-date') do (
for /f %%i in ('get-date') do (
set CURR_DATE_TIME=%%i
rem Don't set GIT_SHA
)
popd
:have_git_hash_and_date
set curr_year=%CURR_DATE_TIME:~0,4%
set curr_month=%CURR_DATE_TIME:~4,2%
set curr_day=%CURR_DATE_TIME:~6,2%
set curr_month=%CURR_DATE_TIME:~5,2%
set curr_day=%CURR_DATE_TIME:~8,2%
:: Make sure this is a decent name and not generic
set exe_name=odin.exe
@@ -61,31 +72,14 @@ if %release_mode% equ 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
rem See https://learn.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170
set compiler_flags= %compiler_flags% /utf-8
set compiler_defines= -DODIN_VERSION_RAW=\"%odin_version_raw%\"
set compiler_defines= -DODIN_VERSION_RAW=\"%odin_version_raw%\" -DGIT_SHA=\"%GIT_SHA%\"
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%\"
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
set rc_flags="-DGIT_SHA=%GIT_SHA% -DVP=dev-%V1%-%V2%:%GIT_SHA% nologo -DV1=%V1% -DV2=%V2% -DV3=%V3% -DV4=%V4% -DVF=%odin_version_full% -DNIGHTLY=%nightly%"
if %nightly% equ 1 set compiler_defines=%compiler_defines% -DNIGHTLY
@@ -138,6 +132,7 @@ 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%
if %errorlevel% neq 0 goto end_of_build
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
@@ -152,4 +147,4 @@ if %release_mode% EQU 0 echo: & echo Debug compiler built. Note: run "build.bat
del *.obj > NUL 2> NUL
:end_of_build
:end_of_build

View File

@@ -6,7 +6,6 @@ set -eu
: ${LDFLAGS=}
: ${LLVM_CONFIG=}
CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"dev-$(date +"%Y-%m")\""
CXXFLAGS="$CXXFLAGS -std=c++14"
DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value"
LDFLAGS="$LDFLAGS -pthread -lm"
@@ -15,8 +14,12 @@ OS_NAME="$(uname -s)"
if [ -d ".git" ] && [ -n "$(command -v git)" ]; then
GIT_SHA=$(git show --pretty='%h' --no-patch --no-notes HEAD)
GIT_DATE=$(git show "--pretty=%cd" "--date=format:%Y-%m" --no-patch --no-notes HEAD)
CPPFLAGS="$CPPFLAGS -DGIT_SHA=\"$GIT_SHA\""
else
GIT_DATE=$(date +"%Y-%m")
fi
CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"dev-$GIT_DATE\""
error() {
printf "ERROR: %s\n" "$1"
@@ -25,7 +28,7 @@ error() {
# Brew advises people not to add llvm to their $PATH, so try and use brew to find it.
if [ -z "$LLVM_CONFIG" ] && [ -n "$(command -v brew)" ]; then
if [ -n "$(command -v $(brew --prefix llvm)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm)/bin/llvm-config"
if [ -n "$(command -v $(brew --prefix llvm@20)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@20)/bin/llvm-config"
elif [ -n "$(command -v $(brew --prefix llvm@19)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@19)/bin/llvm-config"
elif [ -n "$(command -v $(brew --prefix llvm@18)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@18)/bin/llvm-config"
elif [ -n "$(command -v $(brew --prefix llvm@17)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@17)/bin/llvm-config"

View File

@@ -1,8 +1,8 @@
#!/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"
CXX="clang++-20"
LLVM_CONFIG="llvm-config-20"
DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value"

View File

@@ -257,7 +257,7 @@ reader_read_rune :: proc(b: ^Reader) -> (r: rune, size: int, err: io.Error) {
for b.r+utf8.UTF_MAX > b.w &&
!utf8.full_rune(b.buf[b.r:b.w]) &&
b.err == nil &&
b.w-b.w < len(b.buf) {
b.w-b.r < len(b.buf) {
_reader_read_new_chunk(b) or_return
}

View File

@@ -350,7 +350,7 @@ index_byte :: proc "contextless" (s: []byte, c: byte) -> (index: int) #no_bounds
}
c_vec: simd.u8x16 = c
when !simd.IS_EMULATED {
when simd.HAS_HARDWARE_SIMD {
// Note: While this is something that could also logically take
// advantage of AVX512, the various downclocking and power
// consumption related woes make premature to have a dedicated
@@ -485,7 +485,7 @@ last_index_byte :: proc "contextless" (s: []byte, c: byte) -> int #no_bounds_che
}
c_vec: simd.u8x16 = c
when !simd.IS_EMULATED {
when simd.HAS_HARDWARE_SIMD {
// Note: While this is something that could also logically take
// advantage of AVX512, the various downclocking and power
// consumption related woes make premature to have a dedicated

View File

@@ -139,9 +139,6 @@ Context_Memory_Input :: struct #packed {
}
when size_of(rawptr) == 8 {
#assert(size_of(Context_Memory_Input) == 64)
} else {
// e.g. `-target:windows_i386`
#assert(size_of(Context_Memory_Input) == 52)
}
Context_Stream_Input :: struct #packed {

View File

@@ -278,19 +278,19 @@ Example:
iterate_next_example :: proc() {
l: list.List
one := My_Struct{value=1}
two := My_Struct{value=2}
one := My_Next_Struct{value=1}
two := My_Next_Struct{value=2}
list.push_back(&l, &one.node)
list.push_back(&l, &two.node)
it := list.iterator_head(l, My_Struct, "node")
it := list.iterator_head(l, My_Next_Struct, "node")
for num in list.iterate_next(&it) {
fmt.println(num.value)
}
}
My_Struct :: struct {
My_Next_Struct :: struct {
node : list.Node,
value: int,
}
@@ -325,22 +325,22 @@ Example:
import "core:fmt"
import "core:container/intrusive/list"
iterate_next_example :: proc() {
iterate_prev_example :: proc() {
l: list.List
one := My_Struct{value=1}
two := My_Struct{value=2}
one := My_Prev_Struct{value=1}
two := My_Prev_Struct{value=2}
list.push_back(&l, &one.node)
list.push_back(&l, &two.node)
it := list.iterator_tail(l, My_Struct, "node")
it := list.iterator_tail(l, My_Prev_Struct, "node")
for num in list.iterate_prev(&it) {
fmt.println(num.value)
}
}
My_Struct :: struct {
My_Prev_Struct :: struct {
node : list.Node,
value: int,
}

View File

@@ -129,7 +129,7 @@ remove :: proc(c: ^$C/Cache($Key, $Value), key: Key) -> bool {
return false
}
_remove_node(c, e)
free(node, c.node_allocator)
free(e, c.node_allocator)
c.count -= 1
return true
}

View File

@@ -1,6 +1,7 @@
package container_priority_queue
import "base:builtin"
import "base:runtime"
Priority_Queue :: struct($T: typeid) {
queue: [dynamic]T,
@@ -17,13 +18,14 @@ default_swap_proc :: proc($T: typeid) -> proc(q: []T, i, j: int) {
}
}
init :: proc(pq: ^$Q/Priority_Queue($T), less: proc(a, b: T) -> bool, swap: proc(q: []T, i, j: int), capacity := DEFAULT_CAPACITY, allocator := context.allocator) {
init :: proc(pq: ^$Q/Priority_Queue($T), less: proc(a, b: T) -> bool, swap: proc(q: []T, i, j: int), capacity := DEFAULT_CAPACITY, allocator := context.allocator) -> (err: runtime.Allocator_Error) {
if pq.queue.allocator.procedure == nil {
pq.queue.allocator = allocator
}
reserve(pq, capacity)
reserve(pq, capacity) or_return
pq.less = less
pq.swap = swap
return .None
}
init_from_dynamic_array :: proc(pq: ^$Q/Priority_Queue($T), queue: [dynamic]T, less: proc(a, b: T) -> bool, swap: proc(q: []T, i, j: int)) {
@@ -41,8 +43,8 @@ destroy :: proc(pq: ^$Q/Priority_Queue($T)) {
delete(pq.queue)
}
reserve :: proc(pq: ^$Q/Priority_Queue($T), capacity: int) {
builtin.reserve(&pq.queue, capacity)
reserve :: proc(pq: ^$Q/Priority_Queue($T), capacity: int) -> (err: runtime.Allocator_Error) {
return builtin.reserve(&pq.queue, capacity)
}
clear :: proc(pq: ^$Q/Priority_Queue($T)) {
builtin.clear(&pq.queue)
@@ -103,9 +105,10 @@ fix :: proc(pq: ^$Q/Priority_Queue($T), i: int) {
}
}
push :: proc(pq: ^$Q/Priority_Queue($T), value: T) {
append(&pq.queue, value)
push :: proc(pq: ^$Q/Priority_Queue($T), value: T) -> (err: runtime.Allocator_Error) {
append(&pq.queue, value) or_return
_shift_up(pq, builtin.len(pq.queue)-1)
return .None
}
pop :: proc(pq: ^$Q/Priority_Queue($T), loc := #caller_location) -> (value: T) {
@@ -130,12 +133,10 @@ pop_safe :: proc(pq: ^$Q/Priority_Queue($T), loc := #caller_location) -> (value:
remove :: proc(pq: ^$Q/Priority_Queue($T), i: int) -> (value: T, ok: bool) {
n := builtin.len(pq.queue)
if 0 <= i && i < n {
if n != i {
pq.swap(pq.queue[:], i, n)
_shift_down(pq, i, n)
_shift_up(pq, i)
}
value, ok = builtin.pop_safe(&pq.queue)
pq.swap(pq.queue[:], i, n-1)
_shift_down(pq, i, n-1)
_shift_up(pq, i)
value, ok = builtin.pop(&pq.queue), true
}
return
}

View File

@@ -0,0 +1,55 @@
/*
Package small_array implements a dynamic array like
interface on a stack-allocated, fixed-size array.
The Small_Array type is optimal for scenarios where you need
a container for a fixed number of elements of a specific type,
with the total number known at compile time but the exact
number to be used determined at runtime.
Example:
import "core:fmt"
import "core:container/small_array"
create :: proc() -> (result: small_array.Small_Array(10, rune)) {
// appending single elements
small_array.push(&result, 'e')
// pushing a bunch of elements at once
small_array.push(&result, 'l', 'i', 'x', '-', 'e')
// pre-pending
small_array.push_front(&result, 'H')
// removing elements
small_array.ordered_remove(&result, 4)
// resizing to the desired length (the capacity will stay unchanged)
small_array.resize(&result, 7)
// inserting elements
small_array.inject_at(&result, 'p', 5)
// updating elements
small_array.set(&result, 3, 'l')
// getting pointers to elements
o := small_array.get_ptr(&result, 4)
o^ = 'o'
// and much more ....
return
}
// the Small_Array can be an ordinary parameter 'generic' over
// the actual length to be usable with different sizes
print_elements :: proc(arr: ^small_array.Small_Array($N, rune)) {
for r in small_array.slice(arr) {
fmt.print(r)
}
}
main :: proc() {
arr := create()
// ...
print_elements(&arr)
}
Output:
Hellope
*/
package container_small_array

View File

@@ -4,36 +4,171 @@ import "base:builtin"
import "base:runtime"
_ :: runtime
/*
A fixed-size stack-allocated array operated on in a dynamic fashion.
Fields:
- `data`: The underlying array
- `len`: Amount of items that the `Small_Array` currently holds
Example:
import "core:container/small_array"
example :: proc() {
a: small_array.Small_Array(100, int)
small_array.push_back(&a, 10)
}
*/
Small_Array :: struct($N: int, $T: typeid) where N >= 0 {
data: [N]T,
len: int,
}
/*
Returns the amount of items in the small-array.
**Inputs**
- `a`: The small-array
**Returns**
- the amount of items in the array
*/
len :: proc "contextless" (a: $A/Small_Array) -> int {
return a.len
}
/*
Returns the capacity of the small-array.
**Inputs**
- `a`: The small-array
**Returns** the capacity
*/
cap :: proc "contextless" (a: $A/Small_Array) -> int {
return builtin.len(a.data)
}
/*
Returns how many more items the small-array could fit.
**Inputs**
- `a`: The small-array
**Returns**
- the number of unused slots
*/
space :: proc "contextless" (a: $A/Small_Array) -> int {
return builtin.len(a.data) - a.len
}
/*
Returns a slice of the data.
**Inputs**
- `a`: The pointer to the small-array
**Returns**
- the slice
Example:
import "core:container/small_array"
import "core:fmt"
slice_example :: proc() {
print :: proc(a: ^small_array.Small_Array($N, int)) {
for item in small_array.slice(a) {
fmt.println(item)
}
}
a: small_array.Small_Array(5, int)
small_array.push_back(&a, 1)
small_array.push_back(&a, 2)
print(&a)
}
Output:
1
2
*/
slice :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> []T {
return a.data[:a.len]
}
/*
Get a copy of the item at the specified position.
This operation assumes that the small-array is large enough.
This will result in:
- the value if 0 <= index < len
- the zero value of the type if len < index < capacity
- 'crash' if capacity < index or index < 0
**Inputs**
- `a`: The small-array
- `index`: The position of the item to get
**Returns**
- the element at the specified position
*/
get :: proc "contextless" (a: $A/Small_Array($N, $T), index: int) -> T {
return a.data[index]
}
/*
Get a pointer to the item at the specified position.
This operation assumes that the small-array is large enough.
This will result in:
- the pointer if 0 <= index < len
- the pointer to the zero value if len < index < capacity
- 'crash' if capacity < index or index < 0
**Inputs**
- `a`: A pointer to the small-array
- `index`: The position of the item to get
**Returns**
- the pointer to the element at the specified position
*/
get_ptr :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int) -> ^T {
return &a.data[index]
}
/*
Attempt to get a copy of the item at the specified position.
**Inputs**
- `a`: The small-array
- `index`: The position of the item to get
**Returns**
- the element at the specified position
- true if element exists, false otherwise
Example:
import "core:container/small_array"
import "core:fmt"
get_safe_example :: proc() {
a: small_array.Small_Array(5, rune)
small_array.push_back(&a, 'A')
fmt.println(small_array.get_safe(a, 0) or_else 'x')
fmt.println(small_array.get_safe(a, 1) or_else 'x')
}
Output:
A
x
*/
get_safe :: proc(a: $A/Small_Array($N, $T), index: int) -> (T, bool) #no_bounds_check {
if index < 0 || index >= a.len {
return {}, false
@@ -41,6 +176,17 @@ get_safe :: proc(a: $A/Small_Array($N, $T), index: int) -> (T, bool) #no_bounds_
return a.data[index], true
}
/*
Get a pointer to the item at the specified position.
**Inputs**
- `a`: A pointer to the small-array
- `index`: The position of the item to get
**Returns**
- the pointer to the element at the specified position
- true if element exists, false otherwise
*/
get_ptr_safe :: proc(a: ^$A/Small_Array($N, $T), index: int) -> (^T, bool) #no_bounds_check {
if index < 0 || index >= a.len {
return {}, false
@@ -48,15 +194,128 @@ get_ptr_safe :: proc(a: ^$A/Small_Array($N, $T), index: int) -> (^T, bool) #no_b
return &a.data[index], true
}
/*
Set the element at the specified position to the given value.
This operation assumes that the small-array is large enough.
This will result in:
- the value being set if 0 <= index < capacity
- 'crash' otherwise
**Inputs**
- `a`: A pointer to the small-array
- `index`: The position of the item to set
- `value`: The value to set the element to
Example:
import "core:container/small_array"
import "core:fmt"
set_example :: proc() {
a: small_array.Small_Array(5, rune)
small_array.push_back(&a, 'A')
small_array.push_back(&a, 'B')
fmt.println(small_array.slice(&a))
// updates index 0
small_array.set(&a, 0, 'Z')
fmt.println(small_array.slice(&a))
// updates to a position x, where
// len <= x < cap are not visible since
// the length of the small-array remains unchanged
small_array.set(&a, 2, 'X')
small_array.set(&a, 3, 'Y')
small_array.set(&a, 4, 'Z')
fmt.println(small_array.slice(&a))
// resizing makes the change visible
small_array.resize(&a, 100)
fmt.println(small_array.slice(&a))
}
Output:
[A, B]
[Z, B]
[Z, B]
[Z, B, X, Y, Z]
*/
set :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, item: T) {
a.data[index] = item
}
/*
Tries to resize the small-array to the specified length.
The new length will be:
- `length` if `length` <= capacity
- capacity if length > capacity
**Inputs**
- `a`: A pointer to the small-array
- `length`: The new desired length
Example:
import "core:container/small_array"
import "core:fmt"
resize_example :: proc() {
a: small_array.Small_Array(5, int)
small_array.push_back(&a, 1)
small_array.push_back(&a, 2)
fmt.println(small_array.slice(&a))
small_array.resize(&a, 1)
fmt.println(small_array.slice(&a))
small_array.resize(&a, 100)
fmt.println(small_array.slice(&a))
}
Output:
[1, 2]
[1]
[1, 2, 0, 0, 0]
*/
resize :: proc "contextless" (a: ^$A/Small_Array, length: int) {
a.len = min(length, builtin.len(a.data))
}
/*
Attempts to add the given element to the end.
**Inputs**
- `a`: A pointer to the small-array
- `item`: The item to append
**Returns**
- true if there was enough space to fit the element, false otherwise
Example:
import "core:container/small_array"
import "core:fmt"
push_back_example :: proc() {
a: small_array.Small_Array(2, int)
assert(small_array.push_back(&a, 1), "this should fit")
assert(small_array.push_back(&a, 2), "this should fit")
assert(!small_array.push_back(&a, 3), "this should not fit")
fmt.println(small_array.slice(&a))
}
Output:
[1, 2]
*/
push_back :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T) -> bool {
if a.len < cap(a^) {
a.data[a.len] = item
@@ -66,6 +325,39 @@ push_back :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T) -> bool {
return false
}
/*
Attempts to add the given element at the beginning.
This operation assumes that the small-array is not empty.
Note: Performing this operation will cause pointers obtained
through get_ptr(_save) to reference incorrect elements.
**Inputs**
- `a`: A pointer to the small-array
- `item`: The item to append
**Returns**
- true if there was enough space to fit the element, false otherwise
Example:
import "core:container/small_array"
import "core:fmt"
push_front_example :: proc() {
a: small_array.Small_Array(2, int)
assert(small_array.push_front(&a, 2), "this should fit")
assert(small_array.push_front(&a, 1), "this should fit")
assert(!small_array.push_back(&a, 0), "this should not fit")
fmt.println(small_array.slice(&a))
}
Output:
[1, 2]
*/
push_front :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T) -> bool {
if a.len < cap(a^) {
a.len += 1
@@ -77,6 +369,35 @@ push_front :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T) -> bool {
return false
}
/*
Removes and returns the last element of the small-array.
This operation assumes that the small-array is not empty.
**Inputs**
- `a`: A pointer to the small-array
**Returns**
- a copy of the element removed from the end of the small-array
Example:
import "core:container/small_array"
import "core:fmt"
pop_back_example :: proc() {
a: small_array.Small_Array(5, int)
small_array.push(&a, 0, 1, 2)
fmt.println("BEFORE:", small_array.slice(&a))
small_array.pop_back(&a)
fmt.println("AFTER: ", small_array.slice(&a))
}
Output:
BEFORE: [0, 1, 2]
AFTER: [0, 1]
*/
pop_back :: proc "odin" (a: ^$A/Small_Array($N, $T), loc := #caller_location) -> T {
assert(condition=(N > 0 && a.len > 0), loc=loc)
item := a.data[a.len-1]
@@ -84,6 +405,38 @@ pop_back :: proc "odin" (a: ^$A/Small_Array($N, $T), loc := #caller_location) ->
return item
}
/*
Removes and returns the first element of the small-array.
This operation assumes that the small-array is not empty.
Note: Performing this operation will cause pointers obtained
through get_ptr(_save) to reference incorrect elements.
**Inputs**
- `a`: A pointer to the small-array
**Returns**
- a copy of the element removed from the beginning of the small-array
Example:
import "core:container/small_array"
import "core:fmt"
pop_front_example :: proc() {
a: small_array.Small_Array(5, int)
small_array.push(&a, 0, 1, 2)
fmt.println("BEFORE:", small_array.slice(&a))
small_array.pop_front(&a)
fmt.println("AFTER: ", small_array.slice(&a))
}
Output:
BEFORE: [0, 1, 2]
AFTER: [1, 2]
*/
pop_front :: proc "odin" (a: ^$A/Small_Array($N, $T), loc := #caller_location) -> T {
assert(condition=(N > 0 && a.len > 0), loc=loc)
item := a.data[0]
@@ -93,6 +446,32 @@ pop_front :: proc "odin" (a: ^$A/Small_Array($N, $T), loc := #caller_location) -
return item
}
/*
Attempts to remove and return the last element of the small array.
Unlike `pop_back`, it does not assume that the array is non-empty.
**Inputs**
- `a`: A pointer to the small-array
**Returns**
- a copy of the element removed from the end of the small-array
- true if the small-array was not empty, false otherwise
Example:
import "core:container/small_array"
pop_back_safe_example :: proc() {
a: small_array.Small_Array(3, int)
small_array.push(&a, 1)
el, ok := small_array.pop_back_safe(&a)
assert(ok, "there was an element in the array")
el, ok = small_array.pop_back_safe(&a)
assert(!ok, "there was NO element in the array")
}
*/
pop_back_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> (item: T, ok: bool) {
if N > 0 && a.len > 0 {
item = a.data[a.len-1]
@@ -102,6 +481,35 @@ pop_back_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> (item: T, ok
return
}
/*
Attempts to remove and return the first element of the small array.
Unlike `pop_front`, it does not assume that the array is non-empty.
Note: Performing this operation will cause pointers obtained
through get_ptr(_save) to reference incorrect elements.
**Inputs**
- `a`: A pointer to the small-array
**Returns**
- a copy of the element removed from the beginning of the small-array
- true if the small-array was not empty, false otherwise
Example:
import "core:container/small_array"
pop_front_safe_example :: proc() {
a: small_array.Small_Array(3, int)
small_array.push(&a, 1)
el, ok := small_array.pop_front_safe(&a)
assert(ok, "there was an element in the array")
el, ok = small_array.pop_front_(&a)
assert(!ok, "there was NO element in the array")
}
*/
pop_front_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> (item: T, ok: bool) {
if N > 0 && a.len > 0 {
item = a.data[0]
@@ -113,11 +521,70 @@ pop_front_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> (item: T, o
return
}
/*
Decreases the length of the small-array by the given amount.
The elements are therefore not really removed and can be
recovered by calling `resize`.
Note: This procedure assumes that the array has a sufficient length.
**Inputs**
- `a`: A pointer to the small-array
- `count`: The amount the length should be reduced by
Example:
import "core:container/small_array"
import "core:fmt"
consume_example :: proc() {
a: small_array.Small_Array(3, int)
small_array.push(&a, 0, 1, 2)
fmt.println("BEFORE:", small_array.slice(&a))
small_array.consume(&a, 2)
fmt.println("AFTER :", small_array.slice(&a))
}
Output:
BEFORE: [0, 1, 2]
AFTER : [0]
*/
consume :: proc "odin" (a: ^$A/Small_Array($N, $T), count: int, loc := #caller_location) {
assert(condition=a.len >= count, loc=loc)
a.len -= count
}
/*
Removes the element at the specified index while retaining order.
Note: Performing this operation will cause pointers obtained
through get_ptr(_save) to reference incorrect elements.
**Inputs**
- `a`: A pointer to the small-array
- `index`: The position of the element to remove
Example:
import "core:container/small_array"
import "core:fmt"
ordered_remove_example :: proc() {
a: small_array.Small_Array(4, int)
small_array.push(&a, 0, 1, 2, 3)
fmt.println("BEFORE:", small_array.slice(&a))
small_array.ordered_remove(&a, 1)
fmt.println("AFTER :", small_array.slice(&a))
}
Output:
BEFORE: [0, 1, 2, 3]
AFTER : [0, 2, 3]
*/
ordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check {
runtime.bounds_check_error_loc(loc, index, a.len)
if index+1 < a.len {
@@ -126,6 +593,32 @@ ordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, lo
a.len -= 1
}
/*
Removes the element at the specified index without retaining order.
**Inputs**
- `a`: A pointer to the small-array
- `index`: The position of the element to remove
Example:
import "core:container/small_array"
import "core:fmt"
unordered_remove_example :: proc() {
a: small_array.Small_Array(4, int)
small_array.push(&a, 0, 1, 2, 3)
fmt.println("BEFORE:", small_array.slice(&a))
small_array.unordered_remove(&a, 1)
fmt.println("AFTER :", small_array.slice(&a))
}
Output:
BEFORE: [0, 1, 2, 3]
AFTER : [0, 3, 2]
*/
unordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check {
runtime.bounds_check_error_loc(loc, index, a.len)
n := a.len-1
@@ -135,10 +628,63 @@ unordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int,
a.len -= 1
}
/*
Sets the length of the small-array to 0.
**Inputs**
- `a`: A pointer to the small-array
Example:
import "core:container/small_array"
import "core:fmt"
clear_example :: proc() {
a: small_array.Small_Array(4, int)
small_array.push(&a, 0, 1, 2, 3)
fmt.println("BEFORE:", small_array.slice(&a))
small_array.clear(&a)
fmt.println("AFTER :", small_array.slice(&a))
}
Output:
BEFORE: [0, 1, 2, 3]
AFTER : []
*/
clear :: proc "contextless" (a: ^$A/Small_Array($N, $T)) {
resize(a, 0)
}
/*
Attempts to append all elements to the small-array returning
false if there is not enough space to fit all of them.
**Inputs**
- `a`: A pointer to the small-array
- `item`: The item to append
- ..:
**Returns**
- true if there was enough space to fit the element, false otherwise
Example:
import "core:container/small_array"
import "core:fmt"
push_back_elems_example :: proc() {
a: small_array.Small_Array(100, int)
small_array.push_back_elems(&a, 0, 1, 2, 3, 4)
fmt.println(small_array.slice(&a))
}
Output:
[0, 1, 2, 3, 4]
*/
push_back_elems :: proc "contextless" (a: ^$A/Small_Array($N, $T), items: ..T) -> bool {
if a.len + builtin.len(items) <= cap(a^) {
n := copy(a.data[a.len:], items[:])
@@ -148,6 +694,36 @@ push_back_elems :: proc "contextless" (a: ^$A/Small_Array($N, $T), items: ..T) -
return false
}
/*
Tries to insert an element at the specified position.
Note: Performing this operation will cause pointers obtained
through get_ptr(_save) to reference incorrect elements.
**Inputs**
- `a`: A pointer to the small-array
- `item`: The item to insert
- `index`: The index to insert the item at
**Returns**
- true if there was enough space to fit the element, false otherwise
Example:
import "core:container/small_array"
import "core:fmt"
inject_at_example :: proc() {
arr: small_array.Small_Array(100, rune)
small_array.push(&arr, 'A', 'C', 'D')
small_array.inject_at(&arr, 'B', 1)
fmt.println(small_array.slice(&arr))
}
Output:
[A, B, C, D]
*/
inject_at :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T, index: int) -> bool #no_bounds_check {
if a.len < cap(a^) && index >= 0 && index <= len(a^) {
a.len += 1
@@ -160,7 +736,38 @@ inject_at :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T, index: int
return false
}
// Alias for `push_back`
append_elem :: push_back
// Alias for `push_back_elems`
append_elems :: push_back_elems
/*
Tries to append the element(s) to the small-array.
**Inputs**
- `a`: A pointer to the small-array
- `item`: The item to append
- ..:
**Returns**
- true if there was enough space to fit the element, false otherwise
Example:
import "core:container/small_array"
import "core:fmt"
push_example :: proc() {
a: small_array.Small_Array(100, int)
small_array.push(&a, 0)
small_array.push(&a, 1, 2, 3, 4)
fmt.println(small_array.slice(&a))
}
Output:
[0, 1, 2, 3, 4]
*/
push :: proc{push_back, push_back_elems}
// Alias for `push`
append :: proc{push_back, push_back_elems}

View File

@@ -6,7 +6,7 @@ import "core:sys/info"
// is_supported returns true iff hardware accelerated AES
// is supported.
is_supported :: proc "contextless" () -> bool {
features, ok := info.cpu_features.?
features, ok := info.cpu.features.?
if !ok {
return false
}

View File

@@ -39,7 +39,7 @@ when ODIN_ARCH == .arm64 || ODIN_ARCH == .arm32 {
// Some targets lack runtime feature detection, and will flat out refuse
// to load binaries that have unknown instructions. This is distinct from
// `simd.IS_EMULATED` as actually good designs support runtime feature
// `simd.HAS_HARDWARE_SIMD` as actually good designs support runtime feature
// detection and that constant establishes a baseline.
//
// See:
@@ -227,7 +227,7 @@ is_performant :: proc "contextless" () -> bool {
req_features :: info.CPU_Features{.V}
}
features, ok := info.cpu_features.?
features, ok := info.cpu.features.?
if !ok {
return false
}

View File

@@ -41,7 +41,7 @@ _VEC_TWO: simd.u64x4 : {2, 0, 2, 0}
is_performant :: proc "contextless" () -> bool {
req_features :: info.CPU_Features{.avx, .avx2}
features, ok := info.cpu_features.?
features, ok := info.cpu.features.?
if !ok {
return false
}

View File

@@ -52,7 +52,7 @@ K_15 :: simd.u64x2{0xa4506ceb90befffa, 0xc67178f2bef9a3f7}
// is_hardware_accelerated_256 returns true iff hardware accelerated
// SHA-224/SHA-256 is supported.
is_hardware_accelerated_256 :: proc "contextless" () -> bool {
features, ok := info.cpu_features.?
features, ok := info.cpu.features.?
if !ok {
return false
}

View File

@@ -49,7 +49,9 @@ _resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> (
data: [size_of(win32.SYMBOL_INFOW) + size_of([256]win32.WCHAR)]byte
symbol := (^win32.SYMBOL_INFOW)(&data[0])
symbol.SizeOfStruct = size_of(symbol)
// The value of SizeOfStruct must be the size of the whole struct,
// not just the size of the pointer
symbol.SizeOfStruct = size_of(symbol^)
symbol.MaxNameLen = 255
if win32.SymFromAddrW(ctx.impl.hProcess, win32.DWORD64(frame), &{}, symbol) {
fl.procedure, _ = win32.wstring_to_utf8(&symbol.Name[0], -1, allocator)

View File

@@ -385,17 +385,17 @@ to_diagnostic_format_writer :: proc(w: io.Writer, val: Value, padding := 0) -> i
// which we want for the diagnostic format.
case f16:
buf: [64]byte
str := strconv.append_float(buf[:], f64(v), 'f', 2*size_of(f16), 8*size_of(f16))
str := strconv.write_float(buf[:], f64(v), 'f', 2*size_of(f16), 8*size_of(f16))
if str[0] == '+' && str != "+Inf" { str = str[1:] }
io.write_string(w, str) or_return
case f32:
buf: [128]byte
str := strconv.append_float(buf[:], f64(v), 'f', 2*size_of(f32), 8*size_of(f32))
str := strconv.write_float(buf[:], f64(v), 'f', 2*size_of(f32), 8*size_of(f32))
if str[0] == '+' && str != "+Inf" { str = str[1:] }
io.write_string(w, str) or_return
case f64:
buf: [256]byte
str := strconv.append_float(buf[:], f64(v), 'f', 2*size_of(f64), 8*size_of(f64))
str := strconv.write_float(buf[:], f64(v), 'f', 2*size_of(f64), 8*size_of(f64))
if str[0] == '+' && str != "+Inf" { str = str[1:] }
io.write_string(w, str) or_return

View File

@@ -612,6 +612,42 @@ _marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (er
case:
panic("unknown bit_size size")
}
case runtime.Type_Info_Matrix:
count := info.column_count * info.elem_stride
err_conv(_encode_u64(e, u64(count), .Array)) or_return
if impl, ok := _tag_implementations_type[info.elem.id]; ok {
for i in 0..<count {
data := uintptr(v.data) + uintptr(i*info.elem_size)
impl->marshal(e, any{rawptr(data), info.elem.id}) or_return
}
return
}
elem_ti := runtime.type_info_core(type_info_of(info.elem.id))
for i in 0..<count {
data := uintptr(v.data) + uintptr(i*info.elem_size)
_marshal_into_encoder(e, any{rawptr(data), info.elem.id}, elem_ti) or_return
}
return
case runtime.Type_Info_Simd_Vector:
err_conv(_encode_u64(e, u64(info.count), .Array)) or_return
if impl, ok := _tag_implementations_type[info.elem.id]; ok {
for i in 0..<info.count {
data := uintptr(v.data) + uintptr(i*info.elem_size)
impl->marshal(e, any{rawptr(data), info.elem.id}) or_return
}
return
}
elem_ti := runtime.type_info_core(type_info_of(info.elem.id))
for i in 0..<info.count {
data := uintptr(v.data) + uintptr(i*info.elem_size)
_marshal_into_encoder(e, any{rawptr(data), info.elem.id}, elem_ti) or_return
}
return
}
return _unsupported(v.id, nil)

View File

@@ -29,6 +29,7 @@ an input.
unmarshal :: proc {
unmarshal_from_reader,
unmarshal_from_string,
unmarshal_from_bytes,
}
unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
@@ -51,6 +52,11 @@ unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, all
return
}
// Unmarshals from a slice of bytes, see docs on the proc group `Unmarshal` for more info.
unmarshal_from_bytes :: proc(bytes: []byte, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
return unmarshal_from_string(string(bytes), ptr, flags, allocator, temp_allocator, loc)
}
unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
d := d
@@ -487,7 +493,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align, allocator=allocator, loc=loc) or_return
defer if err != nil { mem.free_bytes(data, allocator=allocator, loc=loc) }
da := mem.Raw_Dynamic_Array{raw_data(data), 0, length, context.allocator }
da := mem.Raw_Dynamic_Array{raw_data(data), 0, scap, context.allocator }
assign_array(d, &da, t.elem, length) or_return
@@ -585,6 +591,31 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
if out_of_space { return _unsupported(v, hdr) }
return
case reflect.Type_Info_Matrix:
count := t.column_count * t.elem_stride
length, _ := err_conv(_decode_len_container(d, add)) or_return
if length > count {
return _unsupported(v, hdr)
}
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, allocator }
out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return
if out_of_space { return _unsupported(v, hdr) }
return
case reflect.Type_Info_Simd_Vector:
length, _ := err_conv(_decode_len_container(d, add)) or_return
if length > t.count {
return _unsupported(v, hdr)
}
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, allocator }
out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return
if out_of_space { return _unsupported(v, hdr) }
return
case: return _unsupported(v, hdr)
}
}

View File

@@ -63,8 +63,6 @@ Example:
read_csv_from_string :: proc(filename: string) {
r: csv.Reader
r.trim_leading_space = true
r.reuse_record = true // Without it you have to delete(record)
r.reuse_record_buffer = true // Without it you have to each of the fields within it
defer csv.reader_destroy(&r)
csv_data, ok := os.read_entire_file(filename)

View File

@@ -130,7 +130,7 @@ reader_destroy :: proc(r: ^Reader) {
for record, row_idx in csv.iterator_next(&r) { ... }
TIP: If you process the results within the loop and don't need to own the results,
you can set the Reader's `reuse_record` and `reuse_record_reuse_record_buffer` to true;
you can set the Reader's `reuse_record` and `reuse_record_buffer` to true;
you won't need to delete the record or its fields.
*/
iterator_next :: proc(r: ^Reader) -> (record: []string, idx: int, err: Error, more: bool) {

View File

@@ -108,7 +108,7 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator :=
it couldn't have been part of an XML tag body to be decoded here.
Keep in mind that we could already *be* inside a CDATA tag.
If so, write `>` as a literal and continue.
If so, write `<` as a literal and continue.
*/
if in_data {
write_rune(&builder, '<')
@@ -119,11 +119,9 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator :=
case ']':
// If we're unboxing _and_ decoding CDATA, we'll have to check for the end tag.
if in_data {
if t.read_offset + len(CDATA_END) < len(t.src) {
if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END {
in_data = false
t.read_offset += len(CDATA_END) - 1
}
if strings.has_prefix(t.src[t.offset:], CDATA_END) {
in_data = false
t.read_offset += len(CDATA_END) - 1
}
continue
} else {
@@ -297,40 +295,40 @@ _handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: X
assert(t != nil && t.r == '<')
if t.read_offset + len(CDATA_START) >= len(t.src) { return false, .None }
if string(t.src[t.offset:][:len(CDATA_START)]) == CDATA_START {
t.read_offset += len(CDATA_START) - 1
s := string(t.src[t.offset:])
if strings.has_prefix(s, CDATA_START) {
if .Unbox_CDATA in options && .Decode_CDATA in options {
// We're unboxing _and_ decoding CDATA
t.read_offset += len(CDATA_START) - 1
return true, .None
}
// CDATA is passed through.
offset := t.offset
// Scan until end of CDATA.
// CDATA is passed through. Scan until end of CDATA.
start_offset := t.offset
t.read_offset += len(CDATA_START)
for {
advance(t) or_return
if t.r < 0 { return true, .CDATA_Not_Terminated }
advance(t)
if t.r < 0 {
// error(t, offset, "[scan_string] CDATA was not terminated\n")
return true, .CDATA_Not_Terminated
}
if t.read_offset + len(CDATA_END) < len(t.src) {
if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END {
t.read_offset += len(CDATA_END) - 1
// Scan until the end of a CDATA tag.
if s = string(t.src[t.read_offset:]); strings.has_prefix(s, CDATA_END) {
t.read_offset += len(CDATA_END)
cdata := string(t.src[start_offset:t.read_offset])
cdata := string(t.src[offset : t.read_offset])
if .Unbox_CDATA in options {
cdata = cdata[len(CDATA_START):]
cdata = cdata[:len(cdata) - len(CDATA_END)]
}
write_string(builder, cdata)
return false, .None
if .Unbox_CDATA in options {
cdata = cdata[len(CDATA_START):]
cdata = cdata[:len(cdata) - len(CDATA_END)]
}
write_string(builder, cdata)
return false, .None
}
}
} else if string(t.src[t.offset:][:len(COMMENT_START)]) == COMMENT_START {
} else if strings.has_prefix(s, COMMENT_START) {
t.read_offset += len(COMMENT_START)
// Comment is passed through by default.
offset := t.offset

View File

@@ -79,7 +79,6 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
read_meta :: proc(r: ^Reader, capacity: u32le, allocator := context.allocator, loc := #caller_location) -> (meta_data: []Meta, err: Read_Error) {
meta_data = make([]Meta, int(capacity), allocator=allocator)
count := 0
defer meta_data = meta_data[:count]
for &m in meta_data {
m.name = read_name(r) or_return
@@ -105,6 +104,7 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
count += 1
}
meta_data = meta_data[:count]
return
}
@@ -112,7 +112,6 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
stack_count := read_value(r, u32le) or_return
layer_count := 0
layers = make(Layer_Stack, stack_count, allocator=allocator, loc=loc)
defer layers = layers[:layer_count]
for &layer in layers {
layer.name = read_name(r) or_return
layer.components = read_value(r, u8) or_return
@@ -136,6 +135,7 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
layer_count += 1
}
layers = layers[:layer_count]
return
}

View File

@@ -108,13 +108,13 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
if opt.write_uint_as_hex && (opt.spec == .JSON5 || opt.spec == .MJSON) {
switch i in a {
case u8, u16, u32, u64, u128:
s = strconv.append_bits_128(buf[:], u, 16, info.signed, 8*ti.size, "0123456789abcdef", { .Prefix })
s = strconv.write_bits_128(buf[:], u, 16, info.signed, 8*ti.size, "0123456789abcdef", { .Prefix })
case:
s = strconv.append_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil)
s = strconv.write_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil)
}
} else {
s = strconv.append_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil)
s = strconv.write_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil)
}
io.write_string(w, s) or_return
@@ -286,7 +286,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
case ^runtime.Type_Info_Integer:
buf: [40]byte
u := cast_any_int_to_u128(ka)
name = strconv.append_bits_128(buf[:], u, 10, info.signed, 8*kti.size, "0123456789", nil)
name = strconv.write_bits_128(buf[:], u, 10, info.signed, 8*kti.size, "0123456789", nil)
opt_write_key(w, opt, name) or_return
case: return .Unsupported_Type

View File

@@ -101,7 +101,7 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) {
}
}
scan_espace :: proc(t: ^Tokenizer) -> bool {
scan_escape :: proc(t: ^Tokenizer) -> bool {
switch t.r {
case '"', '\'', '\\', '/', 'b', 'n', 'r', 't', 'f':
next_rune(t)
@@ -310,7 +310,7 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) {
break
}
if r == '\\' {
scan_espace(t)
scan_escape(t)
}
}

View File

@@ -406,6 +406,9 @@ unmarshal_expect_token :: proc(p: ^Parser, kind: Token_Kind, loc := #caller_loca
return prev
}
// Struct tags can include not only the name of the JSON key, but also a tag such as `omitempty`.
// Example: `json:"key_name,omitempty"`
// This returns the first field as `json_name`, and the rest are returned as `extra`.
@(private)
json_name_from_tag_value :: proc(value: string) -> (json_name, extra: string) {
json_name = value
@@ -441,12 +444,6 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
defer delete(key, p.allocator)
unmarshal_expect_token(p, .Colon)
field_test :: #force_inline proc "contextless" (field_used: [^]byte, offset: uintptr) -> bool {
prev_set := field_used[offset/8] & byte(offset&7) != 0
field_used[offset/8] |= byte(offset&7)
return prev_set
}
field_used_bytes := (reflect.size_of_typeid(ti.id)+7)/8
field_used := intrinsics.alloca(field_used_bytes + 1, 1) // + 1 to not overflow on size_of 0 types.
@@ -465,7 +462,9 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
if use_field_idx < 0 {
for field, field_idx in fields {
if key == field.name {
tag_value := reflect.struct_tag_get(field.tag, "json")
json_name, _ := json_name_from_tag_value(tag_value)
if json_name == "" && key == field.name {
use_field_idx = field_idx
break
}
@@ -486,7 +485,9 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
}
}
if field.name == key || (field.tag != "" && reflect.struct_tag_get(field.tag, "json") == key) {
tag_value := reflect.struct_tag_get(field.tag, "json")
json_name, _ := json_name_from_tag_value(tag_value)
if (json_name == "" && field.name == key) || json_name == key {
offset = field.offset
type = field.type
found = true
@@ -508,6 +509,11 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
}
if field_found {
field_test :: #force_inline proc "contextless" (field_used: [^]byte, offset: uintptr) -> bool {
prev_set := field_used[offset/8] & byte(offset&7) != 0
field_used[offset/8] |= byte(offset&7)
return prev_set
}
if field_test(field_used, offset) {
return .Multiple_Use_Field
}

View File

@@ -240,7 +240,7 @@ Example:
import "core:encoding/uuid"
import "core:fmt"
main :: proc() {
generate_v8_hash_bytes_example :: proc() {
my_uuid := uuid.generate_v8_hash(uuid.Namespace_DNS, "www.odin-lang.org", .SHA256)
my_uuid_string := uuid.to_string(my_uuid, context.temp_allocator)
fmt.println(my_uuid_string)
@@ -306,7 +306,7 @@ Example:
import "core:encoding/uuid"
import "core:fmt"
main :: proc() {
generate_v8_hash_string_example :: proc() {
my_uuid := uuid.generate_v8_hash(uuid.Namespace_DNS, "www.odin-lang.org", .SHA256)
my_uuid_string := uuid.to_string(my_uuid, context.temp_allocator)
fmt.println(my_uuid_string)

View File

@@ -16,6 +16,7 @@ package encoding_xml
import "core:fmt"
import "core:unicode"
import "core:unicode/utf8"
import "core:strings"
Error_Handler :: #type proc(pos: Pos, fmt: string, args: ..any)
@@ -121,7 +122,7 @@ default_error_handler :: proc(pos: Pos, msg: string, args: ..any) {
error :: proc(t: ^Tokenizer, offset: int, msg: string, args: ..any) {
pos := offset_to_pos(t, offset)
if t.err != nil {
t.err(pos, msg, ..args)
t.err(pos=pos, fmt=msg, args=args)
}
t.error_count += 1
}
@@ -268,32 +269,27 @@ scan_comment :: proc(t: ^Tokenizer) -> (comment: string, err: Error) {
// Skip CDATA
skip_cdata :: proc(t: ^Tokenizer) -> (err: Error) {
if t.read_offset + len(CDATA_START) >= len(t.src) {
// Can't be the start of a CDATA tag.
if s := string(t.src[t.offset:]); !strings.has_prefix(s, CDATA_START) {
return .None
}
if string(t.src[t.offset:][:len(CDATA_START)]) == CDATA_START {
t.read_offset += len(CDATA_START)
offset := t.offset
t.read_offset += len(CDATA_START)
offset := t.offset
cdata_scan: for {
advance_rune(t)
if t.ch < 0 {
error(t, offset, "[scan_string] CDATA was not terminated\n")
return .Premature_EOF
}
cdata_scan: for {
advance_rune(t)
if t.ch < 0 {
error(t, offset, "[scan_string] CDATA was not terminated\n")
return .Premature_EOF
}
// Scan until the end of a CDATA tag.
if t.read_offset + len(CDATA_END) < len(t.src) {
if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END {
t.read_offset += len(CDATA_END)
break cdata_scan
}
}
// Scan until the end of a CDATA tag.
if s := string(t.src[t.read_offset:]); strings.has_prefix(s, CDATA_END) {
t.read_offset += len(CDATA_END)
break cdata_scan
}
}
return
return .None
}
@(optimization_mode="favor_size")
@@ -393,6 +389,8 @@ scan :: proc(t: ^Tokenizer, multiline_string := false) -> Token {
case '/': kind = .Slash
case '-': kind = .Dash
case ':': kind = .Colon
case '[': kind = .Open_Bracket
case ']': kind = .Close_Bracket
case '"', '\'':
kind = .Invalid

View File

@@ -56,7 +56,7 @@ Option_Flag :: enum {
Option_Flags :: bit_set[Option_Flag; u16]
Document :: struct {
elements: [dynamic]Element,
elements: [dynamic]Element `fmt:"v,element_count"`,
element_count: Element_ID,
prologue: Attributes,
@@ -70,15 +70,15 @@ Document :: struct {
// If we encounter comments before the root node, and the option to intern comments is given, this is where they'll live.
// Otherwise they'll be in the element tree.
comments: [dynamic]string,
comments: [dynamic]string `fmt:"-"`,
// Internal
tokenizer: ^Tokenizer,
allocator: mem.Allocator,
tokenizer: ^Tokenizer `fmt:"-"`,
allocator: mem.Allocator `fmt:"-"`,
// Input. Either the original buffer, or a copy if `.Input_May_Be_Modified` isn't specified.
input: []u8,
strings_to_free: [dynamic]string,
input: []u8 `fmt:"-"`,
strings_to_free: [dynamic]string `fmt:"-"`,
}
Element :: struct {
@@ -175,7 +175,7 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
data = bytes.clone(data)
}
t := &Tokenizer{}
t := new(Tokenizer)
init(t, string(data), path, error_handler)
doc = new(Document)
@@ -195,7 +195,6 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
loop: for {
skip_whitespace(t)
// NOTE(Jeroen): This is faster as a switch.
switch t.ch {
case '<':
// Consume peeked `<`
@@ -306,9 +305,17 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
}
}
case .Open_Bracket:
// This could be a CDATA tag part of a tag's body. Unread the `<![`
t.offset -= 3
// Instead of calling `parse_body` here, we could also `continue loop`
// and fall through to the `case:` at the bottom of the outer loop.
// This makes the intent clearer.
parse_body(doc, element, opts) or_return
case:
error(t, t.offset, "Invalid Token after <!. Expected .Ident, got %#v\n", next)
return
error(t, t.offset, "Unexpected Token after <!: %#v", next)
}
} else if open.kind == .Question {
@@ -341,38 +348,7 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
case:
// This should be a tag's body text.
body_text := scan_string(t, t.offset) or_return
needs_processing := .Unbox_CDATA in opts.flags
needs_processing |= .Decode_SGML_Entities in opts.flags
if !needs_processing {
append(&doc.elements[element].value, body_text)
continue
}
decode_opts := entity.XML_Decode_Options{}
if .Keep_Tag_Body_Comments not_in opts.flags {
decode_opts += { .Comment_Strip }
}
if .Decode_SGML_Entities not_in opts.flags {
decode_opts += { .No_Entity_Decode }
}
if .Unbox_CDATA in opts.flags {
decode_opts += { .Unbox_CDATA }
if .Decode_SGML_Entities in opts.flags {
decode_opts += { .Decode_CDATA }
}
}
decoded, decode_err := entity.decode_xml(body_text, decode_opts)
if decode_err == .None {
append(&doc.elements[element].value, decoded)
append(&doc.strings_to_free, decoded)
} else {
append(&doc.elements[element].value, body_text)
}
parse_body(doc, element, opts) or_return
}
}
@@ -427,6 +403,7 @@ destroy :: proc(doc: ^Document) {
}
delete(doc.strings_to_free)
free(doc.tokenizer)
free(doc)
}
@@ -457,8 +434,6 @@ parse_attribute :: proc(doc: ^Document) -> (attr: Attribute, offset: int, err: E
t := doc.tokenizer
key := expect(t, .Ident) or_return
offset = t.offset - len(key.text)
_ = expect(t, .Eq) or_return
value := expect(t, .String, multiline_string=true) or_return
@@ -591,6 +566,47 @@ parse_doctype :: proc(doc: ^Document) -> (err: Error) {
return .None
}
parse_body :: proc(doc: ^Document, element: Element_ID, opts: Options) -> (err: Error) {
assert(doc != nil)
context.allocator = doc.allocator
t := doc.tokenizer
body_text := scan_string(t, t.offset) or_return
needs_processing := .Unbox_CDATA in opts.flags
needs_processing |= .Decode_SGML_Entities in opts.flags
if !needs_processing {
append(&doc.elements[element].value, body_text)
return
}
decode_opts := entity.XML_Decode_Options{}
if .Keep_Tag_Body_Comments not_in opts.flags {
decode_opts += { .Comment_Strip }
}
if .Decode_SGML_Entities not_in opts.flags {
decode_opts += { .No_Entity_Decode }
}
if .Unbox_CDATA in opts.flags {
decode_opts += { .Unbox_CDATA }
if .Decode_SGML_Entities in opts.flags {
decode_opts += { .Decode_CDATA }
}
}
decoded, decode_err := entity.decode_xml(body_text, decode_opts)
if decode_err == .None {
append(&doc.elements[element].value, decoded)
append(&doc.strings_to_free, decoded)
} else {
append(&doc.elements[element].value, body_text)
}
return
}
Element_ID :: u32
new_element :: proc(doc: ^Document) -> (id: Element_ID) {
@@ -609,4 +625,4 @@ new_element :: proc(doc: ^Document) -> (id: Element_ID) {
cur := doc.element_count
doc.element_count += 1
return cur
}
}

View File

@@ -19,7 +19,7 @@ SUBTAG_NAME :: "name"
SUBTAG_POS :: "pos"
SUBTAG_REQUIRED :: "required"
SUBTAG_HIDDEN :: "hidden"
SUBTAG_VARIADIC :: "variadic"
SUBTAG_MANIFOLD :: "manifold"
SUBTAG_FILE :: "file"
SUBTAG_PERMS :: "perms"
SUBTAG_INDISTINCT :: "indistinct"
@@ -28,7 +28,7 @@ TAG_USAGE :: "usage"
UNDOCUMENTED_FLAG :: "<This flag has not been documented yet.>"
INTERNAL_VARIADIC_FLAG :: "varg"
INTERNAL_OVERFLOW_FLAG :: #config(ODIN_CORE_FLAGS_OVERFLOW_FLAG, "overflow")
RESERVED_HELP_FLAG :: "help"
RESERVED_HELP_FLAG_SHORT :: "h"

View File

@@ -20,6 +20,17 @@ The format is similar to the Odin binary's way of handling compiler flags.
-<map>:<key>=<value> set map[key] to value
Unhandled Arguments:
All unhandled positional arguments are placed into the `overflow` field on a
struct, if it exists. In UNIX-style parsing, the existence of a `--` on the
command line will also pass all arguments afterwards into this field.
If desired, the name of the field may be changed from `overflow` to any string
by setting the `ODIN_CORE_FLAGS_OVERFLOW_FLAG` compile-time config option with
`-define:ODIN_CORE_FLAGS_OVERFLOW_FLAG=<name>`.
Struct Tags:
Users of the `core:encoding/json` package may be familiar with using tags to
@@ -32,7 +43,7 @@ Under the `args` tag, there are the following subtags:
- `pos=N`: place positional argument `N` into this flag.
- `hidden`: hide this flag from the usage documentation.
- `required`: cause verification to fail if this argument is not set.
- `variadic`: take all remaining arguments when set, UNIX-style only.
- `manifold=N`: take several arguments at once, UNIX-style only.
- `file`: for `os.Handle` types, file open mode.
- `perms`: for `os.Handle` types, file open permissions.
- `indistinct`: allow the setting of distinct types by their base type.
@@ -47,8 +58,9 @@ you want to require 3 and only 3 arguments in a dynamic array, you would
specify `required=3<4`.
`variadic` may be given a number (`variadic=N`) above 1 to limit how many extra
arguments it consumes.
`manifold` may be given a number (`manifold=N`) above 1 to limit how many extra
arguments it consumes at once. If this number is not specified, it will take as
many arguments as can be converted to the underlying element type.
`file` determines the file open mode for an `os.Handle`.
@@ -160,7 +172,7 @@ at parse time.
--flag
--flag=argument
--flag argument
--flag argument repeating-argument
--flag argument (manifold-argument)
`-flag` may also be substituted for `--flag`.

View File

@@ -4,7 +4,7 @@ import "core:os"
Parse_Error_Reason :: enum {
None,
// An extra positional argument was given, and there is no `varg` field.
// An extra positional argument was given, and there is no `overflow` field.
Extra_Positional,
// The underlying type does not support the string value it is being set to.
Bad_Value,

View File

@@ -107,14 +107,14 @@ main :: proc() {
// assignments: map[string]u8 `args:"name=assign" usage:"Number of jobs per worker."`,
// (Variadic) Only available in UNIX style:
// (Manifold) Only available in UNIX style:
// bots: [dynamic]string `args:"variadic=2,required"`,
// bots: [dynamic]string `args:"manifold=2,required"`,
verbose: bool `usage:"Show verbose output."`,
debug: bool `args:"hidden" usage:"print debug info"`,
varg: [dynamic]string `usage:"Any extra arguments go here."`,
overflow: [dynamic]string `usage:"Any extra arguments go here."`,
}
opt: Options

View File

@@ -33,9 +33,9 @@ push_positional :: #force_no_inline proc (model: ^$T, parser: ^Parser, arg: stri
field, index, has_pos_assigned := get_field_by_pos(model, pos)
if !has_pos_assigned {
when intrinsics.type_has_field(T, INTERNAL_VARIADIC_FLAG) {
when intrinsics.type_has_field(T, INTERNAL_OVERFLOW_FLAG) {
// Add it to the fallback array.
field = reflect.struct_field_by_name(T, INTERNAL_VARIADIC_FLAG)
field = reflect.struct_field_by_name(T, INTERNAL_OVERFLOW_FLAG)
} else {
return Parse_Error {
.Extra_Positional,
@@ -117,8 +117,8 @@ set_unix_flag :: proc(model: ^$T, parser: ^Parser, name: string) -> (future_args
case ^runtime.Type_Info_Dynamic_Array:
future_args = 1
if tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok {
if length, is_variadic := get_struct_subtag(tag, SUBTAG_VARIADIC); is_variadic {
// Variadic arrays may specify how many arguments they consume at once.
if length, is_manifold := get_struct_subtag(tag, SUBTAG_MANIFOLD); is_manifold {
// Manifold arrays may specify how many arguments they consume at once.
// Otherwise, they take everything that's left.
if value, value_ok := strconv.parse_u64_of_base(length, 10); value_ok {
future_args = cast(int)value

View File

@@ -95,7 +95,7 @@ parse_one_unix_arg :: proc(model: ^$T, parser: ^Parser, arg: string) -> (
// `--`, and only `--`.
// Everything from now on will be treated as an argument.
future_args = max(int)
current_flag = INTERNAL_VARIADIC_FLAG
current_flag = INTERNAL_OVERFLOW_FLAG
return
}
}

View File

@@ -59,7 +59,8 @@ validate_structure :: proc(model_type: $T, style: Parsing_Style, loc := #caller_
}
}
if pos_str, has_pos := get_struct_subtag(args_tag, SUBTAG_POS); has_pos {
pos_str, has_pos := get_struct_subtag(args_tag, SUBTAG_POS)
if has_pos {
#partial switch specific_type_info in field.type.variant {
case ^runtime.Type_Info_Map:
fmt.panicf("%T.%s has `%s` defined, and this does not make sense on a map type.",
@@ -79,7 +80,7 @@ validate_structure :: proc(model_type: $T, style: Parsing_Style, loc := #caller_
fmt.assertf(!reflect.is_boolean(field.type), "%T.%s is a required boolean. This is disallowed.",
model_type, field.name, loc = loc)
fmt.assertf(field.name != INTERNAL_VARIADIC_FLAG, "%T.%s is defined as required. This is disallowed.",
fmt.assertf(field.name != INTERNAL_OVERFLOW_FLAG, "%T.%s is defined as required. This is disallowed.",
model_type, field.name, loc = loc)
if len(requirement) > 0 {
@@ -109,24 +110,28 @@ validate_structure :: proc(model_type: $T, style: Parsing_Style, loc := #caller_
}
}
if length, is_variadic := get_struct_subtag(args_tag, SUBTAG_VARIADIC); is_variadic {
if length, is_manifold := get_struct_subtag(args_tag, SUBTAG_MANIFOLD); is_manifold {
fmt.assertf(!has_pos,
"%T.%s has both `%s` and `%s` defined. This is disallowed.\n\tSuggestion: Use a dynamic array field named `%s` to accept unspecified positional arguments.",
model_type, field.name, SUBTAG_POS, SUBTAG_MANIFOLD, INTERNAL_OVERFLOW_FLAG, loc = loc)
if value, parse_ok := strconv.parse_u64_of_base(length, 10); parse_ok {
fmt.assertf(value > 0,
"%T.%s has `%s` set to %i. It must be greater than zero.",
model_type, field.name, value, SUBTAG_VARIADIC, loc = loc)
model_type, field.name, value, SUBTAG_MANIFOLD, loc = loc)
fmt.assertf(value != 1,
"%T.%s has `%s` set to 1. This has no effect.",
model_type, field.name, SUBTAG_VARIADIC, loc = loc)
"%T.%s has `%s` set to 1. This is equivalent to not defining `%s`.",
model_type, field.name, SUBTAG_MANIFOLD, SUBTAG_MANIFOLD, loc = loc)
}
#partial switch specific_type_info in field.type.variant {
case ^runtime.Type_Info_Dynamic_Array:
fmt.assertf(style != .Odin,
"%T.%s has `%s` defined, but this only makes sense in UNIX-style parsing mode.",
model_type, field.name, SUBTAG_VARIADIC, loc = loc)
model_type, field.name, SUBTAG_MANIFOLD, loc = loc)
case:
fmt.panicf("%T.%s has `%s` defined, but this only makes sense on dynamic arrays.",
model_type, field.name, SUBTAG_VARIADIC, loc = loc)
model_type, field.name, SUBTAG_MANIFOLD, loc = loc)
}
}

View File

@@ -6,7 +6,7 @@ package flags
Parsing_Style :: enum {
// Odin-style: `-flag`, `-flag:option`, `-map:key=value`
Odin,
// UNIX-style: `-flag` or `--flag`, `--flag=argument`, `--flag argument repeating-argument`
// UNIX-style: `-flag` or `--flag`, `--flag=argument`, `--flag argument (manifold-argument)`
Unix,
}
@@ -61,7 +61,7 @@ parse :: proc(
}
case .Unix:
// Support for `-flag argument (repeating-argument ...)`
// Support for `-flag argument (manifold-argument ...)`
future_args: int
current_flag: string

View File

@@ -30,18 +30,18 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
is_positional: bool,
is_required: bool,
is_boolean: bool,
is_variadic: bool,
variadic_length: int,
is_manifold: bool,
manifold_length: int,
}
//
// POSITIONAL+REQUIRED, POSITIONAL, REQUIRED, NON_REQUIRED+NON_POSITIONAL, ...
//
sort_flags :: proc(i, j: Flag) -> slice.Ordering {
// `varg` goes to the end.
if i.name == INTERNAL_VARIADIC_FLAG {
// `overflow` goes to the end.
if i.name == INTERNAL_OVERFLOW_FLAG {
return .Greater
} else if j.name == INTERNAL_VARIADIC_FLAG {
} else if j.name == INTERNAL_OVERFLOW_FLAG {
return .Less
}
@@ -120,10 +120,10 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
flag.is_required = true
flag.required_min, flag.required_max, _ = parse_requirements(requirement)
}
if length_str, is_variadic := get_struct_subtag(args_tag, SUBTAG_VARIADIC); is_variadic {
flag.is_variadic = true
if length_str, is_manifold := get_struct_subtag(args_tag, SUBTAG_MANIFOLD); is_manifold {
flag.is_manifold = true
if length, parse_ok := strconv.parse_u64_of_base(length_str, 10); parse_ok {
flag.variadic_length = cast(int)length
flag.manifold_length = cast(int)length
}
}
}
@@ -147,15 +147,15 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
case ^runtime.Type_Info_Dynamic_Array:
requirement_spec := describe_array_requirements(flag)
if flag.is_variadic || flag.name == INTERNAL_VARIADIC_FLAG {
if flag.variadic_length == 0 {
if flag.is_manifold || flag.name == INTERNAL_OVERFLOW_FLAG {
if flag.manifold_length == 0 {
flag.type_description = fmt.tprintf("<%v, ...>%s",
specific_type_info.elem.id,
requirement_spec)
} else {
flag.type_description = fmt.tprintf("<%v, %i at once>%s",
specific_type_info.elem.id,
flag.variadic_length,
flag.manifold_length,
requirement_spec)
}
} else {
@@ -177,7 +177,7 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
}
}
if flag.name == INTERNAL_VARIADIC_FLAG {
if flag.name == INTERNAL_OVERFLOW_FLAG {
flag.full_length = len(flag.type_description)
} else if flag.is_boolean {
flag.full_length = len(flag_prefix) + len(flag.name) + len(flag.type_description)
@@ -201,13 +201,13 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
strings.write_string(&builder, program)
for flag in visible_flags {
if keep_it_short && !(flag.is_required || flag.is_positional || flag.name == INTERNAL_VARIADIC_FLAG) {
if keep_it_short && !(flag.is_required || flag.is_positional || flag.name == INTERNAL_OVERFLOW_FLAG) {
continue
}
strings.write_byte(&builder, ' ')
if flag.name == INTERNAL_VARIADIC_FLAG {
if flag.name == INTERNAL_OVERFLOW_FLAG {
strings.write_string(&builder, "...")
continue
}
@@ -252,7 +252,7 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
strings.write_byte(&builder, '\t')
if flag.name == INTERNAL_VARIADIC_FLAG {
if flag.name == INTERNAL_OVERFLOW_FLAG {
strings.write_string(&builder, flag.type_description)
} else {
strings.write_string(&builder, flag_prefix)

View File

@@ -95,7 +95,7 @@ Example:
import "core:flags"
import "core:fmt"
subtag_example :: proc() {
get_subtag_example :: proc() {
args_tag := "precision=3,signed"
precision, has_precision := flags.get_subtag(args_tag, "precision")

View File

@@ -1122,7 +1122,7 @@ _fmt_int :: proc(fi: ^Info, u: u64, base: int, is_signed: bool, bit_size: int, d
flags: strconv.Int_Flags
if fi.hash && !fi.zero && start == 0 { flags += {.Prefix} }
if fi.plus { flags += {.Plus} }
s := strconv.append_bits(buf[start:], u, base, is_signed, bit_size, digits, flags)
s := strconv.write_bits(buf[start:], u, base, is_signed, bit_size, digits, flags)
prev_zero := fi.zero
defer fi.zero = prev_zero
fi.zero = false
@@ -1207,7 +1207,7 @@ _fmt_int_128 :: proc(fi: ^Info, u: u128, base: int, is_signed: bool, bit_size: i
flags: strconv.Int_Flags
if fi.hash && !fi.zero && start == 0 { flags += {.Prefix} }
if fi.plus { flags += {.Plus} }
s := strconv.append_bits_128(buf[start:], u, base, is_signed, bit_size, digits, flags)
s := strconv.write_bits_128(buf[start:], u, base, is_signed, bit_size, digits, flags)
if fi.hash && fi.zero && fi.indent == 0 {
c: byte = 0
@@ -1272,7 +1272,7 @@ _fmt_memory :: proc(fi: ^Info, u: u64, is_signed: bool, bit_size: int, units: st
}
buf: [256]byte
str := strconv.append_float(buf[:], amt, 'f', prec, 64)
str := strconv.write_float(buf[:], amt, 'f', prec, 64)
// Add the unit at the end.
copy(buf[len(str):], units[off:off+unit_len])
@@ -1424,7 +1424,7 @@ _fmt_float_as :: proc(fi: ^Info, v: f64, bit_size: int, verb: rune, float_fmt: b
buf: [386]byte
// Can return "NaN", "+Inf", "-Inf", "+<value>", "-<value>".
str := strconv.append_float(buf[:], v, float_fmt, prec, bit_size)
str := strconv.write_float(buf[:], v, float_fmt, prec, bit_size)
if !fi.plus {
// Strip sign from "+<value>" but not "+Inf".
@@ -1449,9 +1449,12 @@ fmt_float :: proc(fi: ^Info, v: f64, bit_size: int, verb: rune) {
_fmt_float_as(fi, v, bit_size, verb, 'g', -1)
case 'f', 'F':
_fmt_float_as(fi, v, bit_size, verb, 'f', 3)
case 'e', 'E':
case 'e':
// BUG(): "%.3e" returns "3.000e+00"
_fmt_float_as(fi, v, bit_size, verb, 'e', 6)
case 'E':
// BUG(): "%.3E" returns "3.000E+00"
_fmt_float_as(fi, v, bit_size, verb, 'E', 6)
case 'h', 'H':
prev_fi := fi^
@@ -1802,11 +1805,8 @@ fmt_bit_set :: proc(fi: ^Info, v: any, name: string = "", verb: rune = 'v') {
e, is_enum := et.variant.(^runtime.Type_Info_Enum)
commas := 0
loop: for i in 0 ..< bit_size {
if bits & (1<<i) == 0 {
continue loop
}
loop: for i in transmute(bit_set[0..<128])bits {
i := i64(i) + info.lower
if commas > 0 {
io.write_string(fi.writer, ", ", &fi.n)
}
@@ -1829,8 +1829,7 @@ fmt_bit_set :: proc(fi: ^Info, v: any, name: string = "", verb: rune = 'v') {
}
}
}
v := i64(i) + info.lower
io.write_i64(fi.writer, v, 10, &fi.n)
io.write_i64(fi.writer, i, 10, &fi.n)
commas += 1
}
}

View File

@@ -317,85 +317,4 @@ crc32_table := [8][256]u32{
0xff6b144a, 0x33c114d4, 0xbd4e1337, 0x71e413a9, 0x7b211ab0, 0xb78b1a2e, 0x39041dcd, 0xf5ae1d53,
0x2c8e0fff, 0xe0240f61, 0x6eab0882, 0xa201081c, 0xa8c40105, 0x646e019b, 0xeae10678, 0x264b06e6,
},
}
/*
@(optimization_mode="speed")
crc32 :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 {
result := ~u32(seed);
#no_bounds_check for b in data {
result = result>>8 ~ _crc32_table[(result ~ u32(b)) & 0xff];
}
return ~result;
}
@private _crc32_table := [256]u32{
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
};
*/
}

View File

@@ -1212,7 +1212,6 @@ Filter_Params :: struct #packed {
depth_scale_table :: []u8{0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01}
// @(optimization_mode="speed")
defilter_8 :: proc(params: ^Filter_Params) -> (ok: bool) {
using params
@@ -1273,7 +1272,6 @@ defilter_8 :: proc(params: ^Filter_Params) -> (ok: bool) {
return
}
// @(optimization_mode="speed")
defilter_less_than_8 :: proc(params: ^Filter_Params) -> bool #no_bounds_check {
using params
@@ -1436,7 +1434,6 @@ defilter_less_than_8 :: proc(params: ^Filter_Params) -> bool #no_bounds_check {
return true
}
// @(optimization_mode="speed")
defilter_16 :: proc(params: ^Filter_Params) -> bool {
using params

View File

@@ -22,12 +22,12 @@ write_ptr_at :: proc(w: Writer_At, p: rawptr, byte_size: int, offset: i64, n_wri
write_u64 :: proc(w: Writer, i: u64, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) {
buf: [32]byte
s := strconv.append_bits(buf[:], i, base, false, 64, strconv.digits, nil)
s := strconv.write_bits(buf[:], i, base, false, 64, strconv.digits, nil)
return write_string(w, s, n_written)
}
write_i64 :: proc(w: Writer, i: i64, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) {
buf: [32]byte
s := strconv.append_bits(buf[:], u64(i), base, true, 64, strconv.digits, nil)
s := strconv.write_bits(buf[:], u64(i), base, true, 64, strconv.digits, nil)
return write_string(w, s, n_written)
}
@@ -40,18 +40,18 @@ write_int :: proc(w: Writer, i: int, base: int = 10, n_written: ^int = nil) -> (
write_u128 :: proc(w: Writer, i: u128, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) {
buf: [39]byte
s := strconv.append_bits_128(buf[:], i, base, false, 128, strconv.digits, nil)
s := strconv.write_bits_128(buf[:], i, base, false, 128, strconv.digits, nil)
return write_string(w, s, n_written)
}
write_i128 :: proc(w: Writer, i: i128, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) {
buf: [40]byte
s := strconv.append_bits_128(buf[:], u128(i), base, true, 128, strconv.digits, nil)
s := strconv.write_bits_128(buf[:], u128(i), base, true, 128, strconv.digits, nil)
return write_string(w, s, n_written)
}
write_f16 :: proc(w: Writer, val: f16, n_written: ^int = nil) -> (n: int, err: Error) {
buf: [386]byte
str := strconv.append_float(buf[1:], f64(val), 'f', 2*size_of(val), 8*size_of(val))
str := strconv.write_float(buf[1:], f64(val), 'f', 2*size_of(val), 8*size_of(val))
s := buf[:len(str)+1]
if s[1] == '+' || s[1] == '-' {
s = s[1:]
@@ -67,7 +67,7 @@ write_f16 :: proc(w: Writer, val: f16, n_written: ^int = nil) -> (n: int, err: E
write_f32 :: proc(w: Writer, val: f32, n_written: ^int = nil) -> (n: int, err: Error) {
buf: [386]byte
str := strconv.append_float(buf[1:], f64(val), 'f', 2*size_of(val), 8*size_of(val))
str := strconv.write_float(buf[1:], f64(val), 'f', 2*size_of(val), 8*size_of(val))
s := buf[:len(str)+1]
if s[1] == '+' || s[1] == '-' {
s = s[1:]
@@ -83,7 +83,7 @@ write_f32 :: proc(w: Writer, val: f32, n_written: ^int = nil) -> (n: int, err: E
write_f64 :: proc(w: Writer, val: f64, n_written: ^int = nil) -> (n: int, err: Error) {
buf: [386]byte
str := strconv.append_float(buf[1:], val, 'f', 2*size_of(val), 8*size_of(val))
str := strconv.write_float(buf[1:], val, 'f', 2*size_of(val), 8*size_of(val))
s := buf[:len(str)+1]
if s[1] == '+' || s[1] == '-' {
s = s[1:]
@@ -130,7 +130,7 @@ write_encoded_rune :: proc(w: Writer, r: rune, write_quote := true, n_written: ^
write_string(w, `\x`, &n) or_return
buf: [2]byte
s := strconv.append_bits(buf[:], u64(r), 16, true, 64, strconv.digits, nil)
s := strconv.write_bits(buf[:], u64(r), 16, true, 64, strconv.digits, nil)
switch len(s) {
case 0:
write_string(w, "00", &n) or_return

View File

@@ -2,10 +2,12 @@
#+build !orca
package log
import "core:encoding/ansi"
import "base:runtime"
import "core:fmt"
import "core:strings"
import "core:os"
import "core:terminal"
import "core:terminal/ansi"
import "core:time"
Level_Headers := [?]string{
@@ -37,11 +39,36 @@ File_Console_Logger_Data :: struct {
ident: string,
}
@(private) global_subtract_stdout_options: Options
@(private) global_subtract_stderr_options: Options
@(init, private)
init_standard_stream_status :: proc() {
// NOTE(Feoramund): While it is technically possible for these streams to
// be redirected during the runtime of the program, the cost of checking on
// every single log message is not worth it to support such an
// uncommonly-used feature.
if terminal.color_enabled {
// This is done this way because it's possible that only one of these
// streams could be redirected to a file.
if !terminal.is_terminal(os.stdout) {
global_subtract_stdout_options = {.Terminal_Color}
}
if !terminal.is_terminal(os.stderr) {
global_subtract_stderr_options = {.Terminal_Color}
}
} else {
// Override any terminal coloring.
global_subtract_stdout_options = {.Terminal_Color}
global_subtract_stderr_options = {.Terminal_Color}
}
}
create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator) -> Logger {
data := new(File_Console_Logger_Data, allocator)
data.file_handle = h
data.ident = ident
return Logger{file_console_logger_proc, data, lowest, opt}
return Logger{file_logger_proc, data, lowest, opt}
}
destroy_file_logger :: proc(log: Logger, allocator := context.allocator) {
@@ -56,19 +83,15 @@ create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logg
data := new(File_Console_Logger_Data, allocator)
data.file_handle = os.INVALID_HANDLE
data.ident = ident
return Logger{file_console_logger_proc, data, lowest, opt}
return Logger{console_logger_proc, data, lowest, opt}
}
destroy_console_logger :: proc(log: Logger, allocator := context.allocator) {
free(log.data, allocator)
}
file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
data := cast(^File_Console_Logger_Data)logger_data
h: os.Handle = os.stdout if level <= Level.Error else os.stderr
if data.file_handle != os.INVALID_HANDLE {
h = data.file_handle
}
@(private)
_file_console_logger_proc :: proc(h: os.Handle, ident: string, level: Level, text: string, options: Options, location: runtime.Source_Code_Location) {
backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths.
buf := strings.builder_from_bytes(backing[:])
@@ -86,13 +109,32 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string
fmt.sbprintf(&buf, "[{}] ", os.current_thread_id())
}
if data.ident != "" {
fmt.sbprintf(&buf, "[%s] ", data.ident)
if ident != "" {
fmt.sbprintf(&buf, "[%s] ", ident)
}
//TODO(Hoej): When we have better atomics and such, make this thread-safe
fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text)
}
file_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
data := cast(^File_Console_Logger_Data)logger_data
_file_console_logger_proc(data.file_handle, data.ident, level, text, options, location)
}
console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
options := options
data := cast(^File_Console_Logger_Data)logger_data
h: os.Handle = ---
if level < Level.Error {
h = os.stdout
options -= global_subtract_stdout_options
} else {
h = os.stderr
options -= global_subtract_stderr_options
}
_file_console_logger_proc(h, data.ident, level, text, options, location)
}
do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) {
RESET :: ansi.CSI + ansi.RESET + ansi.SGR

View File

@@ -1370,8 +1370,8 @@ _private_int_div_recursive :: proc(quotient, remainder, a, b: ^Int, allocator :=
/*
Slower bit-bang division... also smaller.
Prefer `_int_div_school` for speed.
*/
@(deprecated="Use `_int_div_school`, it's 3.5x faster.")
_private_int_div_small :: proc(quotient, remainder, numerator, denominator: ^Int) -> (err: Error) {
ta, tb, tq, q := &Int{}, &Int{}, &Int{}, &Int{}

View File

@@ -280,7 +280,7 @@ int_atoi :: proc(res: ^Int, input: string, radix := i8(10), allocator := context
}
pos := ch - '+'
if RADIX_TABLE_REVERSE_SIZE <= pos {
if RADIX_TABLE_REVERSE_SIZE <= u32(pos) {
break
}
y := RADIX_TABLE_REVERSE[pos]

View File

@@ -103,7 +103,7 @@ round :: proc(x: $T/Fixed($Backing, $Fraction_Width)) -> Backing {
}
@(require_results)
append :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string {
write :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string {
Integer_Width :: 8*size_of(Backing) - Fraction_Width
x := x
@@ -124,16 +124,16 @@ append :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string {
when size_of(Backing) < 16 {
T :: u64
append_uint :: strconv.append_uint
write_uint :: strconv.write_uint
} else {
T :: u128
append_uint :: strconv.append_u128
write_uint :: strconv.write_u128
}
integer := T(x.i) >> Fraction_Width
fraction := T(x.i) & (1<<Fraction_Width - 1)
s := append_uint(buf[i:], integer, 10)
s := write_uint(buf[i:], integer, 10)
i += len(s)
if fraction != 0 {
buf[i] = '.'
@@ -155,7 +155,7 @@ append :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string {
@(require_results)
to_string :: proc(x: $T/Fixed($Backing, $Fraction_Width), allocator := context.allocator) -> string {
buf: [48]byte
s := append(buf[:], x)
s := write(buf[:], x)
str := make([]byte, len(s), allocator)
copy(str, s)
return string(str)
@@ -294,3 +294,8 @@ _power_of_two_table := [129]string{
"85070591730234615865843651857942052864",
"170141183460469231731687303715884105728",
}
@(deprecated="Use write instead")
append :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string {
return write(dst, x)
}

View File

@@ -2296,7 +2296,7 @@ nextafter_f16 :: proc "contextless" (x, y: f16) -> (r: f16) {
case x == y:
r = x
case x == 0:
r = copy_sign_f16(1, y)
r = copy_sign_f16(transmute(f16)u16(1), y)
case (y > x) == (x > 0):
r = transmute(f16)(transmute(u16)x + 1)
case:
@@ -2312,7 +2312,7 @@ nextafter_f32 :: proc "contextless" (x, y: f32) -> (r: f32) {
case x == y:
r = x
case x == 0:
r = copy_sign_f32(1, y)
r = copy_sign_f32(transmute(f32)u32(1), y)
case (y > x) == (x > 0):
r = transmute(f32)(transmute(u32)x + 1)
case:
@@ -2328,7 +2328,7 @@ nextafter_f64 :: proc "contextless" (x, y: f64) -> (r: f64) {
case x == y:
r = x
case x == 0:
r = copy_sign_f64(1, y)
r = copy_sign_f64(transmute(f64)u64(1), y)
case (y > x) == (x > 0):
r = transmute(f64)(transmute(u64)x + 1)
case:
@@ -2349,32 +2349,6 @@ nextafter :: proc{
nextafter_f64, nextafter_f64le, nextafter_f64be,
}
@(require_results)
signbit_f16 :: proc "contextless" (x: f16) -> bool {
return (transmute(u16)x)&(1<<15) != 0
}
@(require_results)
signbit_f32 :: proc "contextless" (x: f32) -> bool {
return (transmute(u32)x)&(1<<31) != 0
}
@(require_results)
signbit_f64 :: proc "contextless" (x: f64) -> bool {
return (transmute(u64)x)&(1<<63) != 0
}
@(require_results) signbit_f16le :: proc "contextless" (x: f16le) -> bool { return signbit_f16(f16(x)) }
@(require_results) signbit_f32le :: proc "contextless" (x: f32le) -> bool { return signbit_f32(f32(x)) }
@(require_results) signbit_f64le :: proc "contextless" (x: f64le) -> bool { return signbit_f64(f64(x)) }
@(require_results) signbit_f16be :: proc "contextless" (x: f16be) -> bool { return signbit_f16(f16(x)) }
@(require_results) signbit_f32be :: proc "contextless" (x: f32be) -> bool { return signbit_f32(f32(x)) }
@(require_results) signbit_f64be :: proc "contextless" (x: f64be) -> bool { return signbit_f64(f64(x)) }
signbit :: proc{
signbit_f16, signbit_f16le, signbit_f16be,
signbit_f32, signbit_f32le, signbit_f32be,
signbit_f64, signbit_f64le, signbit_f64be,
}
@(require_results)
hypot_f16 :: proc "contextless" (x, y: f16) -> (r: f16) {
p, q := abs(x), abs(y)

View File

@@ -132,7 +132,7 @@ gamma_f64 :: proc "contextless" (x: f64) -> f64 {
case is_inf(x, 1):
return inf_f64(1)
case x == 0:
if signbit(x) {
if sign_bit(x) {
return inf_f64(-1)
}
return inf_f64(1)

View File

@@ -33,7 +33,7 @@ Example:
import "core:math/rand"
import "core:fmt"
set_global_seed_example :: proc() {
reset_example :: proc() {
rand.reset(1)
fmt.println(rand.uint64())
}
@@ -350,7 +350,7 @@ Example:
Possible Output:
6
500
13
*/
@(require_results)
@@ -432,7 +432,7 @@ Example:
Possible Output:
15.312
673.130
273.15
*/
@(require_results) float64_range :: proc(low, high: f64, gen := context.random_generator) -> (val: f64) {
@@ -467,7 +467,7 @@ Example:
Possible Output:
15.312
273.130
273.15
*/
@(require_results) float32_range :: proc(low, high: f32, gen := context.random_generator) -> (val: f32) {

View File

@@ -888,6 +888,34 @@ make_aligned :: proc(
return runtime.make_aligned(T, len, alignment, allocator, loc)
}
/*
Allocate a new slice with alignment for allocators that might not support the
specified alignment requirement.
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.
The user should `delete` the return `original_data` slice not the typed `slice`.
*/
@(require_results)
make_over_aligned :: proc(
$T: typeid/[]$E,
#any_int len: int,
alignment: int,
allocator: runtime.Allocator,
loc := #caller_location,
) -> (slice: T, original_data: []byte, err: Allocator_Error) {
size := size_of(E)*len + alignment-1
original_data, err = runtime.make([]byte, size, allocator, loc)
if err == nil {
ptr := align_forward(raw_data(original_data), uintptr(alignment))
slice = ([^]E)(ptr)[:len]
}
return
}
/*
Allocate a new slice.

View File

@@ -2,6 +2,7 @@ package mem
import "base:intrinsics"
import "base:runtime"
import "base:sanitizer"
/*
Nil allocator.
@@ -138,6 +139,7 @@ arena_init :: proc(a: ^Arena, data: []byte) {
a.offset = 0
a.peak_used = 0
a.temp_count = 0
sanitizer.address_poison(a.data)
}
/*
@@ -224,7 +226,9 @@ arena_alloc_bytes_non_zeroed :: proc(
}
a.offset += total_size
a.peak_used = max(a.peak_used, a.offset)
return byte_slice(ptr, size), nil
result := byte_slice(ptr, size)
sanitizer.address_unpoison(result)
return result, nil
}
/*
@@ -232,6 +236,7 @@ Free all memory to an arena.
*/
arena_free_all :: proc(a: ^Arena) {
a.offset = 0
sanitizer.address_poison(a.data)
}
arena_allocator_proc :: proc(
@@ -309,6 +314,7 @@ 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)
sanitizer.address_poison(tmp.arena.data[tmp.prev_offset:tmp.arena.offset])
tmp.arena.offset = tmp.prev_offset
tmp.arena.temp_count -= 1
}
@@ -363,6 +369,7 @@ scratch_init :: proc(s: ^Scratch, size: int, backup_allocator := context.allocat
s.prev_allocation = nil
s.backup_allocator = backup_allocator
s.leaked_allocations.allocator = backup_allocator
sanitizer.address_poison(s.data)
return nil
}
@@ -377,6 +384,7 @@ scratch_destroy :: proc(s: ^Scratch) {
free_bytes(ptr, s.backup_allocator)
}
delete(s.leaked_allocations)
sanitizer.address_unpoison(s.data)
delete(s.data, s.backup_allocator)
s^ = {}
}
@@ -472,7 +480,9 @@ scratch_alloc_bytes_non_zeroed :: proc(
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
result := byte_slice(rawptr(ptr), size)
sanitizer.address_unpoison(result)
return result, nil
} else {
a := s.backup_allocator
if a.procedure == nil {
@@ -516,6 +526,7 @@ scratch_free :: proc(s: ^Scratch, ptr: rawptr, loc := #caller_location) -> Alloc
old_ptr := uintptr(ptr)
if s.prev_allocation == ptr {
s.curr_offset = int(uintptr(s.prev_allocation) - start)
sanitizer.address_poison(s.data[s.curr_offset:])
s.prev_allocation = nil
return nil
}
@@ -546,6 +557,7 @@ scratch_free_all :: proc(s: ^Scratch, loc := #caller_location) {
free_bytes(ptr, s.backup_allocator, loc)
}
clear(&s.leaked_allocations)
sanitizer.address_poison(s.data)
}
/*
@@ -675,7 +687,9 @@ scratch_resize_bytes_non_zeroed :: proc(
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
result := byte_slice(old_memory, size)
sanitizer.address_unpoison(result)
return result, nil
}
data, err := scratch_alloc_bytes_non_zeroed(s, size, alignment, loc)
if err != nil {
@@ -776,6 +790,7 @@ stack_init :: proc(s: ^Stack, data: []byte) {
s.prev_offset = 0
s.curr_offset = 0
s.peak_used = 0
sanitizer.address_poison(data)
}
/*
@@ -861,15 +876,19 @@ stack_alloc_bytes_non_zeroed :: proc(
if s.curr_offset + padding + size > len(s.data) {
return nil, .Out_Of_Memory
}
old_offset := s.prev_offset
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))
sanitizer.address_unpoison(header)
header.padding = padding
header.prev_offset = s.prev_offset
header.prev_offset = old_offset
s.curr_offset += size
s.peak_used = max(s.peak_used, s.curr_offset)
return byte_slice(rawptr(next_addr), size), nil
result := byte_slice(rawptr(next_addr), size)
sanitizer.address_unpoison(result)
return result, nil
}
/*
@@ -902,12 +921,15 @@ stack_free :: proc(
}
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 {
if old_offset != s.prev_offset {
// panic("Out of order stack allocator free");
return .Invalid_Pointer
}
s.curr_offset = old_offset
s.prev_offset = header.prev_offset
sanitizer.address_poison(s.data[old_offset:s.curr_offset])
s.curr_offset = old_offset
return nil
}
@@ -917,6 +939,7 @@ Free all allocations to the stack.
stack_free_all :: proc(s: ^Stack, loc := #caller_location) {
s.prev_offset = 0
s.curr_offset = 0
sanitizer.address_poison(s.data)
}
/*
@@ -1076,7 +1099,9 @@ stack_resize_bytes_non_zeroed :: proc(
if diff > 0 {
zero(rawptr(curr_addr + uintptr(diff)), diff)
}
return byte_slice(old_memory, size), nil
result := byte_slice(old_memory, size)
sanitizer.address_unpoison(result)
return result, nil
}
stack_allocator_proc :: proc(
@@ -1144,6 +1169,7 @@ small_stack_init :: proc(s: ^Small_Stack, data: []byte) {
s.data = data
s.offset = 0
s.peak_used = 0
sanitizer.address_poison(data)
}
/*
@@ -1252,10 +1278,13 @@ small_stack_alloc_bytes_non_zeroed :: proc(
s.offset += padding
next_addr := curr_addr + uintptr(padding)
header := (^Small_Stack_Allocation_Header)(next_addr - size_of(Small_Stack_Allocation_Header))
sanitizer.address_unpoison(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
result := byte_slice(rawptr(next_addr), size)
sanitizer.address_unpoison(result)
return result, nil
}
/*
@@ -1289,6 +1318,7 @@ small_stack_free :: proc(
}
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)))
sanitizer.address_poison(s.data[old_offset:s.offset])
s.offset = old_offset
return nil
}
@@ -1298,6 +1328,7 @@ Free all memory to small stack.
*/
small_stack_free_all :: proc(s: ^Small_Stack) {
s.offset = 0
sanitizer.address_poison(s.data)
}
/*
@@ -1442,7 +1473,9 @@ small_stack_resize_bytes_non_zeroed :: proc(
return nil, nil
}
if old_size == size {
return byte_slice(old_memory, size), nil
result := byte_slice(old_memory, size)
sanitizer.address_unpoison(result)
return result, nil
}
data, err := small_stack_alloc_bytes_non_zeroed(s, size, alignment, loc)
if err == nil {

View File

@@ -171,16 +171,15 @@ If the return value is:
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.
- 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 {
@@ -207,11 +206,11 @@ If the return value is:
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.
- 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)
@@ -233,11 +232,11 @@ If the return value is:
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.
- 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)
@@ -316,18 +315,38 @@ check_zero_ptr :: proc(ptr: rawptr, len: int) -> bool {
Offset a given pointer by a given amount.
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`.
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.
Subtract two pointers of the same type, and return the number of `T` between them.
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`.
This procedure subtracts pointer `b` from pointer `a`, both of type `^T`,
and returns an integer count of the `T` between them.
**Inputs**
- `a`: A pointer to a type T
- `b`: A pointer to a type T
**Returns**
- `b` - `a` in items of T as an `int`.
Example:
import "core:mem"
import "core:fmt"
ptr_sub_example :: proc() {
arr: [2]int
fmt.println(mem.ptr_sub(&arr[1], &arr[0]))
}
Output:
1
*/
ptr_sub :: intrinsics.ptr_sub

View File

@@ -1,6 +1,7 @@
package mem
import "base:runtime"
import "base:sanitizer"
/*
Rollback stack default block size.
@@ -47,14 +48,14 @@ Rollback_Stack :: struct {
block_allocator: Allocator,
}
@(private="file", require_results)
@(private="file", require_results, no_sanitize_address)
rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool {
start := raw_data(block.buffer)
end := start[block.offset:]
return start < ptr && ptr <= end
}
@(private="file", require_results)
@(private="file", require_results, no_sanitize_address)
rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
parent: ^Rollback_Stack_Block,
block: ^Rollback_Stack_Block,
@@ -71,7 +72,7 @@ rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
return nil, nil, nil, .Invalid_Pointer
}
@(private="file", require_results)
@(private="file", require_results, no_sanitize_address)
rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
block: ^Rollback_Stack_Block,
header: ^Rollback_Stack_Header,
@@ -86,9 +87,10 @@ rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
return nil, nil, false
}
@(private="file")
@(private="file", no_sanitize_address)
rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_Header) {
header := header
for block.offset > 0 && header.is_free {
block.offset = header.prev_offset
block.last_alloc = raw_data(block.buffer)[header.prev_ptr:]
@@ -99,9 +101,10 @@ rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_
/*
Free memory to a rollback stack allocator.
*/
@(private="file", require_results)
@(private="file", require_results, no_sanitize_address)
rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error {
parent, block, header := rb_find_ptr(stack, ptr) or_return
if header.is_free {
return .Invalid_Pointer
}
@@ -120,7 +123,7 @@ rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error {
/*
Free all memory owned by the rollback stack allocator.
*/
@(private="file")
@(private="file", no_sanitize_address)
rb_free_all :: proc(stack: ^Rollback_Stack) {
for block := stack.head.next_block; block != nil; /**/ {
next_block := block.next_block
@@ -131,12 +134,13 @@ rb_free_all :: proc(stack: ^Rollback_Stack) {
stack.head.next_block = nil
stack.head.last_alloc = nil
stack.head.offset = 0
sanitizer.address_poison(stack.head.buffer)
}
/*
Allocate memory using the rollback stack allocator.
*/
@(require_results)
@(require_results, no_sanitize_address)
rb_alloc :: proc(
stack: ^Rollback_Stack,
size: int,
@@ -153,7 +157,7 @@ rb_alloc :: proc(
/*
Allocate memory using the rollback stack allocator.
*/
@(require_results)
@(require_results, no_sanitize_address)
rb_alloc_bytes :: proc(
stack: ^Rollback_Stack,
size: int,
@@ -170,7 +174,7 @@ rb_alloc_bytes :: proc(
/*
Allocate non-initialized memory using the rollback stack allocator.
*/
@(require_results)
@(require_results, no_sanitize_address)
rb_alloc_non_zeroed :: proc(
stack: ^Rollback_Stack,
size: int,
@@ -184,7 +188,7 @@ rb_alloc_non_zeroed :: proc(
/*
Allocate non-initialized memory using the rollback stack allocator.
*/
@(require_results)
@(require_results, no_sanitize_address)
rb_alloc_bytes_non_zeroed :: proc(
stack: ^Rollback_Stack,
size: int,
@@ -194,6 +198,7 @@ rb_alloc_bytes_non_zeroed :: proc(
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
@@ -235,7 +240,9 @@ rb_alloc_bytes_non_zeroed :: proc(
// Prevent any further allocations on it.
block.offset = cast(uintptr)len(block.buffer)
}
#no_bounds_check return ptr[:size], nil
res := ptr[:size]
sanitizer.address_unpoison(res)
return res, nil
}
return nil, .Out_Of_Memory
}
@@ -243,7 +250,7 @@ rb_alloc_bytes_non_zeroed :: proc(
/*
Resize an allocation owned by rollback stack allocator.
*/
@(require_results)
@(require_results, no_sanitize_address)
rb_resize :: proc(
stack: ^Rollback_Stack,
old_ptr: rawptr,
@@ -266,7 +273,7 @@ rb_resize :: proc(
/*
Resize an allocation owned by rollback stack allocator.
*/
@(require_results)
@(require_results, no_sanitize_address)
rb_resize_bytes :: proc(
stack: ^Rollback_Stack,
old_memory: []byte,
@@ -289,7 +296,7 @@ rb_resize_bytes :: proc(
Resize an allocation owned by rollback stack allocator without explicit
zero-initialization.
*/
@(require_results)
@(require_results, no_sanitize_address)
rb_resize_non_zeroed :: proc(
stack: ^Rollback_Stack,
old_ptr: rawptr,
@@ -306,7 +313,7 @@ rb_resize_non_zeroed :: proc(
Resize an allocation owned by rollback stack allocator without explicit
zero-initialization.
*/
@(require_results)
@(require_results, no_sanitize_address)
rb_resize_bytes_non_zeroed :: proc(
stack: ^Rollback_Stack,
old_memory: []byte,
@@ -330,7 +337,9 @@ rb_resize_bytes_non_zeroed :: proc(
if len(block.buffer) <= stack.block_size {
block.offset += cast(uintptr)size - cast(uintptr)old_size
}
#no_bounds_check return (ptr)[:size], nil
res := (ptr)[:size]
sanitizer.address_unpoison(res)
#no_bounds_check return res, nil
}
}
}
@@ -340,7 +349,7 @@ rb_resize_bytes_non_zeroed :: proc(
return
}
@(private="file", require_results)
@(private="file", require_results, no_sanitize_address)
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)
@@ -351,6 +360,7 @@ rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stac
/*
Initialize the rollback stack allocator using a fixed backing buffer.
*/
@(no_sanitize_address)
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)
@@ -365,6 +375,7 @@ rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, loc
/*
Initialize the rollback stack alocator using a backing block allocator.
*/
@(no_sanitize_address)
rollback_stack_init_dynamic :: proc(
stack: ^Rollback_Stack,
block_size : int = ROLLBACK_STACK_DEFAULT_BLOCK_SIZE,
@@ -396,6 +407,7 @@ rollback_stack_init :: proc {
/*
Destroy a rollback stack.
*/
@(no_sanitize_address)
rollback_stack_destroy :: proc(stack: ^Rollback_Stack) {
if stack.block_allocator.procedure != nil {
rb_free_all(stack)
@@ -435,7 +447,7 @@ from the last allocation backwards.
Each allocation has an overhead of 8 bytes and any extra bytes to satisfy
the requested alignment.
*/
@(require_results)
@(require_results, no_sanitize_address)
rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator {
return Allocator {
data = stack,
@@ -443,7 +455,7 @@ rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator {
}
}
@(require_results)
@(require_results, no_sanitize_address)
rollback_stack_allocator_proc :: proc(
allocator_data: rawptr,
mode: Allocator_Mode,

View File

@@ -10,6 +10,7 @@
// package mem_tlsf implements a Two Level Segregated Fit memory allocator.
package mem_tlsf
import "base:intrinsics"
import "base:runtime"
Error :: enum byte {
@@ -21,7 +22,6 @@ Error :: enum byte {
Backing_Allocator_Error = 5,
}
Allocator :: struct {
// Empty lists point at this block to indicate they are free.
block_null: Block_Header,
@@ -39,12 +39,13 @@ Allocator :: struct {
// statistics like how much memory is still available,
// fragmentation, etc.
pool: Pool,
// If we're expected to grow when we run out of memory,
// how much should we ask the backing allocator for?
new_pool_size: uint,
}
#assert(size_of(Allocator) % ALIGN_SIZE == 0)
@(require_results)
allocator :: proc(t: ^Allocator) -> runtime.Allocator {
return runtime.Allocator{
@@ -53,6 +54,21 @@ allocator :: proc(t: ^Allocator) -> runtime.Allocator {
}
}
// Tries to estimate a pool size sufficient for `count` allocations, each of `size` and with `alignment`.
estimate_pool_from_size_alignment :: proc(count: int, size: int, alignment: int) -> (pool_size: int) {
per_allocation := align_up(uint(size + alignment) + BLOCK_HEADER_OVERHEAD, ALIGN_SIZE)
return count * int(per_allocation) + int(INITIAL_POOL_OVERHEAD)
}
// Tries to estimate a pool size sufficient for `count` allocations of `type`.
estimate_pool_from_typeid :: proc(count: int, type: typeid) -> (pool_size: int) {
ti := type_info_of(type)
return estimate_pool_size(count, ti.size, ti.align)
}
estimate_pool_size :: proc{estimate_pool_from_size_alignment, estimate_pool_from_typeid}
@(require_results)
init_from_buffer :: proc(control: ^Allocator, buf: []byte) -> Error {
assert(control != nil)
@@ -60,21 +76,25 @@ init_from_buffer :: proc(control: ^Allocator, buf: []byte) -> Error {
return .Invalid_Alignment
}
pool_bytes := align_down(len(buf) - POOL_OVERHEAD, ALIGN_SIZE)
pool_bytes := align_down(len(buf) - INITIAL_POOL_OVERHEAD, ALIGN_SIZE)
if pool_bytes < BLOCK_SIZE_MIN {
return .Backing_Buffer_Too_Small
} else if pool_bytes > BLOCK_SIZE_MAX {
return .Backing_Buffer_Too_Large
}
clear(control)
return pool_add(control, buf[:])
control.pool = Pool{
data = buf,
allocator = {},
}
return free_all(control)
}
@(require_results)
init_from_allocator :: proc(control: ^Allocator, backing: runtime.Allocator, initial_pool_size: int, new_pool_size := 0) -> Error {
assert(control != nil)
pool_bytes := align_up(uint(initial_pool_size) + POOL_OVERHEAD, ALIGN_SIZE)
pool_bytes := uint(estimate_pool_size(1, initial_pool_size, ALIGN_SIZE))
if pool_bytes < BLOCK_SIZE_MIN {
return .Backing_Buffer_Too_Small
} else if pool_bytes > BLOCK_SIZE_MAX {
@@ -85,12 +105,15 @@ init_from_allocator :: proc(control: ^Allocator, backing: runtime.Allocator, ini
if backing_err != nil {
return .Backing_Allocator_Error
}
err := init_from_buffer(control, buf)
control.pool = Pool{
data = buf,
allocator = backing,
}
return err
control.new_pool_size = uint(new_pool_size)
return free_all(control)
}
init :: proc{init_from_buffer, init_from_allocator}
@@ -103,8 +126,6 @@ destroy :: proc(control: ^Allocator) {
// No need to call `pool_remove` or anything, as they're they're embedded in the backing memory.
// We do however need to free the `Pool` tracking entities and the backing memory itself.
// As `Allocator` is embedded in the first backing slice, the `control` pointer will be
// invalid after this call.
for p := control.pool.next; p != nil; {
next := p.next
@@ -136,9 +157,8 @@ allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode,
return nil, nil
case .Free_All:
// NOTE: this doesn't work right at the moment, Jeroen has it on his to-do list :)
// clear(control)
return nil, .Mode_Not_Implemented
free_all(control)
return nil, nil
case .Resize:
return resize(control, old_memory, uint(old_size), uint(size), uint(alignment))
@@ -159,3 +179,23 @@ allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode,
return nil, nil
}
// Exported solely to facilitate testing
@(require_results)
ffs :: proc "contextless" (word: u32) -> (bit: i32) {
return -1 if word == 0 else i32(intrinsics.count_trailing_zeros(word))
}
// Exported solely to facilitate testing
@(require_results)
fls :: proc "contextless" (word: u32) -> (bit: i32) {
N :: (size_of(u32) * 8) - 1
return i32(N - intrinsics.count_leading_zeros(word))
}
// Exported solely to facilitate testing
@(require_results)
fls_uint :: proc "contextless" (size: uint) -> (bit: i32) {
N :: (size_of(uint) * 8) - 1
return i32(N - intrinsics.count_leading_zeros(size))
}

File diff suppressed because it is too large Load Diff

View File

@@ -64,6 +64,7 @@ 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.
*/
@(no_sanitize_address)
tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Allocator, internals_allocator := context.allocator) {
t.backing = backing_allocator
t.allocation_map.allocator = internals_allocator
@@ -77,6 +78,7 @@ tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Alloc
/*
Destroy the tracking allocator.
*/
@(no_sanitize_address)
tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) {
delete(t.allocation_map)
delete(t.bad_free_array)
@@ -90,6 +92,7 @@ This procedure clears the tracked data from a tracking allocator.
**Note**: This procedure clears only the current allocation data while keeping
the totals intact.
*/
@(no_sanitize_address)
tracking_allocator_clear :: proc(t: ^Tracking_Allocator) {
sync.mutex_lock(&t.mutex)
clear(&t.allocation_map)
@@ -103,6 +106,7 @@ Reset the tracking allocator.
Reset all of a Tracking Allocator's allocation data back to zero.
*/
@(no_sanitize_address)
tracking_allocator_reset :: proc(t: ^Tracking_Allocator) {
sync.mutex_lock(&t.mutex)
clear(&t.allocation_map)
@@ -124,6 +128,7 @@ Override Tracking_Allocator.bad_free_callback to have something else happen. For
example, you can use tracking_allocator_bad_free_callback_add_to_array to return
the tracking allocator to the old behavior, where the bad_free_array was used.
*/
@(no_sanitize_address)
tracking_allocator_bad_free_callback_panic :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) {
runtime.print_caller_location(location)
runtime.print_string(" Tracking allocator error: Bad free of pointer ")
@@ -136,6 +141,7 @@ tracking_allocator_bad_free_callback_panic :: proc(t: ^Tracking_Allocator, memor
Alternative behavior for a bad free: Store in `bad_free_array`. If you use this,
then you must make sure to check Tracking_Allocator.bad_free_array at some point.
*/
@(no_sanitize_address)
tracking_allocator_bad_free_callback_add_to_array :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) {
append(&t.bad_free_array, Tracking_Allocator_Bad_Free_Entry {
memory = memory,
@@ -175,7 +181,7 @@ Example:
}
}
*/
@(require_results)
@(require_results, no_sanitize_address)
tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {
return Allocator{
data = data,
@@ -183,6 +189,7 @@ tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {
}
}
@(no_sanitize_address)
tracking_allocator_proc :: proc(
allocator_data: rawptr,
mode: Allocator_Mode,
@@ -191,6 +198,7 @@ tracking_allocator_proc :: proc(
old_size: int,
loc := #caller_location,
) -> (result: []byte, err: Allocator_Error) {
@(no_sanitize_address)
track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) {
data.total_memory_allocated += i64(entry.size)
data.total_allocation_count += 1
@@ -200,6 +208,7 @@ tracking_allocator_proc :: proc(
}
}
@(no_sanitize_address)
track_free :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) {
data.total_memory_freed += i64(entry.size)
data.total_free_count += 1

View File

@@ -3,6 +3,8 @@ package mem_virtual
import "core:mem"
import "core:sync"
import "base:sanitizer"
Arena_Kind :: enum uint {
Growing = 0, // Chained memory blocks (singly linked list).
Static = 1, // Fixed reservation sized.
@@ -43,7 +45,7 @@ DEFAULT_ARENA_STATIC_RESERVE_SIZE :: mem.Gigabyte when size_of(uintptr) == 8 els
// Initialization of an `Arena` to be a `.Growing` variant.
// A growing arena is a linked list of `Memory_Block`s allocated with virtual memory.
@(require_results)
@(require_results, no_sanitize_address)
arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (err: Allocator_Error) {
arena.kind = .Growing
arena.curr_block = memory_block_alloc(0, reserved, {}) or_return
@@ -53,24 +55,26 @@ arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING
if arena.minimum_block_size == 0 {
arena.minimum_block_size = reserved
}
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
return
}
// Initialization of an `Arena` to be a `.Static` variant.
// A static arena contains a single `Memory_Block` allocated with virtual memory.
@(require_results)
@(require_results, no_sanitize_address)
arena_init_static :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_STATIC_RESERVE_SIZE, commit_size: uint = DEFAULT_ARENA_STATIC_COMMIT_SIZE) -> (err: Allocator_Error) {
arena.kind = .Static
arena.curr_block = memory_block_alloc(commit_size, reserved, {}) or_return
arena.total_used = 0
arena.total_reserved = arena.curr_block.reserved
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
return
}
// Initialization of an `Arena` to be a `.Buffer` variant.
// A buffer arena contains single `Memory_Block` created from a user provided []byte.
@(require_results)
@(require_results, no_sanitize_address)
arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Error) {
if len(buffer) < size_of(Memory_Block) {
return .Out_Of_Memory
@@ -78,7 +82,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro
arena.kind = .Buffer
mem.zero_slice(buffer)
sanitizer.address_poison(buffer[:])
block_base := raw_data(buffer)
block := (^Memory_Block)(block_base)
@@ -94,7 +98,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro
}
// Allocates memory from the provided arena.
@(require_results)
@(require_results, no_sanitize_address)
arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc)
@@ -158,10 +162,13 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l
data, err = alloc_from_memory_block(arena.curr_block, size, alignment, default_commit_size=0)
arena.total_used = arena.curr_block.used
}
sanitizer.address_unpoison(data)
return
}
// Resets the memory of a Static or Buffer arena to a specific `position` (offset) and zeroes the previously used memory.
@(no_sanitize_address)
arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location) -> bool {
sync.mutex_guard(&arena.mutex)
@@ -175,6 +182,7 @@ arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location)
mem.zero_slice(arena.curr_block.base[arena.curr_block.used:][:prev_pos-pos])
}
arena.total_used = arena.curr_block.used
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
return true
} else if pos == 0 {
arena.total_used = 0
@@ -184,6 +192,7 @@ arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location)
}
// Frees the last memory block of a Growing Arena
@(no_sanitize_address)
arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) {
if free_block := arena.curr_block; free_block != nil {
assert(arena.kind == .Growing, "expected a .Growing arena", loc)
@@ -191,11 +200,13 @@ arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_locat
arena.total_reserved -= free_block.reserved
arena.curr_block = free_block.prev
sanitizer.address_poison(free_block.base[:free_block.committed])
memory_block_dealloc(free_block)
}
}
// Deallocates all but the first memory block of the arena and resets the allocator's usage to 0.
@(no_sanitize_address)
arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
switch arena.kind {
case .Growing:
@@ -208,7 +219,9 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
if arena.curr_block != nil {
curr_block_used := int(arena.curr_block.used)
arena.curr_block.used = 0
sanitizer.address_unpoison(arena.curr_block.base[:curr_block_used])
mem.zero(arena.curr_block.base, curr_block_used)
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
}
arena.total_used = 0
case .Static, .Buffer:
@@ -219,6 +232,7 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
// Frees all of the memory allocated by the arena and zeros all of the values of an arena.
// A buffer based arena does not `delete` the provided `[]byte` bufffer.
@(no_sanitize_address)
arena_destroy :: proc(arena: ^Arena, loc := #caller_location) {
sync.mutex_guard(&arena.mutex)
switch arena.kind {
@@ -250,7 +264,7 @@ arena_static_bootstrap_new :: proc{
}
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
@(require_results)
@(require_results, no_sanitize_address)
arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
bootstrap: Arena
bootstrap.kind = .Growing
@@ -266,13 +280,13 @@ arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintp
}
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
@(require_results)
@(require_results, no_sanitize_address)
arena_growing_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
return arena_growing_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), minimum_block_size)
}
// Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy.
@(require_results)
@(require_results, no_sanitize_address)
arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
bootstrap: Arena
bootstrap.kind = .Static
@@ -288,19 +302,20 @@ arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintpt
}
// Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy.
@(require_results)
@(require_results, no_sanitize_address)
arena_static_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
return arena_static_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), reserved)
}
// Create an `Allocator` from the provided `Arena`
@(require_results)
@(require_results, no_sanitize_address)
arena_allocator :: proc(arena: ^Arena) -> mem.Allocator {
return mem.Allocator{arena_allocator_proc, arena}
}
// The allocator procedure used by an `Allocator` produced by `arena_allocator`
@(no_sanitize_address)
arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
size, alignment: int,
old_memory: rawptr, old_size: int,
@@ -334,6 +349,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
if size < old_size {
// shrink data in-place
data = old_data[:size]
sanitizer.address_poison(old_data[size:old_size])
return
}
@@ -347,6 +363,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
_ = alloc_from_memory_block(block, new_end - old_end, 1, default_commit_size=arena.default_commit_size) or_return
arena.total_used += block.used - prev_used
data = block.base[start:new_end]
sanitizer.address_unpoison(data)
return
}
}
@@ -357,6 +374,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
return
}
copy(new_memory, old_data[:old_size])
sanitizer.address_poison(old_data[:old_size])
return new_memory, nil
case .Query_Features:
set := (^mem.Allocator_Mode_Set)(old_memory)
@@ -382,7 +400,7 @@ Arena_Temp :: struct {
}
// Begins the section of temporary arena memory.
@(require_results)
@(require_results, no_sanitize_address)
arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena_Temp) {
assert(arena != nil, "nil arena", loc)
sync.mutex_guard(&arena.mutex)
@@ -397,6 +415,7 @@ arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena
}
// Ends the section of temporary arena memory by resetting the memory to the stored position.
@(no_sanitize_address)
arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
assert(temp.arena != nil, "nil arena", loc)
arena := temp.arena
@@ -432,6 +451,7 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
}
// Ignore the use of a `arena_temp_begin` entirely by __not__ resetting to the stored position.
@(no_sanitize_address)
arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
assert(temp.arena != nil, "nil arena", loc)
arena := temp.arena
@@ -442,6 +462,7 @@ arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
}
// Asserts that all uses of `Arena_Temp` has been used by an `Arena`
@(no_sanitize_address)
arena_check_temp :: proc(arena: ^Arena, loc := #caller_location) {
assert(arena.temp_count == 0, "Arena_Temp not been ended", loc)
}

View File

@@ -2,6 +2,7 @@ package mem_virtual
import "core:mem"
import "base:intrinsics"
import "base:sanitizer"
import "base:runtime"
_ :: runtime
@@ -14,27 +15,33 @@ platform_memory_init :: proc() {
Allocator_Error :: mem.Allocator_Error
@(require_results)
@(require_results, no_sanitize_address)
reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
return _reserve(size)
}
@(no_sanitize_address)
commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
sanitizer.address_unpoison(data, size)
return _commit(data, size)
}
@(require_results)
@(require_results, no_sanitize_address)
reserve_and_commit :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
data = reserve(size) or_return
commit(raw_data(data), size) or_return
return
}
@(no_sanitize_address)
decommit :: proc "contextless" (data: rawptr, size: uint) {
sanitizer.address_poison(data, size)
_decommit(data, size)
}
@(no_sanitize_address)
release :: proc "contextless" (data: rawptr, size: uint) {
sanitizer.address_unpoison(data, size)
_release(data, size)
}
@@ -46,13 +53,11 @@ Protect_Flag :: enum u32 {
Protect_Flags :: distinct bit_set[Protect_Flag; u32]
Protect_No_Access :: Protect_Flags{}
@(no_sanitize_address)
protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool {
return _protect(data, size, flags)
}
Memory_Block :: struct {
prev: ^Memory_Block,
base: [^]byte,
@@ -66,13 +71,13 @@ Memory_Block_Flag :: enum u32 {
Memory_Block_Flags :: distinct bit_set[Memory_Block_Flag; u32]
@(private="file", require_results)
@(private="file", require_results, no_sanitize_address)
align_formula :: #force_inline proc "contextless" (size, align: uint) -> uint {
result := size + align-1
return result - result%align
}
@(require_results)
@(require_results, no_sanitize_address)
memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags: Memory_Block_Flags = {}) -> (block: ^Memory_Block, err: Allocator_Error) {
page_size := DEFAULT_PAGE_SIZE
assert(mem.is_power_of_two(uintptr(page_size)))
@@ -116,8 +121,9 @@ memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags
return &pmblock.block, nil
}
@(require_results)
@(require_results, no_sanitize_address)
alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint, default_commit_size: uint = 0) -> (data: []byte, err: Allocator_Error) {
@(no_sanitize_address)
calc_alignment_offset :: proc "contextless" (block: ^Memory_Block, alignment: uintptr) -> uint {
alignment_offset := uint(0)
ptr := uintptr(block.base[block.used:])
@@ -128,6 +134,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint,
return alignment_offset
}
@(no_sanitize_address)
do_commit_if_necessary :: proc(block: ^Memory_Block, size: uint, default_commit_size: uint) -> (err: Allocator_Error) {
if block.committed - block.used < size {
pmblock := (^Platform_Memory_Block)(block)
@@ -172,10 +179,12 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint,
data = block.base[block.used+alignment_offset:][:min_size]
block.used += size
sanitizer.address_unpoison(data)
return
}
@(no_sanitize_address)
memory_block_dealloc :: proc(block_to_free: ^Memory_Block) {
if block := (^Platform_Memory_Block)(block_to_free); block != nil {
platform_memory_free(block)

View File

@@ -7,6 +7,7 @@ Platform_Memory_Block :: struct {
reserved: uint,
}
@(no_sanitize_address)
platform_memory_alloc :: proc "contextless" (to_commit, to_reserve: uint) -> (block: ^Platform_Memory_Block, err: Allocator_Error) {
to_commit, to_reserve := to_commit, to_reserve
to_reserve = max(to_commit, to_reserve)
@@ -26,12 +27,14 @@ platform_memory_alloc :: proc "contextless" (to_commit, to_reserve: uint) -> (bl
}
@(no_sanitize_address)
platform_memory_free :: proc "contextless" (block: ^Platform_Memory_Block) {
if block != nil {
release(block, block.reserved)
}
}
@(no_sanitize_address)
platform_memory_commit :: proc "contextless" (block: ^Platform_Memory_Block, to_commit: uint) -> (err: Allocator_Error) {
if to_commit < block.committed {
return nil

View File

@@ -83,6 +83,8 @@ foreign Kernel32 {
dwNumberOfBytesToMap: uint,
) -> rawptr ---
}
@(no_sanitize_address)
_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
result := VirtualAlloc(nil, size, MEM_RESERVE, PAGE_READWRITE)
if result == nil {
@@ -93,6 +95,7 @@ _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Err
return
}
@(no_sanitize_address)
_commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
result := VirtualAlloc(data, size, MEM_COMMIT, PAGE_READWRITE)
if result == nil {
@@ -107,12 +110,18 @@ _commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
}
return nil
}
@(no_sanitize_address)
_decommit :: proc "contextless" (data: rawptr, size: uint) {
VirtualFree(data, size, MEM_DECOMMIT)
}
@(no_sanitize_address)
_release :: proc "contextless" (data: rawptr, size: uint) {
VirtualFree(data, 0, MEM_RELEASE)
}
@(no_sanitize_address)
_protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool {
pflags: u32
pflags = PAGE_NOACCESS
@@ -136,7 +145,7 @@ _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags)
}
@(no_sanitize_address)
_platform_memory_init :: proc() {
sys_info: SYSTEM_INFO
GetSystemInfo(&sys_info)
@@ -147,6 +156,7 @@ _platform_memory_init :: proc() {
}
@(no_sanitize_address)
_map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
page_flags: u32
if flags == {.Read} {

View File

@@ -53,8 +53,6 @@ ODIN_NET_TCP_NODELAY_DEFAULT :: #config(ODIN_NET_TCP_NODELAY_DEFAULT, true)
Maybe :: runtime.Maybe
Network_Error :: union #shared_nil {
General_Error,
Platform_Error,
Create_Socket_Error,
Dial_Error,
Listen_Error,
@@ -65,6 +63,7 @@ Network_Error :: union #shared_nil {
TCP_Recv_Error,
UDP_Recv_Error,
Shutdown_Error,
Interfaces_Error,
Socket_Option_Error,
Set_Blocking_Error,
Parse_Endpoint_Error,
@@ -74,14 +73,13 @@ Network_Error :: union #shared_nil {
#assert(size_of(Network_Error) == 8)
General_Error :: enum u32 {
None = 0,
Unable_To_Enumerate_Network_Interfaces = 1,
Interfaces_Error :: enum u32 {
None,
Unable_To_Enumerate_Network_Interfaces,
Allocation_Failure,
Unknown,
}
// `Platform_Error` is used to wrap errors returned by the different platforms that don't fit a common error.
Platform_Error :: enum u32 {}
Parse_Endpoint_Error :: enum u32 {
None = 0,
Bad_Port = 1,
@@ -109,7 +107,7 @@ TCP_Options :: struct {
no_delay: bool,
}
default_tcp_options := TCP_Options {
DEFAULT_TCP_OPTIONS :: TCP_Options {
no_delay = ODIN_NET_TCP_NODELAY_DEFAULT,
}

275
core/net/errors.odin Normal file
View File

@@ -0,0 +1,275 @@
package net
/*
Retrieve a platform specific error code, for when the categorized cross-platform errors are not enough.
Platforms specific returns:
- Darwin: `posix.Errno` (`core:sys/posix`)
- Linux: `linux.Errno` (`core:sys/linux`)
- FreeBSD: `freebsd.Errno` (`core:sys/freebsd`)
- Windows: `windows.System_Error` (`core:sys/windows`)
*/
@(require_results)
last_platform_error :: proc() -> i32 {
return _last_platform_error()
}
/*
Retrieve a stringified version of the last platform error.
*/
@(require_results)
last_platform_error_string :: proc() -> string {
return _last_platform_error_string()
}
set_last_platform_error :: proc(err: i32) {
_set_last_platform_error(err)
}
Create_Socket_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Invalid/unsupported family or protocol.
Invalid_Argument,
// The user has no permission to create a socket of this type and/or protocol.
Insufficient_Permissions,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
Dial_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Invalid endpoint and/or options.
Invalid_Argument,
// An attempt was made to connect to a broadcast socket on a socket that doesn't support it.
Broadcast_Not_Supported,
// The socket is already connected.
Already_Connected,
// The socket is already in the progress of making a connection.
Already_Connecting,
// The address is already in use.
Address_In_Use,
// Could not reach the remote host.
Host_Unreachable,
// The remote host refused the connection or isn't listening.
Refused,
// The connection was reset by the remote host.
Reset,
// Timed out before making a connection.
Timeout,
// Non-blocking socket that would need to block waiting to connect.
Would_Block,
// Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows.
Interrupted,
// Endpoint given without a port, which is required.
Port_Required,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
Bind_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Invalid socket or endpoint, or invalid combination of the two.
Invalid_Argument,
// The socket is already bound to an address.
Already_Bound,
// The address is protected and the current user has insufficient permissions to access it.
Insufficient_Permissions_For_Address,
// The address is already in use.
Address_In_Use,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
Listen_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// The socket or backlog is invalid.
Invalid_Argument,
// The socket is valid, but does not support listening.
Unsupported_Socket,
// The socket is already connected.
Already_Connected,
// The address is already in use.
Address_In_Use,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
Accept_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Invalid socket, or options.
Invalid_Argument,
// The given socket does not support accepting connections.
Unsupported_Socket,
// accept called on a socket which is not listening.
Not_Listening,
// A connection arrived but was closed while in the listen queue.
Aborted,
// Timed out before being able to accept a connection.
Timeout,
// Non-blocking socket that would need to block waiting for a connection.
Would_Block,
// Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows.
Interrupted,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
TCP_Recv_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Invalid socket or buffer given.
Invalid_Argument,
// The socket is not connected.
Not_Connected,
// Connection was closed/broken/shutdown while receiving data.
Connection_Closed,
// Timed out before being able to receive any data.
Timeout,
// Non-blocking socket that would need to block waiting on data.
Would_Block,
// Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows.
Interrupted,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
UDP_Recv_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Invalid socket or buffer given.
Invalid_Argument,
// "Connection" was refused by remote, or closed/broken/shutdown while receiving data.
Connection_Refused,
// Timed out before being able to receive any data.
Timeout,
// Non-blocking socket that would need to block waiting on data.
Would_Block,
// Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows.
Interrupted,
// Linux and UDP only: indicates the buffer was too small to receive all data, and the excess is truncated and discarded.
Excess_Truncated,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
TCP_Send_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Invalid socket or buffer given.
Invalid_Argument,
// Connection was closed/broken/shutdown while receiving data.
Connection_Closed,
// The socket is not connected.
Not_Connected,
// Could not reach the remote host.
Host_Unreachable,
// Timed out before being able to send any data.
Timeout,
// Non-blocking socket that would need to block waiting on the remote to be able to receive the data.
Would_Block,
// Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows.
Interrupted,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
UDP_Send_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Invalid socket or buffer given.
Invalid_Argument,
// Could not reach the remote host.
Host_Unreachable,
// "Connection" was refused by remote, or closed/broken/shutdown while sending data.
Connection_Refused,
// Timed out before being able to send any data.
Timeout,
// Non-blocking socket that would need to block waiting on the remote to be able to receive the data.
Would_Block,
// Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows.
Interrupted,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
Shutdown_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Socket is invalid or not connected, or the manner given is invalid.
Invalid_Argument,
// Connection was closed/aborted/shutdown.
Connection_Closed,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
Socket_Option_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Socket is invalid, not connected, or the connection has been closed/reset/shutdown.
Invalid_Socket,
// Unknown or unsupported option for the socket.
Invalid_Option,
// Invalid level or value.
Invalid_Value,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
Set_Blocking_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Socket is invalid.
Invalid_Argument,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}

View File

@@ -20,189 +20,232 @@ package net
Feoramund: FreeBSD platform code
*/
import "core:c"
import "core:os"
import "core:reflect"
import "core:sys/posix"
Create_Socket_Error :: enum c.int {
None = 0,
Family_Not_Supported_For_This_Socket = c.int(os.EAFNOSUPPORT),
No_Socket_Descriptors_Available = c.int(os.EMFILE),
No_Buffer_Space_Available = c.int(os.ENOBUFS),
No_Memory_Available_Available = c.int(os.ENOMEM),
Protocol_Unsupported_By_System = c.int(os.EPROTONOSUPPORT),
Wrong_Protocol_For_Socket = c.int(os.EPROTONOSUPPORT),
Family_And_Socket_Type_Mismatch = c.int(os.EPROTONOSUPPORT),
_last_platform_error :: proc() -> i32 {
return i32(posix.errno())
}
Dial_Error :: enum c.int {
None = 0,
Port_Required = -1, // Attempted to dial an endpointing without a port being set.
Address_In_Use = c.int(os.EADDRINUSE),
In_Progress = c.int(os.EINPROGRESS),
Cannot_Use_Any_Address = c.int(os.EADDRNOTAVAIL),
Wrong_Family_For_Socket = c.int(os.EAFNOSUPPORT),
Refused = c.int(os.ECONNREFUSED),
Is_Listening_Socket = c.int(os.EACCES),
Already_Connected = c.int(os.EISCONN),
Network_Unreachable = c.int(os.ENETUNREACH), // Device is offline
Host_Unreachable = c.int(os.EHOSTUNREACH), // Remote host cannot be reached
No_Buffer_Space_Available = c.int(os.ENOBUFS),
Not_Socket = c.int(os.ENOTSOCK),
Timeout = c.int(os.ETIMEDOUT),
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
Would_Block = c.int(os.EWOULDBLOCK),
_last_platform_error_string :: proc() -> string {
description, _ := reflect.enum_name_from_value(posix.errno())
return description
}
Bind_Error :: enum c.int {
None = 0,
Privileged_Port_Without_Root = -1, // Attempted to bind to a port less than 1024 without root access.
Address_In_Use = c.int(os.EADDRINUSE), // Another application is currently bound to this endpoint.
Given_Nonlocal_Address = c.int(os.EADDRNOTAVAIL), // The address is not a local address on this machine.
Broadcast_Disabled = c.int(os.EACCES), // To bind a UDP socket to the broadcast address, the appropriate socket option must be set.
Address_Family_Mismatch = c.int(os.EFAULT), // The address family of the address does not match that of the socket.
Already_Bound = c.int(os.EINVAL), // The socket is already bound to an address.
No_Ports_Available = c.int(os.ENOBUFS), // There are not enough ephemeral ports available.
_set_last_platform_error :: proc(err: i32) {
posix.errno(posix.Errno(err))
}
Listen_Error :: enum c.int {
None = 0,
Address_In_Use = c.int(os.EADDRINUSE),
Already_Connected = c.int(os.EISCONN),
No_Socket_Descriptors_Available = c.int(os.EMFILE),
No_Buffer_Space_Available = c.int(os.ENOBUFS),
Nonlocal_Address = c.int(os.EADDRNOTAVAIL),
Not_Socket = c.int(os.ENOTSOCK),
Listening_Not_Supported_For_This_Socket = c.int(os.EOPNOTSUPP),
_create_socket_error :: proc() -> Create_Socket_Error {
#partial switch posix.errno() {
case .EMFILE, .ENOBUFS, .ENOMEM, .EPROTONOSUPPORT, .EISCONN, .ENFILE:
return .Insufficient_Resources
case .EAFNOSUPPORT, .EPROTOTYPE:
return .Invalid_Argument
case .EACCES:
return .Insufficient_Permissions
case:
return .Unknown
}
}
Accept_Error :: enum c.int {
None = 0,
// TODO(tetra): Is this error actually possible here? Or is like Linux, in which case we can remove it.
Reset = c.int(os.ECONNRESET),
Not_Listening = c.int(os.EINVAL),
No_Socket_Descriptors_Available_For_Client_Socket = c.int(os.EMFILE),
No_Buffer_Space_Available = c.int(os.ENOBUFS),
Not_Socket = c.int(os.ENOTSOCK),
Not_Connection_Oriented_Socket = c.int(os.EOPNOTSUPP),
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
Would_Block = c.int(os.EWOULDBLOCK),
_dial_error :: proc() -> Dial_Error {
#partial switch posix.errno() {
case .ENOBUFS:
return .Insufficient_Resources
case .EAFNOSUPPORT, .EBADF, .EFAULT, .EINVAL, .ENOTSOCK, .EPROTOTYPE, .EADDRNOTAVAIL:
return .Invalid_Argument
case .EISCONN:
return .Already_Connected
case .EALREADY:
return .Already_Connecting
case .EADDRINUSE:
return .Address_In_Use
case .ENETDOWN:
return .Network_Unreachable
case .EHOSTUNREACH:
return .Host_Unreachable
case .ECONNREFUSED:
return .Refused
case .ECONNRESET:
return .Reset
case .ETIMEDOUT:
return .Timeout
case .EINPROGRESS:
return .Would_Block
case .EINTR:
return .Interrupted
case .EACCES:
return .Broadcast_Not_Supported
case:
return .Unknown
}
}
TCP_Recv_Error :: enum c.int {
None = 0,
Shutdown = c.int(os.ESHUTDOWN),
Not_Connected = c.int(os.ENOTCONN),
// TODO(tetra): Is this error actually possible here?
Connection_Broken = c.int(os.ENETRESET),
Not_Socket = c.int(os.ENOTSOCK),
Aborted = c.int(os.ECONNABORTED),
// TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them?
Connection_Closed = c.int(os.ECONNRESET),
Offline = c.int(os.ENETDOWN),
Host_Unreachable = c.int(os.EHOSTUNREACH),
Interrupted = c.int(os.EINTR),
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
Timeout = c.int(os.EWOULDBLOCK),
_bind_error :: proc() -> Bind_Error {
#partial switch posix.errno() {
case .EADDRNOTAVAIL, .EAFNOSUPPORT, .EBADF, .EDESTADDRREQ, .EFAULT, .ENOTSOCK, .EOPNOTSUPP:
return .Invalid_Argument
case .EINVAL:
return .Already_Bound
case .EACCES:
return .Insufficient_Permissions_For_Address
case .EADDRINUSE:
return .Address_In_Use
case:
return .Unknown
}
}
UDP_Recv_Error :: enum c.int {
None = 0,
Buffer_Too_Small = c.int(os.EMSGSIZE), // The buffer is too small to fit the entire message, and the message was truncated. When this happens, the rest of message is lost.
Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
Not_Descriptor = c.int(os.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
Bad_Buffer = c.int(os.EFAULT), // The buffer did not point to a valid location in memory.
Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7).
// The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
Timeout = c.int(os.EWOULDBLOCK),
Socket_Not_Bound = c.int(os.EINVAL), // The socket must be bound for this operation, but isn't.
_listen_error :: proc() -> Listen_Error {
#partial switch posix.errno() {
case .EBADF, .ENOTSOCK:
return .Invalid_Argument
case .EDESTADDRREQ, .EOPNOTSUPP:
return .Unsupported_Socket
case .EINVAL:
return .Already_Connected
case:
return .Unknown
}
}
TCP_Send_Error :: enum c.int {
None = 0,
Aborted = c.int(os.ECONNABORTED),
Connection_Closed = c.int(os.ECONNRESET),
Not_Connected = c.int(os.ENOTCONN),
Shutdown = c.int(os.ESHUTDOWN),
// The send queue was full.
// This is usually a transient issue.
//
// This also shouldn't normally happen on Linux, as data is dropped if it
// doesn't fit in the send queue.
No_Buffer_Space_Available = c.int(os.ENOBUFS),
Offline = c.int(os.ENETDOWN),
Host_Unreachable = c.int(os.EHOSTUNREACH),
Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7).
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
// The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
Timeout = c.int(os.EWOULDBLOCK),
Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
_accept_error :: proc() -> Accept_Error {
#partial switch posix.errno() {
case .EMFILE, .ENFILE, .ENOMEM:
return .Insufficient_Resources
case .EBADF, .ENOTSOCK, .EFAULT:
return .Invalid_Argument
case .EOPNOTSUPP:
return .Unsupported_Socket
case .ECONNABORTED:
return .Aborted
case .EWOULDBLOCK:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
// TODO
UDP_Send_Error :: enum c.int {
None = 0,
Message_Too_Long = c.int(os.EMSGSIZE), // The message is larger than the maximum UDP packet size. No data was sent.
// TODO: not sure what the exact circumstances for this is yet
Network_Unreachable = c.int(os.ENETUNREACH),
No_Outbound_Ports_Available = c.int(os.EAGAIN), // There are no more emphemeral outbound ports available to bind the socket to, in order to send.
// The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
Timeout = c.int(os.EWOULDBLOCK),
Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
Not_Descriptor = c.int(os.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
Bad_Buffer = c.int(os.EFAULT), // The buffer did not point to a valid location in memory.
Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7).
// The send queue was full.
// This is usually a transient issue.
//
// This also shouldn't normally happen on Linux, as data is dropped if it
// doesn't fit in the send queue.
No_Buffer_Space_Available = c.int(os.ENOBUFS),
No_Memory_Available = c.int(os.ENOMEM), // No memory was available to properly manage the send queue.
_tcp_recv_error :: proc() -> TCP_Recv_Error {
#partial switch posix.errno() {
case .EBADF, .EFAULT, .EINVAL, .ENOTSOCK, .EOPNOTSUPP:
return .Invalid_Argument
case .ENOBUFS:
return .Insufficient_Resources
case .ENOTCONN:
return .Not_Connected
case .ECONNRESET:
return .Connection_Closed
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
Shutdown_Manner :: enum c.int {
Receive = c.int(os.SHUT_RD),
Send = c.int(os.SHUT_WR),
Both = c.int(os.SHUT_RDWR),
_udp_recv_error :: proc() -> UDP_Recv_Error {
#partial switch posix.errno() {
case .EBADF, .EFAULT, .EINVAL, .ENOTSOCK, .EOPNOTSUPP, .EMSGSIZE:
return .Invalid_Argument
case .ENOBUFS, .ENOMEM:
return .Insufficient_Resources
case .ECONNRESET, .ENOTCONN:
return .Connection_Refused
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
Shutdown_Error :: enum c.int {
None = 0,
Aborted = c.int(os.ECONNABORTED),
Reset = c.int(os.ECONNRESET),
Offline = c.int(os.ENETDOWN),
Not_Connected = c.int(os.ENOTCONN),
Not_Socket = c.int(os.ENOTSOCK),
Invalid_Manner = c.int(os.EINVAL),
_tcp_send_error :: proc() -> TCP_Send_Error {
#partial switch posix.errno() {
case .EACCES, .EBADF, .EFAULT, .EMSGSIZE, .ENOTSOCK, .EOPNOTSUPP:
return .Invalid_Argument
case .ENOBUFS:
return .Insufficient_Resources
case .ECONNRESET, .EPIPE:
return .Connection_Closed
case .ENOTCONN:
return .Not_Connected
case .EHOSTUNREACH:
return .Host_Unreachable
case .ENETDOWN, .ENETUNREACH:
return .Network_Unreachable
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
Socket_Option_Error :: enum c.int {
None = 0,
Offline = c.int(os.ENETDOWN),
Timeout_When_Keepalive_Set = c.int(os.ENETRESET),
Invalid_Option_For_Socket = c.int(os.ENOPROTOOPT),
Reset_When_Keepalive_Set = c.int(os.ENOTCONN),
Not_Socket = c.int(os.ENOTSOCK),
_udp_send_error :: proc() -> UDP_Send_Error {
#partial switch posix.errno() {
case .EACCES, .EBADF, .EFAULT, .EMSGSIZE, .ENOTSOCK, .EOPNOTSUPP, .EAFNOSUPPORT, .EDESTADDRREQ:
return .Invalid_Argument
case .ENOBUFS, .ENOMEM:
return .Insufficient_Resources
case .ECONNRESET, .EPIPE:
return .Connection_Refused
case .EHOSTUNREACH:
return .Host_Unreachable
case .ENETDOWN, .ENETUNREACH:
return .Network_Unreachable
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
Set_Blocking_Error :: enum c.int {
None = 0,
_shutdown_error :: proc() -> Shutdown_Error {
#partial switch posix.errno() {
case .EBADF, .EINVAL, .ENOTSOCK, .ENOTCONN:
return .Invalid_Argument
case:
return .Unknown
}
}
// TODO: Add errors for `set_blocking`
}
_socket_option_error :: proc() -> Socket_Option_Error {
#partial switch posix.errno() {
case .ENOBUFS:
return .Insufficient_Resources
case .EBADF, .ENOTSOCK, .EISCONN:
return .Invalid_Socket
case .EINVAL, .ENOPROTOOPT:
return .Invalid_Option
case .EFAULT, .EDOM:
return .Invalid_Value
case:
return .Unknown
}
}
_set_blocking_error :: proc() -> Set_Blocking_Error {
#partial switch posix.errno() {
case .EBADF:
return .Invalid_Argument
case:
return .Unknown
}
}

View File

@@ -20,198 +20,267 @@ package net
Feoramund: FreeBSD platform code
*/
import "core:c"
import "core:reflect"
import "core:sys/freebsd"
Create_Socket_Error :: enum c.int {
None = 0,
Access_Denied = cast(c.int)freebsd.Errno.EACCES,
Family_Not_Supported_For_This_Socket = cast(c.int)freebsd.Errno.EAFNOSUPPORT,
Full_Per_Process_Descriptor_Table = cast(c.int)freebsd.Errno.EMFILE,
Full_System_File_Table = cast(c.int)freebsd.Errno.ENFILE,
No_Buffer_Space_Available = cast(c.int)freebsd.Errno.ENOBUFS,
Insufficient_Permission = cast(c.int)freebsd.Errno.EPERM,
Protocol_Unsupported_In_Family = cast(c.int)freebsd.Errno.EPROTONOSUPPORT,
Socket_Type_Unsupported_By_Protocol = cast(c.int)freebsd.Errno.EPROTOTYPE,
@(private="file", thread_local)
_last_error: freebsd.Errno
_last_platform_error :: proc() -> i32 {
return i32(_last_error)
}
Dial_Error :: enum c.int {
None = 0,
Port_Required = -1,
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
Invalid_Namelen = cast(c.int)freebsd.Errno.EINVAL,
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
Address_Unavailable = cast(c.int)freebsd.Errno.EADDRNOTAVAIL,
Wrong_Family_For_Socket = cast(c.int)freebsd.Errno.EAFNOSUPPORT,
Already_Connected = cast(c.int)freebsd.Errno.EISCONN,
Timeout = cast(c.int)freebsd.Errno.ETIMEDOUT,
Refused_By_Remote_Host = cast(c.int)freebsd.Errno.ECONNREFUSED,
// `Refused` alias for `core:net` tests.
// The above default name `Refused_By_Remote_Host` is more explicit.
Refused = Refused_By_Remote_Host,
Reset_By_Remote_Host = cast(c.int)freebsd.Errno.ECONNRESET,
Network_Unreachable = cast(c.int)freebsd.Errno.ENETUNREACH,
Host_Unreachable = cast(c.int)freebsd.Errno.EHOSTUNREACH,
Address_In_Use = cast(c.int)freebsd.Errno.EADDRINUSE,
Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT,
In_Progress = cast(c.int)freebsd.Errno.EINPROGRESS,
Interrupted_By_Signal = cast(c.int)freebsd.Errno.EINTR,
Previous_Attempt_Incomplete = cast(c.int)freebsd.Errno.EALREADY,
Broadcast_Unavailable = cast(c.int)freebsd.Errno.EACCES,
Auto_Port_Unavailable = cast(c.int)freebsd.Errno.EAGAIN,
// NOTE: There are additional connect() error possibilities, but they are
// strictly for addresses in the UNIX domain.
_last_platform_error_string :: proc() -> string {
description, _ := reflect.enum_name_from_value(_last_error)
return description
}
Bind_Error :: enum c.int {
None = 0,
Kernel_Resources_Unavailable = cast(c.int)freebsd.Errno.EAGAIN,
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
// NOTE: bind() can also return EINVAL if the underlying `addrlen` is an
// invalid length for the address family. This shouldn't happen for the net
// package, but it's worth noting.
Already_Bound = cast(c.int)freebsd.Errno.EINVAL,
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
Given_Nonlocal_Address = cast(c.int)freebsd.Errno.EADDRNOTAVAIL,
Address_In_Use = cast(c.int)freebsd.Errno.EADDRINUSE,
Address_Family_Mismatch = cast(c.int)freebsd.Errno.EAFNOSUPPORT,
Protected_Address = cast(c.int)freebsd.Errno.EACCES,
Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT,
// NOTE: There are additional bind() error possibilities, but they are
// strictly for addresses in the UNIX domain.
_set_last_platform_error :: proc(err: i32) {
_last_error = freebsd.Errno(err)
}
Listen_Error :: enum c.int {
None = 0,
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
Socket_Not_Bound = cast(c.int)freebsd.Errno.EDESTADDRREQ,
Already_Connected = cast(c.int)freebsd.Errno.EINVAL,
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
Listening_Not_Supported_For_This_Socket = cast(c.int)freebsd.Errno.EOPNOTSUPP,
_create_socket_error :: proc(errno: freebsd.Errno) -> Create_Socket_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EMFILE, .ENFILE, .ENOBUFS, .EPROTONOSUPPORT:
return .Insufficient_Resources
case .EAFNOSUPPORT, .EPROTOTYPE:
return .Invalid_Argument
case .EACCES, .EPERM:
return .Insufficient_Permissions
case:
return .Unknown
}
}
Accept_Error :: enum c.int {
None = 0,
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
Interrupted = cast(c.int)freebsd.Errno.EINTR,
Full_Per_Process_Descriptor_Table = cast(c.int)freebsd.Errno.EMFILE,
Full_System_File_Table = cast(c.int)freebsd.Errno.ENFILE,
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
Listen_Not_Called_On_Socket_Yet = cast(c.int)freebsd.Errno.EINVAL,
Address_Not_Writable = cast(c.int)freebsd.Errno.EFAULT,
_dial_error :: proc(errno: freebsd.Errno) -> Dial_Error {
assert(errno != nil)
_last_error = errno
// NOTE: This is the same as EWOULDBLOCK.
No_Connections_Available = cast(c.int)freebsd.Errno.EAGAIN,
// `Would_Block` alias for `core:net` tests.
Would_Block = cast(c.int)freebsd.Errno.EAGAIN,
New_Connection_Aborted = cast(c.int)freebsd.Errno.ECONNABORTED,
#partial switch errno {
case .EBADF, .EINVAL, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT, .EAGAIN:
return .Invalid_Argument
case .EISCONN:
return .Already_Connected
case .EALREADY:
return .Already_Connecting
case .EADDRINUSE:
return .Address_In_Use
case .ENETUNREACH:
return .Network_Unreachable
case .EHOSTUNREACH:
return .Host_Unreachable
case .ECONNREFUSED:
return .Refused
case .ECONNRESET:
return .Reset
case .ETIMEDOUT:
return .Timeout
case .EINPROGRESS:
return .Would_Block
case .EINTR:
return .Interrupted
case .EACCES:
return .Broadcast_Not_Supported
case:
return .Unknown
}
}
TCP_Recv_Error :: enum c.int {
None = 0,
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
Connection_Closed = cast(c.int)freebsd.Errno.ECONNRESET,
Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN,
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
_bind_error :: proc(errno: freebsd.Errno) -> Bind_Error {
assert(errno != nil)
_last_error = errno
// NOTE(Feoramund): The next two errors are only relevant for recvmsg(),
// but I'm including them for completeness's sake.
Full_Table_And_Pending_Data = cast(c.int)freebsd.Errno.EMFILE,
Invalid_Message_Size = cast(c.int)freebsd.Errno.EMSGSIZE,
Timeout = cast(c.int)freebsd.Errno.EAGAIN,
Interrupted_By_Signal = cast(c.int)freebsd.Errno.EINTR,
Buffer_Pointer_Outside_Address_Space = cast(c.int)freebsd.Errno.EFAULT,
#partial switch errno {
case .EAGAIN, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT:
return .Insufficient_Resources
case .EBADF:
return .Invalid_Argument
case .EINVAL:
return .Already_Bound
case .EACCES:
return .Insufficient_Permissions_For_Address
case .EADDRINUSE:
return .Address_In_Use
case:
return .Unknown
}
}
UDP_Recv_Error :: enum c.int {
None = 0,
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
Connection_Closed = cast(c.int)freebsd.Errno.ECONNRESET,
Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN,
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
_listen_error :: proc(errno: freebsd.Errno) -> Listen_Error {
assert(errno != nil)
_last_error = errno
// NOTE(Feoramund): The next two errors are only relevant for recvmsg(),
// but I'm including them for completeness's sake.
Full_Table_And_Data_Discarded = cast(c.int)freebsd.Errno.EMFILE,
Invalid_Message_Size = cast(c.int)freebsd.Errno.EMSGSIZE,
Timeout = cast(c.int)freebsd.Errno.EAGAIN,
Interrupted_By_Signal = cast(c.int)freebsd.Errno.EINTR,
Buffer_Pointer_Outside_Address_Space = cast(c.int)freebsd.Errno.EFAULT,
#partial switch errno {
case .EBADF, .ENOTSOCK:
return .Invalid_Argument
case .EDESTADDRREQ, .EOPNOTSUPP:
return .Unsupported_Socket
case .EINVAL:
return .Already_Connected
case:
return .Unknown
}
}
TCP_Send_Error :: enum c.int {
None = 0,
Connection_Closed = cast(c.int)freebsd.Errno.ECONNRESET,
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
Broadcast_Status_Mismatch = cast(c.int)freebsd.Errno.EACCES,
Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN,
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
Argument_In_Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT,
_accept_error :: proc(errno: freebsd.Errno) -> Accept_Error {
assert(errno != nil)
_last_error = errno
Message_Size_Breaks_Atomicity = cast(c.int)freebsd.Errno.EMSGSIZE,
/* The socket is marked non-blocking, or MSG_DONTWAIT is
specified, and the requested operation would block. */
Would_Block = cast(c.int)freebsd.Errno.EAGAIN,
/* NOTE: This error arises for two distinct reasons:
1. The system was unable to allocate an internal buffer.
The operation may succeed when buffers become available.
2. The output queue for a network interface was full.
This generally indicates that the interface has stopped
sending, but may be caused by transient congestion.
*/
No_Buffer_Space_Available = cast(c.int)freebsd.Errno.ENOBUFS,
Host_Unreachable = cast(c.int)freebsd.Errno.EHOSTUNREACH,
Already_Connected = cast(c.int)freebsd.Errno.EISCONN,
ICMP_Unreachable = cast(c.int)freebsd.Errno.ECONNREFUSED,
Host_Down = cast(c.int)freebsd.Errno.EHOSTDOWN,
Network_Down = cast(c.int)freebsd.Errno.ENETDOWN,
Jailed_Socket_Tried_To_Escape = cast(c.int)freebsd.Errno.EADDRNOTAVAIL,
Cannot_Send_More_Data = cast(c.int)freebsd.Errno.EPIPE,
#partial switch errno {
case .EMFILE, .ENFILE:
return .Insufficient_Resources
case .EBADF, .ENOTSOCK, .EFAULT:
return .Invalid_Argument
case .EINVAL:
return .Not_Listening
case .ECONNABORTED:
return .Aborted
case .EWOULDBLOCK:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
// NOTE(Feoramund): The same as TCP errors go, as far as I'm aware.
UDP_Send_Error :: distinct TCP_Send_Error
_tcp_recv_error :: proc(errno: freebsd.Errno) -> TCP_Recv_Error {
assert(errno != nil)
_last_error = errno
Shutdown_Manner :: enum c.int {
Receive = cast(c.int)freebsd.Shutdown_Method.RD,
Send = cast(c.int)freebsd.Shutdown_Method.WR,
Both = cast(c.int)freebsd.Shutdown_Method.RDWR,
#partial switch errno {
case .EBADF, .ENOTSOCK, .EFAULT:
return .Invalid_Argument
case .ENOTCONN:
return .Not_Connected
case .ECONNRESET:
return .Connection_Closed
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
Shutdown_Error :: enum c.int {
None = 0,
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
Invalid_Manner = cast(c.int)freebsd.Errno.EINVAL,
Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN,
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
_udp_recv_error :: proc(errno: freebsd.Errno) -> UDP_Recv_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF, .ENOTSOCK, .EFAULT:
return .Invalid_Argument
case .ECONNRESET, .ENOTCONN:
return .Connection_Refused
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
Socket_Option_Error :: enum c.int {
None = 0,
Value_Out_Of_Range = -1,
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
Unknown_Option_For_Level = cast(c.int)freebsd.Errno.ENOPROTOOPT,
Argument_In_Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT,
// This error can arise for many different reasons.
Invalid_Value = cast(c.int)freebsd.Errno.EINVAL,
System_Memory_Allocation_Failed = cast(c.int)freebsd.Errno.ENOMEM,
Insufficient_System_Resources = cast(c.int)freebsd.Errno.ENOBUFS,
_tcp_send_error :: proc(errno: freebsd.Errno) -> TCP_Send_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE:
return .Invalid_Argument
case .ENOBUFS:
return .Insufficient_Resources
case .ECONNRESET, .EPIPE:
return .Connection_Closed
case .ENOTCONN:
return .Not_Connected
case .EHOSTUNREACH:
return .Host_Unreachable
case .EHOSTDOWN:
return .Host_Unreachable
case .ENETDOWN:
return .Network_Unreachable
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
Set_Blocking_Error :: enum c.int {
None = 0,
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
Wrong_Descriptor = cast(c.int)freebsd.Errno.ENOTTY,
_udp_send_error :: proc(errno: freebsd.Errno) -> UDP_Send_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE:
return .Invalid_Argument
case .ENOBUFS:
return .Insufficient_Resources
case .ECONNRESET, .EPIPE:
return .Connection_Refused
case .EHOSTUNREACH:
return .Host_Unreachable
case .EHOSTDOWN:
return .Host_Unreachable
case .ENETDOWN:
return .Network_Unreachable
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
_shutdown_error :: proc(errno: freebsd.Errno) -> Shutdown_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF, .EINVAL, .ENOTSOCK, .ENOTCONN:
return .Invalid_Argument
case:
return .Unknown
}
}
_socket_option_error :: proc(errno: freebsd.Errno) -> Socket_Option_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .ENOMEM, .ENOBUFS:
return .Insufficient_Resources
case .EBADF, .ENOTSOCK:
return .Invalid_Socket
case .ENOPROTOOPT:
return .Invalid_Option
case .EINVAL, .EFAULT:
return .Invalid_Value
case:
return .Unknown
}
}
_set_blocking_error :: proc(errno: freebsd.Errno) -> Set_Blocking_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF, .ENOTTY:
return .Invalid_Argument
case:
return .Unknown
}
}

View File

@@ -21,181 +21,269 @@ package net
Feoramund: FreeBSD platform code
*/
import "core:c"
import "core:reflect"
import "core:sys/linux"
Create_Socket_Error :: enum c.int {
None = 0,
Family_Not_Supported_For_This_Socket = c.int(linux.Errno.EAFNOSUPPORT),
No_Socket_Descriptors_Available = c.int(linux.Errno.EMFILE),
No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS),
No_Memory_Available_Available = c.int(linux.Errno.ENOMEM),
Protocol_Unsupported_By_System = c.int(linux.Errno.EPROTONOSUPPORT),
Wrong_Protocol_For_Socket = c.int(linux.Errno.EPROTONOSUPPORT),
Family_And_Socket_Type_Mismatch = c.int(linux.Errno.EPROTONOSUPPORT),
@(private="file", thread_local)
_last_error: linux.Errno
_last_platform_error :: proc() -> i32 {
return i32(_last_error)
}
Dial_Error :: enum c.int {
None = 0,
Port_Required = -1,
Address_In_Use = c.int(linux.Errno.EADDRINUSE),
In_Progress = c.int(linux.Errno.EINPROGRESS),
Cannot_Use_Any_Address = c.int(linux.Errno.EADDRNOTAVAIL),
Wrong_Family_For_Socket = c.int(linux.Errno.EAFNOSUPPORT),
Refused = c.int(linux.Errno.ECONNREFUSED),
Is_Listening_Socket = c.int(linux.Errno.EACCES),
Already_Connected = c.int(linux.Errno.EISCONN),
Network_Unreachable = c.int(linux.Errno.ENETUNREACH), // Device is offline
Host_Unreachable = c.int(linux.Errno.EHOSTUNREACH), // Remote host cannot be reached
No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS),
Not_Socket = c.int(linux.Errno.ENOTSOCK),
Timeout = c.int(linux.Errno.ETIMEDOUT),
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
Would_Block = c.int(linux.Errno.EWOULDBLOCK),
_last_platform_error_string :: proc() -> string {
description, _ := reflect.enum_name_from_value(_last_error)
return description
}
Bind_Error :: enum c.int {
None = 0,
Address_In_Use = c.int(linux.Errno.EADDRINUSE), // Another application is currently bound to this endpoint.
Given_Nonlocal_Address = c.int(linux.Errno.EADDRNOTAVAIL), // The address is not a local address on this machine.
Broadcast_Disabled = c.int(linux.Errno.EACCES), // To bind a UDP socket to the broadcast address, the appropriate socket option must be set.
Address_Family_Mismatch = c.int(linux.Errno.EFAULT), // The address family of the address does not match that of the socket.
Already_Bound = c.int(linux.Errno.EINVAL), // The socket is already bound to an address.
No_Ports_Available = c.int(linux.Errno.ENOBUFS), // There are not enough ephemeral ports available.
_set_last_platform_error :: proc(err: i32) {
_last_error = linux.Errno(err)
}
Listen_Error :: enum c.int {
None = 0,
Address_In_Use = c.int(linux.Errno.EADDRINUSE),
Already_Connected = c.int(linux.Errno.EISCONN),
No_Socket_Descriptors_Available = c.int(linux.Errno.EMFILE),
No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS),
Nonlocal_Address = c.int(linux.Errno.EADDRNOTAVAIL),
Not_Socket = c.int(linux.Errno.ENOTSOCK),
Listening_Not_Supported_For_This_Socket = c.int(linux.Errno.EOPNOTSUPP),
_create_socket_error :: proc(errno: linux.Errno) -> Create_Socket_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EMFILE, .ENFILE, .ENOBUFS, .EPROTONOSUPPORT:
return .Insufficient_Resources
case .EAFNOSUPPORT, .EPROTOTYPE:
return .Invalid_Argument
case .EACCES, .EPERM:
return .Insufficient_Permissions
case:
return .Unknown
}
}
Accept_Error :: enum c.int {
None = 0,
Not_Listening = c.int(linux.Errno.EINVAL),
No_Socket_Descriptors_Available_For_Client_Socket = c.int(linux.Errno.EMFILE),
No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS),
Not_Socket = c.int(linux.Errno.ENOTSOCK),
Not_Connection_Oriented_Socket = c.int(linux.Errno.EOPNOTSUPP),
_dial_error :: proc(errno: linux.Errno) -> Dial_Error {
assert(errno != nil)
_last_error = errno
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
Would_Block = c.int(linux.Errno.EWOULDBLOCK),
#partial switch errno {
case .EAGAIN:
return .Insufficient_Resources
case .EBADF, .EINVAL, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT:
return .Invalid_Argument
case .EISCONN:
return .Already_Connected
case .EALREADY:
return .Already_Connecting
case .EADDRINUSE:
return .Address_In_Use
case .ENETUNREACH:
return .Network_Unreachable
case .EHOSTUNREACH:
return .Host_Unreachable
case .ECONNREFUSED:
return .Refused
case .ECONNRESET:
return .Reset
case .ETIMEDOUT:
return .Timeout
case .EINPROGRESS:
return .Would_Block
case .EINTR:
return .Interrupted
case .EACCES:
return .Broadcast_Not_Supported
case:
return .Unknown
}
}
TCP_Recv_Error :: enum c.int {
None = 0,
Shutdown = c.int(linux.Errno.ESHUTDOWN),
Not_Connected = c.int(linux.Errno.ENOTCONN),
Connection_Broken = c.int(linux.Errno.ENETRESET),
Not_Socket = c.int(linux.Errno.ENOTSOCK),
Aborted = c.int(linux.Errno.ECONNABORTED),
_bind_error :: proc(errno: linux.Errno) -> Bind_Error {
assert(errno != nil)
_last_error = errno
// TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them?
Connection_Closed = c.int(linux.Errno.ECONNRESET),
Offline = c.int(linux.Errno.ENETDOWN),
Host_Unreachable = c.int(linux.Errno.EHOSTUNREACH),
Interrupted = c.int(linux.Errno.EINTR),
Timeout = c.int(linux.Errno.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets...
#partial switch errno {
case .EAGAIN, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT:
return .Insufficient_Resources
case .EINVAL:
return .Already_Bound
case .EBADF:
return .Invalid_Argument
case .EACCES:
return .Insufficient_Permissions_For_Address
case .EADDRINUSE:
return .Address_In_Use
case:
return .Unknown
}
}
UDP_Recv_Error :: enum c.int {
None = 0,
_listen_error :: proc(errno: linux.Errno) -> Listen_Error {
assert(errno != nil)
_last_error = errno
Buffer_Too_Small = c.int(linux.Errno.EMSGSIZE), // The buffer is too small to fit the entire message, and the message was truncated. When this happens, the rest of message is lost.
Not_Socket = c.int(linux.Errno.ENOTSOCK), // The so-called socket is not an open socket.
Not_Descriptor = c.int(linux.Errno.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
Bad_Buffer = c.int(linux.Errno.EFAULT), // The buffer did not point to a valid location in memory.
Interrupted = c.int(linux.Errno.EINTR), // A signal occurred before any data was transmitted. See signal(7).
// The send timeout duration passed before all data was received. See Socket_Option.Receive_Timeout.
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
Timeout = c.int(linux.Errno.EWOULDBLOCK),
Socket_Not_Bound = c.int(linux.Errno.EINVAL), // The socket must be bound for this operation, but isn't.
#partial switch errno {
case .EBADF, .ENOTSOCK:
return .Invalid_Argument
case .EDESTADDRREQ, .EOPNOTSUPP:
return .Unsupported_Socket
case .EINVAL:
return .Already_Connected
case:
return .Unknown
}
}
TCP_Send_Error :: enum c.int {
None = 0,
Aborted = c.int(linux.Errno.ECONNABORTED),
Connection_Closed = c.int(linux.Errno.ECONNRESET),
Not_Connected = c.int(linux.Errno.ENOTCONN),
Shutdown = c.int(linux.Errno.ESHUTDOWN),
_accept_error :: proc(errno: linux.Errno) -> Accept_Error {
assert(errno != nil)
_last_error = errno
// The send queue was full.
// This is usually a transient issue.
//
// This also shouldn't normally happen on Linux, as data is dropped if it
// doesn't fit in the send queue.
No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS),
Offline = c.int(linux.Errno.ENETDOWN),
Host_Unreachable = c.int(linux.Errno.EHOSTUNREACH),
Interrupted = c.int(linux.Errno.EINTR), // A signal occurred before any data was transmitted. See signal(7).
Timeout = c.int(linux.Errno.EWOULDBLOCK), // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
Not_Socket = c.int(linux.Errno.ENOTSOCK), // The so-called socket is not an open socket.
#partial switch errno {
case .EMFILE, .ENFILE, .ENOBUFS, .ENOMEM:
return .Insufficient_Resources
case .EBADF, .ENOTSOCK, .EFAULT:
return .Invalid_Argument
case .EINVAL:
return .Not_Listening
case .ECONNABORTED:
return .Aborted
case .EWOULDBLOCK:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
// TODO
UDP_Send_Error :: enum c.int {
None = 0,
Message_Too_Long = c.int(linux.Errno.EMSGSIZE), // The message is larger than the maximum UDP packet size. No data was sent.
_tcp_recv_error :: proc(errno: linux.Errno) -> TCP_Recv_Error {
assert(errno != nil)
_last_error = errno
// TODO: not sure what the exact circumstances for this is yet
Network_Unreachable = c.int(linux.Errno.ENETUNREACH),
No_Outbound_Ports_Available = c.int(linux.Errno.EAGAIN), // There are no more emphemeral outbound ports available to bind the socket to, in order to send.
// The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
Timeout = c.int(linux.Errno.EWOULDBLOCK),
Not_Socket = c.int(linux.Errno.ENOTSOCK), // The so-called socket is not an open socket.
Not_Descriptor = c.int(linux.Errno.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
Bad_Buffer = c.int(linux.Errno.EFAULT), // The buffer did not point to a valid location in memory.
Interrupted = c.int(linux.Errno.EINTR), // A signal occurred before any data was transmitted. See signal(7).
// The send queue was full.
// This is usually a transient issue.
//
// This also shouldn't normally happen on Linux, as data is dropped if it
// doesn't fit in the send queue.
No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS),
No_Memory_Available = c.int(linux.Errno.ENOMEM), // No memory was available to properly manage the send queue.
#partial switch errno {
case .EBADF, .ENOTSOCK, .EFAULT:
return .Invalid_Argument
case .ENOTCONN:
return .Not_Connected
case .ECONNREFUSED, .ECONNRESET:
return .Connection_Closed
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
// TODO(flysand): slight regression
Shutdown_Manner :: enum c.int {
Receive = c.int(linux.Shutdown_How.RD),
Send = c.int(linux.Shutdown_How.WR),
Both = c.int(linux.Shutdown_How.RDWR),
_udp_recv_error :: proc(errno: linux.Errno) -> UDP_Recv_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF, .ENOTSOCK, .EFAULT:
return .Invalid_Argument
case .ECONNREFUSED, .ENOTCONN, .ECONNRESET:
return .Connection_Refused
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
Shutdown_Error :: enum c.int {
None = 0,
Aborted = c.int(linux.Errno.ECONNABORTED),
Reset = c.int(linux.Errno.ECONNRESET),
Offline = c.int(linux.Errno.ENETDOWN),
Not_Connected = c.int(linux.Errno.ENOTCONN),
Not_Socket = c.int(linux.Errno.ENOTSOCK),
Invalid_Manner = c.int(linux.Errno.EINVAL),
_tcp_send_error :: proc(errno: linux.Errno) -> TCP_Send_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE, .EDESTADDRREQ, .EINVAL, .EISCONN, .EOPNOTSUPP:
return .Invalid_Argument
case .ENOBUFS, .ENOMEM:
return .Insufficient_Resources
case .ECONNRESET, .EPIPE:
return .Connection_Closed
case .ENOTCONN:
return .Not_Connected
case .EHOSTUNREACH:
return .Host_Unreachable
case .EHOSTDOWN:
return .Host_Unreachable
case .ENETDOWN:
return .Network_Unreachable
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
Socket_Option_Error :: enum c.int {
None = 0,
Offline = c.int(linux.Errno.ENETDOWN),
Timeout_When_Keepalive_Set = c.int(linux.Errno.ENETRESET),
Invalid_Option_For_Socket = c.int(linux.Errno.ENOPROTOOPT),
Reset_When_Keepalive_Set = c.int(linux.Errno.ENOTCONN),
Not_Socket = c.int(linux.Errno.ENOTSOCK),
_udp_send_error :: proc(errno: linux.Errno) -> UDP_Send_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE, .EDESTADDRREQ, .EINVAL, .EISCONN, .EOPNOTSUPP:
return .Invalid_Argument
case .ENOBUFS, .ENOMEM:
return .Insufficient_Resources
case .ECONNRESET, .EPIPE:
return .Connection_Refused
case .EHOSTUNREACH:
return .Host_Unreachable
case .EHOSTDOWN:
return .Host_Unreachable
case .ENETDOWN:
return .Network_Unreachable
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
Set_Blocking_Error :: enum c.int {
None = 0,
_shutdown_error :: proc(errno: linux.Errno) -> Shutdown_Error {
assert(errno != nil)
_last_error = errno
// TODO: add errors occuring on followig calls:
// flags, _ := linux.Errno.fcntl(sd, linux.Errno.F_GETFL, 0)
// linux.Errno.fcntl(sd, linux.Errno.F_SETFL, flags | int(linux.Errno.O_NONBLOCK))
}
#partial switch errno {
case .EBADF, .EINVAL, .ENOTSOCK, .ENOTCONN:
return .Invalid_Argument
case:
return .Unknown
}
}
_socket_option_error :: proc(errno: linux.Errno) -> Socket_Option_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .ENOMEM, .ENOBUFS:
return .Insufficient_Resources
case .EBADF, .ENOTSOCK:
return .Invalid_Socket
case .ENOPROTOOPT, .EINVAL:
return .Invalid_Option
case .EFAULT, .EDOM:
return .Invalid_Value
case:
return .Unknown
}
}
_set_blocking_error :: proc(errno: linux.Errno) -> Set_Blocking_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF:
return .Invalid_Argument
case:
return .Unknown
}
}

View File

@@ -0,0 +1,20 @@
#+build !darwin
#+build !linux
#+build !freebsd
#+build !windows
package net
@(private="file", thread_local)
_last_error: i32
_last_platform_error :: proc() -> i32 {
return _last_error
}
_last_platform_error_string :: proc() -> string {
return ""
}
_set_last_platform_error :: proc(err: i32) {
_last_error = err
}

View File

@@ -20,250 +20,242 @@ package net
Feoramund: FreeBSD platform code
*/
import "core:c"
import "core:reflect"
import win "core:sys/windows"
Create_Socket_Error :: enum c.int {
None = 0,
Network_Subsystem_Failure = win.WSAENETDOWN,
Family_Not_Supported_For_This_Socket = win.WSAEAFNOSUPPORT,
No_Socket_Descriptors_Available = win.WSAEMFILE,
No_Buffer_Space_Available = win.WSAENOBUFS,
Protocol_Unsupported_By_System = win.WSAEPROTONOSUPPORT,
Wrong_Protocol_For_Socket = win.WSAEPROTOTYPE,
Family_And_Socket_Type_Mismatch = win.WSAESOCKTNOSUPPORT,
_last_platform_error :: proc() -> i32 {
return i32(win.WSAGetLastError())
}
Dial_Error :: enum c.int {
None = 0,
Port_Required = -1,
Address_In_Use = win.WSAEADDRINUSE,
In_Progress = win.WSAEALREADY,
Cannot_Use_Any_Address = win.WSAEADDRNOTAVAIL,
Wrong_Family_For_Socket = win.WSAEAFNOSUPPORT,
Refused = win.WSAECONNREFUSED,
Is_Listening_Socket = win.WSAEINVAL,
Already_Connected = win.WSAEISCONN,
Network_Unreachable = win.WSAENETUNREACH, // Device is offline
Host_Unreachable = win.WSAEHOSTUNREACH, // Remote host cannot be reached
No_Buffer_Space_Available = win.WSAENOBUFS,
Not_Socket = win.WSAENOTSOCK,
Timeout = win.WSAETIMEDOUT,
Would_Block = win.WSAEWOULDBLOCK, // TODO: we may need special handling for this; maybe make a socket a struct with metadata?
_last_platform_error_string :: proc() -> string {
description, _ := reflect.enum_name_from_value(win.System_Error(win.WSAGetLastError()))
return description
}
Bind_Error :: enum c.int {
None = 0,
Address_In_Use = win.WSAEADDRINUSE, // Another application is currently bound to this endpoint.
Given_Nonlocal_Address = win.WSAEADDRNOTAVAIL, // The address is not a local address on this machine.
Broadcast_Disabled = win.WSAEACCES, // To bind a UDP socket to the broadcast address, the appropriate socket option must be set.
Address_Family_Mismatch = win.WSAEFAULT, // The address family of the address does not match that of the socket.
Already_Bound = win.WSAEINVAL, // The socket is already bound to an address.
No_Ports_Available = win.WSAENOBUFS, // There are not enough ephemeral ports available.
_set_last_platform_error :: proc(err: i32) {
win.WSASetLastError(err)
}
Listen_Error :: enum c.int {
None = 0,
Address_In_Use = win.WSAEADDRINUSE,
Already_Connected = win.WSAEISCONN,
No_Socket_Descriptors_Available = win.WSAEMFILE,
No_Buffer_Space_Available = win.WSAENOBUFS,
Nonlocal_Address = win.WSAEADDRNOTAVAIL,
Not_Socket = win.WSAENOTSOCK,
Listening_Not_Supported_For_This_Socket = win.WSAEOPNOTSUPP,
_create_socket_error :: proc() -> Create_Socket_Error {
#partial switch win.System_Error(win.WSAGetLastError()) {
case .WSANOTINITIALISED, .WSAENETDOWN, .WSAEINVALIDPROVIDER, .WSAEINVALIDPROCTABLE, .WSAEPROVIDERFAILEDINIT:
return .Network_Unreachable
case .WSAEAFNOSUPPORT, .WSAEINPROGRESS, .WSAEINVAL, .WSAEPROTOTYPE, .WSAESOCKTNOSUPPORT:
return .Invalid_Argument
case .WSAEMFILE, .WSAENOBUFS, .WSAEPROTONOSUPPORT:
return .Insufficient_Resources
case:
return .Unknown
}
}
Accept_Error :: enum c.int {
None = 0,
Not_Listening = win.WSAEINVAL,
No_Socket_Descriptors_Available_For_Client_Socket = win.WSAEMFILE,
No_Buffer_Space_Available = win.WSAENOBUFS,
Not_Socket = win.WSAENOTSOCK,
Not_Connection_Oriented_Socket = win.WSAEOPNOTSUPP,
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
Would_Block = win.WSAEWOULDBLOCK,
_dial_error :: proc() -> Dial_Error {
#partial switch win.System_Error(win.WSAGetLastError()) {
case .WSANOTINITIALISED, .WSAENETDOWN:
return .Network_Unreachable
case .WSAEADDRINUSE:
return .Address_In_Use
case .WSAEINTR:
return .Interrupted
case .WSAEWOULDBLOCK:
return .Would_Block
case .WSAEALREADY:
return .Already_Connecting
case .WSAEADDRNOTAVAIL, .WSAEAFNOSUPPORT, .WSAEFAULT, .WSAENOTSOCK, .WSAEINPROGRESS, .WSAEINVAL:
return .Invalid_Argument
case .WSAECONNREFUSED:
return .Refused
case .WSAEISCONN:
return .Already_Connected
case .WSAEHOSTUNREACH:
return .Host_Unreachable
case .WSAENOBUFS:
return .Insufficient_Resources
case .WSAETIMEDOUT:
return .Timeout
case .WSAEACCES:
return .Broadcast_Not_Supported
case:
return .Unknown
}
}
TCP_Recv_Error :: enum c.int {
None = 0,
Network_Subsystem_Failure = win.WSAENETDOWN,
Not_Connected = win.WSAENOTCONN,
Bad_Buffer = win.WSAEFAULT,
Keepalive_Failure = win.WSAENETRESET,
Not_Socket = win.WSAENOTSOCK,
Shutdown = win.WSAESHUTDOWN,
Would_Block = win.WSAEWOULDBLOCK,
Aborted = win.WSAECONNABORTED,
Timeout = win.WSAETIMEDOUT,
// TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them?
Connection_Closed = win.WSAECONNRESET,
// TODO: verify can actually happen
Host_Unreachable = win.WSAEHOSTUNREACH,
_bind_error :: proc() -> Bind_Error {
#partial switch win.System_Error(win.WSAGetLastError()) {
case .WSANOTINITIALISED, .WSAENETDOWN:
return .Network_Unreachable
case .WSAEADDRINUSE:
return .Address_In_Use
case .WSAEADDRNOTAVAIL, .WSAEFAULT, .WSAEINPROGRESS, .WSAEACCES, .WSAEINVAL, .WSAENOTSOCK:
return .Invalid_Argument
case:
return .Unknown
}
}
UDP_Recv_Error :: enum c.int {
None = 0,
Network_Subsystem_Failure = win.WSAENETDOWN,
Aborted = win.WSAECONNABORTED,
Buffer_Too_Small = win.WSAEMSGSIZE, // The buffer is too small to fit the entire message, and the message was truncated. When this happens, the rest of message is lost.
Remote_Not_Listening = win.WSAECONNRESET, // The machine at the remote endpoint doesn't have the given port open to receiving UDP data.
Shutdown = win.WSAESHUTDOWN,
Broadcast_Disabled = win.WSAEACCES, // A broadcast address was specified, but the .Broadcast socket option isn't set.
Bad_Buffer = win.WSAEFAULT,
No_Buffer_Space_Available = win.WSAENOBUFS,
Not_Socket = win.WSAENOTSOCK, // The socket is not valid socket handle.
Would_Block = win.WSAEWOULDBLOCK,
Host_Unreachable = win.WSAEHOSTUNREACH, // The remote host cannot be reached from this host at this time.
Offline = win.WSAENETUNREACH, // The network cannot be reached from this host at this time.
Timeout = win.WSAETIMEDOUT,
// TODO: can this actually happen? The socket isn't bound; an unknown flag specified; or MSG_OOB specified with SO_OOBINLINE enabled.
Incorrectly_Configured = win.WSAEINVAL,
TTL_Expired = win.WSAENETRESET, // The message took more hops than was allowed (the Time To Live) to reach the remote endpoint.
_listen_error :: proc() -> Listen_Error {
#partial switch win.System_Error(win.WSAGetLastError()) {
case .WSANOTINITIALISED, .WSAENETDOWN:
return .Network_Unreachable
case .WSAEMFILE, .WSAENOBUFS:
return .Insufficient_Resources
case .WSAEADDRINUSE:
return .Address_In_Use
case .WSAEINPROGRESS, .WSAENOTSOCK:
return .Invalid_Argument
case .WSAEISCONN:
return .Already_Connected
case .WSAEOPNOTSUPP, .WSAEINVAL:
return .Unsupported_Socket
case:
return .Unknown
}
}
// TODO: consider merging some errors to make handling them easier
// TODO: verify once more what errors to actually expose
TCP_Send_Error :: enum c.int {
None = 0,
Aborted = win.WSAECONNABORTED,
Not_Connected = win.WSAENOTCONN,
Shutdown = win.WSAESHUTDOWN,
Connection_Closed = win.WSAECONNRESET,
No_Buffer_Space_Available = win.WSAENOBUFS,
Network_Subsystem_Failure = win.WSAENETDOWN,
Host_Unreachable = win.WSAEHOSTUNREACH,
Would_Block = win.WSAEWOULDBLOCK,
// TODO: verify possible, as not mentioned in docs
Offline = win.WSAENETUNREACH,
Timeout = win.WSAETIMEDOUT,
// A broadcast address was specified, but the .Broadcast socket option isn't set.
Broadcast_Disabled = win.WSAEACCES,
Bad_Buffer = win.WSAEFAULT,
// Connection is broken due to keepalive activity detecting a failure during the operation.
Keepalive_Failure = win.WSAENETRESET, // TODO: not functionally different from Reset; merge?
Not_Socket = win.WSAENOTSOCK, // The so-called socket is not an open socket.
_accept_error :: proc() -> Accept_Error {
#partial switch win.System_Error(win.WSAGetLastError()) {
case .WSANOTINITIALISED, .WSAENETDOWN:
return .Network_Unreachable
case .WSAEMFILE, .WSAENOBUFS:
return .Insufficient_Resources
case .WSAECONNRESET:
return .Aborted
case .WSAEFAULT, .WSAEINPROGRESS, .WSAENOTSOCK:
return .Invalid_Argument
case .WSAEINTR:
return .Interrupted
case .WSAEINVAL:
return .Not_Listening
case .WSAEWOULDBLOCK:
return .Would_Block
case .WSAEOPNOTSUPP:
return .Unsupported_Socket
case:
return .Unknown
}
}
UDP_Send_Error :: enum c.int {
None = 0,
Network_Subsystem_Failure = win.WSAENETDOWN,
Aborted = win.WSAECONNABORTED,
Message_Too_Long = win.WSAEMSGSIZE, // The message is larger than the maximum UDP packet size.
Remote_Not_Listening = win.WSAECONNRESET, // The machine at the remote endpoint doesn't have the given port open to receiving UDP data.
Shutdown = win.WSAESHUTDOWN, // A broadcast address was specified, but the .Broadcast socket option isn't set.
Broadcast_Disabled = win.WSAEACCES,
Bad_Buffer = win.WSAEFAULT, // Connection is broken due to keepalive activity detecting a failure during the operation.
// TODO: not functionally different from Reset; merge?
Keepalive_Failure = win.WSAENETRESET,
No_Buffer_Space_Available = win.WSAENOBUFS,
Not_Socket = win.WSAENOTSOCK, // The socket is not valid socket handle.
// This socket is unidirectional and cannot be used to send any data.
// TODO: verify possible; decide whether to keep if not
Receive_Only = win.WSAEOPNOTSUPP,
Would_Block = win.WSAEWOULDBLOCK,
Host_Unreachable = win.WSAEHOSTUNREACH, // The remote host cannot be reached from this host at this time.
Cannot_Use_Any_Address = win.WSAEADDRNOTAVAIL, // Attempt to send to the Any address.
Family_Not_Supported_For_This_Socket = win.WSAEAFNOSUPPORT, // The address is of an incorrect address family for this socket.
Offline = win.WSAENETUNREACH, // The network cannot be reached from this host at this time.
Timeout = win.WSAETIMEDOUT,
_tcp_recv_error :: proc() -> TCP_Recv_Error {
#partial switch win.System_Error(win.WSAGetLastError()) {
case .WSANOTINITIALISED, .WSAENETDOWN:
return .Network_Unreachable
case .WSAEFAULT, .WSAEINPROGRESS, .WSAENOTSOCK, .WSAEMSGSIZE, .WSAEINVAL, .WSAEOPNOTSUPP:
return .Invalid_Argument
case .WSAENOTCONN:
return .Not_Connected
case .WSAEINTR:
return .Interrupted
case .WSAENETRESET, .WSAESHUTDOWN, .WSAECONNABORTED, .WSAECONNRESET:
return .Connection_Closed
case .WSAEWOULDBLOCK:
return .Would_Block
case .WSAETIMEDOUT:
return .Timeout
case:
return .Unknown
}
}
Shutdown_Manner :: enum c.int {
Receive = win.SD_RECEIVE,
Send = win.SD_SEND,
Both = win.SD_BOTH,
_udp_recv_error :: proc() -> UDP_Recv_Error {
#partial switch win.System_Error(win.WSAGetLastError()) {
case .WSANOTINITIALISED, .WSAENETDOWN:
return .Network_Unreachable
case .WSAEFAULT, .WSAEINPROGRESS, .WSAEINVAL, .WSAEISCONN, .WSAENOTSOCK, .WSAEOPNOTSUPP, .WSAEMSGSIZE:
return .Invalid_Argument
case .WSAEINTR:
return .Interrupted
case .WSAENETRESET, .WSAESHUTDOWN, .WSAECONNRESET:
return .Connection_Refused
case .WSAEWOULDBLOCK:
return .Would_Block
case .WSAETIMEDOUT:
return .Timeout
case:
return .Unknown
}
}
Shutdown_Error :: enum c.int {
None = 0,
Aborted = win.WSAECONNABORTED,
Reset = win.WSAECONNRESET,
Offline = win.WSAENETDOWN,
Not_Connected = win.WSAENOTCONN,
Not_Socket = win.WSAENOTSOCK,
Invalid_Manner = win.WSAEINVAL,
_tcp_send_error :: proc() -> TCP_Send_Error {
#partial switch win.System_Error(win.WSAGetLastError()) {
case .WSANOTINITIALISED, .WSAENETDOWN:
return .Network_Unreachable
case .WSAENOBUFS:
return .Insufficient_Resources
case .WSAEACCES, .WSAEINPROGRESS, .WSAEFAULT, .WSAENOTSOCK, .WSAEOPNOTSUPP, .WSAEMSGSIZE, .WSAEINVAL:
return .Invalid_Argument
case .WSAEINTR:
return .Interrupted
case .WSAENETRESET, .WSAESHUTDOWN, .WSAECONNABORTED, .WSAECONNRESET:
return .Connection_Closed
case .WSAENOTCONN:
return .Not_Connected
case .WSAEWOULDBLOCK:
return .Would_Block
case .WSAETIMEDOUT:
return .Timeout
case .WSAEHOSTUNREACH:
return .Host_Unreachable
case:
return .Unknown
}
}
Socket_Option :: enum c.int {
// bool: Whether the address that this socket is bound to can be reused by other sockets.
// This allows you to bypass the cooldown period if a program dies while the socket is bound.
Reuse_Address = win.SO_REUSEADDR,
// bool: Whether other programs will be inhibited from binding the same endpoint as this socket.
Exclusive_Addr_Use = win.SO_EXCLUSIVEADDRUSE,
// bool: When true, keepalive packets will be automatically be sent for this connection. TODO: verify this understanding
Keep_Alive = win.SO_KEEPALIVE,
// bool: When true, client connections will immediately be sent a TCP/IP RST response, rather than being accepted.
Conditional_Accept = win.SO_CONDITIONAL_ACCEPT,
// bool: If true, when the socket is closed, but data is still waiting to be sent, discard that data.
Dont_Linger = win.SO_DONTLINGER,
// bool: When true, 'out-of-band' data sent over the socket will be read by a normal net.recv() call, the same as normal 'in-band' data.
Out_Of_Bounds_Data_Inline = win.SO_OOBINLINE,
// bool: When true, disables send-coalescing, therefore reducing latency.
TCP_Nodelay = win.TCP_NODELAY,
// win.LINGER: Customizes how long (if at all) the socket will remain open when there
// is some remaining data waiting to be sent, and net.close() is called.
Linger = win.SO_LINGER,
// win.DWORD: The size, in bytes, of the OS-managed receive-buffer for this socket.
Receive_Buffer_Size = win.SO_RCVBUF,
// win.DWORD: The size, in bytes, of the OS-managed send-buffer for this socket.
Send_Buffer_Size = win.SO_SNDBUF,
// win.DWORD: For blocking sockets, the time in milliseconds to wait for incoming data to be received, before giving up and returning .Timeout.
// For non-blocking sockets, ignored.
// Use a value of zero to potentially wait forever.
Receive_Timeout = win.SO_RCVTIMEO,
// win.DWORD: For blocking sockets, the time in milliseconds to wait for outgoing data to be sent, before giving up and returning .Timeout.
// For non-blocking sockets, ignored.
// Use a value of zero to potentially wait forever.
Send_Timeout = win.SO_SNDTIMEO,
// bool: Allow sending to, receiving from, and binding to, a broadcast address.
Broadcast = win.SO_BROADCAST,
_udp_send_error :: proc() -> UDP_Send_Error {
#partial switch win.System_Error(win.WSAGetLastError()) {
case .WSANOTINITIALISED, .WSAENETDOWN, .WSAENETUNREACH:
return .Network_Unreachable
case .WSAENOBUFS:
return .Insufficient_Resources
case .WSAEACCES, .WSAEINVAL, .WSAEINPROGRESS, .WSAEFAULT, .WSAENOTCONN, .WSAENOTSOCK, .WSAEOPNOTSUPP, .WSAEADDRNOTAVAIL, .WSAEAFNOSUPPORT, .WSAEDESTADDRREQ:
return .Invalid_Argument
case .WSAEINTR:
return .Interrupted
case .WSAENETRESET, .WSAESHUTDOWN, .WSAECONNRESET:
return .Connection_Refused
case .WSAEWOULDBLOCK:
return .Would_Block
case .WSAETIMEDOUT:
return .Timeout
case:
return .Unknown
}
}
Socket_Option_Error :: enum c.int {
None = 0,
Linger_Only_Supports_Whole_Seconds = 1,
// The given value is too big or small to be given to the OS.
Value_Out_Of_Range,
Network_Subsystem_Failure = win.WSAENETDOWN,
Timeout_When_Keepalive_Set = win.WSAENETRESET,
Invalid_Option_For_Socket = win.WSAENOPROTOOPT,
Reset_When_Keepalive_Set = win.WSAENOTCONN,
Not_Socket = win.WSAENOTSOCK,
_shutdown_error :: proc() -> Shutdown_Error {
#partial switch win.System_Error(win.WSAGetLastError()) {
case .WSAENETDOWN, .WSANOTINITIALISED:
return .Network_Unreachable
case .WSAECONNABORTED, .WSAECONNRESET:
return .Connection_Closed
case .WSAEINPROGRESS, .WSAEINVAL, .WSAENOTCONN, .WSAENOTSOCK:
return .Invalid_Argument
case:
return .Unknown
}
}
Set_Blocking_Error :: enum c.int {
None = 0,
_socket_option_error :: proc() -> Socket_Option_Error {
#partial switch win.System_Error(win.WSAGetLastError()) {
case .WSAENETDOWN, .WSANOTINITIALISED:
return .Network_Unreachable
case .WSAEFAULT, .WSAEINVAL:
return .Invalid_Value
case .WSAENETRESET, .WSAENOTCONN, .WSAENOTSOCK:
return .Invalid_Socket
case .WSAENOPROTOOPT:
return .Invalid_Option
case:
return .Unknown
}
}
Network_Subsystem_Failure = win.WSAENETDOWN,
Blocking_Call_In_Progress = win.WSAEINPROGRESS,
Not_Socket = win.WSAENOTSOCK,
// TODO: are those errors possible?
Network_Subsystem_Not_Initialized = win.WSAENOTINITIALISED,
Invalid_Argument_Pointer = win.WSAEFAULT,
}
_set_blocking_error :: proc() -> Set_Blocking_Error {
#partial switch win.System_Error(win.WSAGetLastError()) {
case .WSAENETDOWN, .WSANOTINITIALISED:
return .Network_Unreachable
case .WSAEINPROGRESS, .WSAENOTSOCK, .WSAEFAULT:
return .Invalid_Argument
case:
return .Unknown
}
}

View File

@@ -27,7 +27,7 @@ MAX_INTERFACE_ENUMERATION_TRIES :: 3
/*
`enumerate_interfaces` retrieves a list of network interfaces with their associated properties.
*/
enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) {
return _enumerate_interfaces(allocator)
}

View File

@@ -20,60 +20,57 @@ package net
Feoramund: FreeBSD platform code
*/
import "core:os"
import "core:strings"
import "core:sys/posix"
foreign import lib "system:System.framework"
@(private)
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) {
context.allocator = allocator
head: ^os.ifaddrs
if res := os._getifaddrs(&head); res < 0 {
head: ^ifaddrs
if getifaddrs(&head) != .OK {
return {}, .Unable_To_Enumerate_Network_Interfaces
}
defer freeifaddrs(head)
/*
Unlike Windows, *nix regrettably doesn't return all it knows about an interface in one big struct.
We're going to have to iterate over a list and coalesce information as we go.
*/
ifaces: map[string]^Network_Interface
ifaces: map[string]Network_Interface
defer delete(ifaces)
for ifaddr := head; ifaddr != nil; ifaddr = ifaddr.next {
adapter_name := string(ifaddr.name)
/*
Check if we have seen this interface name before so we can reuse the `Network_Interface`.
Else, create a new one.
*/
if adapter_name not_in ifaces {
ifaces[adapter_name] = new(Network_Interface)
ifaces[adapter_name].adapter_name = strings.clone(adapter_name)
key_ptr, iface, inserted, mem_err := map_entry(&ifaces, adapter_name)
if mem_err == nil && inserted {
key_ptr^, mem_err = strings.clone(adapter_name)
iface.adapter_name = key_ptr^
}
if mem_err != nil {
return {}, .Allocation_Failure
}
iface := ifaces[adapter_name]
address: Address
netmask: Netmask
if ifaddr.address != nil {
switch int(ifaddr.address.family) {
case os.AF_INET, os.AF_INET6:
address = _sockaddr_basic_to_endpoint(ifaddr.address).address
if ifaddr.addr != nil {
#partial switch ifaddr.addr.sa_family {
case .INET, .INET6:
address = _sockaddr_basic_to_endpoint(ifaddr.addr).address
}
}
if ifaddr.netmask != nil {
switch int(ifaddr.netmask.family) {
case os.AF_INET, os.AF_INET6:
#partial switch ifaddr.netmask.sa_family {
case .INET, .INET6:
netmask = Netmask(_sockaddr_basic_to_endpoint(ifaddr.netmask).address)
}
}
if ifaddr.broadcast_or_dest != nil && .BROADCAST in ifaddr.flags {
switch int(ifaddr.broadcast_or_dest.family) {
case os.AF_INET, os.AF_INET6:
broadcast := _sockaddr_basic_to_endpoint(ifaddr.broadcast_or_dest).address
if ifaddr.dstaddr != nil && .BROADCAST in ifaddr.flags {
#partial switch ifaddr.dstaddr.sa_family {
case .INET, .INET6:
broadcast := _sockaddr_basic_to_endpoint(ifaddr.dstaddr).address
append(&iface.multicast, broadcast)
}
}
@@ -105,18 +102,51 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []
iface.link.state = state
}
/*
Free the OS structures.
*/
os._freeifaddrs(head)
/*
Turn the map into a slice to return.
*/
_interfaces := make([dynamic]Network_Interface, 0, allocator)
interfaces = make([]Network_Interface, len(ifaces))
i: int
for _, iface in ifaces {
append(&_interfaces, iface^)
free(iface)
interfaces[i] = iface
i += 1
}
return _interfaces[:], {}
return interfaces, nil
}
@(private)
IF_Flag :: enum u32 {
UP,
BROADCAST,
DEBUG,
LOOPBACK,
POINTTOPOINT,
NOTRAILERS,
RUNNING,
NOARP,
PROMISC,
ALLMULTI,
OACTIVE,
SIMPLEX,
LINK0,
LINK1,
LINK2,
MULTICAST,
}
@(private)
IF_Flags :: bit_set[IF_Flag; u32]
@(private)
ifaddrs :: struct {
next: ^ifaddrs,
name: cstring,
flags: IF_Flags,
addr: ^posix.sockaddr,
netmask: ^posix.sockaddr,
dstaddr: ^posix.sockaddr,
data: rawptr,
}
@(private)
foreign lib {
getifaddrs :: proc(ifap: ^^ifaddrs) -> posix.result ---
freeifaddrs :: proc(ifp: ^ifaddrs) ---
}

View File

@@ -25,7 +25,7 @@ import "core:strings"
import "core:sys/freebsd"
@(private)
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) {
// This is a simplified implementation of `getifaddrs` from the FreeBSD
// libc using only Odin and syscalls.
context.allocator = allocator
@@ -50,7 +50,7 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []
// Allocate and get the entries.
buf, alloc_err := make([]byte, needed)
if alloc_err != nil {
return nil, .Unable_To_Enumerate_Network_Interfaces
return nil, .Allocation_Failure
}
defer delete(buf)

View File

@@ -30,7 +30,7 @@ package net
// NOTE(flysand): https://man7.org/linux/man-pages/man7/netlink.7.html
// apparently musl libc uses this to enumerate network interfaces
@(private)
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) {
context.allocator = allocator
// head: ^os.ifaddrs
@@ -143,4 +143,4 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []
// }
// return _interfaces[:], {}
return nil, {}
}
}

View File

@@ -23,7 +23,7 @@ package net
import sys "core:sys/windows"
import strings "core:strings"
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) {
context.allocator = allocator
buf: []u8
@@ -52,7 +52,8 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []
case 0:
break gaa
case:
return {}, Platform_Error(res)
set_last_platform_error(i32(res))
return {}, .Unknown
}
}
@@ -63,13 +64,13 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []
_interfaces := make([dynamic]Network_Interface, 0, allocator)
for adapter := (^sys.IP_Adapter_Addresses)(raw_data(buf)); adapter != nil; adapter = adapter.Next {
friendly_name, err1 := sys.wstring_to_utf8(sys.wstring(adapter.FriendlyName), 256, allocator)
if err1 != nil { return {}, Platform_Error(err1) }
if err1 != nil { return {}, .Allocation_Failure }
description, err2 := sys.wstring_to_utf8(sys.wstring(adapter.Description), 256, allocator)
if err2 != nil { return {}, Platform_Error(err2) }
if err2 != nil { return {}, .Allocation_Failure }
dns_suffix, err3 := sys.wstring_to_utf8(sys.wstring(adapter.DnsSuffix), 256, allocator)
if err3 != nil { return {}, Platform_Error(err3) }
if err3 != nil { return {}, .Allocation_Failure }
interface := Network_Interface{
adapter_name = strings.clone(string(adapter.AdapterName)),
@@ -176,4 +177,4 @@ parse_socket_address :: proc(addr_in: sys.SOCKET_ADDRESS) -> (addr: Endpoint) {
case: return // Empty or invalid address type
}
unreachable()
}
}

View File

@@ -35,8 +35,10 @@ any_socket_to_socket :: proc "contextless" (socket: Any_Socket) -> Socket {
`a.host.name:9999`, or as `1.2.3.4:9999`, or IP6 equivalent.
Calls `parse_hostname_or_endpoint` and `dial_tcp_from_host_or_endpoint`.
Errors that can be returned: `Parse_Endpoint_Error`, `Resolve_Error`, `DNS_Error`, `Create_Socket_Error`, or `Dial_Error`
*/
dial_tcp_from_hostname_and_port_string :: proc(hostname_and_port: string, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
dial_tcp_from_hostname_and_port_string :: proc(hostname_and_port: string, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) {
target := parse_hostname_or_endpoint(hostname_and_port) or_return
return dial_tcp_from_host_or_endpoint(target, options)
@@ -47,8 +49,10 @@ dial_tcp_from_hostname_and_port_string :: proc(hostname_and_port: string, option
`parse_hostname_or_endpoint` is called and the `hostname` will be resolved into an IP.
If a `hostname` of form `a.host.name:9999` is given, the port will be ignored in favor of the explicit `port` param.
Errors that can be returned: `Parse_Endpoint_Error`, `Resolve_Error`, `DNS_Error`, `Create_Socket_Error`, or `Dial_Error`
*/
dial_tcp_from_hostname_with_port_override :: proc(hostname: string, port: int, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
dial_tcp_from_hostname_with_port_override :: proc(hostname: string, port: int, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) {
target := parse_hostname_or_endpoint(hostname) or_return
switch &t in target {
case Endpoint:
@@ -62,8 +66,10 @@ dial_tcp_from_hostname_with_port_override :: proc(hostname: string, port: int, o
/*
Expects the `host` as Host.
Errors that can be returned: `Resolve_Error`, `DNS_Error`, `Create_Socket_Error`, or `Dial_Error`
*/
dial_tcp_from_host :: proc(host: Host, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
dial_tcp_from_host :: proc(host: Host, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) {
if host.port == 0 {
return 0, .Port_Required
}
@@ -76,8 +82,10 @@ dial_tcp_from_host :: proc(host: Host, options := default_tcp_options) -> (socke
/*
Expects the `target` as a Host_OrEndpoint.
Unwraps the underlying type and calls `dial_tcp_from_host` or `dial_tcp_from_endpoint`.
Errors that can be returned: `Parse_Endpoint_Error`, `Resolve_Error`, `DNS_Error`, `Create_Socket_Error`, or `Dial_Error`
*/
dial_tcp_from_host_or_endpoint :: proc(target: Host_Or_Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
dial_tcp_from_host_or_endpoint :: proc(target: Host_Or_Endpoint, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) {
switch t in target {
case Endpoint:
return dial_tcp_from_endpoint(t, options)
@@ -87,12 +95,21 @@ dial_tcp_from_host_or_endpoint :: proc(target: Host_Or_Endpoint, options := defa
unreachable()
}
// Dial from an Address
dial_tcp_from_address_and_port :: proc(address: Address, port: int, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
/*
Dial from an Address.
Errors that can be returned: `Create_Socket_Error`, or `Dial_Error`
*/
dial_tcp_from_address_and_port :: proc(address: Address, port: int, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) {
return dial_tcp_from_endpoint({address, port}, options)
}
dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
/*
Dial from an Endpoint.
Errors that can be returned: `Create_Socket_Error`, or `Dial_Error`
*/
dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) {
return _dial_tcp_from_endpoint(endpoint, options)
}
@@ -105,11 +122,11 @@ dial_tcp :: proc{
dial_tcp_from_host_or_endpoint,
}
create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) {
return _create_socket(family, protocol)
}
bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Bind_Error) {
return _bind(socket, ep)
}
@@ -119,7 +136,7 @@ bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
This is like a client TCP socket, except that it can send data to any remote endpoint without needing to establish a connection first.
*/
make_unbound_udp_socket :: proc(family: Address_Family) -> (socket: UDP_Socket, err: Network_Error) {
make_unbound_udp_socket :: proc(family: Address_Family) -> (socket: UDP_Socket, err: Create_Socket_Error) {
sock := create_socket(family, .UDP) or_return
socket = sock.(UDP_Socket)
return
@@ -131,6 +148,8 @@ make_unbound_udp_socket :: proc(family: Address_Family) -> (socket: UDP_Socket,
This is like a listening TCP socket, except that data packets can be sent and received without needing to establish a connection first.
The `bound_address` is the address of the network interface that you want to use, or a loopback address if you don't care which to use.
Errors that can be returned: `Parse_Endpoint_Error`, `Create_Socket_Error`, or `Bind_Error`
*/
make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (socket: UDP_Socket, err: Network_Error) {
if bound_address == nil {
@@ -141,6 +160,11 @@ make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (socket: UDP
return
}
/*
Creates a TCP socket and starts listening on the given endpoint.
Errors that can be returned: `Create_Socket_Error`, `Bind_Error`, or `Listen_Error`
*/
listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket, err: Network_Error) {
assert(backlog > 0 && backlog < int(max(i32)))
@@ -150,11 +174,11 @@ listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TC
/*
Returns the endpoint that the given socket is listening / bound on.
*/
bound_endpoint :: proc(socket: Any_Socket) -> (endpoint: Endpoint, err: Network_Error) {
bound_endpoint :: proc(socket: Any_Socket) -> (endpoint: Endpoint, err: Listen_Error) {
return _bound_endpoint(socket)
}
accept_tcp :: proc(socket: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
accept_tcp :: proc(socket: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) {
return _accept_tcp(socket, options)
}
@@ -162,11 +186,11 @@ close :: proc(socket: Any_Socket) {
_close(socket)
}
recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) {
return _recv_tcp(socket, buf)
}
recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) {
return _recv_udp(socket, buf)
}
@@ -175,6 +199,8 @@ recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_en
Note: `remote_endpoint` parameter is non-nil only if the socket type is UDP. On TCP sockets it
will always return `nil`.
Errors that can be returned: `TCP_Recv_Error`, or `UDP_Recv_Error`
*/
recv_any :: proc(socket: Any_Socket, buf: []byte) -> (
bytes_read: int,
@@ -197,7 +223,7 @@ recv :: proc{recv_tcp, recv_udp, recv_any}
Repeatedly sends data until the entire buffer is sent.
If a send fails before all data is sent, returns the amount sent up to that point.
*/
send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: TCP_Send_Error) {
return _send_tcp(socket, buf)
}
@@ -207,10 +233,15 @@ send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: N
Datagrams are limited in size; attempting to send more than this limit at once will result in a Message_Too_Long error.
UDP packets are not guarenteed to be received in order.
*/
send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: UDP_Send_Error) {
return _send_udp(socket, buf, to)
}
/*
Sends data over the socket.
Errors that can be returned: `TCP_Send_Error`, or `UDP_Send_Error`
*/
send_any :: proc(socket: Any_Socket, buf: []byte, to: Maybe(Endpoint) = nil) -> (
bytes_written: int,
err: Network_Error,
@@ -226,14 +257,14 @@ send_any :: proc(socket: Any_Socket, buf: []byte, to: Maybe(Endpoint) = nil) ->
send :: proc{send_tcp, send_udp, send_any}
shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) {
return _shutdown(socket, manner)
}
set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error {
return _set_option(socket, option, value, loc)
}
set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) {
return _set_blocking(socket, should_block)
}

View File

@@ -21,44 +21,51 @@ package net
*/
import "core:c"
import "core:os"
import "core:sys/posix"
import "core:time"
Socket_Option :: enum c.int {
Broadcast = c.int(os.SO_BROADCAST),
Reuse_Address = c.int(os.SO_REUSEADDR),
Keep_Alive = c.int(os.SO_KEEPALIVE),
Out_Of_Bounds_Data_Inline = c.int(os.SO_OOBINLINE),
TCP_Nodelay = c.int(os.TCP_NODELAY),
Linger = c.int(os.SO_LINGER),
Receive_Buffer_Size = c.int(os.SO_RCVBUF),
Send_Buffer_Size = c.int(os.SO_SNDBUF),
Receive_Timeout = c.int(os.SO_RCVTIMEO),
Send_Timeout = c.int(os.SO_SNDTIMEO),
Broadcast = c.int(posix.Sock_Option.BROADCAST),
Reuse_Address = c.int(posix.Sock_Option.REUSEADDR),
Keep_Alive = c.int(posix.Sock_Option.KEEPALIVE),
Out_Of_Bounds_Data_Inline = c.int(posix.Sock_Option.OOBINLINE),
TCP_Nodelay = c.int(posix.TCP_NODELAY),
Linger = c.int(posix.Sock_Option.LINGER),
Receive_Buffer_Size = c.int(posix.Sock_Option.RCVBUF),
Send_Buffer_Size = c.int(posix.Sock_Option.SNDBUF),
Receive_Timeout = c.int(posix.Sock_Option.RCVTIMEO),
Send_Timeout = c.int(posix.Sock_Option.SNDTIMEO),
}
Shutdown_Manner :: enum c.int {
Receive = c.int(posix.SHUT_RD),
Send = c.int(posix.SHUT_WR),
Both = c.int(posix.SHUT_RDWR),
}
@(private)
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
c_type, c_protocol, c_family: int
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) {
c_type: posix.Sock
c_protocol: posix.Protocol
c_family: posix.AF
switch family {
case .IP4: c_family = os.AF_INET
case .IP6: c_family = os.AF_INET6
case .IP4: c_family = .INET
case .IP6: c_family = .INET6
case:
unreachable()
}
switch protocol {
case .TCP: c_type = os.SOCK_STREAM; c_protocol = os.IPPROTO_TCP
case .UDP: c_type = os.SOCK_DGRAM; c_protocol = os.IPPROTO_UDP
case .TCP: c_type = .STREAM; c_protocol = .TCP
case .UDP: c_type = .DGRAM; c_protocol = .UDP
case:
unreachable()
}
sock, sock_err := os.socket(c_family, c_type, c_protocol)
if sock_err != nil {
err = Create_Socket_Error(os.is_platform_error(sock_err) or_else -1)
sock := posix.socket(c_family, c_type, c_protocol)
if sock < 0 {
err = _create_socket_error()
return
}
@@ -71,7 +78,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so
}
@(private)
_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) {
_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := DEFAULT_TCP_OPTIONS) -> (skt: TCP_Socket, err: Network_Error) {
if endpoint.port == 0 {
return 0, .Port_Required
}
@@ -86,30 +93,22 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
_ = set_option(skt, .Reuse_Address, true)
sockaddr := _endpoint_to_sockaddr(endpoint)
res := os.connect(os.Socket(skt), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len))
if res != nil {
if posix.connect(posix.FD(skt), (^posix.sockaddr)(&sockaddr), posix.socklen_t(sockaddr.ss_len)) != .OK {
err = _dial_error()
close(skt)
return {}, Dial_Error(os.is_platform_error(res) or_else -1)
}
return
}
// On Darwin, any port below 1024 is 'privileged' - which means that you need root access in order to use it.
MAX_PRIVILEGED_PORT :: 1023
@(private)
_bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
_bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Bind_Error) {
sockaddr := _endpoint_to_sockaddr(ep)
s := any_socket_to_socket(skt)
res := os.bind(os.Socket(s), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len))
if res != nil {
if res == os.EACCES && ep.port <= MAX_PRIVILEGED_PORT {
err = .Privileged_Port_Without_Root
} else {
err = Bind_Error(os.is_platform_error(res) or_else -1)
}
if posix.bind(posix.FD(s), (^posix.sockaddr)(&sockaddr), posix.socklen_t(sockaddr.ss_len)) != .OK {
err = _bind_error()
}
return
}
@@ -126,78 +125,77 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_
// 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!
set_option(sock, .Reuse_Address, true) or_return
_ = set_option(sock, .Reuse_Address, true)
bind(sock, interface_endpoint) or_return
res := os.listen(os.Socket(skt), backlog)
if res != nil {
err = Listen_Error(os.is_platform_error(res) or_else -1)
return
if posix.listen(posix.FD(skt), i32(backlog)) != .OK {
err = _listen_error()
}
return
}
@(private)
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) {
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_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())
if posix.getsockname(posix.FD(any_socket_to_socket(sock)), (^posix.sockaddr)(&addr), &addr_len) != .OK {
err = _listen_error()
return
}
ep = _sockaddr_to_endpoint((^os.SOCKADDR_STORAGE_LH)(&addr))
ep = _sockaddr_to_endpoint(&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
sockaddrlen := c.int(size_of(sockaddr))
client_sock, client_sock_err := os.accept(os.Socket(sock), cast(^os.SOCKADDR) &sockaddr, &sockaddrlen)
if client_sock_err != nil {
err = Accept_Error(os.is_platform_error(client_sock_err) or_else -1)
_accept_tcp :: proc(sock: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) {
addr: posix.sockaddr_storage
addr_len := posix.socklen_t(size_of(addr))
client_sock := posix.accept(posix.FD(sock), (^posix.sockaddr)(&addr), &addr_len)
if client_sock < 0 {
err = _accept_error()
return
}
client = TCP_Socket(client_sock)
source = _sockaddr_to_endpoint(&sockaddr)
source = _sockaddr_to_endpoint(&addr)
return
}
@(private)
_close :: proc(skt: Any_Socket) {
s := any_socket_to_socket(skt)
os.close(os.Handle(os.Socket(s)))
posix.close(posix.FD(s))
}
@(private)
_recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
_recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) {
if len(buf) <= 0 {
return
}
res, res_err := os.recv(os.Socket(skt), buf, 0)
if res_err != nil {
err = TCP_Recv_Error(os.is_platform_error(res_err) or_else -1)
res := posix.recv(posix.FD(skt), raw_data(buf), len(buf), {})
if res < 0 {
err = _tcp_recv_error()
return
}
return int(res), nil
}
@(private)
_recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
_recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) {
if len(buf) <= 0 {
return
}
from: os.SOCKADDR_STORAGE_LH
fromsize := c.int(size_of(from))
res, res_err := os.recvfrom(os.Socket(skt), buf, 0, cast(^os.SOCKADDR) &from, &fromsize)
if res_err != nil {
err = UDP_Recv_Error(os.is_platform_error(res_err) or_else -1)
from: posix.sockaddr_storage
fromsize := posix.socklen_t(size_of(from))
res := posix.recvfrom(posix.FD(skt), raw_data(buf), len(buf), {}, (^posix.sockaddr)(&from), &fromsize)
if res < 0 {
err = _udp_recv_error()
return
}
@@ -207,67 +205,60 @@ _recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endp
}
@(private)
_send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
_send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: TCP_Send_Error) {
for bytes_written < len(buf) {
limit := min(int(max(i32)), len(buf) - bytes_written)
remaining := buf[bytes_written:][:limit]
res, res_err := os.send(os.Socket(skt), remaining, os.MSG_NOSIGNAL)
if res_err == os.EPIPE {
// EPIPE arises if the socket has been closed remotely.
err = TCP_Send_Error.Connection_Closed
return
} else if res_err != nil {
err = TCP_Send_Error(os.is_platform_error(res_err) or_else -1)
res := posix.send(posix.FD(skt), raw_data(remaining), len(remaining), {.NOSIGNAL})
if res < 0 {
err = _tcp_send_error()
return
}
bytes_written += int(res)
}
return
}
@(private)
_send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
_send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: UDP_Send_Error) {
toaddr := _endpoint_to_sockaddr(to)
for bytes_written < len(buf) {
limit := min(1<<31, len(buf) - bytes_written)
remaining := buf[bytes_written:][:limit]
res, res_err := os.sendto(os.Socket(skt), remaining, os.MSG_NOSIGNAL, cast(^os.SOCKADDR)&toaddr, i32(toaddr.len))
if res_err == os.EPIPE {
// EPIPE arises if the socket has been closed remotely.
err = UDP_Send_Error.Not_Socket
return
} else if res_err != nil {
err = UDP_Send_Error(os.is_platform_error(res_err) or_else -1)
res := posix.sendto(posix.FD(skt), raw_data(remaining), len(remaining), {.NOSIGNAL}, (^posix.sockaddr)(&toaddr), posix.socklen_t(toaddr.ss_len))
if res < 0 {
err = _udp_send_error()
return
}
bytes_written += int(res)
}
return
}
@(private)
_shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
_shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) {
s := any_socket_to_socket(skt)
res := os.shutdown(os.Socket(s), int(manner))
if res != nil {
return Shutdown_Error(os.is_platform_error(res) or_else -1)
if posix.shutdown(posix.FD(s), posix.Shut(manner)) != .OK {
err = _shutdown_error()
}
return
}
@(private)
_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
level := os.SOL_SOCKET if option != .TCP_Nodelay else os.IPPROTO_TCP
_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error {
level := posix.SOL_SOCKET if option != .TCP_Nodelay else posix.IPPROTO_TCP
// NOTE(tetra, 2022-02-15): On Linux, you cannot merely give a single byte for a bool;
// it _has_ to be a b32.
// I haven't tested if you can give more than that.
bool_value: b32
int_value: i32
timeval_value: os.Timeval
int_value: posix.socklen_t
timeval_value: posix.timeval
ptr: rawptr
len: os.socklen_t
len: posix.socklen_t
switch option {
case
@@ -302,8 +293,8 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca
t := value.(time.Duration) or_else panic("set_option() value must be a time.Duration here", loc)
micros := i64(time.duration_microseconds(t))
timeval_value.microseconds = int(micros % 1e6)
timeval_value.seconds = (micros - i64(timeval_value.microseconds)) / 1e6
timeval_value.tv_usec = posix.suseconds_t(micros % 1e6)
timeval_value.tv_sec = posix.time_t(micros - i64(timeval_value.tv_usec)) / 1e6
ptr = &timeval_value
len = size_of(timeval_value)
@@ -312,12 +303,12 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca
.Send_Buffer_Size:
// TODO: check for out of range values and return .Value_Out_Of_Range?
switch i in value {
case i8, u8: i2 := i; int_value = os.socklen_t((^u8)(&i2)^)
case i16, u16: i2 := i; int_value = os.socklen_t((^u16)(&i2)^)
case i32, u32: i2 := i; int_value = os.socklen_t((^u32)(&i2)^)
case i64, u64: i2 := i; int_value = os.socklen_t((^u64)(&i2)^)
case i128, u128: i2 := i; int_value = os.socklen_t((^u128)(&i2)^)
case int, uint: i2 := i; int_value = os.socklen_t((^uint)(&i2)^)
case i8, u8: i2 := i; int_value = posix.socklen_t((^u8)(&i2)^)
case i16, u16: i2 := i; int_value = posix.socklen_t((^u16)(&i2)^)
case i32, u32: i2 := i; int_value = posix.socklen_t((^u32)(&i2)^)
case i64, u64: i2 := i; int_value = posix.socklen_t((^u64)(&i2)^)
case i128, u128: i2 := i; int_value = posix.socklen_t((^u128)(&i2)^)
case int, uint: i2 := i; int_value = posix.socklen_t((^uint)(&i2)^)
case:
panic("set_option() value must be an integer here", loc)
}
@@ -326,54 +317,53 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca
}
skt := any_socket_to_socket(s)
res := os.setsockopt(os.Socket(skt), int(level), int(option), ptr, len)
if res != nil {
return Socket_Option_Error(os.is_platform_error(res) or_else -1)
if posix.setsockopt(posix.FD(skt), i32(level), posix.Sock_Option(option), ptr, len) != .OK {
return _socket_option_error()
}
return nil
}
@(private)
_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) {
socket := any_socket_to_socket(socket)
flags, getfl_err := os.fcntl(int(socket), os.F_GETFL, 0)
if getfl_err != nil {
return Set_Blocking_Error(os.is_platform_error(getfl_err) or_else -1)
flags_ := posix.fcntl(posix.FD(socket), .GETFL, 0)
if flags_ < 0 {
return _set_blocking_error()
}
flags := transmute(posix.O_Flags)flags_
if should_block {
flags &~= int(os.O_NONBLOCK)
flags -= {.NONBLOCK}
} else {
flags |= int(os.O_NONBLOCK)
flags += {.NONBLOCK}
}
_, setfl_err := os.fcntl(int(socket), os.F_SETFL, flags)
if setfl_err != nil {
return Set_Blocking_Error(os.is_platform_error(setfl_err) or_else -1)
if posix.fcntl(posix.FD(socket), .SETFL, flags) < 0 {
return _set_blocking_error()
}
return nil
}
@private
_endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: os.SOCKADDR_STORAGE_LH) {
_endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: posix.sockaddr_storage) {
switch a in ep.address {
case IP4_Address:
(^os.sockaddr_in)(&sockaddr)^ = os.sockaddr_in {
(^posix.sockaddr_in)(&sockaddr)^ = posix.sockaddr_in {
sin_port = u16be(ep.port),
sin_addr = transmute(os.in_addr) a,
sin_family = u8(os.AF_INET),
sin_len = size_of(os.sockaddr_in),
sin_addr = transmute(posix.in_addr)a,
sin_family = .INET,
sin_len = size_of(posix.sockaddr_in),
}
return
case IP6_Address:
(^os.sockaddr_in6)(&sockaddr)^ = os.sockaddr_in6 {
(^posix.sockaddr_in6)(&sockaddr)^ = posix.sockaddr_in6 {
sin6_port = u16be(ep.port),
sin6_addr = transmute(os.in6_addr) a,
sin6_family = u8(os.AF_INET6),
sin6_len = size_of(os.sockaddr_in6),
sin6_addr = transmute(posix.in6_addr)a,
sin6_family = .INET6,
sin6_len = size_of(posix.sockaddr_in6),
}
return
}
@@ -381,21 +371,21 @@ _endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: os.SOCKADDR_STORAGE_LH
}
@private
_sockaddr_to_endpoint :: proc(native_addr: ^os.SOCKADDR_STORAGE_LH) -> (ep: Endpoint) {
switch native_addr.family {
case u8(os.AF_INET):
addr := cast(^os.sockaddr_in) native_addr
_sockaddr_to_endpoint :: proc(native_addr: ^posix.sockaddr_storage) -> (ep: Endpoint) {
#partial switch native_addr.ss_family {
case .INET:
addr := cast(^posix.sockaddr_in)native_addr
port := int(addr.sin_port)
ep = Endpoint {
address = IP4_Address(transmute([4]byte) addr.sin_addr),
port = port,
address = IP4_Address(transmute([4]byte)addr.sin_addr),
port = port,
}
case u8(os.AF_INET6):
addr := cast(^os.sockaddr_in6) native_addr
case .INET6:
addr := cast(^posix.sockaddr_in6)native_addr
port := int(addr.sin6_port)
ep = Endpoint {
address = IP6_Address(transmute([8]u16be) addr.sin6_addr),
port = port,
address = IP6_Address(transmute([8]u16be)addr.sin6_addr),
port = port,
}
case:
panic("native_addr is neither IP4 or IP6 address")
@@ -404,21 +394,21 @@ _sockaddr_to_endpoint :: proc(native_addr: ^os.SOCKADDR_STORAGE_LH) -> (ep: Endp
}
@(private)
_sockaddr_basic_to_endpoint :: proc(native_addr: ^os.SOCKADDR) -> (ep: Endpoint) {
switch u16(native_addr.family) {
case u16(os.AF_INET):
addr := cast(^os.sockaddr_in) native_addr
_sockaddr_basic_to_endpoint :: proc(native_addr: ^posix.sockaddr) -> (ep: Endpoint) {
#partial switch native_addr.sa_family {
case .INET:
addr := cast(^posix.sockaddr_in)native_addr
port := int(addr.sin_port)
ep = Endpoint {
address = IP4_Address(transmute([4]byte) addr.sin_addr),
port = port,
address = IP4_Address(transmute([4]byte)addr.sin_addr),
port = port,
}
case u16(os.AF_INET6):
addr := cast(^os.sockaddr_in6) native_addr
case .INET6:
addr := cast(^posix.sockaddr_in6)native_addr
port := int(addr.sin6_port)
ep = Endpoint {
address = IP6_Address(transmute([8]u16be) addr.sin6_addr),
port = port,
address = IP6_Address(transmute([8]u16be)addr.sin6_addr),
port = port,
}
case:
panic("native_addr is neither IP4 or IP6 address")

View File

@@ -54,8 +54,14 @@ Socket_Option :: enum c.int {
Receive_Timeout = cast(c.int)freebsd.Socket_Option.RCVTIMEO,
}
Shutdown_Manner :: enum c.int {
Receive = cast(c.int)freebsd.Shutdown_Method.RD,
Send = cast(c.int)freebsd.Shutdown_Method.WR,
Both = cast(c.int)freebsd.Shutdown_Method.RDWR,
}
@(private)
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) {
sys_family: freebsd.Protocol_Family = ---
sys_protocol: freebsd.Protocol = ---
sys_socket_type: freebsd.Socket_Type = ---
@@ -72,24 +78,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so
new_socket, errno := freebsd.socket(sys_family, sys_socket_type, sys_protocol)
if errno != nil {
err = cast(Create_Socket_Error)errno
return
}
// NOTE(Feoramund): By default, FreeBSD will generate SIGPIPE if an EPIPE
// error is raised during the writing of a socket that may be closed.
// This behavior is unlikely to be expected by general users.
//
// There are two workarounds. One is to apply the .NOSIGNAL flag when using
// the `sendto` syscall. However, that would prevent users of this library
// from re-enabling the SIGPIPE-raising functionality, if they really
// wanted it.
//
// So I have disabled it here with this socket option for all sockets.
truth: b32 = true
errno = freebsd.setsockopt(new_socket, .SOCKET, .NOSIGPIPE, &truth, size_of(truth))
if errno != nil {
err = cast(Socket_Option_Error)errno
err = _create_socket_error(errno)
return
}
@@ -102,7 +91,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so
}
@(private)
_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) {
if endpoint.port == 0 {
return 0, .Port_Required
}
@@ -115,19 +104,19 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
errno := freebsd.connect(cast(Fd)socket, &sockaddr, cast(freebsd.socklen_t)sockaddr.len)
if errno != nil {
close(socket)
return {}, cast(Dial_Error)errno
return {}, _dial_error(errno)
}
return
}
@(private)
_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Bind_Error) {
sockaddr := _endpoint_to_sockaddr(ep)
real_socket := any_socket_to_socket(socket)
errno := freebsd.bind(cast(Fd)real_socket, &sockaddr, cast(freebsd.socklen_t)sockaddr.len)
if errno != nil {
err = cast(Bind_Error)errno
err = _bind_error(errno)
}
return
}
@@ -143,7 +132,7 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T
errno := freebsd.listen(cast(Fd)socket, backlog)
if errno != nil {
err = cast(Listen_Error)errno
err = _listen_error(errno)
return
}
@@ -151,12 +140,12 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T
}
@(private)
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) {
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_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
err = _listen_error(errno)
return
}
@@ -165,12 +154,12 @@ _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error)
}
@(private)
_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
_accept_tcp :: proc(sock: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) {
sockaddr: freebsd.Socket_Address_Storage
result, errno := freebsd.accept(cast(Fd)sock, &sockaddr)
if errno != nil {
err = cast(Accept_Error)errno
err = _accept_error(errno)
return
}
@@ -187,20 +176,20 @@ _close :: proc(socket: Any_Socket) {
}
@(private)
_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) {
if len(buf) == 0 {
return
}
result, errno := freebsd.recv(cast(Fd)socket, buf, .NONE)
if errno != nil {
err = cast(TCP_Recv_Error)errno
err = _tcp_recv_error(errno)
return
}
return result, nil
}
@(private)
_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) {
if len(buf) == 0 {
return
}
@@ -208,21 +197,21 @@ _recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_e
result, errno := freebsd.recvfrom(cast(Fd)socket, buf, .NONE, &from)
if errno != nil {
err = cast(UDP_Recv_Error)errno
err = _udp_recv_error(errno)
return
}
return result, _sockaddr_to_endpoint(&from), nil
}
@(private)
_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: TCP_Send_Error) {
for bytes_written < len(buf) {
limit := min(int(max(i32)), len(buf) - bytes_written)
remaining := buf[bytes_written:][:limit]
result, errno := freebsd.send(cast(Fd)socket, remaining, .NONE)
result, errno := freebsd.send(cast(Fd)socket, remaining, .NOSIGNAL)
if errno != nil {
err = cast(TCP_Send_Error)errno
err = _tcp_send_error(errno)
return
}
bytes_written += result
@@ -231,15 +220,15 @@ _send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err:
}
@(private)
_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: UDP_Send_Error) {
toaddr := _endpoint_to_sockaddr(to)
for bytes_written < len(buf) {
limit := min(int(max(i32)), len(buf) - bytes_written)
remaining := buf[bytes_written:][:limit]
result, errno := freebsd.sendto(cast(Fd)socket, remaining, .NONE, &toaddr)
result, errno := freebsd.sendto(cast(Fd)socket, remaining, .NOSIGNAL, &toaddr)
if errno != nil {
err = cast(UDP_Send_Error)errno
err = _udp_send_error(errno)
return
}
bytes_written += result
@@ -248,17 +237,17 @@ _send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_writt
}
@(private)
_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) {
real_socket := cast(Fd)any_socket_to_socket(socket)
errno := freebsd.shutdown(real_socket, cast(freebsd.Shutdown_Method)manner)
if errno != nil {
return cast(Shutdown_Error)errno
return _shutdown_error(errno)
}
return
}
@(private)
_set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
_set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error {
// NOTE(Feoramund): I found that FreeBSD, like Linux, requires at least 32
// bits for a boolean socket option value. Nothing less will work.
bool_value: b32
@@ -315,25 +304,25 @@ _set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc :
case u16: int_value = cast(i32)real
case i32: int_value = real
case u32:
if real > u32(max(i32)) { return .Value_Out_Of_Range }
if real > u32(max(i32)) { return .Invalid_Value }
int_value = cast(i32)real
case i64:
if real > i64(max(i32)) || real < i64(min(i32)) { return .Value_Out_Of_Range }
if real > i64(max(i32)) || real < i64(min(i32)) { return .Invalid_Value }
int_value = cast(i32)real
case u64:
if real > u64(max(i32)) { return .Value_Out_Of_Range }
if real > u64(max(i32)) { return .Invalid_Value }
int_value = cast(i32)real
case i128:
if real > i128(max(i32)) || real < i128(min(i32)) { return .Value_Out_Of_Range }
if real > i128(max(i32)) || real < i128(min(i32)) { return .Invalid_Value }
int_value = cast(i32)real
case u128:
if real > u128(max(i32)) { return .Value_Out_Of_Range }
if real > u128(max(i32)) { return .Invalid_Value }
int_value = cast(i32)real
case int:
if real > int(max(i32)) || real < int(min(i32)) { return .Value_Out_Of_Range }
if real > int(max(i32)) || real < int(min(i32)) { return .Invalid_Value }
int_value = cast(i32)real
case uint:
if real > uint(max(i32)) { return .Value_Out_Of_Range }
if real > uint(max(i32)) { return .Invalid_Value }
int_value = cast(i32)real
case:
panic("set_option() value must be an integer here", loc)
@@ -347,19 +336,19 @@ _set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc :
real_socket := any_socket_to_socket(socket)
errno := freebsd.setsockopt(cast(Fd)real_socket, .SOCKET, cast(freebsd.Socket_Option)option, ptr, len)
if errno != nil {
return cast(Socket_Option_Error)errno
return _socket_option_error(errno)
}
return nil
}
@(private)
_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) {
real_socket := any_socket_to_socket(socket)
flags, errno := freebsd.fcntl_getfl(cast(freebsd.Fd)real_socket)
if errno != nil {
return cast(Set_Blocking_Error)errno
return _set_blocking_error(errno)
}
if should_block {
@@ -370,7 +359,7 @@ _set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_E
errno = freebsd.fcntl_setfl(cast(freebsd.Fd)real_socket, flags)
if errno != nil {
return cast(Set_Blocking_Error)errno
return _set_blocking_error(errno)
}
return

View File

@@ -38,15 +38,21 @@ Socket_Option :: enum c.int {
Broadcast = c.int(linux.Socket_Option.BROADCAST),
}
Shutdown_Manner :: enum c.int {
Receive = c.int(linux.Shutdown_How.RD),
Send = c.int(linux.Shutdown_How.WR),
Both = c.int(linux.Shutdown_How.RDWR),
}
// Wrappers and unwrappers for system-native types
@(private="file")
_unwrap_os_socket :: proc "contextless" (sock: Any_Socket)->linux.Fd {
_unwrap_os_socket :: proc "contextless" (sock: Any_Socket) -> linux.Fd {
return linux.Fd(any_socket_to_socket(sock))
}
@(private="file")
_wrap_os_socket :: proc "contextless" (sock: linux.Fd, protocol: Socket_Protocol)->Any_Socket {
_wrap_os_socket :: proc "contextless" (sock: linux.Fd, protocol: Socket_Protocol) -> Any_Socket {
switch protocol {
case .TCP: return TCP_Socket(Socket(sock))
case .UDP: return UDP_Socket(Socket(sock))
@@ -56,7 +62,7 @@ _wrap_os_socket :: proc "contextless" (sock: linux.Fd, protocol: Socket_Protocol
}
@(private="file")
_unwrap_os_family :: proc "contextless" (family: Address_Family)->linux.Address_Family {
_unwrap_os_family :: proc "contextless" (family: Address_Family) -> linux.Address_Family {
switch family {
case .IP4: return .INET
case .IP6: return .INET6
@@ -66,7 +72,7 @@ _unwrap_os_family :: proc "contextless" (family: Address_Family)->linux.Address_
}
@(private="file")
_unwrap_os_proto_socktype :: proc "contextless" (protocol: Socket_Protocol)->(linux.Protocol, linux.Socket_Type) {
_unwrap_os_proto_socktype :: proc "contextless" (protocol: Socket_Protocol) -> (linux.Protocol, linux.Socket_Type) {
switch protocol {
case .TCP: return .TCP, .STREAM
case .UDP: return .UDP, .DGRAM
@@ -76,7 +82,7 @@ _unwrap_os_proto_socktype :: proc "contextless" (protocol: Socket_Protocol)->(li
}
@(private="file")
_unwrap_os_addr :: proc "contextless" (endpoint: Endpoint)->(linux.Sock_Addr_Any) {
_unwrap_os_addr :: proc "contextless" (endpoint: Endpoint) -> linux.Sock_Addr_Any {
switch address in endpoint.address {
case IP4_Address:
return {
@@ -100,7 +106,7 @@ _unwrap_os_addr :: proc "contextless" (endpoint: Endpoint)->(linux.Sock_Addr_Any
}
@(private="file")
_wrap_os_addr :: proc "contextless" (addr: linux.Sock_Addr_Any)->(Endpoint) {
_wrap_os_addr :: proc "contextless" (addr: linux.Sock_Addr_Any) -> Endpoint {
#partial switch addr.family {
case .INET:
return {
@@ -117,18 +123,18 @@ _wrap_os_addr :: proc "contextless" (addr: linux.Sock_Addr_Any)->(Endpoint) {
}
}
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (Any_Socket, Network_Error) {
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (Any_Socket, Create_Socket_Error) {
family := _unwrap_os_family(family)
proto, socktype := _unwrap_os_proto_socktype(protocol)
sock, errno := linux.socket(family, socktype, {.CLOEXEC}, proto)
if errno != .NONE {
return {}, Create_Socket_Error(errno)
return {}, _create_socket_error(errno)
}
return _wrap_os_socket(sock, protocol), nil
}
@(private)
_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (TCP_Socket, Network_Error) {
_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := DEFAULT_TCP_OPTIONS) -> (TCP_Socket, Network_Error) {
errno: linux.Errno
if endpoint.port == 0 {
return 0, .Port_Required
@@ -138,7 +144,7 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
os_sock, errno = linux.socket(_unwrap_os_family(family_from_endpoint(endpoint)), .STREAM, {.CLOEXEC}, .TCP)
if errno != .NONE {
// TODO(flysand): should return invalid file descriptor here casted as TCP_Socket
return {}, Create_Socket_Error(errno)
return {}, _create_socket_error(errno)
}
// 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
@@ -149,7 +155,7 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
errno = linux.connect(linux.Fd(os_sock), &addr)
if errno != .NONE {
close(cast(TCP_Socket) os_sock)
return {}, Dial_Error(errno)
return {}, _dial_error(errno)
}
// NOTE(tetra): Not vital to succeed; error ignored
no_delay: b32 = cast(b32) options.no_delay
@@ -158,11 +164,11 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
}
@(private)
_bind :: proc(sock: Any_Socket, endpoint: Endpoint) -> (Network_Error) {
_bind :: proc(sock: Any_Socket, endpoint: Endpoint) -> (Bind_Error) {
addr := _unwrap_os_addr(endpoint)
errno := linux.bind(_unwrap_os_socket(sock), &addr)
if errno != .NONE {
return Bind_Error(errno)
return _bind_error(errno)
}
return nil
}
@@ -180,7 +186,7 @@ _listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket,
os_sock: linux.Fd
os_sock, errno = linux.socket(ep_family, .STREAM, {.CLOEXEC}, .TCP)
if errno != .NONE {
err = Create_Socket_Error(errno)
err = _create_socket_error(errno)
return
}
socket = cast(TCP_Socket)os_sock
@@ -193,31 +199,30 @@ _listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket,
// TODO(tetra, 2022-02-15): Confirm that this doesn't mean other processes can hijack the address!
do_reuse_addr: b32 = true
if errno = linux.setsockopt(os_sock, linux.SOL_SOCKET, linux.Socket_Option.REUSEADDR, &do_reuse_addr); errno != .NONE {
err = Listen_Error(errno)
err = _listen_error(errno)
return
}
// Bind the socket to endpoint address
if errno = linux.bind(os_sock, &ep_address); errno != .NONE {
err = Bind_Error(errno)
err = _bind_error(errno)
return
}
// Listen on bound socket
if errno = linux.listen(os_sock, cast(i32) backlog); errno != .NONE {
err = Listen_Error(errno)
return
err = _listen_error(errno)
}
return
}
@(private)
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) {
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) {
addr: linux.Sock_Addr_Any
errno := linux.getsockname(_unwrap_os_socket(sock), &addr)
if errno != .NONE {
err = Listen_Error(errno)
err = _listen_error(errno)
return
}
@@ -226,11 +231,11 @@ _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error)
}
@(private)
_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (tcp_client: TCP_Socket, endpoint: Endpoint, err: Network_Error) {
_accept_tcp :: proc(sock: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (tcp_client: TCP_Socket, endpoint: Endpoint, err: Accept_Error) {
addr: linux.Sock_Addr_Any
client_sock, errno := linux.accept(linux.Fd(sock), &addr)
if errno != .NONE {
return {}, {}, Accept_Error(errno)
return {}, {}, _accept_error(errno)
}
// NOTE(tetra): Not vital to succeed; error ignored
val: b32 = cast(b32) options.no_delay
@@ -244,19 +249,19 @@ _close :: proc(sock: Any_Socket) {
}
@(private)
_recv_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, Network_Error) {
_recv_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, TCP_Recv_Error) {
if len(buf) <= 0 {
return 0, nil
}
bytes_read, errno := linux.recv(linux.Fd(tcp_sock), buf, {})
if errno != .NONE {
return 0, TCP_Recv_Error(errno)
return 0, _tcp_recv_error(errno)
}
return int(bytes_read), nil
}
@(private)
_recv_udp :: proc(udp_sock: UDP_Socket, buf: []byte) -> (int, Endpoint, Network_Error) {
_recv_udp :: proc(udp_sock: UDP_Socket, buf: []byte) -> (int, Endpoint, UDP_Recv_Error) {
if len(buf) <= 0 {
// NOTE(flysand): It was returning no error, I didn't change anything
return 0, {}, {}
@@ -268,28 +273,24 @@ _recv_udp :: proc(udp_sock: UDP_Socket, buf: []byte) -> (int, Endpoint, Network_
from_addr: linux.Sock_Addr_Any
bytes_read, errno := linux.recvfrom(linux.Fd(udp_sock), buf, {.TRUNC}, &from_addr)
if errno != .NONE {
return 0, {}, UDP_Recv_Error(errno)
return 0, {}, _udp_recv_error(errno)
}
if bytes_read > len(buf) {
// NOTE(tetra): The buffer has been filled, with a partial message.
return len(buf), {}, .Buffer_Too_Small
return len(buf), {}, .Excess_Truncated
}
return bytes_read, _wrap_os_addr(from_addr), nil
}
@(private)
_send_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, Network_Error) {
_send_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, TCP_Send_Error) {
total_written := 0
for total_written < len(buf) {
limit := min(int(max(i32)), len(buf) - total_written)
remaining := buf[total_written:][:limit]
res, errno := linux.send(linux.Fd(tcp_sock), remaining, {.NOSIGNAL})
if errno == .EPIPE {
// If the peer is disconnected when we are trying to send we will get an `EPIPE` error,
// so we turn that into a clearer error
return total_written, TCP_Send_Error.Connection_Closed
} else if errno != .NONE {
return total_written, TCP_Send_Error(errno)
if errno != .NONE {
return total_written, _tcp_send_error(errno)
}
total_written += int(res)
}
@@ -297,28 +298,28 @@ _send_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, Network_Error) {
}
@(private)
_send_udp :: proc(udp_sock: UDP_Socket, buf: []byte, to: Endpoint) -> (int, Network_Error) {
_send_udp :: proc(udp_sock: UDP_Socket, buf: []byte, to: Endpoint) -> (int, UDP_Send_Error) {
to_addr := _unwrap_os_addr(to)
bytes_written, errno := linux.sendto(linux.Fd(udp_sock), buf, {}, &to_addr)
if errno != .NONE {
return bytes_written, UDP_Send_Error(errno)
return bytes_written, _udp_send_error(errno)
}
return int(bytes_written), nil
}
@(private)
_shutdown :: proc(sock: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
_shutdown :: proc(sock: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) {
os_sock := _unwrap_os_socket(sock)
errno := linux.shutdown(os_sock, cast(linux.Shutdown_How) manner)
if errno != .NONE {
return Shutdown_Error(errno)
return _shutdown_error(errno)
}
return nil
}
// TODO(flysand): Figure out what we want to do with this on core:sys/ level.
@(private)
_set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
_set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error {
level: int
if option == .TCP_Nodelay {
level = int(linux.SOL_TCP)
@@ -388,19 +389,19 @@ _set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc :=
errno = linux.setsockopt(os_sock, level, int(option), &int_value)
}
if errno != .NONE {
return Socket_Option_Error(errno)
return _socket_option_error(errno)
}
return nil
}
@(private)
_set_blocking :: proc(sock: Any_Socket, should_block: bool) -> (err: Network_Error) {
_set_blocking :: proc(sock: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) {
errno: linux.Errno
flags: linux.Open_Flags
os_sock := _unwrap_os_socket(sock)
flags, errno = linux.fcntl(os_sock, linux.F_GETFL)
if errno != .NONE {
return Set_Blocking_Error(errno)
return _set_blocking_error(errno)
}
if should_block {
flags -= {.NONBLOCK}
@@ -409,7 +410,7 @@ _set_blocking :: proc(sock: Any_Socket, should_block: bool) -> (err: Network_Err
}
errno = linux.fcntl(os_sock, linux.F_SETFL, flags)
if errno != .NONE {
return Set_Blocking_Error(errno)
return _set_blocking_error(errno)
}
return nil
}

View File

@@ -24,13 +24,67 @@ import "core:c"
import win "core:sys/windows"
import "core:time"
Socket_Option :: enum c.int {
// bool: Whether the address that this socket is bound to can be reused by other sockets.
// This allows you to bypass the cooldown period if a program dies while the socket is bound.
Reuse_Address = win.SO_REUSEADDR,
// bool: Whether other programs will be inhibited from binding the same endpoint as this socket.
Exclusive_Addr_Use = win.SO_EXCLUSIVEADDRUSE,
// bool: When true, keepalive packets will be automatically be sent for this connection. TODO: verify this understanding
Keep_Alive = win.SO_KEEPALIVE,
// bool: When true, client connections will immediately be sent a TCP/IP RST response, rather than being accepted.
Conditional_Accept = win.SO_CONDITIONAL_ACCEPT,
// bool: If true, when the socket is closed, but data is still waiting to be sent, discard that data.
Dont_Linger = win.SO_DONTLINGER,
// bool: When true, 'out-of-band' data sent over the socket will be read by a normal net.recv() call, the same as normal 'in-band' data.
Out_Of_Bounds_Data_Inline = win.SO_OOBINLINE,
// bool: When true, disables send-coalescing, therefore reducing latency.
TCP_Nodelay = win.TCP_NODELAY,
// win.LINGER: Customizes how long (if at all) the socket will remain open when there
// is some remaining data waiting to be sent, and net.close() is called.
Linger = win.SO_LINGER,
// win.DWORD: The size, in bytes, of the OS-managed receive-buffer for this socket.
Receive_Buffer_Size = win.SO_RCVBUF,
// win.DWORD: The size, in bytes, of the OS-managed send-buffer for this socket.
Send_Buffer_Size = win.SO_SNDBUF,
// win.DWORD: For blocking sockets, the time in milliseconds to wait for incoming data to be received, before giving up and returning .Timeout.
// For non-blocking sockets, ignored.
// Use a value of zero to potentially wait forever.
Receive_Timeout = win.SO_RCVTIMEO,
// win.DWORD: For blocking sockets, the time in milliseconds to wait for outgoing data to be sent, before giving up and returning .Timeout.
// For non-blocking sockets, ignored.
// Use a value of zero to potentially wait forever.
Send_Timeout = win.SO_SNDTIMEO,
// bool: Allow sending to, receiving from, and binding to, a broadcast address.
Broadcast = win.SO_BROADCAST,
}
Shutdown_Manner :: enum c.int {
Receive = win.SD_RECEIVE,
Send = win.SD_SEND,
Both = win.SD_BOTH,
}
@(init, private)
ensure_winsock_initialized :: proc() {
win.ensure_winsock_initialized()
}
@(private)
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) {
c_type, c_protocol, c_family: c.int
switch family {
@@ -49,7 +103,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so
sock := win.socket(c_family, c_type, c_protocol)
if sock == win.INVALID_SOCKET {
err = Create_Socket_Error(win.WSAGetLastError())
err = _create_socket_error()
return
}
@@ -62,7 +116,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so
}
@(private)
_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) {
if endpoint.port == 0 {
err = .Port_Required
return
@@ -80,7 +134,7 @@ _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())
err = _dial_error()
close(socket)
return {}, err
}
@@ -93,12 +147,12 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
}
@(private)
_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Bind_Error) {
sockaddr := _endpoint_to_sockaddr(ep)
sock := any_socket_to_socket(socket)
res := win.bind(win.SOCKET(sock), &sockaddr, size_of(sockaddr))
if res < 0 {
err = Bind_Error(win.WSAGetLastError())
err = _bind_error()
}
return
}
@@ -117,17 +171,17 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T
bind(sock, interface_endpoint) or_return
if res := win.listen(win.SOCKET(socket), i32(backlog)); res == win.SOCKET_ERROR {
err = Listen_Error(win.WSAGetLastError())
err = _listen_error()
}
return
}
@(private)
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) {
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_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())
err = _listen_error()
return
}
@@ -136,7 +190,7 @@ _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error)
}
@(private)
_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
_accept_tcp :: proc(sock: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) {
for {
sockaddr: win.SOCKADDR_STORAGE_LH
sockaddrlen := c.int(size_of(sockaddr))
@@ -150,7 +204,7 @@ _accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client
// can do this to match the behaviour.
continue
}
err = Accept_Error(e)
err = _accept_error()
return
}
client = TCP_Socket(client_sock)
@@ -170,20 +224,20 @@ _close :: proc(socket: Any_Socket) {
}
@(private)
_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) {
if len(buf) <= 0 {
return
}
res := win.recv(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0)
if res < 0 {
err = TCP_Recv_Error(win.WSAGetLastError())
err = _tcp_recv_error()
return
}
return int(res), nil
}
@(private)
_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) {
if len(buf) <= 0 {
return
}
@@ -192,7 +246,7 @@ _recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_e
fromsize := c.int(size_of(from))
res := win.recvfrom(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0, &from, &fromsize)
if res < 0 {
err = UDP_Recv_Error(win.WSAGetLastError())
err = _udp_recv_error()
return
}
@@ -202,13 +256,13 @@ _recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_e
}
@(private)
_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: TCP_Send_Error) {
for bytes_written < len(buf) {
limit := min(int(max(i32)), len(buf) - bytes_written)
remaining := buf[bytes_written:]
res := win.send(win.SOCKET(socket), raw_data(remaining), c.int(limit), 0)
if res < 0 {
err = TCP_Send_Error(win.WSAGetLastError())
err = _tcp_send_error()
return
}
bytes_written += int(res)
@@ -217,34 +271,34 @@ _send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err:
}
@(private)
_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
if len(buf) > int(max(c.int)) {
// NOTE(tetra): If we don't guard this, we'll return (0, nil) instead, which is misleading.
err = .Message_Too_Long
return
}
_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: UDP_Send_Error) {
toaddr := _endpoint_to_sockaddr(to)
res := win.sendto(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0, &toaddr, size_of(toaddr))
if res < 0 {
err = UDP_Send_Error(win.WSAGetLastError())
return
for bytes_written < len(buf) {
limit := min(int(max(i32)), len(buf) - bytes_written)
remaining := buf[bytes_written:]
res := win.sendto(win.SOCKET(socket), raw_data(remaining), c.int(limit), 0, &toaddr, size_of(toaddr))
if res < 0 {
err = _udp_send_error()
return
}
bytes_written += int(res)
}
bytes_written = int(res)
return
}
@(private)
_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) {
s := any_socket_to_socket(socket)
res := win.shutdown(win.SOCKET(s), c.int(manner))
if res < 0 {
return Shutdown_Error(win.WSAGetLastError())
return _shutdown_error()
}
return
}
@(private)
_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error {
level := win.SOL_SOCKET if option != .TCP_Nodelay else win.IPPROTO_TCP
bool_value: b32
@@ -283,11 +337,8 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca
t := value.(time.Duration) or_else panic("set_option() value must be a time.Duration here", loc)
num_secs := i64(time.duration_seconds(t))
if time.Duration(num_secs * 1e9) != t {
return .Linger_Only_Supports_Whole_Seconds
}
if num_secs > i64(max(u16)) {
return .Value_Out_Of_Range
return .Invalid_Value
}
linger_value.l_onoff = 1
linger_value.l_linger = c.ushort(num_secs)
@@ -323,19 +374,19 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca
socket := any_socket_to_socket(s)
res := win.setsockopt(win.SOCKET(socket), c.int(level), c.int(option), ptr, len)
if res < 0 {
return Socket_Option_Error(win.WSAGetLastError())
return _socket_option_error()
}
return nil
}
@(private)
_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) {
socket := any_socket_to_socket(socket)
arg: win.DWORD = 0 if should_block else 1
res := win.ioctlsocket(win.SOCKET(socket), transmute(win.c_long)win.FIONBIO, &arg)
if res == win.SOCKET_ERROR {
return Set_Blocking_Error(win.WSAGetLastError())
return _set_blocking_error()
}
return nil

View File

@@ -125,7 +125,7 @@ percent_encode :: proc(s: string, allocator := context.allocator) -> string {
bytes, n := utf8.encode_rune(ch)
for byte in bytes[:n] {
buf: [2]u8 = ---
t := strconv.append_int(buf[:], i64(byte), 16)
t := strconv.write_int(buf[:], i64(byte), 16)
strings.write_rune(&b, '%')
strings.write_string(&b, t)
}

View File

@@ -180,7 +180,7 @@ Type_Kind :: enum u32le {
Struct = 10,
Union = 11,
Enum = 12,
Tuple = 13,
Parameters = 13,
Proc = 14,
Bit_Set = 15,
Simd_Vector = 16,
@@ -256,10 +256,10 @@ Type :: struct {
types: Array(Type_Index),
// Used by:
// .Named - 1 field for the definition
// .Struct - fields
// .Enum - fields
// .Tuple - parameters (procedures only)
// .Named - 1 field for the definition
// .Struct - fields
// .Enum - fields
// .Parameters - parameters (procedures only)
entities: Array(Entity_Index),
// Used By: .Struct, .Union

View File

@@ -30,14 +30,27 @@ File_Tags :: struct {
}
@require_results
get_build_os_from_string :: proc(str: string) -> runtime.Odin_OS_Type {
get_build_os_from_string :: proc(str: string) -> (found_os: runtime.Odin_OS_Type, found_subtarget: runtime.Odin_Platform_Subtarget_Type) {
str_os, _, str_subtarget := strings.partition(str, ":")
fields := reflect.enum_fields_zipped(runtime.Odin_OS_Type)
for os in fields {
if strings.equal_fold(os.name, str) {
return runtime.Odin_OS_Type(os.value)
if strings.equal_fold(os.name, str_os) {
found_os = runtime.Odin_OS_Type(os.value)
break
}
}
return .Unknown
if str_subtarget != "" {
st_fields := reflect.enum_fields_zipped(runtime.Odin_Platform_Subtarget_Type)
for subtarget in st_fields {
if strings.equal_fold(subtarget.name, str_subtarget) {
found_subtarget = runtime.Odin_Platform_Subtarget_Type(subtarget.value)
break
}
}
}
return
}
@require_results
get_build_arch_from_string :: proc(str: string) -> runtime.Odin_Arch_Type {
@@ -187,7 +200,8 @@ parse_file_tags :: proc(file: ast.File, allocator := context.allocator) -> (tags
if value == "ignore" {
tags.ignore = true
} else if os := get_build_os_from_string(value); os != .Unknown {
} else if os, subtarget := get_build_os_from_string(value); os != .Unknown {
_ = subtarget // TODO(bill): figure out how to handle the subtarget logic
if is_notted {
os_negative += {os}
} else {

View File

@@ -1276,28 +1276,28 @@ parse_unrolled_for_loop :: proc(p: ^Parser, inline_tok: tokenizer.Token) -> ^ast
args = make([dynamic]^ast.Expr)
for p.curr_tok.kind != .Close_Paren &&
p.curr_tok.kind != .EOF {
arg := parse_value(p)
arg := parse_value(p)
if p.curr_tok.kind == .Eq {
eq := expect_token(p, .Eq)
if arg != nil {
if _, ok := arg.derived.(^ast.Ident); !ok {
error(p, arg.pos, "expected an identifier for 'key=value'")
}
}
value := parse_value(p)
fv := ast.new(ast.Field_Value, arg.pos, value)
fv.field = arg
fv.sep = eq.pos
fv.value = value
if p.curr_tok.kind == .Eq {
eq := expect_token(p, .Eq)
if arg != nil {
if _, ok := arg.derived.(^ast.Ident); !ok {
error(p, arg.pos, "expected an identifier for 'key=value'")
}
}
value := parse_value(p)
fv := ast.new(ast.Field_Value, arg.pos, value)
fv.field = arg
fv.sep = eq.pos
fv.value = value
arg = fv
}
arg = fv
}
append(&args, arg)
append(&args, arg)
allow_token(p, .Comma) or_break
}
}
}
p.expr_level -= 1

View File

@@ -57,7 +57,7 @@ write_encoded_rune :: proc(f: Handle, r: rune) -> (n: int, err: Error) {
if r < 32 {
if wrap(write_string(f, "\\x"), &n, &err) { return }
b: [2]byte
s := strconv.append_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil)
s := strconv.write_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil)
switch len(s) {
case 0: if wrap(write_string(f, "00"), &n, &err) { return }
case 1: if wrap(write_rune(f, '0'), &n, &err) { return }

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