Merge remote-tracking branch 'offical/master'

This commit is contained in:
2024-06-09 22:25:45 -04:00
101 changed files with 3606 additions and 848 deletions
+69 -166
View File
@@ -29,186 +29,101 @@ jobs:
gmake release
./odin version
./odin report
gmake -C vendor/stb/src
gmake -C vendor/cgltf/src
gmake -C vendor/miniaudio/src
./odin check examples/all -vet -strict-style -target:netbsd_amd64
./odin check examples/all -vet -strict-style -target:netbsd_arm64
(cd tests/core; gmake all_bsd)
(cd tests/internal; gmake all_bsd)
./odin test tests/core/normal.odin -file -all-packages -define:ODIN_TEST_FANCY=false
./odin test tests/core/speed.odin -file -all-packages -o:speed -define:ODIN_TEST_FANCY=false
./odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false
./odin test tests/benchmark -all-packages -define:ODIN_TEST_FANCY=false
(cd tests/issues; ./run.sh)
(cd tests/benchmark; gmake all)
build_linux:
name: Ubuntu Build, Check, and Test
runs-on: ubuntu-latest
ci:
strategy:
fail-fast: false
matrix:
# MacOS 13 runs on Intel, 14 runs on ARM
os: [ubuntu-latest, macos-13, macos-14]
runs-on: ${{ matrix.os }}
name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel' || 'Ubuntu') }} Build, Check, and Test
timeout-minutes: 15
steps:
- uses: actions/checkout@v1
- name: Download LLVM
- uses: actions/checkout@v4
- name: Download LLVM (Linux)
if: matrix.os == 'ubuntu-latest'
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 17
echo "/usr/lib/llvm-17/bin" >> $GITHUB_PATH
- name: build odin
run: ./build_odin.sh release
- name: Odin version
run: ./odin version
timeout-minutes: 1
- name: Odin report
run: ./odin report
timeout-minutes: 1
- name: Compile needed Vendor
run: |
make -C $(./odin root)/vendor/stb/src
make -C $(./odin root)/vendor/cgltf/src
make -C $(./odin root)/vendor/miniaudio/src
timeout-minutes: 10
- name: Odin check
run: ./odin check examples/demo -vet
timeout-minutes: 10
- name: Odin run
run: ./odin run examples/demo
timeout-minutes: 10
- name: Odin run -debug
run: ./odin run examples/demo -debug
timeout-minutes: 10
- name: Odin check examples/all
run: ./odin check examples/all -strict-style
timeout-minutes: 10
- name: Core library tests
run: |
cd tests/core
make
timeout-minutes: 10
- name: Vendor library tests
run: |
cd tests/vendor
make
timeout-minutes: 10
- name: Odin internals tests
run: |
cd tests/internal
make
timeout-minutes: 10
- name: Odin core library benchmarks
run: |
cd tests/benchmark
make
timeout-minutes: 10
- name: Odin check examples/all for Linux i386
run: ./odin check examples/all -vet -strict-style -target:linux_i386
timeout-minutes: 10
- name: Odin check examples/all for Linux arm64
run: ./odin check examples/all -vet -strict-style -target:linux_arm64
timeout-minutes: 10
- name: Odin check examples/all for FreeBSD amd64
run: ./odin check examples/all -vet -strict-style -target:freebsd_amd64
timeout-minutes: 10
- name: Odin check examples/all for OpenBSD amd64
run: ./odin check examples/all -vet -strict-style -target:openbsd_amd64
timeout-minutes: 10
build_macOS:
name: MacOS Build, Check, and Test
runs-on: macos-13
steps:
- uses: actions/checkout@v1
- name: Download LLVM, and setup PATH
- name: Download LLVM (MacOS Intel)
if: matrix.os == 'macos-13'
run: |
brew install llvm@17
echo "/usr/local/opt/llvm@17/bin" >> $GITHUB_PATH
- name: build odin
run: ./build_odin.sh release
- name: Odin version
run: ./odin version
timeout-minutes: 1
- name: Odin report
run: ./odin report
timeout-minutes: 1
- name: Compile needed Vendor
run: |
make -C $(./odin root)/vendor/stb/src
make -C $(./odin root)/vendor/cgltf/src
make -C $(./odin root)/vendor/miniaudio/src
timeout-minutes: 10
- name: Odin check
run: ./odin check examples/demo -vet
timeout-minutes: 10
- name: Odin run
run: ./odin run examples/demo
timeout-minutes: 10
- name: Odin run -debug
run: ./odin run examples/demo -debug
timeout-minutes: 10
- name: Odin check examples/all
run: ./odin check examples/all -strict-style
timeout-minutes: 10
- name: Core library tests
run: |
cd tests/core
make
timeout-minutes: 10
- name: Odin internals tests
run: |
cd tests/internal
make
timeout-minutes: 10
- name: Odin core library benchmarks
run: |
cd tests/benchmark
make
timeout-minutes: 10
build_macOS_arm:
name: MacOS ARM Build, Check, and Test
runs-on: macos-14 # This is an arm/m1 runner.
steps:
- uses: actions/checkout@v1
- name: Download LLVM and setup PATH
- name: Download LLVM (MacOS ARM)
if: matrix.os == 'macos-14'
run: |
brew install llvm@17
echo "/opt/homebrew/opt/llvm@17/bin" >> $GITHUB_PATH
- name: build odin
- name: Build Odin
run: ./build_odin.sh release
- name: Odin version
run: ./odin version
timeout-minutes: 1
- name: Odin report
run: ./odin report
timeout-minutes: 1
- name: Compile needed Vendor
run: |
make -C $(./odin root)/vendor/stb/src
make -C $(./odin root)/vendor/cgltf/src
make -C $(./odin root)/vendor/miniaudio/src
timeout-minutes: 10
make -C vendor/stb/src
make -C vendor/cgltf/src
make -C vendor/miniaudio/src
- name: Odin check
run: ./odin check examples/demo -vet
timeout-minutes: 10
- name: Odin run
run: ./odin run examples/demo
timeout-minutes: 10
- name: Odin run -debug
run: ./odin run examples/demo -debug
timeout-minutes: 10
- name: Odin check examples/all
run: ./odin check examples/all -strict-style
timeout-minutes: 10
- name: Core library tests
- name: Normal Core library tests
run: ./odin test tests/core/normal.odin -file -all-packages -define:ODIN_TEST_FANCY=false
- name: Optimized Core library tests
run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -define:ODIN_TEST_FANCY=false
- name: Vendor library tests
run: ./odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false
- name: Internals tests
run: ./odin test tests/internal -all-packages -define:ODIN_TEST_FANCY=false
- name: Core library benchmarks
run: ./odin test tests/benchmark -all-packages -define:ODIN_TEST_FANCY=false
- name: GitHub Issue tests
run: |
cd tests/core
make
timeout-minutes: 10
- name: Odin internals tests
run: |
cd tests/internal
make
timeout-minutes: 10
- name: Odin core library benchmarks
run: |
cd tests/benchmark
make
timeout-minutes: 10
cd tests/issues
./run.sh
- name: Odin check examples/all for Linux i386
run: ./odin check examples/all -vet -strict-style -target:linux_i386
if: matrix.os == 'ubuntu-latest'
- name: Odin check examples/all for Linux arm64
run: ./odin check examples/all -vet -strict-style -target:linux_arm64
if: matrix.os == 'ubuntu-latest'
- name: Odin check examples/all for FreeBSD amd64
run: ./odin check examples/all -vet -strict-style -target:freebsd_amd64
if: matrix.os == 'ubuntu-latest'
- name: Odin check examples/all for OpenBSD amd64
run: ./odin check examples/all -vet -strict-style -target:openbsd_amd64
if: matrix.os == 'ubuntu-latest'
build_windows:
name: Windows Build, Check, and Test
runs-on: windows-2022
timeout-minutes: 15
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v4
- name: build Odin
shell: cmd
run: |
@@ -216,79 +131,67 @@ jobs:
./build.bat 1
- name: Odin version
run: ./odin version
timeout-minutes: 1
- name: Odin report
run: ./odin report
timeout-minutes: 1
- name: Odin check
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin check examples/demo -vet
timeout-minutes: 10
- name: Odin run
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin run examples/demo
timeout-minutes: 10
- name: Odin run -debug
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin run examples/demo -debug
timeout-minutes: 10
- name: Odin check examples/all
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin check examples/all -strict-style
timeout-minutes: 10
- name: Core library tests
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
cd tests\core
call build.bat
timeout-minutes: 10
odin test tests/core/normal.odin -file -all-packages -define:ODIN_TEST_FANCY=false
- 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 -define:ODIN_TEST_FANCY=false
- name: Core library benchmarks
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
cd tests\benchmark
call build.bat
timeout-minutes: 10
odin test tests/benchmark -all-packages -define:ODIN_TEST_FANCY=false
- name: Vendor library tests
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
cd tests\vendor
call build.bat
timeout-minutes: 10
odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false
- name: Odin internals tests
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
cd tests\internal
call build.bat
timeout-minutes: 10
odin test tests/internal -all-packages -define:ODIN_TEST_FANCY=false
- name: Odin documentation tests
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
cd tests\documentation
call build.bat
timeout-minutes: 10
- name: core:math/big tests
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
cd tests\core\math\big
call build.bat
timeout-minutes: 10
- name: Odin check examples/all for Windows 32bits
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin check examples/all -strict-style -target:windows_i386
timeout-minutes: 10
+6 -6
View File
@@ -11,7 +11,7 @@ jobs:
if: github.repository == 'odin-lang/Odin'
runs-on: windows-2022
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v4
- name: build Odin
shell: cmd
run: |
@@ -45,7 +45,7 @@ jobs:
if: github.repository == 'odin-lang/Odin'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v4
- name: (Linux) Download LLVM
run: |
wget https://apt.llvm.org/llvm.sh
@@ -79,7 +79,7 @@ jobs:
if: github.repository == 'odin-lang/Odin'
runs-on: macos-13
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v4
- name: Download LLVM and setup PATH
run: |
brew install llvm@17 dylibbundler
@@ -113,7 +113,7 @@ jobs:
if: github.repository == 'odin-lang/Odin'
runs-on: macos-14 # ARM machine
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v4
- name: Download LLVM and setup PATH
run: |
brew install llvm@17 dylibbundler
@@ -146,7 +146,7 @@ jobs:
runs-on: [ubuntu-latest]
needs: [build_windows, build_macos, build_macos_arm, build_ubuntu]
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v4
- uses: actions/setup-python@v2
with:
python-version: '3.8.x'
@@ -193,4 +193,4 @@ jobs:
python3 ci/nightly.py artifact macos-amd64 macos_artifacts/dist.zip
python3 ci/nightly.py artifact macos-arm64 macos_arm_artifacts/dist.zip
python3 ci/nightly.py prune
python3 ci/nightly.py json
python3 ci/nightly.py json
+13 -2
View File
@@ -701,7 +701,7 @@ default_assertion_failure_proc :: proc(prefix, message: string, loc: Source_Code
when ODIN_OS == .Freestanding {
// Do nothing
} else {
when !ODIN_DISABLE_ASSERT {
when ODIN_OS != .Orca && !ODIN_DISABLE_ASSERT {
print_caller_location(loc)
print_string(" ")
}
@@ -710,7 +710,18 @@ default_assertion_failure_proc :: proc(prefix, message: string, loc: Source_Code
print_string(": ")
print_string(message)
}
print_byte('\n')
when ODIN_OS == .Orca {
assert_fail(
cstring(raw_data(loc.file_path)),
cstring(raw_data(loc.procedure)),
loc.line,
"",
cstring(raw_data(orca_stderr_buffer[:orca_stderr_buffer_idx])),
)
} else {
print_byte('\n')
}
}
trap()
}
+1 -1
View File
@@ -6,7 +6,7 @@ when ODIN_DEFAULT_TO_NIL_ALLOCATOR {
} else when ODIN_DEFAULT_TO_PANIC_ALLOCATOR {
default_allocator_proc :: panic_allocator_proc
default_allocator :: panic_allocator
} else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 {
} else when ODIN_OS != .Orca && (ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32) {
default_allocator :: default_wasm_allocator
default_allocator_proc :: wasm_allocator_proc
} else {
+25 -11
View File
@@ -6,15 +6,29 @@ package runtime
import "base:intrinsics"
when !ODIN_TEST && !ODIN_NO_ENTRY_POINT {
@(link_name="_start", linkage="strong", require, export)
_start :: proc "c" () {
context = default_context()
#force_no_inline _startup_runtime()
intrinsics.__entry_point()
when ODIN_OS == .Orca {
@(linkage="strong", require, export)
oc_on_init :: proc "c" () {
context = default_context()
#force_no_inline _startup_runtime()
intrinsics.__entry_point()
}
@(linkage="strong", require, export)
oc_on_terminate :: proc "c" () {
context = default_context()
#force_no_inline _cleanup_runtime()
}
} else {
@(link_name="_start", linkage="strong", require, export)
_start :: proc "c" () {
context = default_context()
#force_no_inline _startup_runtime()
intrinsics.__entry_point()
}
@(link_name="_end", linkage="strong", require, export)
_end :: proc "c" () {
context = default_context()
#force_no_inline _cleanup_runtime()
}
}
@(link_name="_end", linkage="strong", require, export)
_end :: proc "c" () {
context = default_context()
#force_no_inline _cleanup_runtime()
}
}
}
+4
View File
@@ -4,6 +4,8 @@ package runtime
bounds_trap :: proc "contextless" () -> ! {
when ODIN_OS == .Windows {
windows_trap_array_bounds()
} else when ODIN_OS == .Orca {
abort_ext("", "", 0, "bounds trap")
} else {
trap()
}
@@ -13,6 +15,8 @@ bounds_trap :: proc "contextless" () -> ! {
type_assertion_trap :: proc "contextless" () -> ! {
when ODIN_OS == .Windows {
windows_trap_type_assertion()
} else when ODIN_OS == .Orca {
abort_ext("", "", 0, "type assertion trap")
} else {
trap()
}
+29
View File
@@ -0,0 +1,29 @@
//+build orca
//+private
package runtime
foreign {
@(link_name="malloc") _orca_malloc :: proc "c" (size: int) -> rawptr ---
@(link_name="calloc") _orca_calloc :: proc "c" (num, size: int) -> rawptr ---
@(link_name="free") _orca_free :: proc "c" (ptr: rawptr) ---
@(link_name="realloc") _orca_realloc :: proc "c" (ptr: rawptr, size: int) -> rawptr ---
}
_heap_alloc :: proc(size: int, zero_memory := true) -> rawptr {
if size <= 0 {
return nil
}
if zero_memory {
return _orca_calloc(1, size)
} else {
return _orca_malloc(size)
}
}
_heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr {
return _orca_realloc(ptr, new_size)
}
_heap_free :: proc(ptr: rawptr) {
_orca_free(ptr)
}
+1 -1
View File
@@ -12,4 +12,4 @@ _heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr {
_heap_free :: proc(ptr: rawptr) {
unimplemented("base:runtime 'heap_free' procedure is not supported on this platform")
}
}
+2 -2
View File
@@ -483,7 +483,7 @@ quaternion256_ne :: #force_inline proc "contextless" (a, b: quaternion256) -> bo
string_decode_rune :: #force_inline proc "contextless" (s: string) -> (rune, int) {
// NOTE(bill): Duplicated here to remove dependency on package unicode/utf8
@static accept_sizes := [256]u8{
@(static, rodata) accept_sizes := [256]u8{
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x00-0x0f
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x10-0x1f
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x20-0x2f
@@ -504,7 +504,7 @@ string_decode_rune :: #force_inline proc "contextless" (s: string) -> (rune, int
}
Accept_Range :: struct {lo, hi: u8}
@static accept_ranges := [5]Accept_Range{
@(static, rodata) accept_ranges := [5]Accept_Range{
{0x80, 0xbf},
{0xa0, 0xbf},
{0x80, 0x9f},
+43
View File
@@ -0,0 +1,43 @@
//+build orca
//+private
package runtime
import "base:intrinsics"
// Constants allowing to specify the level of logging verbosity.
log_level :: enum u32 {
// Only errors are logged.
ERROR = 0,
// Only warnings and errors are logged.
WARNING = 1,
// All messages are logged.
INFO = 2,
COUNT = 3,
}
@(default_calling_convention="c", link_prefix="oc_")
foreign {
abort_ext :: proc(file: cstring, function: cstring, line: i32, fmt: cstring, #c_vararg args: ..any) -> ! ---
assert_fail :: proc(file: cstring, function: cstring, line: i32, src: cstring, fmt: cstring, #c_vararg args: ..any) -> ! ---
log_ext :: proc(level: log_level, function: cstring, file: cstring, line: i32, fmt: cstring, #c_vararg args: ..any) ---
}
// NOTE: This is all pretty gross, don't look.
// WASM is single threaded so this should be fine.
orca_stderr_buffer: [4096]byte
orca_stderr_buffer_idx: int
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
for b in data {
orca_stderr_buffer[orca_stderr_buffer_idx] = b
orca_stderr_buffer_idx += 1
if b == '\n' || orca_stderr_buffer_idx == len(orca_stderr_buffer)-1 {
log_ext(.ERROR, "", "", 0, cstring(raw_data(orca_stderr_buffer[:orca_stderr_buffer_idx])))
orca_stderr_buffer_idx = 0
}
}
return len(data), 0
}
+1 -1
View File
@@ -25,7 +25,7 @@ when ODIN_NO_CRT && ODIN_OS == .Windows {
RtlMoveMemory(dst, src, len)
return dst
}
} else when ODIN_NO_CRT || (ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32) {
} else when ODIN_NO_CRT || (ODIN_OS != .Orca && (ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32)) {
// NOTE: on wasm, calls to these procs are generated (by LLVM) with type `i32` instead of `int`.
//
// NOTE: `#any_int` is also needed, because calls that we generate (and package code)
+14 -14
View File
@@ -7,20 +7,20 @@ import "base:intrinsics"
Port of emmalloc, modified for use in Odin.
Invariants:
- Per-allocation header overhead is 8 bytes, smallest allocated payload
amount is 8 bytes, and a multiple of 4 bytes.
- Acquired memory blocks are subdivided into disjoint regions that lie
next to each other.
- A region is either in used or free.
Used regions may be adjacent, and a used and unused region
may be adjacent, but not two unused ones - they would be
merged.
- Memory allocation takes constant time, unless the alloc needs to wasm_memory_grow()
or memory is very close to being exhausted.
- Free and used regions are managed inside "root regions", which are slabs
of memory acquired via wasm_memory_grow().
- Memory retrieved using wasm_memory_grow() can not be given back to the OS.
Therefore, frees are internal to the allocator.
- Per-allocation header overhead is 8 bytes, smallest allocated payload
amount is 8 bytes, and a multiple of 4 bytes.
- Acquired memory blocks are subdivided into disjoint regions that lie
next to each other.
- A region is either in used or free.
Used regions may be adjacent, and a used and unused region
may be adjacent, but not two unused ones - they would be
merged.
- Memory allocation takes constant time, unless the alloc needs to wasm_memory_grow()
or memory is very close to being exhausted.
- Free and used regions are managed inside "root regions", which are slabs
of memory acquired via wasm_memory_grow().
- Memory retrieved using wasm_memory_grow() can not be given back to the OS.
Therefore, frees are internal to the allocator.
Copyright (c) 2010-2014 Emscripten authors, see AUTHORS file.
+1 -1
View File
@@ -1495,7 +1495,7 @@ fmt_pointer :: proc(fi: ^Info, p: rawptr, verb: rune) {
u := u64(uintptr(p))
switch verb {
case 'p', 'v', 'w':
if !fi.hash && verb == 'v' {
if !fi.hash {
io.write_string(fi.writer, "0x", &fi.n)
}
_fmt_int(fi, u, 16, false, 8*size_of(rawptr), __DIGITS_UPPER)
+1
View File
@@ -1,5 +1,6 @@
//+build !freestanding
//+build !js
//+build !orca
package fmt
import "base:runtime"
+746
View File
@@ -0,0 +1,746 @@
// package bmp implements a Microsoft BMP image reader
package core_image_bmp
import "core:image"
import "core:bytes"
import "core:compress"
import "core:mem"
import "base:intrinsics"
import "base:runtime"
Error :: image.Error
Image :: image.Image
Options :: image.Options
RGB_Pixel :: image.RGB_Pixel
RGBA_Pixel :: image.RGBA_Pixel
FILE_HEADER_SIZE :: 14
INFO_STUB_SIZE :: FILE_HEADER_SIZE + size_of(image.BMP_Version)
save_to_buffer :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
context.allocator = allocator
if img == nil {
return .Invalid_Input_Image
}
if output == nil {
return .Invalid_Output
}
pixels := img.width * img.height
if pixels == 0 || pixels > image.MAX_DIMENSIONS {
return .Invalid_Input_Image
}
// While the BMP spec (and our loader) support more fanciful image types,
// `bmp.save` supports only 3 and 4 channel images with a bit depth of 8.
if img.depth != 8 || img.channels < 3 || img.channels > 4 {
return .Invalid_Input_Image
}
if img.channels * pixels != len(img.pixels.buf) {
return .Invalid_Input_Image
}
// Calculate and allocate size.
header_size := u32le(image.BMP_Version.V3)
total_header_size := header_size + 14 // file header = 14
pixel_count_bytes := u32le(align4(img.width * img.channels) * img.height)
header := image.BMP_Header{
// File header
magic = .Bitmap,
size = total_header_size + pixel_count_bytes,
_res1 = 0,
_res2 = 0,
pixel_offset = total_header_size,
// V3
info_size = .V3,
width = i32le(img.width),
height = i32le(img.height),
planes = 1,
bpp = u16le(8 * img.channels),
compression = .RGB,
image_size = pixel_count_bytes,
pels_per_meter = {2835, 2835}, // 72 DPI
colors_used = 0,
colors_important = 0,
}
written := 0
if resize(&output.buf, int(header.size)) != nil {
return .Unable_To_Allocate_Or_Resize
}
header_bytes := transmute([size_of(image.BMP_Header)]u8)header
written += int(total_header_size)
copy(output.buf[:], header_bytes[:written])
switch img.channels {
case 3:
row_bytes := img.width * img.channels
row_padded := align4(row_bytes)
pixels := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:])
for y in 0..<img.height {
row_offset := row_padded * (img.height - y - 1) + written
for x in 0..<img.width {
pix_offset := 3 * x
output.buf[row_offset + pix_offset + 0] = pixels[0].b
output.buf[row_offset + pix_offset + 1] = pixels[0].g
output.buf[row_offset + pix_offset + 2] = pixels[0].r
pixels = pixels[1:]
}
}
case 4:
row_bytes := img.width * img.channels
pixels := mem.slice_data_cast([]RGBA_Pixel, img.pixels.buf[:])
for y in 0..<img.height {
row_offset := row_bytes * (img.height - y - 1) + written
for x in 0..<img.width {
pix_offset := 4 * x
output.buf[row_offset + pix_offset + 0] = pixels[0].b
output.buf[row_offset + pix_offset + 1] = pixels[0].g
output.buf[row_offset + pix_offset + 2] = pixels[0].r
output.buf[row_offset + pix_offset + 3] = pixels[0].a
pixels = pixels[1:]
}
}
}
return
}
load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
ctx := &compress.Context_Memory_Input{
input_data = data,
}
img, err = load_from_context(ctx, options, allocator)
return img, err
}
@(optimization_mode="speed")
load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
context.allocator = allocator
options := options
// For compress.read_slice(), until that's rewritten to not use temp allocator
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
if .info in options {
options |= {.return_metadata, .do_not_decompress_image}
options -= {.info}
}
if .return_header in options && .return_metadata in options {
options -= {.return_header}
}
info_buf: [size_of(image.BMP_Header)]u8
// Read file header (14) + info size (4)
stub_data := compress.read_slice(ctx, INFO_STUB_SIZE) or_return
copy(info_buf[:], stub_data[:])
stub_info := transmute(image.BMP_Header)info_buf
if stub_info.magic != .Bitmap {
for v in image.BMP_Magic {
if stub_info.magic == v {
return img, .Unsupported_OS2_File
}
}
return img, .Invalid_Signature
}
info: image.BMP_Header
switch stub_info.info_size {
case .OS2_v1:
// Read the remainder of the header
os2_data := compress.read_data(ctx, image.OS2_Header) or_return
info = transmute(image.BMP_Header)info_buf
info.width = i32le(os2_data.width)
info.height = i32le(os2_data.height)
info.planes = os2_data.planes
info.bpp = os2_data.bpp
switch info.bpp {
case 1, 4, 8, 24:
case:
return img, .Unsupported_BPP
}
case .ABBR_16 ..= .V5:
// Sizes include V3, V4, V5 and OS2v2 outright, but can also handle truncated headers.
// Sometimes called BITMAPV2INFOHEADER or BITMAPV3INFOHEADER.
// Let's just try to process it.
to_read := int(stub_info.info_size) - size_of(image.BMP_Version)
info_data := compress.read_slice(ctx, to_read) or_return
copy(info_buf[INFO_STUB_SIZE:], info_data[:])
// Update info struct with the rest of the data we read
info = transmute(image.BMP_Header)info_buf
case:
return img, .Unsupported_BMP_Version
}
/* TODO(Jeroen): Add a "strict" option to catch these non-issues that violate spec?
if info.planes != 1 {
return img, .Invalid_Planes_Value
}
*/
if img == nil {
img = new(Image)
}
img.which = .BMP
img.metadata = new_clone(image.BMP_Info{
info = info,
})
img.width = abs(int(info.width))
img.height = abs(int(info.height))
img.channels = 3
img.depth = 8
if img.width == 0 || img.height == 0 {
return img, .Invalid_Image_Dimensions
}
total_pixels := abs(img.width * img.height)
if total_pixels > image.MAX_DIMENSIONS {
return img, .Image_Dimensions_Too_Large
}
// TODO(Jeroen): Handle RGBA.
switch info.compression {
case .Bit_Fields, .Alpha_Bit_Fields:
switch info.bpp {
case 16, 32:
make_output(img, allocator) or_return
decode_rgb(ctx, img, info, allocator) or_return
case:
if is_os2(info.info_size) {
return img, .Unsupported_Compression
}
return img, .Unsupported_BPP
}
case .RGB:
make_output(img, allocator) or_return
decode_rgb(ctx, img, info, allocator) or_return
case .RLE4, .RLE8:
make_output(img, allocator) or_return
decode_rle(ctx, img, info, allocator) or_return
case .CMYK, .CMYK_RLE4, .CMYK_RLE8: fallthrough
case .PNG, .JPEG: fallthrough
case: return img, .Unsupported_Compression
}
// Flipped vertically
if info.height < 0 {
pixels := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:])
for y in 0..<img.height / 2 {
for x in 0..<img.width {
top := y * img.width + x
bot := (img.height - y - 1) * img.width + x
pixels[top], pixels[bot] = pixels[bot], pixels[top]
}
}
}
return
}
is_os2 :: proc(version: image.BMP_Version) -> (res: bool) {
#partial switch version {
case .OS2_v1, .OS2_v2: return true
case: return false
}
}
make_output :: proc(img: ^Image, allocator := context.allocator) -> (err: Error) {
assert(img != nil)
bytes_needed := img.channels * img.height * img.width
img.pixels.buf = make([dynamic]u8, bytes_needed, allocator)
if len(img.pixels.buf) != bytes_needed {
return .Unable_To_Allocate_Or_Resize
}
return
}
write :: proc(img: ^Image, x, y: int, pix: RGB_Pixel) -> (err: Error) {
if y >= img.height || x >= img.width {
return .Corrupt
}
out := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:])
assert(img.height >= 1 && img.width >= 1)
out[(img.height - y - 1) * img.width + x] = pix
return
}
Bitmask :: struct {
mask: [4]u32le `fmt:"b"`,
shift: [4]u32le,
bits: [4]u32le,
}
read_or_make_bit_masks :: proc(ctx: ^$C, info: image.BMP_Header) -> (res: Bitmask, read: int, err: Error) {
ctz :: intrinsics.count_trailing_zeros
c1s :: intrinsics.count_ones
#partial switch info.compression {
case .RGB:
switch info.bpp {
case 16:
return {
mask = {31 << 10, 31 << 5, 31, 0},
shift = { 10, 5, 0, 0},
bits = { 5, 5, 5, 0},
}, int(4 * info.colors_used), nil
case 32:
return {
mask = {255 << 16, 255 << 8, 255, 255 << 24},
shift = { 16, 8, 0, 24},
bits = { 8, 8, 8, 8},
}, int(4 * info.colors_used), nil
case: return {}, 0, .Unsupported_BPP
}
case .Bit_Fields, .Alpha_Bit_Fields:
bf := info.masks
alpha_mask := false
bit_count: u32le
#partial switch info.info_size {
case .ABBR_52 ..= .V5:
// All possible BMP header sizes 52+ bytes long, includes V4 + V5
// Bit fields were read as part of the header
// V3 header is 40 bytes. We need 56 at a minimum for RGBA bit fields in the next section.
if info.info_size >= .ABBR_56 {
alpha_mask = true
}
case .V3:
// Version 3 doesn't have a bit field embedded, but can still have a 3 or 4 color bit field.
// Because it wasn't read as part of the header, we need to read it now.
if info.compression == .Alpha_Bit_Fields {
bf = compress.read_data(ctx, [4]u32le) or_return
alpha_mask = true
read = 16
} else {
bf.xyz = compress.read_data(ctx, [3]u32le) or_return
read = 12
}
case:
// Bit fields are unhandled for this BMP version
return {}, 0, .Bitfield_Version_Unhandled
}
if alpha_mask {
res = {
mask = {bf.r, bf.g, bf.b, bf.a},
shift = {ctz(bf.r), ctz(bf.g), ctz(bf.b), ctz(bf.a)},
bits = {c1s(bf.r), c1s(bf.g), c1s(bf.b), c1s(bf.a)},
}
bit_count = res.bits.r + res.bits.g + res.bits.b + res.bits.a
} else {
res = {
mask = {bf.r, bf.g, bf.b, 0},
shift = {ctz(bf.r), ctz(bf.g), ctz(bf.b), 0},
bits = {c1s(bf.r), c1s(bf.g), c1s(bf.b), 0},
}
bit_count = res.bits.r + res.bits.g + res.bits.b
}
if bit_count > u32le(info.bpp) {
err = .Bitfield_Sum_Exceeds_BPP
}
overlapped := res.mask.r | res.mask.g | res.mask.b | res.mask.a
if c1s(overlapped) < bit_count {
err = .Bitfield_Overlapped
}
return res, read, err
case:
return {}, 0, .Unsupported_Compression
}
return
}
scale :: proc(val: $T, mask, shift, bits: u32le) -> (res: u8) {
if bits == 0 { return 0 } // Guard against malformed bit fields
v := (u32le(val) & mask) >> shift
mask_in := u32le(1 << bits) - 1
return u8(v * 255 / mask_in)
}
decode_rgb :: proc(ctx: ^$C, img: ^Image, info: image.BMP_Header, allocator := context.allocator) -> (err: Error) {
pixel_offset := int(info.pixel_offset)
pixel_offset -= int(info.info_size) + FILE_HEADER_SIZE
palette: [256]RGBA_Pixel
// Palette size is info.colors_used if populated. If not it's min(1 << bpp, offset to the pixels / channel count)
colors_used := min(256, 1 << info.bpp if info.colors_used == 0 else info.colors_used)
max_colors := pixel_offset / 3 if info.info_size == .OS2_v1 else pixel_offset / 4
colors_used = min(colors_used, u32le(max_colors))
switch info.bpp {
case 1:
if info.info_size == .OS2_v1 {
// 2 x RGB palette of instead of variable RGBA palette
for i in 0..<colors_used {
palette[i].rgb = image.read_data(ctx, RGB_Pixel) or_return
}
pixel_offset -= int(3 * colors_used)
} else {
for i in 0..<colors_used {
palette[i] = image.read_data(ctx, RGBA_Pixel) or_return
}
pixel_offset -= int(4 * colors_used)
}
skip_space(ctx, pixel_offset)
stride := (img.width + 7) / 8
for y in 0..<img.height {
data := compress.read_slice(ctx, stride) or_return
for x in 0..<img.width {
shift := u8(7 - (x & 0x07))
p := (data[x / 8] >> shift) & 0x01
write(img, x, y, palette[p].bgr) or_return
}
}
case 2: // Non-standard on modern Windows, but was allowed on WinCE
for i in 0..<colors_used {
palette[i] = image.read_data(ctx, RGBA_Pixel) or_return
}
pixel_offset -= int(4 * colors_used)
skip_space(ctx, pixel_offset)
stride := (img.width + 3) / 4
for y in 0..<img.height {
data := compress.read_slice(ctx, stride) or_return
for x in 0..<img.width {
shift := 6 - (x & 0x03) << 1
p := (data[x / 4] >> u8(shift)) & 0x03
write(img, x, y, palette[p].bgr) or_return
}
}
case 4:
if info.info_size == .OS2_v1 {
// 16 x RGB palette of instead of variable RGBA palette
for i in 0..<colors_used {
palette[i].rgb = image.read_data(ctx, RGB_Pixel) or_return
}
pixel_offset -= int(3 * colors_used)
} else {
for i in 0..<colors_used {
palette[i] = image.read_data(ctx, RGBA_Pixel) or_return
}
pixel_offset -= int(4 * colors_used)
}
skip_space(ctx, pixel_offset)
stride := (img.width + 1) / 2
for y in 0..<img.height {
data := compress.read_slice(ctx, stride) or_return
for x in 0..<img.width {
p := data[x / 2] >> 4 if x & 1 == 0 else data[x / 2]
write(img, x, y, palette[p & 0x0f].bgr) or_return
}
}
case 8:
if info.info_size == .OS2_v1 {
// 256 x RGB palette of instead of variable RGBA palette
for i in 0..<colors_used {
palette[i].rgb = image.read_data(ctx, RGB_Pixel) or_return
}
pixel_offset -= int(3 * colors_used)
} else {
for i in 0..<colors_used {
palette[i] = image.read_data(ctx, RGBA_Pixel) or_return
}
pixel_offset -= int(4 * colors_used)
}
skip_space(ctx, pixel_offset)
stride := align4(img.width)
for y in 0..<img.height {
data := compress.read_slice(ctx, stride) or_return
for x in 0..<img.width {
write(img, x, y, palette[data[x]].bgr) or_return
}
}
case 16:
bm, read := read_or_make_bit_masks(ctx, info) or_return
// Skip optional palette and other data
pixel_offset -= read
skip_space(ctx, pixel_offset)
stride := align4(img.width * 2)
for y in 0..<img.height {
data := compress.read_slice(ctx, stride) or_return
pixels := mem.slice_data_cast([]u16le, data)
for x in 0..<img.width {
v := pixels[x]
r := scale(v, bm.mask.r, bm.shift.r, bm.bits.r)
g := scale(v, bm.mask.g, bm.shift.g, bm.bits.g)
b := scale(v, bm.mask.b, bm.shift.b, bm.bits.b)
write(img, x, y, RGB_Pixel{r, g, b}) or_return
}
}
case 24:
// Eat useless palette and other padding
skip_space(ctx, pixel_offset)
stride := align4(img.width * 3)
for y in 0..<img.height {
data := compress.read_slice(ctx, stride) or_return
pixels := mem.slice_data_cast([]RGB_Pixel, data)
for x in 0..<img.width {
write(img, x, y, pixels[x].bgr) or_return
}
}
case 32:
bm, read := read_or_make_bit_masks(ctx, info) or_return
// Skip optional palette and other data
pixel_offset -= read
skip_space(ctx, pixel_offset)
for y in 0..<img.height {
data := compress.read_slice(ctx, img.width * size_of(RGBA_Pixel)) or_return
pixels := mem.slice_data_cast([]u32le, data)
for x in 0..<img.width {
v := pixels[x]
r := scale(v, bm.mask.r, bm.shift.r, bm.bits.r)
g := scale(v, bm.mask.g, bm.shift.g, bm.bits.g)
b := scale(v, bm.mask.b, bm.shift.b, bm.bits.b)
write(img, x, y, RGB_Pixel{r, g, b}) or_return
}
}
case:
return .Unsupported_BPP
}
return nil
}
decode_rle :: proc(ctx: ^$C, img: ^Image, info: image.BMP_Header, allocator := context.allocator) -> (err: Error) {
pixel_offset := int(info.pixel_offset)
pixel_offset -= int(info.info_size) + FILE_HEADER_SIZE
bytes_needed := size_of(RGB_Pixel) * img.height * img.width
if resize(&img.pixels.buf, bytes_needed) != nil {
return .Unable_To_Allocate_Or_Resize
}
out := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:])
assert(len(out) == img.height * img.width)
palette: [256]RGBA_Pixel
switch info.bpp {
case 4:
colors_used := info.colors_used if info.colors_used > 0 else 16
colors_used = min(colors_used, 16)
for i in 0..<colors_used {
palette[i] = image.read_data(ctx, RGBA_Pixel) or_return
pixel_offset -= size_of(RGBA_Pixel)
}
skip_space(ctx, pixel_offset)
pixel_size := info.size - info.pixel_offset
remaining := compress.input_size(ctx) or_return
if remaining < i64(pixel_size) {
return .Corrupt
}
data := make([]u8, int(pixel_size) + 4)
defer delete(data)
for i in 0..<pixel_size {
data[i] = image.read_u8(ctx) or_return
}
y, x := 0, 0
index := 0
for {
if len(data[index:]) < 2 {
return .Corrupt
}
if data[index] > 0 {
for count in 0..<data[index] {
if count & 1 == 1 {
write(img, x, y, palette[(data[index + 1] >> 0) & 0x0f].bgr)
} else {
write(img, x, y, palette[(data[index + 1] >> 4) & 0x0f].bgr)
}
x += 1
}
index += 2
} else {
switch data[index + 1] {
case 0: // EOL
x = 0; y += 1
index += 2
case 1: // EOB
return
case 2: // MOVE
x += int(data[index + 2])
y += int(data[index + 3])
index += 4
case: // Literals
run_length := int(data[index + 1])
aligned := (align4(run_length) >> 1) + 2
if index + aligned >= len(data) {
return .Corrupt
}
for count in 0..<run_length {
val := data[index + 2 + count / 2]
if count & 1 == 1 {
val &= 0xf
} else {
val = val >> 4
}
write(img, x, y, palette[val].bgr)
x += 1
}
index += aligned
}
}
}
case 8:
colors_used := info.colors_used if info.colors_used > 0 else 256
colors_used = min(colors_used, 256)
for i in 0..<colors_used {
palette[i] = image.read_data(ctx, RGBA_Pixel) or_return
pixel_offset -= size_of(RGBA_Pixel)
}
skip_space(ctx, pixel_offset)
pixel_size := info.size - info.pixel_offset
remaining := compress.input_size(ctx) or_return
if remaining < i64(pixel_size) {
return .Corrupt
}
data := make([]u8, int(pixel_size) + 4)
defer delete(data)
for i in 0..<pixel_size {
data[i] = image.read_u8(ctx) or_return
}
y, x := 0, 0
index := 0
for {
if len(data[index:]) < 2 {
return .Corrupt
}
if data[index] > 0 {
for _ in 0..<data[index] {
write(img, x, y, palette[data[index + 1]].bgr)
x += 1
}
index += 2
} else {
switch data[index + 1] {
case 0: // EOL
x = 0; y += 1
index += 2
case 1: // EOB
return
case 2: // MOVE
x += int(data[index + 2])
y += int(data[index + 3])
index += 4
case: // Literals
run_length := int(data[index + 1])
aligned := align2(run_length) + 2
if index + aligned >= len(data) {
return .Corrupt
}
for count in 0..<run_length {
write(img, x, y, palette[data[index + 2 + count]].bgr)
x += 1
}
index += aligned
}
}
}
case:
return .Unsupported_BPP
}
return nil
}
align2 :: proc(width: int) -> (stride: int) {
stride = width
if width & 1 != 0 {
stride += 2 - (width & 1)
}
return
}
align4 :: proc(width: int) -> (stride: int) {
stride = width
if width & 3 != 0 {
stride += 4 - (width & 3)
}
return
}
skip_space :: proc(ctx: ^$C, bytes_to_skip: int) -> (err: Error) {
if bytes_to_skip < 0 {
return .Corrupt
}
for _ in 0..<bytes_to_skip {
image.read_u8(ctx) or_return
}
return
}
// Cleanup of image-specific data.
destroy :: proc(img: ^Image) {
if img == nil {
// Nothing to do. Load must've returned with an error.
return
}
bytes.buffer_destroy(&img.pixels)
if v, ok := img.metadata.(^image.BMP_Info); ok {
free(v)
}
free(img)
}
@(init, private)
_register :: proc() {
image.register(.BMP, load_from_bytes, destroy)
}
+4
View File
@@ -0,0 +1,4 @@
//+build js
package core_image_bmp
load :: proc{load_from_bytes, load_from_context}
+34
View File
@@ -0,0 +1,34 @@
//+build !js
package core_image_bmp
import "core:os"
import "core:bytes"
load :: proc{load_from_file, load_from_bytes, load_from_context}
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
context.allocator = allocator
data, ok := os.read_entire_file(filename)
defer delete(data)
if ok {
return load_from_bytes(data, options)
} else {
return nil, .Unable_To_Read_File
}
}
save :: proc{save_to_buffer, save_to_file}
save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
context.allocator = allocator
out := &bytes.Buffer{}
defer bytes.buffer_destroy(out)
save_to_buffer(out, img, options) or_return
write_ok := os.write_entire_file(output, out.buf[:])
return nil if write_ok else .Unable_To_Write_File
}
+161 -1
View File
@@ -12,6 +12,7 @@ package image
import "core:bytes"
import "core:mem"
import "core:io"
import "core:compress"
import "base:runtime"
@@ -62,6 +63,7 @@ Image_Metadata :: union #shared_nil {
^PNG_Info,
^QOI_Info,
^TGA_Info,
^BMP_Info,
}
@@ -159,11 +161,13 @@ Error :: union #shared_nil {
Netpbm_Error,
PNG_Error,
QOI_Error,
BMP_Error,
compress.Error,
compress.General_Error,
compress.Deflate_Error,
compress.ZLIB_Error,
io.Error,
runtime.Allocator_Error,
}
@@ -196,6 +200,128 @@ General_Image_Error :: enum {
Unable_To_Allocate_Or_Resize,
}
/*
BMP-specific
*/
BMP_Error :: enum {
None = 0,
Invalid_File_Size,
Unsupported_BMP_Version,
Unsupported_OS2_File,
Unsupported_Compression,
Unsupported_BPP,
Invalid_Stride,
Invalid_Color_Count,
Implausible_File_Size,
Bitfield_Version_Unhandled, // We don't (yet) handle bit fields for this BMP version.
Bitfield_Sum_Exceeds_BPP, // Total mask bit count > bpp
Bitfield_Overlapped, // Channel masks overlap
}
// img.metadata is wrapped in a struct in case we need to add to it later
// without putting it in BMP_Header
BMP_Info :: struct {
info: BMP_Header,
}
BMP_Magic :: enum u16le {
Bitmap = 0x4d42, // 'BM'
OS2_Bitmap_Array = 0x4142, // 'BA'
OS2_Icon = 0x4349, // 'IC',
OS2_Color_Icon = 0x4943, // 'CI'
OS2_Pointer = 0x5450, // 'PT'
OS2_Color_Pointer = 0x5043, // 'CP'
}
// See: http://justsolve.archiveteam.org/wiki/BMP#Well-known_versions
BMP_Version :: enum u32le {
OS2_v1 = 12, // BITMAPCOREHEADER (Windows V2 / OS/2 version 1.0)
OS2_v2 = 64, // BITMAPCOREHEADER2 (OS/2 version 2.x)
V3 = 40, // BITMAPINFOHEADER
V4 = 108, // BITMAPV4HEADER
V5 = 124, // BITMAPV5HEADER
ABBR_16 = 16, // Abbreviated
ABBR_24 = 24, // ..
ABBR_48 = 48, // ..
ABBR_52 = 52, // ..
ABBR_56 = 56, // ..
}
BMP_Header :: struct #packed {
// File header
magic: BMP_Magic,
size: u32le,
_res1: u16le, // Reserved; must be zero
_res2: u16le, // Reserved; must be zero
pixel_offset: u32le, // Offset in bytes, from the beginning of BMP_Header to the pixel data
// V3
info_size: BMP_Version,
width: i32le,
height: i32le,
planes: u16le,
bpp: u16le,
compression: BMP_Compression,
image_size: u32le,
pels_per_meter: [2]u32le,
colors_used: u32le,
colors_important: u32le, // OS2_v2 is equal up to here
// V4
masks: [4]u32le `fmt:"32b"`,
colorspace: BMP_Logical_Color_Space,
endpoints: BMP_CIEXYZTRIPLE,
gamma: [3]BMP_GAMMA16_16,
// V5
intent: BMP_Gamut_Mapping_Intent,
profile_data: u32le,
profile_size: u32le,
reserved: u32le,
}
#assert(size_of(BMP_Header) == 138)
OS2_Header :: struct #packed {
// BITMAPCOREHEADER minus info_size field
width: i16le,
height: i16le,
planes: u16le,
bpp: u16le,
}
#assert(size_of(OS2_Header) == 8)
BMP_Compression :: enum u32le {
RGB = 0x0000,
RLE8 = 0x0001,
RLE4 = 0x0002,
Bit_Fields = 0x0003, // If Windows
Huffman1D = 0x0003, // If OS2v2
JPEG = 0x0004, // If Windows
RLE24 = 0x0004, // If OS2v2
PNG = 0x0005,
Alpha_Bit_Fields = 0x0006,
CMYK = 0x000B,
CMYK_RLE8 = 0x000C,
CMYK_RLE4 = 0x000D,
}
BMP_Logical_Color_Space :: enum u32le {
CALIBRATED_RGB = 0x00000000,
sRGB = 0x73524742, // 'sRGB'
WINDOWS_COLOR_SPACE = 0x57696E20, // 'Win '
}
BMP_FXPT2DOT30 :: u32le
BMP_CIEXYZ :: [3]BMP_FXPT2DOT30
BMP_CIEXYZTRIPLE :: [3]BMP_CIEXYZ
BMP_GAMMA16_16 :: [2]u16le
BMP_Gamut_Mapping_Intent :: enum u32le {
INVALID = 0x00000000, // If not V5, this field will just be zero-initialized and not valid.
ABS_COLORIMETRIC = 0x00000008,
BUSINESS = 0x00000001,
GRAPHICS = 0x00000002,
IMAGES = 0x00000004,
}
/*
Netpbm-specific definitions
*/
@@ -1133,6 +1259,40 @@ apply_palette_rgba :: proc(img: ^Image, palette: [256]RGBA_Pixel, allocator := c
}
apply_palette :: proc{apply_palette_rgb, apply_palette_rgba}
blend_single_channel :: #force_inline proc(fg, alpha, bg: $T) -> (res: T) where T == u8 || T == u16 {
MAX :: 256 when T == u8 else 65536
c := u32(fg) * (MAX - u32(alpha)) + u32(bg) * (1 + u32(alpha))
return T(c & (MAX - 1))
}
blend_pixel :: #force_inline proc(fg: [$N]$T, alpha: T, bg: [N]T) -> (res: [N]T) where (T == u8 || T == u16), N >= 1 && N <= 4 {
MAX :: 256 when T == u8 else 65536
when N == 1 {
r := u32(fg.r) * (MAX - u32(alpha)) + u32(bg.r) * (1 + u32(alpha))
return {T(r & (MAX - 1))}
}
when N == 2 {
r := u32(fg.r) * (MAX - u32(alpha)) + u32(bg.r) * (1 + u32(alpha))
g := u32(fg.g) * (MAX - u32(alpha)) + u32(bg.g) * (1 + u32(alpha))
return {T(r & (MAX - 1)), T(g & (MAX - 1))}
}
when N == 3 || N == 4 {
r := u32(fg.r) * (MAX - u32(alpha)) + u32(bg.r) * (1 + u32(alpha))
g := u32(fg.g) * (MAX - u32(alpha)) + u32(bg.g) * (1 + u32(alpha))
b := u32(fg.b) * (MAX - u32(alpha)) + u32(bg.b) * (1 + u32(alpha))
when N == 3 {
return {T(r & (MAX - 1)), T(g & (MAX - 1)), T(b & (MAX - 1))}
} else {
return {T(r & (MAX - 1)), T(g & (MAX - 1)), T(b & (MAX - 1)), MAX - 1}
}
}
unreachable()
}
blend :: proc{blend_single_channel, blend_pixel}
// Replicates grayscale values into RGB(A) 8- or 16-bit images as appropriate.
// Returns early with `false` if already an RGB(A) image.
@@ -1245,4 +1405,4 @@ write_bytes :: proc(buf: ^bytes.Buffer, data: []u8) -> (err: compress.General_Er
return .Resize_Failed
}
return nil
}
}
+28 -40
View File
@@ -597,7 +597,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
dsc := depth_scale_table
scale := dsc[info.header.bit_depth]
if scale != 1 {
key := mem.slice_data_cast([]u16be, c.data)[0] * u16be(scale)
key := (^u16be)(raw_data(c.data))^ * u16be(scale)
c.data = []u8{0, u8(key & 255)}
}
}
@@ -735,59 +735,48 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
return {}, .Unable_To_Allocate_Or_Resize
}
i := 0; j := 0
// If we don't have transparency or drop it without applying it, we can do this:
if (!seen_trns || (seen_trns && .alpha_drop_if_present in options && .alpha_premultiply not_in options)) && .alpha_add_if_missing not_in options {
for h := 0; h < int(img.height); h += 1 {
for w := 0; w < int(img.width); w += 1 {
c := _plte.entries[temp.buf[i]]
t.buf[j ] = c.r
t.buf[j+1] = c.g
t.buf[j+2] = c.b
i += 1; j += 3
}
output := mem.slice_data_cast([]image.RGB_Pixel, t.buf[:])
for pal_idx, idx in temp.buf {
output[idx] = _plte.entries[pal_idx]
}
} else if add_alpha || .alpha_drop_if_present in options {
bg := [3]f32{0, 0, 0}
bg := PLTE_Entry{0, 0, 0}
if premultiply && seen_bkgd {
c16 := img.background.([3]u16)
bg = [3]f32{f32(c16.r), f32(c16.g), f32(c16.b)}
bg = {u8(c16.r), u8(c16.g), u8(c16.b)}
}
no_alpha := (.alpha_drop_if_present in options || premultiply) && .alpha_add_if_missing not_in options
blend_background := seen_bkgd && .blend_background in options
for h := 0; h < int(img.height); h += 1 {
for w := 0; w < int(img.width); w += 1 {
index := temp.buf[i]
c := _plte.entries[index]
a := int(index) < len(trns.data) ? trns.data[index] : 255
alpha := f32(a) / 255.0
if no_alpha {
output := mem.slice_data_cast([]image.RGB_Pixel, t.buf[:])
for orig, idx in temp.buf {
c := _plte.entries[orig]
a := int(orig) < len(trns.data) ? trns.data[orig] : 255
if blend_background {
c.r = u8((1.0 - alpha) * bg[0] + f32(c.r) * alpha)
c.g = u8((1.0 - alpha) * bg[1] + f32(c.g) * alpha)
c.b = u8((1.0 - alpha) * bg[2] + f32(c.b) * alpha)
output[idx] = image.blend(c, a, bg)
} else if premultiply {
output[idx] = image.blend(PLTE_Entry{}, a, c)
}
}
} else {
output := mem.slice_data_cast([]image.RGBA_Pixel, t.buf[:])
for orig, idx in temp.buf {
c := _plte.entries[orig]
a := int(orig) < len(trns.data) ? trns.data[orig] : 255
if blend_background {
c = image.blend(c, a, bg)
a = 255
} else if premultiply {
c.r = u8(f32(c.r) * alpha)
c.g = u8(f32(c.g) * alpha)
c.b = u8(f32(c.b) * alpha)
c = image.blend(PLTE_Entry{}, a, c)
}
t.buf[j ] = c.r
t.buf[j+1] = c.g
t.buf[j+2] = c.b
i += 1
if no_alpha {
j += 3
} else {
t.buf[j+3] = u8(a)
j += 4
}
output[idx] = {c.r, c.g, c.b, u8(a)}
}
}
} else {
@@ -1015,8 +1004,8 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
return {}, .Unable_To_Allocate_Or_Resize
}
p := mem.slice_data_cast([]u8, temp.buf[:])
o := mem.slice_data_cast([]u8, t.buf[:])
p := temp.buf[:]
o := t.buf[:]
switch raw_image_channels {
case 1:
@@ -1627,7 +1616,6 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IH
return nil
}
@(init, private)
_register :: proc() {
image.register(.PNG, load_from_bytes, destroy)
+60
View File
@@ -0,0 +1,60 @@
package math_big
/*
With `n` items, calculate how many ways that `r` of them can be ordered.
*/
permutations_with_repetition :: int_pow_int
/*
With `n` items, calculate how many ways that `r` of them can be ordered without any repeats.
*/
permutations_without_repetition :: proc(dest: ^Int, n, r: int) -> (error: Error) {
if n == r {
return factorial(dest, n)
}
tmp := &Int{}
defer internal_destroy(tmp)
// n!
// --------
// (n - r)!
factorial(dest, n) or_return
factorial(tmp, n - r) or_return
div(dest, dest, tmp) or_return
return
}
/*
With `n` items, calculate how many ways that `r` of them can be chosen.
Also known as the multiset coefficient or (n multichoose k).
*/
combinations_with_repetition :: proc(dest: ^Int, n, r: int) -> (error: Error) {
// (n + r - 1)!
// ------------
// r! (n - 1)!
return combinations_without_repetition(dest, n + r - 1, r)
}
/*
With `n` items, calculate how many ways that `r` of them can be chosen without any repeats.
Also known as the binomial coefficient or (n choose k).
*/
combinations_without_repetition :: proc(dest: ^Int, n, r: int) -> (error: Error) {
tmp_a, tmp_b := &Int{}, &Int{}
defer internal_destroy(tmp_a, tmp_b)
// n!
// ------------
// r! (n - r)!
factorial(dest, n) or_return
factorial(tmp_a, r) or_return
factorial(tmp_b, n - r) or_return
mul(tmp_a, tmp_a, tmp_b) or_return
div(dest, dest, tmp_a) or_return
return
}
+1
View File
@@ -315,6 +315,7 @@ int_atoi :: proc(res: ^Int, input: string, radix := i8(10), allocator := context
atoi :: proc { int_atoi, }
string_to_int :: int_atoi
/*
We size for `string` by default.
+1 -1
View File
@@ -350,7 +350,7 @@ _reduce_pi_f64 :: proc "contextless" (x: f64) -> f64 #no_bounds_check {
// that is, 1/PI = SUM bdpi[i]*2^(-64*i).
// 19 64-bit digits give 1216 bits of precision
// to handle the largest possible f64 exponent.
@static bdpi := [?]u64{
@(static, rodata) bdpi := [?]u64{
0x0000000000000000,
0x517cc1b727220a94,
0xfe13abe8fa9a6ee0,
+9 -9
View File
@@ -130,10 +130,10 @@ pow10 :: proc{
@(require_results)
pow10_f16 :: proc "contextless" (n: f16) -> f16 {
@static pow10_pos_tab := [?]f16{
@(static, rodata) pow10_pos_tab := [?]f16{
1e00, 1e01, 1e02, 1e03, 1e04,
}
@static pow10_neg_tab := [?]f16{
@(static, rodata) pow10_neg_tab := [?]f16{
1e-00, 1e-01, 1e-02, 1e-03, 1e-04, 1e-05, 1e-06, 1e-07,
}
@@ -151,13 +151,13 @@ pow10_f16 :: proc "contextless" (n: f16) -> f16 {
@(require_results)
pow10_f32 :: proc "contextless" (n: f32) -> f32 {
@static pow10_pos_tab := [?]f32{
@(static, rodata) pow10_pos_tab := [?]f32{
1e00, 1e01, 1e02, 1e03, 1e04, 1e05, 1e06, 1e07, 1e08, 1e09,
1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
1e20, 1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29,
1e30, 1e31, 1e32, 1e33, 1e34, 1e35, 1e36, 1e37, 1e38,
}
@static pow10_neg_tab := [?]f32{
@(static, rodata) pow10_neg_tab := [?]f32{
1e-00, 1e-01, 1e-02, 1e-03, 1e-04, 1e-05, 1e-06, 1e-07, 1e-08, 1e-09,
1e-10, 1e-11, 1e-12, 1e-13, 1e-14, 1e-15, 1e-16, 1e-17, 1e-18, 1e-19,
1e-20, 1e-21, 1e-22, 1e-23, 1e-24, 1e-25, 1e-26, 1e-27, 1e-28, 1e-29,
@@ -179,16 +179,16 @@ pow10_f32 :: proc "contextless" (n: f32) -> f32 {
@(require_results)
pow10_f64 :: proc "contextless" (n: f64) -> f64 {
@static pow10_tab := [?]f64{
@(static, rodata) pow10_tab := [?]f64{
1e00, 1e01, 1e02, 1e03, 1e04, 1e05, 1e06, 1e07, 1e08, 1e09,
1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
1e20, 1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29,
1e30, 1e31,
}
@static pow10_pos_tab32 := [?]f64{
@(static, rodata) pow10_pos_tab32 := [?]f64{
1e00, 1e32, 1e64, 1e96, 1e128, 1e160, 1e192, 1e224, 1e256, 1e288,
}
@static pow10_neg_tab32 := [?]f64{
@(static, rodata) pow10_neg_tab32 := [?]f64{
1e-00, 1e-32, 1e-64, 1e-96, 1e-128, 1e-160, 1e-192, 1e-224, 1e-256, 1e-288, 1e-320,
}
@@ -1274,7 +1274,7 @@ binomial :: proc "contextless" (n, k: int) -> int {
@(require_results)
factorial :: proc "contextless" (n: int) -> int {
when size_of(int) == size_of(i64) {
@static table := [21]int{
@(static, rodata) table := [21]int{
1,
1,
2,
@@ -1298,7 +1298,7 @@ factorial :: proc "contextless" (n: int) -> int {
2_432_902_008_176_640_000,
}
} else {
@static table := [13]int{
@(static, rodata) table := [13]int{
1,
1,
2,
+3 -3
View File
@@ -67,7 +67,7 @@ package math
// masks any imprecision in the polynomial.
@(private="file", require_results)
stirling :: proc "contextless" (x: f64) -> (f64, f64) {
@(static) gamS := [?]f64{
@(static, rodata) gamS := [?]f64{
+7.87311395793093628397e-04,
-2.29549961613378126380e-04,
-2.68132617805781232825e-03,
@@ -103,7 +103,7 @@ gamma_f64 :: proc "contextless" (x: f64) -> f64 {
return false
}
@(static) gamP := [?]f64{
@(static, rodata) gamP := [?]f64{
1.60119522476751861407e-04,
1.19135147006586384913e-03,
1.04213797561761569935e-02,
@@ -112,7 +112,7 @@ gamma_f64 :: proc "contextless" (x: f64) -> f64 {
4.94214826801497100753e-01,
9.99999999999999996796e-01,
}
@(static) gamQ := [?]f64{
@(static, rodata) gamQ := [?]f64{
-2.31581873324120129819e-05,
+5.39605580493303397842e-04,
-4.45641913851797240494e-03,
+7 -7
View File
@@ -123,7 +123,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) {
return -x
}
@static lgamA := [?]f64{
@(static, rodata) lgamA := [?]f64{
0h3FB3C467E37DB0C8,
0h3FD4A34CC4A60FAD,
0h3FB13E001A5562A7,
@@ -137,7 +137,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) {
0h3EFA7074428CFA52,
0h3F07858E90A45837,
}
@static lgamR := [?]f64{
@(static, rodata) lgamR := [?]f64{
1.0,
0h3FF645A762C4AB74,
0h3FE71A1893D3DCDC,
@@ -146,7 +146,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) {
0h3F497DDACA41A95B,
0h3EDEBAF7A5B38140,
}
@static lgamS := [?]f64{
@(static, rodata) lgamS := [?]f64{
0hBFB3C467E37DB0C8,
0h3FCB848B36E20878,
0h3FD4D98F4F139F59,
@@ -155,7 +155,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) {
0h3F5E26B67368F239,
0h3F00BFECDD17E945,
}
@static lgamT := [?]f64{
@(static, rodata) lgamT := [?]f64{
0h3FDEF72BC8EE38A2,
0hBFC2E4278DC6C509,
0h3FB08B4294D5419B,
@@ -172,7 +172,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) {
0hBF347F24ECC38C38,
0h3F35FD3EE8C2D3F4,
}
@static lgamU := [?]f64{
@(static, rodata) lgamU := [?]f64{
0hBFB3C467E37DB0C8,
0h3FE4401E8B005DFF,
0h3FF7475CD119BD6F,
@@ -180,7 +180,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) {
0h3FCD4EAEF6010924,
0h3F8B678BBF2BAB09,
}
@static lgamV := [?]f64{
@(static, rodata) lgamV := [?]f64{
1.0,
0h4003A5D7C2BD619C,
0h40010725A42B18F5,
@@ -188,7 +188,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) {
0h3FBAAE55D6537C88,
0h3F6A5ABB57D0CF61,
}
@static lgamW := [?]f64{
@(static, rodata) lgamW := [?]f64{
0h3FDACFE390C97D69,
0h3FB555555555553B,
0hBF66C16C16B02E5C,
+1 -1
View File
@@ -234,7 +234,7 @@ _trig_reduce_f64 :: proc "contextless" (x: f64) -> (j: u64, z: f64) #no_bounds_c
// that is, 4/pi = Sum bd_pi4[i]*2^(-64*i)
// 19 64-bit digits and the leading one bit give 1217 bits
// of precision to handle the largest possible f64 exponent.
@static bd_pi4 := [?]u64{
@(static, rodata) bd_pi4 := [?]u64{
0x0000000000000001,
0x45f306dc9c882a53,
0xf84eafa3ea69bb81,
+3 -3
View File
@@ -19,7 +19,7 @@ import "core:math"
exp_float64 :: proc(r: ^Rand = nil) -> f64 {
re :: 7.69711747013104972
@(static)
@(static, rodata)
ke := [256]u32{
0xe290a139, 0x0, 0x9beadebc, 0xc377ac71, 0xd4ddb990,
0xde893fb8, 0xe4a8e87c, 0xe8dff16a, 0xebf2deab, 0xee49a6e8,
@@ -74,7 +74,7 @@ exp_float64 :: proc(r: ^Rand = nil) -> f64 {
0xf7b577d2, 0xf69c650c, 0xf51530f0, 0xf2cb0e3c, 0xeeefb15d,
0xe6da6ecf,
}
@(static)
@(static, rodata)
we := [256]f32{
2.0249555e-09, 1.486674e-11, 2.4409617e-11, 3.1968806e-11,
3.844677e-11, 4.4228204e-11, 4.9516443e-11, 5.443359e-11,
@@ -141,7 +141,7 @@ exp_float64 :: proc(r: ^Rand = nil) -> f64 {
1.2393786e-09, 1.276585e-09, 1.3193139e-09, 1.3695435e-09,
1.4305498e-09, 1.508365e-09, 1.6160854e-09, 1.7921248e-09,
}
@(static)
@(static, rodata)
fe := [256]f32{
1, 0.9381437, 0.90046996, 0.87170434, 0.8477855, 0.8269933,
0.8084217, 0.7915276, 0.77595687, 0.7614634, 0.7478686,
+3 -3
View File
@@ -21,7 +21,7 @@ import "core:math"
norm_float64 :: proc(r: ^Rand = nil) -> f64 {
rn :: 3.442619855899
@(static)
@(static, rodata)
kn := [128]u32{
0x76ad2212, 0x00000000, 0x600f1b53, 0x6ce447a6, 0x725b46a2,
0x7560051d, 0x774921eb, 0x789a25bd, 0x799045c3, 0x7a4bce5d,
@@ -50,7 +50,7 @@ norm_float64 :: proc(r: ^Rand = nil) -> f64 {
0x7da61a1e, 0x7d72a0fb, 0x7d30e097, 0x7cd9b4ab, 0x7c600f1a,
0x7ba90bdc, 0x7a722176, 0x77d664e5,
}
@(static)
@(static, rodata)
wn := [128]f32{
1.7290405e-09, 1.2680929e-10, 1.6897518e-10, 1.9862688e-10,
2.2232431e-10, 2.4244937e-10, 2.601613e-10, 2.7611988e-10,
@@ -85,7 +85,7 @@ norm_float64 :: proc(r: ^Rand = nil) -> f64 {
1.2601323e-09, 1.2857697e-09, 1.3146202e-09, 1.347784e-09,
1.3870636e-09, 1.4357403e-09, 1.5008659e-09, 1.6030948e-09,
}
@(static)
@(static, rodata)
fn := [128]f32{
1.00000000, 0.9635997, 0.9362827, 0.9130436, 0.89228165,
0.87324303, 0.8555006, 0.8387836, 0.8229072, 0.8077383,
+36
View File
@@ -0,0 +1,36 @@
Original BSD-3 license:
Two Level Segregated Fit memory allocator, version 3.1.
Written by Matthew Conte
http://tlsf.baisoku.org
Based on the original documentation by Miguel Masmano:
http://www.gii.upv.es/tlsf/main/docs
This implementation was written to the specification
of the document, therefore no GPL restrictions apply.
Copyright (c) 2006-2016, Matthew Conte
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL MATTHEW CONTE BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+156
View File
@@ -0,0 +1,156 @@
/*
Copyright 2024 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-3 license.
List of contributors:
Matt Conte: Original C implementation, see LICENSE file in this package
Jeroen van Rijn: Source port
*/
// package mem_tlsf implements a Two Level Segregated Fit memory allocator.
package mem_tlsf
import "base:runtime"
Error :: enum byte {
None = 0,
Invalid_Backing_Allocator = 1,
Invalid_Alignment = 2,
Backing_Buffer_Too_Small = 3,
Backing_Buffer_Too_Large = 4,
Backing_Allocator_Error = 5,
}
Allocator :: struct {
// Empty lists point at this block to indicate they are free.
block_null: Block_Header,
// Bitmaps for free lists.
fl_bitmap: u32 `fmt:"-"`,
sl_bitmap: [FL_INDEX_COUNT]u32 `fmt:"-"`,
// Head of free lists.
blocks: [FL_INDEX_COUNT][SL_INDEX_COUNT]^Block_Header `fmt:"-"`,
// Keep track of pools so we can deallocate them.
// If `pool.allocator` is blank, we don't do anything.
// We also use this linked list of pools to report
// statistics like how much memory is still available,
// fragmentation, etc.
pool: Pool,
}
#assert(size_of(Allocator) % ALIGN_SIZE == 0)
@(require_results)
allocator :: proc(t: ^Allocator) -> runtime.Allocator {
return runtime.Allocator{
procedure = allocator_proc,
data = t,
}
}
@(require_results)
init_from_buffer :: proc(control: ^Allocator, buf: []byte) -> Error {
assert(control != nil)
if uintptr(raw_data(buf)) % ALIGN_SIZE != 0 {
return .Invalid_Alignment
}
pool_bytes := align_down(len(buf) - 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[:])
}
@(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)
if pool_bytes < BLOCK_SIZE_MIN {
return .Backing_Buffer_Too_Small
} else if pool_bytes > BLOCK_SIZE_MAX {
return .Backing_Buffer_Too_Large
}
buf, backing_err := runtime.make_aligned([]byte, pool_bytes, ALIGN_SIZE, backing)
if backing_err != nil {
return .Backing_Allocator_Error
}
err := init_from_buffer(control, buf)
control.pool = Pool{
data = buf,
allocator = backing,
}
return err
}
init :: proc{init_from_buffer, init_from_allocator}
destroy :: proc(control: ^Allocator) {
if control == nil { return }
// 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
// Free the allocation on the backing allocator
runtime.delete(p.data, p.allocator)
free(p, p.allocator)
p = next
}
}
allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode,
size, alignment: int,
old_memory: rawptr, old_size: int, location := #caller_location) -> ([]byte, runtime.Allocator_Error) {
control := (^Allocator)(allocator_data)
if control == nil {
return nil, .Invalid_Argument
}
switch mode {
case .Alloc:
return alloc_bytes(control, uint(size), uint(alignment))
case .Alloc_Non_Zeroed:
return alloc_bytes_non_zeroed(control, uint(size), uint(alignment))
case .Free:
free_with_size(control, old_memory, uint(old_size))
return nil, nil
case .Free_All:
clear(control)
return nil, nil
case .Resize:
return resize(control, old_memory, uint(old_size), uint(size), uint(alignment))
case .Resize_Non_Zeroed:
return resize_non_zeroed(control, old_memory, uint(old_size), uint(size), uint(alignment))
case .Query_Features:
set := (^runtime.Allocator_Mode_Set)(old_memory)
if set != nil {
set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Resize_Non_Zeroed, .Query_Features}
}
return nil, nil
case .Query_Info:
return nil, .Mode_Not_Implemented
}
return nil, nil
}
+738
View File
@@ -0,0 +1,738 @@
/*
Copyright 2024 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-3 license.
List of contributors:
Matt Conte: Original C implementation, see LICENSE file in this package
Jeroen van Rijn: Source port
*/
package mem_tlsf
import "base:intrinsics"
import "base:runtime"
// import "core:fmt"
// log2 of number of linear subdivisions of block sizes.
// Larger values require more memory in the control structure.
// Values of 4 or 5 are typical.
TLSF_SL_INDEX_COUNT_LOG2 :: #config(TLSF_SL_INDEX_COUNT_LOG2, 5)
// All allocation sizes and addresses are aligned to 4/8 bytes
ALIGN_SIZE_LOG2 :: 3 when size_of(uintptr) == 8 else 2
// We can increase this to support larger allocation sizes,
// at the expense of more overhead in the TLSF structure
FL_INDEX_MAX :: 32 when size_of(uintptr) == 8 else 30
#assert(FL_INDEX_MAX < 36)
ALIGN_SIZE :: 1 << ALIGN_SIZE_LOG2
SL_INDEX_COUNT :: 1 << TLSF_SL_INDEX_COUNT_LOG2
FL_INDEX_SHIFT :: TLSF_SL_INDEX_COUNT_LOG2 + ALIGN_SIZE_LOG2
FL_INDEX_COUNT :: FL_INDEX_MAX - FL_INDEX_SHIFT + 1
SMALL_BLOCK_SIZE :: 1 << FL_INDEX_SHIFT
/*
We support allocations of sizes up to (1 << `FL_INDEX_MAX`) bits.
However, because we linearly subdivide the second-level lists, and
our minimum size granularity is 4 bytes, it doesn't make sense to
create first-level lists for sizes smaller than `SL_INDEX_COUNT` * 4,
or (1 << (`TLSF_SL_INDEX_COUNT_LOG2` + 2)) bytes, as there we will be
trying to split size ranges into more slots than we have available.
Instead, we calculate the minimum threshold size, and place all
blocks below that size into the 0th first-level list.
*/
// SL_INDEX_COUNT must be <= number of bits in sl_bitmap's storage tree
#assert(size_of(uint) * 8 >= SL_INDEX_COUNT)
// Ensure we've properly tuned our sizes.
#assert(ALIGN_SIZE == SMALL_BLOCK_SIZE / SL_INDEX_COUNT)
#assert(size_of(Allocator) % ALIGN_SIZE == 0)
Pool :: struct {
data: []u8 `fmt:"-"`,
allocator: runtime.Allocator,
next: ^Pool,
}
/*
Block header structure.
There are several implementation subtleties involved:
- The `prev_phys_block` field is only valid if the previous block is free.
- The `prev_phys_block` field is actually stored at the end of the
previous block. It appears at the beginning of this structure only to
simplify the implementation.
- The `next_free` / `prev_free` fields are only valid if the block is free.
*/
Block_Header :: struct {
prev_phys_block: ^Block_Header,
size: uint, // The size of this block, excluding the block header
// Next and previous free blocks.
next_free: ^Block_Header,
prev_free: ^Block_Header,
}
#assert(offset_of(Block_Header, prev_phys_block) == 0)
/*
Since block sizes are always at least a multiple of 4, the two least
significant bits of the size field are used to store the block status:
- bit 0: whether block is busy or free
- bit 1: whether previous block is busy or free
*/
BLOCK_HEADER_FREE :: uint(1 << 0)
BLOCK_HEADER_PREV_FREE :: uint(1 << 1)
/*
The size of the block header exposed to used blocks is the `size` field.
The `prev_phys_block` field is stored *inside* the previous free block.
*/
BLOCK_HEADER_OVERHEAD :: uint(size_of(uint))
POOL_OVERHEAD :: 2 * BLOCK_HEADER_OVERHEAD
// User data starts directly after the size field in a used block.
BLOCK_START_OFFSET :: offset_of(Block_Header, size) + size_of(Block_Header{}.size)
/*
A free block must be large enough to store its header minus the size of
the `prev_phys_block` field, and no larger than the number of addressable
bits for `FL_INDEX`.
*/
BLOCK_SIZE_MIN :: uint(size_of(Block_Header) - size_of(^Block_Header))
BLOCK_SIZE_MAX :: uint(1) << FL_INDEX_MAX
/*
TLSF achieves O(1) cost for `alloc` and `free` operations by limiting
the search for a free block to a free list of guaranteed size
adequate to fulfill the request, combined with efficient free list
queries using bitmasks and architecture-specific bit-manipulation
routines.
NOTE: TLSF spec relies on ffs/fls returning value 0..31.
*/
@(require_results)
ffs :: proc "contextless" (word: u32) -> (bit: i32) {
return -1 if word == 0 else i32(intrinsics.count_trailing_zeros(word))
}
@(require_results)
fls :: proc "contextless" (word: u32) -> (bit: i32) {
N :: (size_of(u32) * 8) - 1
return i32(N - intrinsics.count_leading_zeros(word))
}
@(require_results)
fls_uint :: proc "contextless" (size: uint) -> (bit: i32) {
N :: (size_of(uint) * 8) - 1
return i32(N - intrinsics.count_leading_zeros(size))
}
@(require_results)
block_size :: proc "contextless" (block: ^Block_Header) -> (size: uint) {
return block.size &~ (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE)
}
block_set_size :: proc "contextless" (block: ^Block_Header, size: uint) {
old_size := block.size
block.size = size | (old_size & (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE))
}
@(require_results)
block_is_last :: proc "contextless" (block: ^Block_Header) -> (is_last: bool) {
return block_size(block) == 0
}
@(require_results)
block_is_free :: proc "contextless" (block: ^Block_Header) -> (is_free: bool) {
return (block.size & BLOCK_HEADER_FREE) == BLOCK_HEADER_FREE
}
block_set_free :: proc "contextless" (block: ^Block_Header) {
block.size |= BLOCK_HEADER_FREE
}
block_set_used :: proc "contextless" (block: ^Block_Header) {
block.size &~= BLOCK_HEADER_FREE
}
@(require_results)
block_is_prev_free :: proc "contextless" (block: ^Block_Header) -> (is_prev_free: bool) {
return (block.size & BLOCK_HEADER_PREV_FREE) == BLOCK_HEADER_PREV_FREE
}
block_set_prev_free :: proc "contextless" (block: ^Block_Header) {
block.size |= BLOCK_HEADER_PREV_FREE
}
block_set_prev_used :: proc "contextless" (block: ^Block_Header) {
block.size &~= BLOCK_HEADER_PREV_FREE
}
@(require_results)
block_from_ptr :: proc(ptr: rawptr) -> (block_ptr: ^Block_Header) {
return (^Block_Header)(uintptr(ptr) - BLOCK_START_OFFSET)
}
@(require_results)
block_to_ptr :: proc(block: ^Block_Header) -> (ptr: rawptr) {
return rawptr(uintptr(block) + BLOCK_START_OFFSET)
}
// Return location of next block after block of given size.
@(require_results)
offset_to_block :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) {
return (^Block_Header)(uintptr(ptr) + uintptr(size))
}
@(require_results)
offset_to_block_backwards :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) {
return (^Block_Header)(uintptr(ptr) - uintptr(size))
}
// Return location of previous block.
@(require_results)
block_prev :: proc(block: ^Block_Header) -> (prev: ^Block_Header) {
assert(block_is_prev_free(block), "previous block must be free")
return block.prev_phys_block
}
// Return location of next existing block.
@(require_results)
block_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) {
return offset_to_block(block_to_ptr(block), block_size(block) - BLOCK_HEADER_OVERHEAD)
}
// Link a new block with its physical neighbor, return the neighbor.
@(require_results)
block_link_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) {
next = block_next(block)
next.prev_phys_block = block
return
}
block_mark_as_free :: proc(block: ^Block_Header) {
// Link the block to the next block, first.
next := block_link_next(block)
block_set_prev_free(next)
block_set_free(block)
}
block_mark_as_used :: proc(block: ^Block_Header) {
next := block_next(block)
block_set_prev_used(next)
block_set_used(block)
}
@(require_results)
align_up :: proc(x, align: uint) -> (aligned: uint) {
assert(0 == (align & (align - 1)), "must align to a power of two")
return (x + (align - 1)) &~ (align - 1)
}
@(require_results)
align_down :: proc(x, align: uint) -> (aligned: uint) {
assert(0 == (align & (align - 1)), "must align to a power of two")
return x - (x & (align - 1))
}
@(require_results)
align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) {
assert(0 == (align & (align - 1)), "must align to a power of two")
align_mask := uintptr(align) - 1
_ptr := uintptr(ptr)
_aligned := (_ptr + align_mask) &~ (align_mask)
return rawptr(_aligned)
}
// Adjust an allocation size to be aligned to word size, and no smaller than internal minimum.
@(require_results)
adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) {
if size == 0 {
return 0
}
// aligned size must not exceed `BLOCK_SIZE_MAX`, or we'll go out of bounds on `sl_bitmap`.
if aligned := align_up(size, align); aligned < BLOCK_SIZE_MAX {
adjusted = min(aligned, BLOCK_SIZE_MAX)
}
return
}
// Adjust an allocation size to be aligned to word size, and no smaller than internal minimum.
@(require_results)
adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err: runtime.Allocator_Error) {
if size == 0 {
return 0, nil
}
// aligned size must not exceed `BLOCK_SIZE_MAX`, or we'll go out of bounds on `sl_bitmap`.
if aligned := align_up(size, align); aligned < BLOCK_SIZE_MAX {
adjusted = min(aligned, BLOCK_SIZE_MAX)
} else {
err = .Out_Of_Memory
}
return
}
// TLSF utility functions. In most cases these are direct translations of
// the documentation in the research paper.
@(optimization_mode="speed", require_results)
mapping_insert :: proc(size: uint) -> (fl, sl: i32) {
if size < SMALL_BLOCK_SIZE {
// Store small blocks in first list.
sl = i32(size) / (SMALL_BLOCK_SIZE / SL_INDEX_COUNT)
} else {
fl = fls_uint(size)
sl = i32(size >> (uint(fl) - TLSF_SL_INDEX_COUNT_LOG2)) ~ (1 << TLSF_SL_INDEX_COUNT_LOG2)
fl -= (FL_INDEX_SHIFT - 1)
}
return
}
@(optimization_mode="speed", require_results)
mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) {
rounded = size
if size >= SMALL_BLOCK_SIZE {
round := uint(1 << (uint(fls_uint(size) - TLSF_SL_INDEX_COUNT_LOG2))) - 1
rounded += round
}
return
}
// This version rounds up to the next block size (for allocations)
@(optimization_mode="speed", require_results)
mapping_search :: proc(size: uint) -> (fl, sl: i32) {
return mapping_insert(mapping_round(size))
}
@(require_results)
search_suitable_block :: proc(control: ^Allocator, fli, sli: ^i32) -> (block: ^Block_Header) {
// First, search for a block in the list associated with the given fl/sl index.
fl := fli^; sl := sli^
sl_map := control.sl_bitmap[fli^] & (~u32(0) << uint(sl))
if sl_map == 0 {
// No block exists. Search in the next largest first-level list.
fl_map := control.fl_bitmap & (~u32(0) << uint(fl + 1))
if fl_map == 0 {
// No free blocks available, memory has been exhausted.
return {}
}
fl = ffs(fl_map)
fli^ = fl
sl_map = control.sl_bitmap[fl]
}
assert(sl_map != 0, "internal error - second level bitmap is null")
sl = ffs(sl_map)
sli^ = sl
// Return the first block in the free list.
return control.blocks[fl][sl]
}
// Remove a free block from the free list.
remove_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) {
prev := block.prev_free
next := block.next_free
assert(prev != nil, "prev_free can not be nil")
assert(next != nil, "next_free can not be nil")
next.prev_free = prev
prev.next_free = next
// If this block is the head of the free list, set new head.
if control.blocks[fl][sl] == block {
control.blocks[fl][sl] = next
// If the new head is nil, clear the bitmap
if next == &control.block_null {
control.sl_bitmap[fl] &~= (u32(1) << uint(sl))
// If the second bitmap is now empty, clear the fl bitmap
if control.sl_bitmap[fl] == 0 {
control.fl_bitmap &~= (u32(1) << uint(fl))
}
}
}
}
// Insert a free block into the free block list.
insert_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) {
current := control.blocks[fl][sl]
assert(current != nil, "free lists cannot have a nil entry")
assert(block != nil, "cannot insert a nil entry into the free list")
block.next_free = current
block.prev_free = &control.block_null
current.prev_free = block
assert(block_to_ptr(block) == align_ptr(block_to_ptr(block), ALIGN_SIZE), "block not properly aligned")
// Insert the new block at the head of the list, and mark the first- and second-level bitmaps appropriately.
control.blocks[fl][sl] = block
control.fl_bitmap |= (u32(1) << uint(fl))
control.sl_bitmap[fl] |= (u32(1) << uint(sl))
}
// Remove a given block from the free list.
block_remove :: proc(control: ^Allocator, block: ^Block_Header) {
fl, sl := mapping_insert(block_size(block))
remove_free_block(control, block, fl, sl)
}
// Insert a given block into the free list.
block_insert :: proc(control: ^Allocator, block: ^Block_Header) {
fl, sl := mapping_insert(block_size(block))
insert_free_block(control, block, fl, sl)
}
@(require_results)
block_can_split :: proc(block: ^Block_Header, size: uint) -> (can_split: bool) {
return block_size(block) >= size_of(Block_Header) + size
}
// Split a block into two, the second of which is free.
@(require_results)
block_split :: proc(block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) {
// Calculate the amount of space left in the remaining block.
remaining = offset_to_block(block_to_ptr(block), size - BLOCK_HEADER_OVERHEAD)
remain_size := block_size(block) - (size + BLOCK_HEADER_OVERHEAD)
assert(block_to_ptr(remaining) == align_ptr(block_to_ptr(remaining), ALIGN_SIZE),
"remaining block not aligned properly")
assert(block_size(block) == remain_size + size + BLOCK_HEADER_OVERHEAD)
block_set_size(remaining, remain_size)
assert(block_size(remaining) >= BLOCK_SIZE_MIN, "block split with invalid size")
block_set_size(block, size)
block_mark_as_free(remaining)
return remaining
}
// Absorb a free block's storage into an adjacent previous free block.
@(require_results)
block_absorb :: proc(prev: ^Block_Header, block: ^Block_Header) -> (absorbed: ^Block_Header) {
assert(!block_is_last(prev), "previous block can't be last")
// Note: Leaves flags untouched.
prev.size += block_size(block) + BLOCK_HEADER_OVERHEAD
_ = block_link_next(prev)
return prev
}
// Merge a just-freed block with an adjacent previous free block.
@(require_results)
block_merge_prev :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) {
merged = block
if (block_is_prev_free(block)) {
prev := block_prev(block)
assert(prev != nil, "prev physical block can't be nil")
assert(block_is_free(prev), "prev block is not free though marked as such")
block_remove(control, prev)
merged = block_absorb(prev, block)
}
return merged
}
// Merge a just-freed block with an adjacent free block.
@(require_results)
block_merge_next :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) {
merged = block
next := block_next(block)
assert(next != nil, "next physical block can't be nil")
if (block_is_free(next)) {
assert(!block_is_last(block), "previous block can't be last")
block_remove(control, next)
merged = block_absorb(block, next)
}
return merged
}
// Trim any trailing block space off the end of a free block, return to pool.
block_trim_free :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
assert(block_is_free(block), "block must be free")
if (block_can_split(block, size)) {
remaining_block := block_split(block, size)
_ = block_link_next(block)
block_set_prev_free(remaining_block)
block_insert(control, remaining_block)
}
}
// Trim any trailing block space off the end of a used block, return to pool.
block_trim_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
assert(!block_is_free(block), "Block must be used")
if (block_can_split(block, size)) {
// If the next block is free, we must coalesce.
remaining_block := block_split(block, size)
block_set_prev_used(remaining_block)
remaining_block = block_merge_next(control, remaining_block)
block_insert(control, remaining_block)
}
}
// Trim leading block space, return to pool.
@(require_results)
block_trim_free_leading :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) {
remaining = block
if block_can_split(block, size) {
// We want the 2nd block.
remaining = block_split(block, size - BLOCK_HEADER_OVERHEAD)
block_set_prev_free(remaining)
_ = block_link_next(block)
block_insert(control, block)
}
return remaining
}
@(require_results)
block_locate_free :: proc(control: ^Allocator, size: uint) -> (block: ^Block_Header) {
fl, sl: i32
if size != 0 {
fl, sl = mapping_search(size)
/*
`mapping_search` can futz with the size, so for excessively large sizes it can sometimes wind up
with indices that are off the end of the block array. So, we protect against that here,
since this is the only call site of `mapping_search`. Note that we don't need to check `sl`,
as it comes from a modulo operation that guarantees it's always in range.
*/
if fl < FL_INDEX_COUNT {
block = search_suitable_block(control, &fl, &sl)
}
}
if block != nil {
assert(block_size(block) >= size)
remove_free_block(control, block, fl, sl)
}
return block
}
@(require_results)
block_prepare_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (res: []byte, err: runtime.Allocator_Error) {
if block != nil {
assert(size != 0, "Size must be non-zero")
block_trim_free(control, block, size)
block_mark_as_used(block)
res = ([^]byte)(block_to_ptr(block))[:size]
}
return
}
// Clear control structure and point all empty lists at the null block
clear :: proc(control: ^Allocator) {
control.block_null.next_free = &control.block_null
control.block_null.prev_free = &control.block_null
control.fl_bitmap = 0
for i in 0..<FL_INDEX_COUNT {
control.sl_bitmap[i] = 0
for j in 0..<SL_INDEX_COUNT {
control.blocks[i][j] = &control.block_null
}
}
}
@(require_results)
pool_add :: proc(control: ^Allocator, pool: []u8) -> (err: Error) {
assert(uintptr(raw_data(pool)) % ALIGN_SIZE == 0, "Added memory must be aligned")
pool_overhead := POOL_OVERHEAD
pool_bytes := align_down(len(pool) - 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
}
// Create the main free block. Offset the start of the block slightly,
// so that the `prev_phys_block` field falls outside of the pool -
// it will never be used.
block := offset_to_block_backwards(raw_data(pool), BLOCK_HEADER_OVERHEAD)
block_set_size(block, pool_bytes)
block_set_free(block)
block_set_prev_used(block)
block_insert(control, block)
// Split the block to create a zero-size sentinel block
next := block_link_next(block)
block_set_size(next, 0)
block_set_used(next)
block_set_prev_free(next)
return
}
pool_remove :: proc(control: ^Allocator, pool: []u8) {
block := offset_to_block_backwards(raw_data(pool), BLOCK_HEADER_OVERHEAD)
assert(block_is_free(block), "Block should be free")
assert(!block_is_free(block_next(block)), "Next block should not be free")
assert(block_size(block_next(block)) == 0, "Next block size should be zero")
fl, sl := mapping_insert(block_size(block))
remove_free_block(control, block, fl, sl)
}
@(require_results)
alloc_bytes_non_zeroed :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) {
assert(control != nil)
adjust := adjust_request_size(size, ALIGN_SIZE)
GAP_MINIMUM :: size_of(Block_Header)
size_with_gap := adjust_request_size(adjust + align + GAP_MINIMUM, align)
aligned_size := size_with_gap if adjust != 0 && align > ALIGN_SIZE else adjust
if aligned_size == 0 && size > 0 {
return nil, .Out_Of_Memory
}
block := block_locate_free(control, aligned_size)
if block == nil {
return nil, .Out_Of_Memory
}
ptr := block_to_ptr(block)
aligned := align_ptr(ptr, align)
gap := uint(int(uintptr(aligned)) - int(uintptr(ptr)))
if gap != 0 && gap < GAP_MINIMUM {
gap_remain := GAP_MINIMUM - gap
offset := uintptr(max(gap_remain, align))
next_aligned := rawptr(uintptr(aligned) + offset)
aligned = align_ptr(next_aligned, align)
gap = uint(int(uintptr(aligned)) - int(uintptr(ptr)))
}
if gap != 0 {
assert(gap >= GAP_MINIMUM, "gap size too small")
block = block_trim_free_leading(control, block, gap)
}
return block_prepare_used(control, block, adjust)
}
@(require_results)
alloc_bytes :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) {
res, err = alloc_bytes_non_zeroed(control, size, align)
if err != nil {
intrinsics.mem_zero(raw_data(res), len(res))
}
return
}
free_with_size :: proc(control: ^Allocator, ptr: rawptr, size: uint) {
assert(control != nil)
// `size` is currently ignored
if ptr == nil {
return
}
block := block_from_ptr(ptr)
assert(!block_is_free(block), "block already marked as free") // double free
block_mark_as_free(block)
block = block_merge_prev(control, block)
block = block_merge_next(control, block)
block_insert(control, block)
}
@(require_results)
resize :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size: uint, alignment: uint) -> (res: []byte, err: runtime.Allocator_Error) {
assert(control != nil)
if ptr != nil && new_size == 0 {
free_with_size(control, ptr, old_size)
return
} else if ptr == nil {
return alloc_bytes(control, new_size, alignment)
}
block := block_from_ptr(ptr)
next := block_next(block)
curr_size := block_size(block)
combined := curr_size + block_size(next) + BLOCK_HEADER_OVERHEAD
adjust := adjust_request_size(new_size, max(ALIGN_SIZE, alignment))
assert(!block_is_free(block), "block already marked as free") // double free
min_size := min(curr_size, new_size, old_size)
if adjust > curr_size && (!block_is_free(next) || adjust > combined) {
res = alloc_bytes(control, new_size, alignment) or_return
if res != nil {
copy(res, ([^]byte)(ptr)[:min_size])
free_with_size(control, ptr, curr_size)
}
return
}
if adjust > curr_size {
_ = block_merge_next(control, block)
block_mark_as_used(block)
}
block_trim_used(control, block, adjust)
res = ([^]byte)(ptr)[:new_size]
if min_size < new_size {
to_zero := ([^]byte)(ptr)[min_size:new_size]
runtime.mem_zero(raw_data(to_zero), len(to_zero))
}
return
}
@(require_results)
resize_non_zeroed :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size: uint, alignment: uint) -> (res: []byte, err: runtime.Allocator_Error) {
assert(control != nil)
if ptr != nil && new_size == 0 {
free_with_size(control, ptr, old_size)
return
} else if ptr == nil {
return alloc_bytes_non_zeroed(control, new_size, alignment)
}
block := block_from_ptr(ptr)
next := block_next(block)
curr_size := block_size(block)
combined := curr_size + block_size(next) + BLOCK_HEADER_OVERHEAD
adjust := adjust_request_size(new_size, max(ALIGN_SIZE, alignment))
assert(!block_is_free(block), "block already marked as free") // double free
min_size := min(curr_size, new_size, old_size)
if adjust > curr_size && (!block_is_free(next) || adjust > combined) {
res = alloc_bytes_non_zeroed(control, new_size, alignment) or_return
if res != nil {
copy(res, ([^]byte)(ptr)[:min_size])
free_with_size(control, ptr, old_size)
}
return
}
if adjust > curr_size {
_ = block_merge_next(control, block)
block_mark_as_used(block)
}
block_trim_used(control, block, adjust)
res = ([^]byte)(ptr)[:new_size]
return
}
+5 -1
View File
@@ -87,8 +87,12 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
find_data := &win32.WIN32_FIND_DATAW{}
find_handle := win32.FindFirstFileW(raw_data(wpath_search), find_data)
if find_handle == win32.INVALID_HANDLE_VALUE {
err = Errno(win32.GetLastError())
return dfi[:], err
}
defer win32.FindClose(find_handle)
for n != 0 && find_handle != nil {
for n != 0 {
fi: File_Info
fi = find_data_to_file_info(path, find_data)
if fi.name != "" {
+1 -1
View File
@@ -111,7 +111,7 @@ next_random :: proc(r: ^[2]u64) -> u64 {
@(require_results)
random_string :: proc(buf: []byte) -> string {
@static digits := "0123456789"
@(static, rodata) digits := "0123456789"
u := next_random(&random_string_seed)
+105
View File
@@ -0,0 +1,105 @@
package slice
import "base:runtime"
// An in-place permutation iterator.
Permutation_Iterator :: struct($T: typeid) {
index: int,
slice: []T,
counters: []int,
}
/*
Make an iterator to permute a slice in-place.
*Allocates Using Provided Allocator*
This procedure allocates some state to assist in permutation and does not make
a copy of the underlying slice. If you want to permute a slice without altering
the underlying data, use `clone` to create a copy, then permute that instead.
Inputs:
- slice: The slice to permute.
- allocator: (default is context.allocator)
Returns:
- iter: The iterator, to be passed to `permute`.
- error: An `Allocator_Error`, if allocation failed.
*/
make_permutation_iterator :: proc(
slice: []$T,
allocator := context.allocator,
) -> (
iter: Permutation_Iterator(T),
error: runtime.Allocator_Error,
) #optional_allocator_error {
iter.slice = slice
iter.counters = make([]int, len(iter.slice), allocator) or_return
return
}
/*
Free the state allocated by `make_permutation_iterator`.
Inputs:
- iter: The iterator created by `make_permutation_iterator`.
- allocator: The allocator used to create the iterator. (default is context.allocator)
*/
destroy_permutation_iterator :: proc(
iter: Permutation_Iterator($T),
allocator := context.allocator,
) {
delete(iter.counters, allocator = allocator)
}
/*
Permute a slice in-place.
Note that the first iteration will always be the original, unpermuted slice.
Inputs:
- iter: The iterator created by `make_permutation_iterator`.
Returns:
- ok: True if the permutation succeeded, false if the iteration is complete.
*/
permute :: proc(iter: ^Permutation_Iterator($T)) -> (ok: bool) {
// This is an iterative, resumable implementation of Heap's algorithm.
//
// The original algorithm was described by B. R. Heap as "Permutations by
// interchanges" in The Computer Journal, 1963.
//
// This implementation is based on the nonrecursive version described by
// Robert Sedgewick in "Permutation Generation Methods" which was published
// in ACM Computing Surveys in 1977.
i := iter.index
if i == 0 {
iter.index = 1
return true
}
n := len(iter.counters)
#no_bounds_check for i < n {
if iter.counters[i] < i {
if i & 1 == 0 {
iter.slice[0], iter.slice[i] = iter.slice[i], iter.slice[0]
} else {
iter.slice[iter.counters[i]], iter.slice[i] = iter.slice[i], iter.slice[iter.counters[i]]
}
iter.counters[i] += 1
i = 1
break
} else {
iter.counters[i] = 0
i += 1
}
}
if i == n {
return false
}
iter.index = i
return true
}
+1 -1
View File
@@ -375,7 +375,7 @@ decimal_to_float_bits :: proc(d: ^decimal.Decimal, info: ^Float_Info) -> (b: u64
return
}
@static power_table := [?]int{1, 3, 6, 9, 13, 16, 19, 23, 26}
@(static, rodata) power_table := [?]int{1, 3, 6, 9, 13, 16, 19, 23, 26}
exp = 0
for d.decimal_point > 0 {
+1 -1
View File
@@ -1095,7 +1095,7 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) {
}
trunc_block: if !trunc {
@static pow10 := [?]f64{
@(static, rodata) pow10 := [?]f64{
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9,
1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
1e20, 1e21, 1e22,
+1 -1
View File
@@ -52,7 +52,7 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, durati
}
} else {
timeout_ns := u32(duration) * 1000
timeout_ns := u32(duration)
s := __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, u64(expected), timeout_ns)
if s >= 0 {
return true
+1
View File
@@ -527,6 +527,7 @@ macos_release_map: map[string]Darwin_To_Release = {
"23D60" = {{23, 3, 0}, "macOS", {"Sonoma", {14, 3, 1}}},
"23E214" = {{23, 4, 0}, "macOS", {"Sonoma", {14, 4, 0}}},
"23E224" = {{23, 4, 0}, "macOS", {"Sonoma", {14, 4, 1}}},
"23F79" = {{23, 5, 0}, "macOS", {"Sonoma", {14, 5, 0}}},
}
@(private)
+3
View File
@@ -53,6 +53,9 @@ get_log_level :: #force_inline proc() -> runtime.Logger_Level {
else when LOG_LEVEL == "warning" { return .Warning }
else when LOG_LEVEL == "error" { return .Error }
else when LOG_LEVEL == "fatal" { return .Fatal }
else {
#panic("Unknown `ODIN_TEST_LOG_LEVEL`: \"" + LOG_LEVEL + "\", possible levels are: \"debug\", \"info\", \"warning\", \"error\", or \"fatal\".")
}
}
}
+12 -11
View File
@@ -2,11 +2,11 @@
// +private
package thread
import "base:intrinsics"
import "core:sync"
import "core:sys/unix"
import "core:time"
CAS :: intrinsics.atomic_compare_exchange_strong
CAS :: sync.atomic_compare_exchange_strong
// NOTE(tetra): Aligned here because of core/unix/pthread_linux.odin/pthread_t.
// Also see core/sys/darwin/mach_darwin.odin/semaphore_t.
@@ -32,11 +32,13 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
t.id = sync.current_thread_id()
for (.Started not_in t.flags) {
sync.wait(&t.cond, &t.mutex)
for (.Started not_in sync.atomic_load(&t.flags)) {
// HACK: use a timeout so in the event that the condition is signalled at THIS comment's exact point
// (after checking flags, before starting the wait) it gets itself out of that deadlock after a ms.
sync.wait_with_timeout(&t.cond, &t.mutex, time.Millisecond)
}
if .Joined in t.flags {
if .Joined in sync.atomic_load(&t.flags) {
return nil
}
@@ -60,11 +62,11 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
t.procedure(t)
}
intrinsics.atomic_store(&t.flags, t.flags + { .Done })
sync.atomic_or(&t.flags, { .Done })
sync.unlock(&t.mutex)
if .Self_Cleanup in t.flags {
if .Self_Cleanup in sync.atomic_load(&t.flags) {
t.unix_thread = {}
// NOTE(ftphikari): It doesn't matter which context 'free' received, right?
context = {}
@@ -122,13 +124,12 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
}
_start :: proc(t: ^Thread) {
// sync.guard(&t.mutex)
t.flags += { .Started }
sync.atomic_or(&t.flags, { .Started })
sync.signal(&t.cond)
}
_is_done :: proc(t: ^Thread) -> bool {
return .Done in intrinsics.atomic_load(&t.flags)
return .Done in sync.atomic_load(&t.flags)
}
_join :: proc(t: ^Thread) {
@@ -139,7 +140,7 @@ _join :: proc(t: ^Thread) {
}
// Preserve other flags besides `.Joined`, like `.Started`.
unjoined := intrinsics.atomic_load(&t.flags) - {.Joined}
unjoined := sync.atomic_load(&t.flags) - {.Joined}
joined := unjoined + {.Joined}
// Try to set `t.flags` from unjoined to joined. If it returns joined,
+1
View File
@@ -389,6 +389,7 @@ is_leap_year :: proc "contextless" (year: int) -> (leap: bool) {
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
}
@(rodata)
days_before := [?]i32{
0,
31,
+24
View File
@@ -0,0 +1,24 @@
//+private
//+build orca
package time
_IS_SUPPORTED :: false
_now :: proc "contextless" () -> Time {
return {}
}
_sleep :: proc "contextless" (d: Duration) {
}
_tick_now :: proc "contextless" () -> Tick {
// mul_div_u64 :: proc "contextless" (val, num, den: i64) -> i64 {
// q := val / den
// r := val % den
// return q * num + r * num / den
// }
return {}
}
_yield :: proc "contextless" () {
}
+10
View File
@@ -12,6 +12,7 @@ package unicode
@(private) pLo :: pLl | pLu // a letter that is neither upper nor lower case.
@(private) pLmask :: pLo
@(rodata)
char_properties := [MAX_LATIN1+1]u8{
0x00 = pC, // '\x00'
0x01 = pC, // '\x01'
@@ -272,6 +273,7 @@ char_properties := [MAX_LATIN1+1]u8{
}
@(rodata)
alpha_ranges := [?]i32{
0x00d8, 0x00f6,
0x00f8, 0x01f5,
@@ -427,6 +429,7 @@ alpha_ranges := [?]i32{
0xffda, 0xffdc,
}
@(rodata)
alpha_singlets := [?]i32{
0x00aa,
0x00b5,
@@ -462,6 +465,7 @@ alpha_singlets := [?]i32{
0xfe74,
}
@(rodata)
space_ranges := [?]i32{
0x0009, 0x000d, // tab and newline
0x0020, 0x0020, // space
@@ -477,6 +481,7 @@ space_ranges := [?]i32{
0xfeff, 0xfeff,
}
@(rodata)
unicode_spaces := [?]i32{
0x0009, // tab
0x000a, // LF
@@ -494,6 +499,7 @@ unicode_spaces := [?]i32{
0xfeff, // unknown
}
@(rodata)
to_upper_ranges := [?]i32{
0x0061, 0x007a, 468, // a-z A-Z
0x00e0, 0x00f6, 468,
@@ -532,6 +538,7 @@ to_upper_ranges := [?]i32{
0xff41, 0xff5a, 468,
}
@(rodata)
to_upper_singlets := [?]i32{
0x00ff, 621,
0x0101, 499,
@@ -875,6 +882,7 @@ to_upper_singlets := [?]i32{
0x1ff3, 509,
}
@(rodata)
to_lower_ranges := [?]i32{
0x0041, 0x005a, 532, // A-Z a-z
0x00c0, 0x00d6, 532, // - -
@@ -914,6 +922,7 @@ to_lower_ranges := [?]i32{
0xff21, 0xff3a, 532, // - -
}
@(rodata)
to_lower_singlets := [?]i32{
0x0100, 501,
0x0102, 501,
@@ -1250,6 +1259,7 @@ to_lower_singlets := [?]i32{
0x1ffc, 491,
}
@(rodata)
to_title_singlets := [?]i32{
0x01c4, 501,
0x01c6, 499,
+1
View File
@@ -910,6 +910,7 @@ gb_internal void report_os_info() {
{"23D60", {23, 3, 0}, "macOS", {"Sonoma", {14, 3, 1}}},
{"23E214", {23, 4, 0}, "macOS", {"Sonoma", {14, 4, 0}}},
{"23E224", {23, 4, 0}, "macOS", {"Sonoma", {14, 4, 1}}},
{"23F79", {23, 5, 0}, "macOS", {"Sonoma", {14, 5, 0}}},
};
+22 -6
View File
@@ -1088,7 +1088,6 @@ gb_global TargetMetrics target_orca_wasm32 = {
TargetArch_wasm32,
4, 4, 8, 16,
str_lit("wasm32-wasi-js"),
// str_lit("e-m:e-p:32:32-i64:64-n32:64-S128"),
};
@@ -1138,6 +1137,14 @@ gb_global TargetMetrics target_freestanding_arm64 = {
str_lit("aarch64-none-elf"),
};
gb_global TargetMetrics target_freestanding_arm32 = {
TargetOs_freestanding,
TargetArch_arm32,
4, 4, 4, 8,
str_lit("arm-unknown-unknown-gnueabihf"),
};
struct NamedTargetMetrics {
String name;
TargetMetrics *metrics;
@@ -1170,6 +1177,7 @@ gb_global NamedTargetMetrics named_targets[] = {
{ str_lit("freestanding_wasm32"), &target_freestanding_wasm32 },
{ str_lit("wasi_wasm32"), &target_wasi_wasm32 },
{ str_lit("js_wasm32"), &target_js_wasm32 },
{ str_lit("orca_wasm32"), &target_orca_wasm32 },
{ str_lit("freestanding_wasm64p32"), &target_freestanding_wasm64p32 },
{ str_lit("js_wasm64p32"), &target_js_wasm64p32 },
@@ -1179,6 +1187,7 @@ gb_global NamedTargetMetrics named_targets[] = {
{ str_lit("freestanding_amd64_win64"), &target_freestanding_amd64_win64 },
{ str_lit("freestanding_arm64"), &target_freestanding_arm64 },
{ str_lit("freestanding_arm32"), &target_freestanding_arm32 },
};
gb_global NamedTargetMetrics *selected_target_metrics;
@@ -2035,6 +2044,9 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta
bc->link_flags = str_lit("/machine:x86 ");
break;
}
} else if (bc->metrics.os == TargetOs_darwin) {
bc->link_flags = concatenate3_strings(permanent_allocator(),
str_lit("-target "), bc->metrics.target_triplet, str_lit(" "));
} else if (is_arch_wasm()) {
gbString link_flags = gb_string_make(heap_allocator(), " ");
// link_flags = gb_string_appendc(link_flags, "--export-all ");
@@ -2045,16 +2057,20 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta
// }
if (bc->no_entry_point || bc->metrics.os == TargetOs_orca) {
link_flags = gb_string_appendc(link_flags, "--no-entry ");
bc->no_entry_point = true; // just in case for the "orca" target
}
bc->link_flags = make_string_c(link_flags);
// Disallow on wasm
bc->use_separate_modules = false;
} else {
bc->link_flags = concatenate3_strings(permanent_allocator(),
str_lit("-target "), bc->metrics.target_triplet, str_lit(" "));
// NOTE: for targets other than darwin, we don't specify a `-target` link flag.
// This is because we don't support cross-linking and clang is better at figuring
// out what the actual target for linking is,
// for example, on x86/alpine/musl it HAS to be `x86_64-alpine-linux-musl` to link correctly.
//
// Note that codegen will still target the triplet we specify, but the intricate details of
// a target shouldn't matter as much to codegen (if it does at all) as it does to linking.
}
// NOTE: needs to be done after adding the -target flag to the linker flags so the linker
+11 -23
View File
@@ -2433,6 +2433,9 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
if (arg_count > max_count) {
error(call, "Too many 'swizzle' indices, %td > %td", arg_count, max_count);
return false;
} else if (arg_count < 2) {
error(call, "Not enough 'swizzle' indices, %td < 2", arg_count);
return false;
}
if (type->kind == Type_Array) {
@@ -5926,15 +5929,9 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
if (operand->mode != Addressing_Type) {
error(operand->expr, "Expected a record type for '%.*s'", LIT(builtin_name));
} else {
Type *bt = base_type(operand->type);
if (bt->kind == Type_Struct) {
if (bt->Struct.polymorphic_params != nullptr) {
operand->value = exact_value_i64(bt->Struct.polymorphic_params->Tuple.variables.count);
}
} else if (bt->kind == Type_Union) {
if (bt->Union.polymorphic_params != nullptr) {
operand->value = exact_value_i64(bt->Union.polymorphic_params->Tuple.variables.count);
}
TypeTuple *tuple = get_record_polymorphic_params(operand->type);
if (tuple) {
operand->value = exact_value_i64(tuple->variables.count);
} else {
error(operand->expr, "Expected a record type for '%.*s'", LIT(builtin_name));
}
@@ -5966,20 +5963,11 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
Entity *param = nullptr;
i64 count = 0;
Type *bt = base_type(operand->type);
if (bt->kind == Type_Struct) {
if (bt->Struct.polymorphic_params != nullptr) {
count = bt->Struct.polymorphic_params->Tuple.variables.count;
if (index < count) {
param = bt->Struct.polymorphic_params->Tuple.variables[cast(isize)index];
}
}
} else if (bt->kind == Type_Union) {
if (bt->Union.polymorphic_params != nullptr) {
count = bt->Union.polymorphic_params->Tuple.variables.count;
if (index < count) {
param = bt->Union.polymorphic_params->Tuple.variables[cast(isize)index];
}
TypeTuple *tuple = get_record_polymorphic_params(operand->type);
if (tuple) {
count = tuple->variables.count;
if (index < count) {
param = tuple->variables[cast(isize)index];
}
} else {
error(operand->expr, "Expected a specialized polymorphic record type for '%.*s'", LIT(builtin_name));
+6
View File
@@ -1264,6 +1264,9 @@ gb_internal void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast
if (ac.is_static) {
error(e->token, "@(static) is not supported for global variables, nor required");
}
if (ac.rodata) {
e->Variable.is_rodata = true;
}
ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix, ac.link_suffix);
if (is_arch_wasm() && e->Variable.thread_local_model.len != 0) {
@@ -1350,6 +1353,9 @@ gb_internal void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast
Operand o = {};
check_expr_with_type_hint(ctx, &o, init_expr, e->type);
check_init_variable(ctx, e, &o, str_lit("variable declaration"));
if (e->Variable.is_rodata && o.mode != Addressing_Constant) {
error(o.expr, "Variables declared with @(rodata) must have constant initialization");
}
check_rtti_type_disallowed(e->token, e->type, "A variable declaration is using a type, %s, which has been disallowed");
}
+22 -11
View File
@@ -281,8 +281,20 @@ gb_internal void error_operand_not_expression(Operand *o) {
gb_internal void error_operand_no_value(Operand *o) {
if (o->mode == Addressing_NoValue) {
gbString err = expr_to_string(o->expr);
Ast *x = unparen_expr(o->expr);
if (x->kind == Ast_CallExpr) {
Ast *p = unparen_expr(x->CallExpr.proc);
if (p->kind == Ast_BasicDirective) {
String tag = p->BasicDirective.name.string;
if (tag == "panic" ||
tag == "assert") {
return;
}
}
}
gbString err = expr_to_string(o->expr);
if (x->kind == Ast_CallExpr) {
error(o->expr, "'%s' call does not return a value and cannot be used as a value", err);
} else {
@@ -3338,6 +3350,9 @@ gb_internal void check_cast(CheckerContext *c, Operand *x, Type *type) {
if (is_type_untyped(x->type)) {
Type *final_type = type;
if (is_const_expr && !is_type_constant_type(type)) {
if (is_type_union(type)) {
convert_to_typed(c, x, type);
}
final_type = default_type(x->type);
}
update_untyped_expr_type(c, x->expr, final_type, true);
@@ -4286,7 +4301,8 @@ gb_internal void convert_to_typed(CheckerContext *c, Operand *operand, Type *tar
} else {
switch (operand->type->Basic.kind) {
case Basic_UntypedBool:
if (!is_type_boolean(target_type)) {
if (!is_type_boolean(target_type) &&
!is_type_integer(target_type)) {
operand->mode = Addressing_Invalid;
convert_untyped_error(c, operand, target_type);
return;
@@ -7331,14 +7347,9 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O
gbString s = gb_string_make_reserve(heap_allocator(), e->token.string.len+3);
s = gb_string_append_fmt(s, "%.*s(", LIT(e->token.string));
Type *params = nullptr;
switch (bt->kind) {
case Type_Struct: params = bt->Struct.polymorphic_params; break;
case Type_Union: params = bt->Union.polymorphic_params; break;
}
if (params != nullptr) for_array(i, params->Tuple.variables) {
Entity *v = params->Tuple.variables[i];
TypeTuple *tuple = get_record_polymorphic_params(e->type);
if (tuple != nullptr) for_array(i, tuple->variables) {
Entity *v = tuple->variables[i];
String name = v->token.string;
if (i > 0) {
s = gb_string_append_fmt(s, ", ");
@@ -8344,7 +8355,7 @@ gb_internal ExprKind check_basic_directive_expr(CheckerContext *c, Operand *o, A
name == "assert" ||
name == "defined" ||
name == "config" ||
name == "exists" ||
name == "exists" ||
name == "load" ||
name == "load_hash" ||
name == "load_directory" ||
+14 -2
View File
@@ -501,6 +501,9 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O
return nullptr;
case Addressing_Variable:
if (e && e->kind == Entity_Variable && e->Variable.is_rodata) {
error(lhs->expr, "Assignment to variable '%.*s' marked as @(rodata) is not allowed", LIT(e->token.string));
}
break;
case Addressing_MapIndex: {
@@ -1252,8 +1255,6 @@ gb_internal void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags
error_line("\t%.*s\n", LIT(f->token.string));
}
}
error_line("\n");
error_line("\tSuggestion: Was '#partial switch' wanted?\n");
}
}
@@ -2055,6 +2056,13 @@ gb_internal void check_value_decl_stmt(CheckerContext *ctx, Ast *node, u32 mod_f
}
}
}
if (ac.rodata) {
if (ac.is_static) {
e->Variable.is_rodata = true;
} else {
error(e->token, "Only global or @(static) variables can have @(rodata) applied");
}
}
if (ac.thread_local_model != "") {
String name = e->token.string;
if (name == "_") {
@@ -2493,6 +2501,10 @@ gb_internal void check_return_stmt(CheckerContext *ctx, Ast *node) {
unsafe_return_error(o, "the address of an indexed variable", f->type);
}
}
} else if (o.mode == Addressing_Constant && is_type_slice(o.type)) {
ERROR_BLOCK();
unsafe_return_error(o, "a compound literal of a slice");
error_line("\tNote: A constant slice value will use the memory of the current stack frame\n");
}
}
+10 -18
View File
@@ -564,19 +564,7 @@ gb_internal bool check_record_poly_operand_specialization(CheckerContext *ctx, T
gb_internal Entity *find_polymorphic_record_entity(GenTypesData *found_gen_types, isize param_count, Array<Operand> const &ordered_operands) {
for (Entity *e : found_gen_types->types) {
Type *t = base_type(e->type);
TypeTuple *tuple = nullptr;
switch (t->kind) {
case Type_Struct:
if (t->Struct.polymorphic_params) {
tuple = &t->Struct.polymorphic_params->Tuple;
}
break;
case Type_Union:
if (t->Union.polymorphic_params) {
tuple = &t->Union.polymorphic_params->Tuple;
}
break;
}
TypeTuple *tuple = get_record_polymorphic_params(t);
GB_ASSERT_MSG(tuple != nullptr, "%s :: %s", type_to_string(e->type), type_to_string(t));
GB_ASSERT(param_count == tuple->variables.count);
@@ -663,6 +651,8 @@ gb_internal void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast *
&struct_type->Struct.is_polymorphic,
node, poly_operands
);
wait_signal_set(&struct_type->Struct.polymorphic_wait_signal);
struct_type->Struct.is_poly_specialized = check_record_poly_operand_specialization(ctx, struct_type, poly_operands, &struct_type->Struct.is_polymorphic);
if (original_type_for_poly) {
GB_ASSERT(named_type != nullptr);
@@ -712,6 +702,8 @@ gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *no
&union_type->Union.is_polymorphic,
node, poly_operands
);
wait_signal_set(&union_type->Union.polymorphic_wait_signal);
union_type->Union.is_poly_specialized = check_record_poly_operand_specialization(ctx, union_type, poly_operands, &union_type->Union.is_polymorphic);
if (original_type_for_poly) {
GB_ASSERT(named_type != nullptr);
@@ -784,7 +776,7 @@ gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *no
}
}
if (variants.count < 2) {
error(ut->align, "A union with #no_nil must have at least 2 variants");
error(node, "A union with #no_nil must have at least 2 variants");
}
break;
}
@@ -1457,8 +1449,8 @@ gb_internal bool check_type_specialization_to(CheckerContext *ctx, Type *special
s->Struct.polymorphic_params != nullptr &&
t->Struct.polymorphic_params != nullptr) {
TypeTuple *s_tuple = &s->Struct.polymorphic_params->Tuple;
TypeTuple *t_tuple = &t->Struct.polymorphic_params->Tuple;
TypeTuple *s_tuple = get_record_polymorphic_params(s);
TypeTuple *t_tuple = get_record_polymorphic_params(t);
GB_ASSERT(t_tuple->variables.count == s_tuple->variables.count);
for_array(i, s_tuple->variables) {
Entity *s_e = s_tuple->variables[i];
@@ -1510,8 +1502,8 @@ gb_internal bool check_type_specialization_to(CheckerContext *ctx, Type *special
s->Union.polymorphic_params != nullptr &&
t->Union.polymorphic_params != nullptr) {
TypeTuple *s_tuple = &s->Union.polymorphic_params->Tuple;
TypeTuple *t_tuple = &t->Union.polymorphic_params->Tuple;
TypeTuple *s_tuple = get_record_polymorphic_params(s);
TypeTuple *t_tuple = get_record_polymorphic_params(t);
GB_ASSERT(t_tuple->variables.count == s_tuple->variables.count);
for_array(i, s_tuple->variables) {
Entity *s_e = s_tuple->variables[i];
+6
View File
@@ -3628,6 +3628,12 @@ gb_internal DECL_ATTRIBUTE_PROC(var_decl_attribute) {
}
ac->is_static = true;
return true;
} else if (name == "rodata") {
if (value != nullptr) {
error(elem, "'rodata' does not have any parameters");
}
ac->rodata = true;
return true;
} else if (name == "thread_local") {
ExactValue ev = check_decl_attribute_value(c, value);
if (ac->init_expr_list_count > 0) {
+1
View File
@@ -133,6 +133,7 @@ struct AttributeContext {
bool entry_point_only : 1;
bool instrumentation_enter : 1;
bool instrumentation_exit : 1;
bool rodata : 1;
u32 optimization_mode; // ProcedureOptimizationMode
i64 foreign_import_priority_index;
String extra_linker_flags;
+1
View File
@@ -230,6 +230,7 @@ struct Entity {
bool is_foreign;
bool is_export;
bool is_global;
bool is_rodata;
} Variable;
struct {
Type * type_parameter_specialization;
+33 -32
View File
@@ -390,8 +390,6 @@ gb_internal void error_va(TokenPos const &pos, TokenPos end, char const *fmt, va
error_out_empty();
} else {
error_out_pos(pos);
}
if (has_ansi_terminal_colours()) {
error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red);
}
error_out_va(fmt, va);
@@ -407,29 +405,31 @@ gb_internal void warning_va(TokenPos const &pos, TokenPos end, char const *fmt,
error_va(pos, end, fmt, va);
return;
}
if (global_ignore_warnings()) {
return;
}
global_error_collector.warning_count.fetch_add(1);
mutex_lock(&global_error_collector.mutex);
push_error_value(pos, ErrorValue_Warning);
if (!global_ignore_warnings()) {
if (pos.line == 0) {
if (pos.line == 0) {
error_out_empty();
error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow);
error_out_va(fmt, va);
error_out("\n");
} else {
// global_error_collector.prev = pos;
if (json_errors()) {
error_out_empty();
error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow);
error_out_va(fmt, va);
error_out("\n");
} else {
// global_error_collector.prev = pos;
if (json_errors()) {
error_out_empty();
} else {
error_out_pos(pos);
}
error_out_pos(pos);
error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow);
error_out_va(fmt, va);
error_out("\n");
show_error_on_line(pos, end);
}
error_out_va(fmt, va);
error_out("\n");
show_error_on_line(pos, end);
}
try_pop_error_value();
mutex_unlock(&global_error_collector.mutex);
@@ -544,30 +544,31 @@ gb_internal void syntax_warning_va(TokenPos const &pos, TokenPos end, char const
syntax_error_va(pos, end, fmt, va);
return;
}
if (global_ignore_warnings()) {
return;
}
mutex_lock(&global_error_collector.mutex);
global_error_collector.warning_count++;
push_error_value(pos, ErrorValue_Warning);
if (!global_ignore_warnings()) {
if (pos.line == 0) {
if (pos.line == 0) {
error_out_empty();
error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow);
error_out_va(fmt, va);
error_out("\n");
} else {
// global_error_collector.prev = pos;
if (json_errors()) {
error_out_empty();
error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow);
error_out_va(fmt, va);
error_out("\n");
} else {
// global_error_collector.prev = pos;
if (json_errors()) {
error_out_empty();
} else {
error_out_pos(pos);
}
error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow);
error_out_va(fmt, va);
error_out("\n");
// show_error_on_line(pos, end);
error_out_pos(pos);
}
error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow);
error_out_va(fmt, va);
error_out("\n");
// show_error_on_line(pos, end);
}
try_pop_error_value();
@@ -838,4 +839,4 @@ gb_internal void print_all_errors(void) {
gb_file_write(f, res, gb_string_length(res));
errors_already_printed = true;
}
}
+29 -15
View File
@@ -13,6 +13,7 @@ struct LinkerData {
};
gb_internal i32 system_exec_command_line_app(char const *name, char const *fmt, ...);
gb_internal bool system_exec_command_line_app_output(char const *command, gbString *output);
#if defined(GB_SYSTEM_OSX)
gb_internal void linker_enable_system_library_linking(LinkerData *ld) {
@@ -69,27 +70,40 @@ gb_internal i32 linker_stage(LinkerData *gen) {
if (is_arch_wasm()) {
timings_start_section(timings, str_lit("wasm-ld"));
String extra_orca_flags = {};
gbString extra_orca_flags = gb_string_make(temporary_allocator(), "");
gbString inputs = gb_string_make(temporary_allocator(), "");
inputs = gb_string_append_fmt(inputs, "\"%.*s.o\"", LIT(output_filename));
#if defined(GB_SYSTEM_WINDOWS)
if (build_context.metrics.os == TargetOs_orca) {
extra_orca_flags = str_lit(" W:/orca/installation/dev-afb9591/bin/liborca_wasm.a --export-dynamic");
gbString orca_sdk_path = gb_string_make(temporary_allocator(), "");
if (!system_exec_command_line_app_output("orca sdk-path", &orca_sdk_path)) {
gb_printf_err("executing `orca sdk-path` failed, make sure Orca is installed and added to your path\n");
return 1;
}
if (gb_string_length(orca_sdk_path) == 0) {
gb_printf_err("executing `orca sdk-path` did not produce output\n");
return 1;
}
inputs = gb_string_append_fmt(inputs, " \"%s/orca-libc/lib/crt1.o\" \"%s/orca-libc/lib/libc.o\"", orca_sdk_path, orca_sdk_path);
extra_orca_flags = gb_string_append_fmt(extra_orca_flags, " -L \"%s/bin\" -lorca_wasm --export-dynamic", orca_sdk_path);
}
result = system_exec_command_line_app("wasm-ld",
"\"%.*s\\bin\\wasm-ld\" \"%.*s.o\" -o \"%.*s\" %.*s %.*s %.*s",
LIT(build_context.ODIN_ROOT),
LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags),
LIT(extra_orca_flags));
#else
if (build_context.metrics.os == TargetOs_orca) {
extra_orca_flags = str_lit(" -L . -lorca --export-dynamic");
}
#if defined(GB_SYSTEM_WINDOWS)
result = system_exec_command_line_app("wasm-ld",
"wasm-ld \"%.*s.o\" -o \"%.*s\" %.*s %.*s %.*s",
LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags),
LIT(extra_orca_flags));
"\"%.*s\\bin\\wasm-ld\" %s -o \"%.*s\" %.*s %.*s %s",
LIT(build_context.ODIN_ROOT),
inputs, LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags),
extra_orca_flags);
#else
result = system_exec_command_line_app("wasm-ld",
"wasm-ld %s -o \"%.*s\" %.*s %.*s %s",
inputs, LIT(output_filename),
LIT(build_context.link_flags),
LIT(build_context.extra_linker_flags),
extra_orca_flags);
#endif
return result;
}
+9 -1
View File
@@ -900,7 +900,15 @@ namespace lbAbiAmd64SysV {
}
switch (LLVMGetTypeKind(t)) {
case LLVMIntegerTypeKind:
case LLVMIntegerTypeKind: {
i64 s = t_size;
while (s > 0) {
unify(cls, ix + off/8, RegClass_Int);
off += 8;
s -= 8;
}
break;
}
case LLVMPointerTypeKind:
case LLVMHalfTypeKind:
unify(cls, ix + off/8, RegClass_Int);
+18 -2
View File
@@ -1160,6 +1160,10 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc
if (is_type_untyped_nil(init.type)) {
LLVMSetInitializer(var.var.value, LLVMConstNull(global_type));
var.is_initialized = true;
if (e->Variable.is_rodata) {
LLVMSetGlobalConstant(var.var.value, true);
}
continue;
}
GB_PANIC("Invalid init value, got %s", expr_to_string(init_expr));
@@ -1174,6 +1178,10 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc
}
LLVMSetInitializer(var.var.value, init.value);
var.is_initialized = true;
if (e->Variable.is_rodata) {
LLVMSetGlobalConstant(var.var.value, true);
}
continue;
}
} else {
@@ -1206,8 +1214,9 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc
var.is_initialized = true;
}
}
CheckerInfo *info = main_module->gen->info;
for (Entity *e : info->init_procedures) {
@@ -3210,14 +3219,21 @@ gb_internal bool lb_generate_code(lbGenerator *gen) {
lbValue init = lb_const_value(m, tav.type, v);
LLVMSetInitializer(g.value, init.value);
var.is_initialized = true;
if (e->kind == Entity_Variable && e->Variable.is_rodata) {
LLVMSetGlobalConstant(g.value, true);
}
}
}
}
if (!var.is_initialized && is_type_untyped_nil(tav.type)) {
var.is_initialized = true;
if (e->kind == Entity_Variable && e->Variable.is_rodata) {
LLVMSetGlobalConstant(g.value, true);
}
}
} else if (e->kind == Entity_Variable && e->Variable.is_rodata) {
LLVMSetGlobalConstant(g.value, true);
}
array_add(&global_variables, var);
lb_add_entity(m, e, g);
+33 -2
View File
@@ -504,6 +504,10 @@ gb_internal bool lb_is_matrix_simdable(Type *t) {
if ((mt->Matrix.row_count & 1) ^ (mt->Matrix.column_count & 1)) {
return false;
}
if (mt->Matrix.is_row_major) {
// TODO(bill): make #row_major matrices work with SIMD
return false;
}
if (elem->kind == Type_Basic) {
switch (elem->Basic.kind) {
@@ -1869,13 +1873,40 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) {
lbValue res_i128 = lb_emit_runtime_call(p, call, args);
return lb_emit_conv(p, res_i128, t);
}
i64 sz = type_size_of(src);
lbValue res = {};
res.type = t;
if (is_type_unsigned(dst)) {
res.value = LLVMBuildFPToUI(p->builder, value.value, lb_type(m, t), "");
switch (sz) {
case 2:
case 4:
res.value = LLVMBuildFPToUI(p->builder, value.value, lb_type(m, t_u32), "");
res.value = LLVMBuildIntCast2(p->builder, res.value, lb_type(m, t), false, "");
break;
case 8:
res.value = LLVMBuildFPToUI(p->builder, value.value, lb_type(m, t_u64), "");
res.value = LLVMBuildIntCast2(p->builder, res.value, lb_type(m, t), false, "");
break;
default:
GB_PANIC("Unhandled float type");
break;
}
} else {
res.value = LLVMBuildFPToSI(p->builder, value.value, lb_type(m, t), "");
switch (sz) {
case 2:
case 4:
res.value = LLVMBuildFPToSI(p->builder, value.value, lb_type(m, t_i32), "");
res.value = LLVMBuildIntCast2(p->builder, res.value, lb_type(m, t), true, "");
break;
case 8:
res.value = LLVMBuildFPToSI(p->builder, value.value, lb_type(m, t_i64), "");
res.value = LLVMBuildIntCast2(p->builder, res.value, lb_type(m, t), true, "");
break;
default:
GB_PANIC("Unhandled float type");
break;
}
}
return res;
}
+2 -2
View File
@@ -1383,8 +1383,6 @@ gb_internal lbValue lb_addr_load(lbProcedure *p, lbAddr const &addr) {
LLVMTypeRef vector_type = nullptr;
if (lb_try_vector_cast(p->module, addr.addr, &vector_type)) {
LLVMSetAlignment(res.addr.value, cast(unsigned)lb_alignof(vector_type));
LLVMValueRef vp = LLVMBuildPointerCast(p->builder, addr.addr.value, LLVMPointerType(vector_type, 0), "");
LLVMValueRef v = LLVMBuildLoad2(p->builder, vector_type, vp, "");
LLVMValueRef scalars[4] = {};
@@ -1394,6 +1392,8 @@ gb_internal lbValue lb_addr_load(lbProcedure *p, lbAddr const &addr) {
LLVMValueRef mask = LLVMConstVector(scalars, addr.swizzle.count);
LLVMValueRef sv = llvm_basic_shuffle(p, v, mask);
LLVMSetAlignment(res.addr.value, cast(unsigned)lb_alignof(LLVMTypeOf(sv)));
LLVMValueRef dst = LLVMBuildPointerCast(p->builder, ptr.value, LLVMPointerType(LLVMTypeOf(sv), 0), "");
LLVMBuildStore(p->builder, sv, dst);
} else {
+2 -3
View File
@@ -710,13 +710,12 @@ gb_internal void lb_begin_procedure_body(lbProcedure *p) {
lb_set_debug_position_to_procedure_begin(p);
if (p->debug_info != nullptr) {
if (p->context_stack.count != 0) {
lbBlock *prev_block = p->curr_block;
p->curr_block = p->decl_block;
lb_add_debug_context_variable(p, lb_find_or_generate_context_ptr(p));
p->curr_block = prev_block;
}
}
lb_start_block(p, p->entry_block);
}
gb_internal void lb_end_procedure_body(lbProcedure *p) {
+3 -1
View File
@@ -1850,7 +1850,9 @@ gb_internal void lb_build_static_variables(lbProcedure *p, AstValueDecl *vd) {
LLVMSetInitializer(global, LLVMConstNull(lb_type(p->module, e->type)));
if (value.value != nullptr) {
LLVMSetInitializer(global, value.value);
} else {
}
if (e->Variable.is_rodata) {
LLVMSetGlobalConstant(global, true);
}
if (e->Variable.thread_local_model != "") {
LLVMSetThreadLocal(global, true);
+32
View File
@@ -155,6 +155,38 @@ gb_internal i32 system_exec_command_line_app(char const *name, char const *fmt,
return exit_code;
}
#if defined(GB_SYSTEM_WINDOWS)
#define popen _popen
#define pclose _pclose
#endif
gb_internal bool system_exec_command_line_app_output(char const *command, gbString *output) {
GB_ASSERT(output);
u8 buffer[256];
FILE *stream;
stream = popen(command, "r");
if (!stream) {
return false;
}
defer (pclose(stream));
while (!feof(stream)) {
size_t n = fread(buffer, 1, 255, stream);
*output = gb_string_append_length(*output, buffer, n);
if (ferror(stream)) {
return false;
}
}
if (build_context.show_system_calls) {
gb_printf_err("[SYSTEM CALL OUTPUT] %s -> %s\n", command, *output);
}
return true;
}
gb_internal Array<String> setup_args(int argc, char const **argv) {
gbAllocator a = heap_allocator();
+8 -2
View File
@@ -140,6 +140,7 @@ struct TypeStruct {
i64 custom_field_align;
Type * polymorphic_params; // Type_Tuple
Type * polymorphic_parent;
Wait_Signal polymorphic_wait_signal;
Type * soa_elem;
i32 soa_count;
@@ -167,6 +168,7 @@ struct TypeUnion {
i64 custom_align;
Type * polymorphic_params; // Type_Tuple
Type * polymorphic_parent;
Wait_Signal polymorphic_wait_signal;
i16 tag_size;
bool is_polymorphic;
@@ -1093,6 +1095,7 @@ gb_internal Type *alloc_type_struct() {
gb_internal Type *alloc_type_struct_complete() {
Type *t = alloc_type(Type_Struct);
wait_signal_set(&t->Struct.fields_wait_signal);
wait_signal_set(&t->Struct.polymorphic_wait_signal);
return t;
}
@@ -1491,10 +1494,10 @@ gb_internal i64 matrix_align_of(Type *t, struct TypePath *tp) {
i64 total_expected_size = row_count*t->Matrix.column_count*elem_size;
// i64 min_alignment = prev_pow2(elem_align * row_count);
i64 min_alignment = prev_pow2(total_expected_size);
while ((total_expected_size % min_alignment) != 0) {
while (total_expected_size != 0 && (total_expected_size % min_alignment) != 0) {
min_alignment >>= 1;
}
GB_ASSERT(min_alignment >= elem_align);
min_alignment = gb_max(min_alignment, elem_align);
i64 align = gb_min(min_alignment, build_context.max_simd_align);
return align;
@@ -2136,15 +2139,18 @@ gb_internal bool is_type_polymorphic_record_unspecialized(Type *t) {
return false;
}
gb_internal TypeTuple *get_record_polymorphic_params(Type *t) {
t = base_type(t);
switch (t->kind) {
case Type_Struct:
wait_signal_until_available(&t->Struct.polymorphic_wait_signal);
if (t->Struct.polymorphic_params) {
return &t->Struct.polymorphic_params->Tuple;
}
break;
case Type_Union:
wait_signal_until_available(&t->Union.polymorphic_wait_signal);
if (t->Union.polymorphic_params) {
return &t->Union.polymorphic_params->Tuple;
}
-14
View File
@@ -1,14 +0,0 @@
ODIN=../../odin
COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false
all: crypto_bench \
hash_bench
crypto_bench:
$(ODIN) test crypto $(COMMON) -o:speed -out:bench_crypto
hash_bench:
$(ODIN) test hash $(COMMON) -o:speed -out:bench_hash
clean:
rm bench_*
+4
View File
@@ -0,0 +1,4 @@
package benchmarks
@(require) import "crypto"
@(require) import "hash"
-13
View File
@@ -1,13 +0,0 @@
@echo off
set COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false
set PATH_TO_ODIN==..\..\odin
echo ---
echo Running core:crypto benchmarks
echo ---
%PATH_TO_ODIN% test crypto %COMMON% -o:speed -out:bench_crypto.exe || exit /b
echo ---
echo Running core:hash benchmarks
echo ---
%PATH_TO_ODIN% test hash %COMMON% -o:speed -out:bench_hash.exe || exit /b
+1
View File
@@ -1,3 +1,4 @@
*.bmp
*.zip
*.png
math_big_test_library.*
-115
View File
@@ -1,115 +0,0 @@
ODIN=../../odin
PYTHON=$(shell which python3)
COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false
all: all_bsd \
net_test
all_bsd: download_test_assets \
c_libc_test \
compress_test \
container_test \
crypto_test \
encoding_test \
filepath_test \
fmt_test \
hash_test \
i18n_test \
image_test \
linalg_glsl_math_test \
match_test \
math_test \
noise_test \
odin_test \
os_exit_test \
reflect_test \
runtime_test \
slice_test \
strconv_test \
strings_test \
thread_test \
time_test
download_test_assets:
$(PYTHON) download_assets.py
c_libc_test:
$(ODIN) test c/libc $(COMMON) -out:test_core_libc
compress_test:
$(ODIN) test compress $(COMMON) -out:test_core_compress
container_test:
$(ODIN) test container $(COMMON) -out:test_core_container
crypto_test:
$(ODIN) test crypto $(COMMON) -o:speed -out:test_crypto
encoding_test:
$(ODIN) test encoding/base64 $(COMMON) -out:test_base64
$(ODIN) test encoding/cbor $(COMMON) -out:test_cbor
$(ODIN) test encoding/hex $(COMMON) -out:test_hex
$(ODIN) test encoding/hxa $(COMMON) -out:test_hxa
$(ODIN) test encoding/json $(COMMON) -out:test_json
$(ODIN) test encoding/varint $(COMMON) -out:test_varint
$(ODIN) test encoding/xml $(COMMON) -out:test_xml
filepath_test:
$(ODIN) test path/filepath $(COMMON) -out:test_core_filepath
fmt_test:
$(ODIN) test fmt $(COMMON) -out:test_core_fmt
hash_test:
$(ODIN) test hash $(COMMON) -o:speed -out:test_hash
image_test:
$(ODIN) test image $(COMMON) -out:test_core_image
i18n_test:
$(ODIN) test text/i18n $(COMMON) -out:test_core_i18n
match_test:
$(ODIN) test text/match $(COMMON) -out:test_core_match
math_test:
$(ODIN) test math $(COMMON) -out:test_core_math
linalg_glsl_math_test:
$(ODIN) test math/linalg/glsl $(COMMON) -out:test_linalg_glsl_math
noise_test:
$(ODIN) test math/noise $(COMMON) -out:test_noise
net_test:
$(ODIN) test net $(COMMON) -out:test_core_net
os_exit_test:
$(ODIN) run os/test_core_os_exit.odin -file -out:test_core_os_exit && exit 1 || exit 0
odin_test:
$(ODIN) test odin $(COMMON) -out:test_core_odin
reflect_test:
$(ODIN) test reflect $(COMMON) -out:test_core_reflect
runtime_test:
$(ODIN) test runtime $(COMMON) -out:test_core_runtime
slice_test:
$(ODIN) test slice $(COMMON) -out:test_core_slice
strconv_test:
$(ODIN) test strconv $(COMMON) -out:test_core_strconv
strings_test:
$(ODIN) test strings $(COMMON) -out:test_core_strings
thread_test:
$(ODIN) test thread $(COMMON) -out:test_core_thread
time_test:
$(ODIN) test time $(COMMON) -out:test_core_time
clean:
rm test_*
-124
View File
@@ -1,124 +0,0 @@
@echo off
set COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false
set PATH_TO_ODIN==..\..\odin
python3 download_assets.py
echo ---
echo Running core:c/libc tests
echo ---
%PATH_TO_ODIN% test c\libc %COMMON% -out:test_libc.exe || exit /b
echo ---
echo Running core:compress tests
echo ---
%PATH_TO_ODIN% test compress %COMMON% -out:test_core_compress.exe || exit /b
echo ---
echo Running core:container tests
echo ---
%PATH_TO_ODIN% test container %COMMON% -out:test_core_container.exe || exit /b
echo ---
echo Running core:crypto tests
echo ---
%PATH_TO_ODIN% test crypto %COMMON% -o:speed -out:test_crypto.exe || exit /b
echo ---
echo Running core:encoding tests
echo ---
%PATH_TO_ODIN% test encoding/base64 %COMMON% -out:test_base64.exe || exit /b
%PATH_TO_ODIN% test encoding/cbor %COMMON% -out:test_cbor.exe || exit /b
%PATH_TO_ODIN% test encoding/hex %COMMON% -out:test_hex.exe || exit /b
%PATH_TO_ODIN% test encoding/hxa %COMMON% -out:test_hxa.exe || exit /b
%PATH_TO_ODIN% test encoding/json %COMMON% -out:test_json.exe || exit /b
%PATH_TO_ODIN% test encoding/varint %COMMON% -out:test_varint.exe || exit /b
%PATH_TO_ODIN% test encoding/xml %COMMON% -out:test_xml.exe || exit /b
echo ---
echo Running core:path/filepath tests
echo ---
%PATH_TO_ODIN% test path/filepath %COMMON% -out:test_core_filepath.exe || exit /b
echo ---
echo Running core:fmt tests
echo ---
%PATH_TO_ODIN% test fmt %COMMON% -out:test_core_fmt.exe || exit /b
echo ---
echo Running core:hash tests
echo ---
%PATH_TO_ODIN% test hash %COMMON% -o:speed -out:test_core_hash.exe || exit /b
echo ---
echo Running core:image tests
echo ---
%PATH_TO_ODIN% test image %COMMON% -out:test_core_image.exe || exit /b
echo ---
echo Running core:text/i18n tests
echo ---
%PATH_TO_ODIN% test text\i18n %COMMON% -out:test_core_i18n.exe || exit /b
echo ---
echo Running text:match tests
echo ---
%PATH_TO_ODIN% test text/match %COMMON% -out:test_core_match.exe || exit /b
echo ---
echo Running core:math tests
echo ---
%PATH_TO_ODIN% test math %COMMON% -out:test_core_math.exe || exit /b
echo ---
echo Running core:math/linalg/glsl tests
echo ---
%PATH_TO_ODIN% test math/linalg/glsl %COMMON% -out:test_linalg_glsl.exe || exit /b
echo ---
echo Running core:math/noise tests
echo ---
%PATH_TO_ODIN% test math/noise %COMMON% -out:test_noise.exe || exit /b
echo ---
echo Running core:net
echo ---
%PATH_TO_ODIN% test net %COMMON% -out:test_core_net.exe || exit /b
echo ---
echo Running core:odin tests
echo ---
%PATH_TO_ODIN% test odin %COMMON% -o:size -out:test_core_odin.exe || exit /b
echo ---
echo Running core:reflect tests
echo ---
%PATH_TO_ODIN% test reflect %COMMON% -out:test_core_reflect.exe || exit /b
echo ---
echo Running core:runtime tests
echo ---
%PATH_TO_ODIN% test runtime %COMMON% -out:test_core_runtime.exe || exit /b
echo ---
echo Running core:slice tests
echo ---
%PATH_TO_ODIN% test slice %COMMON% -out:test_core_slice.exe || exit /b
echo ---
echo Running core:strconv tests
echo ---
%PATH_TO_ODIN% test strconv %COMMON% -out:test_core_strconv.exe || exit /b
echo ---
echo Running core:strings tests
echo ---
%PATH_TO_ODIN% test strings %COMMON% -out:test_core_strings.exe || exit /b
echo ---
echo Running core:thread tests
echo ---
%PATH_TO_ODIN% test thread %COMMON% -out:test_core_thread.exe || exit /b
echo ---
echo Running core:time tests
echo ---
%PATH_TO_ODIN% test time %COMMON% -out:test_core_time.exe || exit /b
+91 -2
View File
@@ -7,8 +7,8 @@ import zipfile
import hashlib
import hmac
TEST_SUITES = ['PNG', 'XML']
DOWNLOAD_BASE_PATH = "assets/{}"
TEST_SUITES = ['PNG', 'XML', 'BMP']
DOWNLOAD_BASE_PATH = sys.argv[1] + "/{}"
ASSETS_BASE_URL = "https://raw.githubusercontent.com/odin-lang/test-assets/master/{}/{}"
HMAC_KEY = "https://odin-lang.org"
HMAC_HASH = hashlib.sha3_512
@@ -192,6 +192,94 @@ HMAC_DIGESTS = {
'z06n2c08.png': "94268c1998de1f4304d24219e31175def7375cc26e2bbfc7d1ac20465a42fae49bcc8ff7626873138b537588e8bce21b6d5e1373efaade1f83cae455334074aa",
'z09n2c08.png': "3cbb1bb58d78ecc9dd5568a8e9093ba020b63449ef3ab102f98fac4220fc9619feaa873336a25f3c1ad99cfb3e5d32bcfe52d966bc8640d1d5ba4e061741743e",
'ba-bm.bmp': "2f76d46b1b9bea62e08e7fc5306452a495616cb7af7a0cbb79237ed457b083418d5859c9e6cfd0d9fbf1fe24495319b6f206135f36f2bd19330de01a8eaf20c8",
'badbitcount.bmp': "2d37e22aa2e659416c950815841e5a402f2e9c21eb677390fc026eefaeb5be64345a7ef0fac2965a2cae8abe78c1e12086a7d93d8e62cc8659b35168c82f6d5f",
'badbitssize.bmp': "f59cc30827bcb56f7e946dcffcaab22a5e197f2e3884cf80a2e596f5653f5203b3927674d9d5190486239964e65228f4e3f359cdd2f7d061b09846f5f26bfaa9",
'baddens1.bmp': "aa84bebc41b3d50329269da9ee61fd7e1518ffd0e8f733af6872323bc46ace6ed1c9931a65a367d97b8b2cb2aa772ccd94fd3def0a79fd1c0baf185d669c386f",
'baddens2.bmp': "5c254a8cde716fae77ebf20294a404383fd6afc705d783c5418762e7c4138aa621625bc6d08a8946ee3f1e8c40c767681a39806735bb3b3026fee5eb91d8fadc",
'badfilesize.bmp': "9019b6853a91f69bd246f9b28da47007aec871c0e46fea7cd6ab5c30460a6938a1b09da8fa7ba8895650e37ce14a79d4183e9f2401eb510f60455410e2266eb5",
'badheadersize.bmp': "90412d7c3bff7336d5e0c7ae899d8a53b82235072034f00783fb2403479447cd2959644f7ec70ae0988f99cc49b63356c8710b808ddd2280e19dca484f34074e",
'badpalettesize.bmp': "d914a89f7b78fcdd6ab4433c176355755687b65c3cfc23db57de9d04447c440fa31d993db184940c1dc09b37e8e044324d8237877d3d1b1ad5657c4929d8435a",
'badplanes.bmp': "46f583d4a43ef0c9964765b9d8820369955f0568a4eae0bf215434f508e8e03457bd759b73c344c2f88de7f33fc5379517ce3cf5b2e5a16ebc20c05df73aa723",
'badrle.bmp': "a64e1551fd60159ff469ce25e1f5b4575dc462684f4ff66c7ea69b2990c7c9d2547b72237020e2d001f69dfd31f1ac45e0a9630d0ddd11c77584881f3e25609e",
'badrle4.bmp': "2bd22418010b1ac3eac50932ed06e578411ac2741bfa50a9edd1b360686efa28c74df8b14d92e05b711eeb88a5e826256c6a5cf5a0176a29369fb92b336efb93",
'badrle4bis.bmp': "d7a24ab095e1ca5e888dd1bcb732b19bb1983f787c64c1eb5a273da0f58c4b8cd137197df9ac47572a74c3026aab5af1f08551a2121af37b8941cffa71df1951",
'badrle4ter.bmp': "825cc5361378d44524205b117825f95228c4d093d39ac2fc2ab755be743df78784529f2019418deca31059f3e46889a66658e7424b4f896668ee4cfa281574bc",
'badrlebis.bmp': "f41acfd4f989302bb5ec42a2e759a56f71a5ecac5a814842e32542742ca015464f8579ebeec0e7e9cea45e2aafe51456cfe18b48b509bc3704f992bcc9d321af",
'badrleter.bmp': "a8f3e0b0668fc4f43353028d5fca87d6cac6ff0c917c4e7a61c624918360ff598ec9eaa32f5c6a070da9bf6e90c58426f5a901fdab9dfb0a4fdca0c72ba67de4",
'badwidth.bmp': "68f192a55b8de66f8e13fe316647262a5e4641365eb77d4987c84ab1eae35b7cba20827277cd569583543819de70ec75f383367f72cd229e48743ad1e45bfa9e",
'pal1.bmp': "0194c9b501ac7e043fab78746e6f142e0c880917d0fd6dbb7215765b8fc1ce4403ad85146c555665ba6e37d3b47edad5e687b9260e7a61a27d8a059bc81bb525",
'pal1bg.bmp': "3aafc29122bd6e97d88d740be1f61cb9febe8373d19ae6d731f4af776c868dd489260287bf0cf1c960f9d9afcbc7448e83e45435d3e42e913823c0f5c2a80d9f",
'pal1huffmsb.bmp': "4e122f602c3556f4f5ab45f9e13a617d8210d81f587d08cbd6c2110dc6231573aec92a6344aeb4734c00d3dcf380130f53a887002756811d8edd6bc5aabbafc0",
'pal1p1.bmp': "33e2b2b1c1bed43ba64888d7739eb830c7789857352513de08b6e35718ac0e421afcdae0e7bab97c25d1ad972eb4f09e2c6556c416d4d7367c545330c4123df0",
'pal1wb.bmp': "bc583ad4eaae40f5d2e3a6280aeb3c62ee11b2cf05ba7c8386f9578587e29b66819293992bdcd31c2750c21cd9bf97daa603ce1051fbfdd40fadbc1860156853",
'pal2.bmp': "7b560ba972cf58ca1ed01910fa4f630ca74e657d46d134e2ac0df733eb5773d0a1788e745d5240efa18f182bd8dce22c7ac7cee6f99ddc946a27b65297762764",
'pal2color.bmp': "b868a8aaa22fac3aa86bbd5270eb5ffee06959461be8177880829d838be0391d9617d11d73fab1643520a92364dc333c25c0510bb2628c8fb945719518d2675f",
'pal4.bmp': "53a39fdb86630c828d9003a1e95dbd59c47524c4ec044d8ce72e1b643166b4c2b6ec06ab5191cb25d17be2fcb18bd7a9e0b7ec169722e6d89b725609a15b1df1",
'pal4gs.bmp': "ab4c2078943afdf19bcc02b1ebbe5a69cfa93d1152f7882db6176c39b917191a2760fbb2127e5207b0bfb3dafd711593a6aed61d312807605913062aa1ce9c2f",
'pal4rle.bmp': "c86c86280b75a252ccf484e4bba2df45d3747dc1e4879795e925613959a0c451e2fc4890532e8aef9911e38e45e7d6a8baf29d57e573d26c20923a5823700443",
'pal4rlecut.bmp': "f38d485dbb8e67bdeaefba181f9a05556a986ed3f834edca723c088e813764bb2b42240d4fbb938a1775370b79b9ea2f14277ffe9c7247c1e0e77766fec27189",
'pal4rletrns.bmp': "b81e7fed38854d201a3199ce50ca05e92ca287c860797142857ac20b4a2f28952b058e21687c0fae60712f5784cd2c950ce70148ba1316efe31d4f3fc4006817",
'pal8-0.bmp': "f39a4f1827c52bb620d975f8c72f5e95f90ac6c65ae0a6776ff1ad95808c090de17cbd182188a85157396fd9649ea4b5d84bb7c9175ab49ce2845da214c16bff",
'pal8.bmp': "be27e55a866cbb655fdd917435cd6a5b62c20ae0d6ef7c1533c5a01dd9a893f058cc4ba2d902ab9315380009808e06b7f180116c9b790587cf62aa770c7a4a07",
'pal8badindex.bmp': "bd5fc036985ae705182915a560dee2e5dfb3bd8b50932337b9085e190259c66e6bae5fbc813a261d352a60dcb0755798bdc251d6c2a0b638a7e337ba58811811",
'pal8gs.bmp': "228f944b3e45359f62a2839d4e7b94d7f3a1074ad9e25661fdb9e8fff4c15581c85a7bb0ac75c92b95c7537ececc9d80b835cfe55bc7560a513118224a9ed36f",
'pal8nonsquare.bmp': "b8adc9b03880975b232849ea1e8f87705937929d743df3d35420902b32557065354ab71d0d8176646bf0ad72c583c884cfcd1511017260ffca8c41d5a358a3eb",
'pal8offs.bmp': "c92f1e1835d753fd8484be5198b2b8a2660d5e54117f6c4fc6d2ebc8d1def72a8d09cd820b1b8dcee15740b47151d62b8b7aca0b843e696252e28226b51361cf",
'pal8os2-hs.bmp': "477c04048787eb412f192e7fe47ae96f14d7995391e78b10cc4c365f8c762f60c54cad7ef9d1705a78bd490a578fb346ee0a383c3a3fdf790558a12589eb04eb",
'pal8os2-sz.bmp': "fd0eeb733be9b39f492d0f67dd28fc67207149e41691c206d4de4c693b5dea9458b88699a781383e7050a3b343259659aae64fec0616c98f3f8555cbf5c9e46c",
'pal8os2.bmp': "cdab3ed7bc9f38d89117332a21418b3c916a99a8d8fb6b7ce456d54288c96152af12c0380293b04e96594a7867b83be5c99913d224c9750c7d38295924e0735a",
'pal8os2sp.bmp': "f6e595a6db992ab7d1f79442d31f39f648061e7de13e51b07933283df065ce405c0208e6101ac916e4eb0613e412116f008510021a2d17543aa7f0a32349c96f",
'pal8os2v2-16.bmp': "f52877d434218aa6b772a7aa0aaba4c2ae6ce35ecfa6876943bb350fdf9554f1f763a8d5bb054362fb8f9848eb71ce14a371f4a76da4b9475cdcee4f286109a4",
'pal8os2v2-40sz.bmp': "9481691ada527df1f529316d44b5857c6a840c5dafa7e9795c9cb92dac02c6cc35739d3f6ce33d4ab6ff6bcd6b949741e89dc8c42cf52ad4546ff58cd3b5b66a",
'pal8os2v2-sz.bmp': "99cd2836f90591cd27b0c8696ecff1e7a1debcef284bbe5d21e68759270c1bfe1f32ee8f576c49f3e64d8f4e4d9096574f3c8c79bfdae0545689da18364de3e7",
'pal8os2v2.bmp': "7859b265956c7d369db7a0a357ce09bcda74e98954de88f454cae5e7cb021222146687a7770ce0cc2c58f1439c7c21c45c0c27464944e73913e1c88afc836c8a",
'pal8oversizepal.bmp': "e778864e0669a33fce27c0ccd5b6460b572a5db01975e8d56acec8a9447e1c58d6051ad3516cfa96a39f4eb7f2576154188ea62ec187bcf4ae323883499383c0",
'pal8rle.bmp': "88942a1cd2e36d1e0f0e2748a888034057919c7ec0f8d9b2664deb1daf1a6e45ed3e722dff5d810f413d6fc182e700a16d6563dd25f67dc6d135d751cd736dea",
'pal8rlecut.bmp': "cda9fa274cde590aeaca81516e0465684cfae84e934eb983301801e978e6e2e9c590d22af992d9912e51bb9c2761945276bdbe0b6c47f3a021514414e1f3f455",
'pal8rletrns.bmp': "0b2d5618dc9c81caa72c070070a4245dd9cd3de5d344b76ce9c15d0eeb72e2675efc264201f8709dfcffd234df09e76d6f328f16f2ad873ba846f870cadfa486",
'pal8topdown.bmp': "d470a2b7556fa88eac820427cb900f59a121732cdb4a7f3530ed457798139c946a884a34ab79d822feb84c2ca6f4d9a65f6e792994eafc3a189948b9e4543546",
'pal8v4.bmp': "0382610e32c49d5091a096cb48a54ebbf44d9ba1def96e2f30826fd3ddf249f5aed70ca5b74c484b6cdc3924f4d4bfed2f5194ad0bcf1d99bfaa3a619e299d86",
'pal8v5.bmp': "50fadaa93aac2a377b565c4dc852fd4602538863b913cb43155f5ad7cf79928127ca28b33e5a3b0230076ea4a6e339e3bf57f019333f42c4e9f003a8f2376325",
'pal8w124.bmp': "e54a901b9badda655cad828d226b381831aea7e36aec8729147e9e95a9f2b21a9d74d93756e908e812902a01197f1379fe7e35498dbafed02e27c853a24097b7",
'pal8w125.bmp': "d7a04d45ef5b3830da071ca598f1e2a413c46834968b2db7518985cf8d8c7380842145899e133e71355b6b7d040ee9e97adec1e928ce4739282e0533058467c0",
'pal8w126.bmp': "4b93845a98797679423c669c541a248b4cdfee80736f01cec29d8b40584bf55a27835c80656a2bf5c7ad3ed211c1f7d3c7d5831a6726904b39f10043a76b658d",
'reallybig.bmp': "babbf0335bac63fd2e95a89210c61ae6bbaaeeab5f07974034e76b4dc2a5c755f77501e3d056479357445aac442e2807d7170ec44067bab8fd35742f0e7b8440",
'rgb16-231.bmp': "611a87cb5d29f16ef71971714a3b0e3863a6df51fff16ce4d4df8ee028442f9ce03669fb5d7a6a838a12a75d8a887b56b5a2e44a3ad62f4ef3fc2e238c33f6a1",
'rgb16-3103.bmp': "7fdff66f4d94341da522b4e40586b3b8c327be9778e461bca1600e938bfbaa872b484192b35cd84d9430ca20aa922ec0301567a74fb777c808400819db90b09d",
'rgb16-565.bmp': "777883f64b9ae80d77bf16c6d062082b7a4702f8260c183680afee6ec26e48681bcca75f0f81c470be1ac8fcb55620b3af5ce31d9e114b582dfd82300a3d6654",
'rgb16-565pal.bmp': "57e9dcf159415b3574a1b343a484391b0859ab2f480e22157f2a84bc188fde141a48826f960c6f30b1d1f17ef6503ec3afc883a2f25ff09dd50c437244f9ae7f",
'rgb16-880.bmp': "8d61183623002da4f7a0a66b42aa58a120e3a91578bb0c4a5e2c5ba7d08b875d43a22f2b5b3a449d3caf4cc303cb05111dd1d5169953f288493b7ea3c2423d24",
'rgb16.bmp': "1c0fe56661d4998edd76efedda520a441171d42ae4dad95b350e3b61deda984c3a3255392481fe1903e5e751357da3f35164935e323377f015774280036ba39e",
'rgb16bfdef.bmp': "ed55d086e27ba472076df418be0046b740944958afeb84d05aa2bbe578dec27ced122ffefb6d549e1d07e05eb608979b3ac9b1bd809f8237cf0984ffdaa24716",
'rgb16faketrns.bmp': "9cd4a8e05fe125649e360715851ef912e78a56d30e0ea1b1cfb6eaafd386437d45de9c1e1a845dd8d63ff5a414832355b8ae0e2a96d72a42a7205e8a2742d37c",
'rgb24.bmp': "4f0ce2978bbfea948798b2fdcc4bdbe8983a6c94d1b7326f39daa6059368e08ebf239260984b64eeb0948f7c8089a523e74b7fa6b0437f9205d8af8891340389",
'rgb24largepal.bmp': "b377aee1594c5d9fc806a70bc62ee83cf8d1852b4a2b18fd3e9409a31aa3b5a4cf5e3b4af2cbdebcef2b5861b7985a248239684a72072437c50151adc524e9df",
'rgb24pal.bmp': "f40bb6e01f6ecb3d55aa992bf1d1e2988ea5eb11e3e58a0c59a4fea2448de26f231f45e9f378b7ee1bdd529ec57a1de38ea536e397f5e1ac6998921e066ab427",
'rgb24png.bmp': "c60890bbd79c12472205665959eb6e2dd2103671571f80117b9e963f897cffca103181374a4797f53c7768af01a705e830a0de4dd8fab7241d24c17bee4a4dbe",
'rgb24rle24.bmp': "ea0ff3f512dd04176d14b43dfbee73ac7f1913aa1b77587e187e271781c7dacec880cec73850c4127ea9f8dd885f069e281f159bb5232e93cbb2d1ee9cb50438",
'rgb32-111110.bmp': "732884e300d4edbcf31556a038947beefc0c5e749131a66d2d7aa1d4ec8c8eba07833133038a03bbe4c4fa61a805a5df1f797b5853339ee6a8140478f5f70a76",
'rgb32-7187.bmp': "4c55aab2e4ecf63dc30b04e5685c5d9fba7354ca0e1327d7c4b15d6da10ac66ca1cea6f0303f9c5f046da3bcd2566275384e0e7bb14fcc5196ec39ca90fac194",
'rgb32-xbgr.bmp': "1e9f240eaec6ac2833f8c719f1fb53cc7551809936620e871ccacfab26402e1afc6503b9f707e4ec25f15529e3ce6433c7f999d5714af31dfb856eb67e772f64",
'rgb32.bmp': "32033dbf9462e5321b1182ba739624ed535aa4d33b775ffeeaf09d2d4cb663e4c3505e8c05489d940f754dde4b50a2e0b0688b21d06755e717e6e511b0947525",
'rgb32bf.bmp': "7243c215827a9b4a1d7d52d67fb04ffb43b0b176518fbdab43d413e2a0c18883b106797f1acd85ba68d494ec939b0caab8789564670d058caf0e1175ce7983fb",
'rgb32bfdef.bmp': "a81433babb67ce714285346a77bfccd19cf6203ac1d8245288855aff20cf38146a783f4a7eac221db63d1ee31345da1329e945b432f0e7bcf279ea88ff5bb302",
'rgb32fakealpha.bmp': "abecaf1b5bfad322de7aec897efe7aa6525f2a77a0af86cc0a0a366ed1650da703cf4b7b117a7ba34f21d03a8a0045e5821248cdefa00b0c78e01d434b55e746",
'rgb32h52.bmp': "707d734393c83384bc75720330075ec9ffefc69167343259ebf95f9393948364a40f33712619f962e7056483b73334584570962c16da212cd5291f764b3f2cd1",
'rgba16-1924.bmp': "3e41a5d8d951bac580c780370ca21e0783de8154f4081106bc58d1185bb2815fc5b7f08f2a1c75cd205fc52c888e9d07c91856651603a2d756c9cfc392585598",
'rgba16-4444.bmp': "a6662186b23bd434a7e019d2a71cd95f53a47b64a1afea4c27ae1120685d041a9ff98800a43a9d8f332682670585bdb2fa77ff77b6def65139fe725323f91561",
'rgba16-5551.bmp': "a7d9f8ae7f8349cd6df651ab9d814411939fa2a235506ddfdd0df5a8f8abcf75552c32481ea035ff29e683bdcd34da68eb23730857e0716b79af51d69a60757b",
'rgba32-1.bmp': "3958d18d2a7f32ada69cb11e0b4397821225a5c40acc7b6d36ff28ee4565e150cc508971278a2ddf8948aaff86f66ec6a0c24513db44962d81b79c4239b3e612",
'rgba32-1010102.bmp': "59a28db5563caf954d31b20a1d1cc59366fcfd258b7ba2949f7281978460a3d94bedcc314c089243dd7463bb18d36a9473355158a7d903912cb25b98eab6b068",
'rgba32-2.bmp': "9b7e5965ff9888f011540936ab6b3022edf9f6c5d7e541d6882cb81820cf1d68326d65695a6f0d01999ac10a496a504440906aa45e413da593405563c54c1a05",
'rgba32-61754.bmp': "784ae0a32b19fa925e0c86dbff3bd38d80904d0fa7dc3b03e9d4f707d42b1604c1f54229e901ccc249cab8c2976d58b1e16980157d9bf3dbc4e035e2b2fd1779",
'rgba32-81284.bmp': "fcfca645017c0d15d44b08598a90d238d063763fd06db665d9a8e36ef5099ce0bf4d440e615c6d6b1bf99f38230d4848318bfa1e6d9bfdd6dfd521cc633ba110",
'rgba32abf.bmp': "2883d676966d298d235196f102e044e52ef18f3cb5bb0dd84738c679f0a1901181483ca2df1cccf6e4b3b4e98be39e81de69c9a58f0d70bc3ebb0fcea80daa0c",
'rgba32h56.bmp': "507d0caf29ccb011c83c0c069c21105ea1d58e06b92204f9c612f26102123a7680eae53fef023c701952d903e11b61f8aa07618c381ea08f6808c523f5a84546",
'rgba64.bmp': "d01f14f649c1c33e3809508cc6f089dd2ab0a538baf833a91042f2e54eca3f8e409908e15fa8763b059d7fa022cf5c074d9f5720eed5293a4c922e131c2eae68",
'rletopdown.bmp': "37500893aad0b40656aa80fd5c7c5f9b35d033018b8070d8b1d7baeb34c90f90462288b13295204b90aa3e5c9be797d22a328e3714ab259334e879a09a3de175",
'shortfile.bmp': "be3ffade7999304f00f9b7d152b5b27811ad1166d0fd43004392467a28f44b6a4ec02a23c0296bacd4f02f8041cd824b9ca6c9fc31fed27e36e572113bb47d73",
'unicode.xml': "e0cdc94f07fdbb15eea811ed2ae6dcf494a83d197dafe6580c740270feb0d8f5f7146d4a7d4c2d2ea25f8bd9678bc986123484b39399819a6b7262687959d1ae",
}
@@ -233,6 +321,7 @@ def try_download_and_unpack_zip(suite):
hmac_digest = hmac.new(HMAC_KEY.encode(), file_data, HMAC_HASH).hexdigest()
print("{} *{}".format(hmac_digest, file.filename))
if not hmac.compare_digest(hmac_digest, HMAC_DIGESTS[file.filename]):
print("FAIL! Expected: {}".format(HMAC_DIGESTS[file.filename]))
return 4
+40 -3
View File
@@ -221,8 +221,45 @@ test_fmt_python_syntax :: proc(t: ^testing.T) {
check(t, "%!(MISSING CLOSE BRACE)%!(EXTRA 1)", "{0", 1 )
}
@(test)
test_pointers :: proc(t: ^testing.T) {
S :: struct { i: int }
a: rawptr
b: ^int
c: ^S
d: ^S = cast(^S)cast(uintptr)0xFFFF
check(t, "0x0", "%p", a)
check(t, "0x0", "%p", b)
check(t, "0x0", "%p", c)
check(t, "0xFFFF", "%p", d)
check(t, "0x0", "%#p", a)
check(t, "0x0", "%#p", b)
check(t, "0x0", "%#p", c)
check(t, "0xFFFF", "%#p", d)
check(t, "0x0", "%v", a)
check(t, "0x0", "%v", b)
check(t, "<nil>", "%v", c)
check(t, "0x0", "%#v", a)
check(t, "0x0", "%#v", b)
check(t, "<nil>", "%#v", c)
check(t, "0x0000", "%4p", a)
check(t, "0x0000", "%4p", b)
check(t, "0x0000", "%4p", c)
check(t, "0xFFFF", "%4p", d)
check(t, "0x0000", "%#4p", a)
check(t, "0x0000", "%#4p", b)
check(t, "0x0000", "%#4p", c)
check(t, "0xFFFF", "%#4p", d)
}
@(private)
check :: proc(t: ^testing.T, exp: string, format: string, args: ..any) {
check :: proc(t: ^testing.T, exp: string, format: string, args: ..any, loc := #caller_location) {
got := fmt.tprintf(format, ..args)
testing.expectf(t, got == exp, "(%q, %v): %q != %q", format, args, got, exp)
}
testing.expectf(t, got == exp, "(%q, %v): %q != %q", format, args, got, exp, loc = loc)
}
-2
View File
@@ -1,2 +0,0 @@
@echo off
odin test . -define:ODIN_TEST_TRACK_MEMORY=true -define:ODIN_TEST_PROGRESS_WIDTH=12 -vet -strict-style
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -8,7 +8,7 @@
This file exports procedures for use with the test.py test suite.
*/
package math_big_tests
package test_core_math_big
/*
TODO: Write tests for `internal_*` and test reusing parameters with the public implementations.
@@ -0,0 +1,37 @@
package test_core_math_big
import "core:math/big"
import "core:testing"
@(test)
test_permutations_and_combinations :: proc(t: ^testing.T) {
{
calc, exp := &big.Int{}, &big.Int{}
defer big.destroy(calc, exp)
big.permutations_without_repetition(calc, 9000, 10)
big.int_atoi(exp, "3469387884476822917768284664849390080000")
equals, error := big.equals(calc, exp)
testing.expect(t, equals)
testing.expect_value(t, error, nil)
}
{
calc, exp := &big.Int{}, &big.Int{}
defer big.destroy(calc, exp)
big.combinations_with_repetition(calc, 9000, 10)
big.int_atoi(exp, "965678962435231708695393645683400")
equals, error := big.equals(calc, exp)
testing.expect(t, equals)
testing.expect_value(t, error, nil)
}
{
calc, exp := &big.Int{}, &big.Int{}
defer big.destroy(calc, exp)
big.combinations_without_repetition(calc, 9000, 10)
big.int_atoi(exp, "956070294443568925751842114431600")
equals, error := big.equals(calc, exp)
testing.expect(t, equals)
testing.expect_value(t, error, nil)
}
}
+41
View File
@@ -0,0 +1,41 @@
package test_core_mem
import "core:mem/tlsf"
import "core:testing"
@test
test_tlsf_bitscan :: proc(t: ^testing.T) {
Vector :: struct {
op: enum{ffs, fls, fls_uint},
v: union{u32, uint},
exp: i32,
}
Tests := []Vector{
{.ffs, u32 (0x0000_0000_0000_0000), -1},
{.ffs, u32 (0x0000_0000_0000_0000), -1},
{.fls, u32 (0x0000_0000_0000_0000), -1},
{.ffs, u32 (0x0000_0000_0000_0001), 0},
{.fls, u32 (0x0000_0000_0000_0001), 0},
{.ffs, u32 (0x0000_0000_8000_0000), 31},
{.ffs, u32 (0x0000_0000_8000_8000), 15},
{.fls, u32 (0x0000_0000_8000_0008), 31},
{.fls, u32 (0x0000_0000_7FFF_FFFF), 30},
{.fls_uint, uint(0x0000_0000_8000_0000), 31},
{.fls_uint, uint(0x0000_0001_0000_0000), 32},
{.fls_uint, uint(0xffff_ffff_ffff_ffff), 63},
}
for test in Tests {
switch test.op {
case .ffs:
res := tlsf.ffs(test.v.?)
testing.expectf(t, res == test.exp, "Expected tlsf.ffs(0x%08x) == %v, got %v", test.v, test.exp, res)
case .fls:
res := tlsf.fls(test.v.?)
testing.expectf(t, res == test.exp, "Expected tlsf.fls(0x%08x) == %v, got %v", test.v, test.exp, res)
case .fls_uint:
res := tlsf.fls_uint(test.v.?)
testing.expectf(t, res == test.exp, "Expected tlsf.fls_uint(0x%16x) == %v, got %v", test.v, test.exp, res)
}
}
}
+2 -1
View File
@@ -8,6 +8,7 @@
A test suite for `core:net`
*/
//+build !netbsd
package test_core_net
import "core:testing"
@@ -552,4 +553,4 @@ binstr_to_address :: proc(t: ^testing.T, binstr: string) -> (address: net.Addres
return nil
}
panic("Invalid test case")
}
}
+39
View File
@@ -0,0 +1,39 @@
package tests_core
import rlibc "core:c/libc"
@(init)
download_assets :: proc() {
if rlibc.system("python3 " + ODIN_ROOT + "tests/core/download_assets.py " + ODIN_ROOT + "tests/core/assets") != 0 {
panic("downloading test assets failed!")
}
}
@(require) import "c/libc"
@(require) import "compress"
@(require) import "container"
@(require) import "encoding/base64"
@(require) import "encoding/cbor"
@(require) import "encoding/hex"
@(require) import "encoding/hxa"
@(require) import "encoding/json"
@(require) import "encoding/varint"
@(require) import "encoding/xml"
@(require) import "fmt"
@(require) import "math"
@(require) import "math/big"
@(require) import "math/linalg/glsl"
@(require) import "math/noise"
@(require) import "mem"
@(require) import "net"
@(require) import "odin"
@(require) import "path/filepath"
@(require) import "reflect"
@(require) import "runtime"
@(require) import "slice"
@(require) import "strconv"
@(require) import "strings"
@(require) import "text/i18n"
@(require) import "text/match"
@(require) import "thread"
@(require) import "time"
-10
View File
@@ -1,10 +0,0 @@
// Tests that Odin run returns exit code of built executable on Unix
// Needs exit status to be inverted to return 0 on success, e.g.
// $(./odin run tests/core/os/test_core_os_exit.odin && exit 1 || exit 0)
package test_core_os_exit
import "core:os"
main :: proc() {
os.exit(1)
}
+31 -1
View File
@@ -184,4 +184,34 @@ test_binary_search :: proc(t: ^testing.T) {
index, found = slice.binary_search(b, 0)
testing.expect(t, index == 0, "Expected index to be 0.")
testing.expect(t, found == false, "Expected found to be false.")
}
}
@test
test_permutation_iterator :: proc(t: ^testing.T) {
// Big enough to do some sanity checking but not overly large.
FAC_5 :: 120
s := []int{1, 2, 3, 4, 5}
seen: map[int]bool
defer delete(seen)
iter := slice.make_permutation_iterator(s)
defer slice.destroy_permutation_iterator(iter)
permutations_counted: int
for slice.permute(&iter) {
n := 0
for item, index in s {
n *= 10
n += item
}
if n in seen {
testing.fail_now(t, "Permutation iterator made a duplicate permutation.")
return
}
seen[n] = true
permutations_counted += 1
}
testing.expect_value(t, len(seen), FAC_5)
testing.expect_value(t, permutations_counted, FAC_5)
}
+6
View File
@@ -0,0 +1,6 @@
// Tests intended to be ran with optimizations on
package tests_core
@(require) import "crypto"
@(require) import "hash"
@(require) import "image"
-24
View File
@@ -1,24 +0,0 @@
ODIN=../../odin
COMMON=-file -vet -strict-style -o:minimal
all: all_bsd asan_test
all_bsd: rtti_test map_test pow_test 128_test string_compare_test
rtti_test:
$(ODIN) test test_rtti.odin $(COMMON)
map_test:
$(ODIN) test test_map.odin $(COMMON)
pow_test:
$(ODIN) test test_pow.odin $(COMMON)
128_test:
$(ODIN) test test_128.odin $(COMMON)
string_compare_test:
$(ODIN) test test_string_compare.odin $(COMMON)
asan_test:
$(ODIN) test test_asan.odin $(COMMON) -sanitize:address -debug
-9
View File
@@ -1,9 +0,0 @@
@echo off
set PATH_TO_ODIN==..\..\odin
set COMMON=-file -vet -strict-style -o:minimal
%PATH_TO_ODIN% test test_rtti.odin %COMMON% || exit /b
%PATH_TO_ODIN% test test_map.odin %COMMON% || exit /b
%PATH_TO_ODIN% test test_pow.odin %COMMON% || exit /b
%PATH_TO_ODIN% test test_asan.odin %COMMON% || exit /b
%PATH_TO_ODIN% test test_128.odin %COMMON% || exit /b
%PATH_TO_ODIN% test test_string_compare.odin %COMMON% || exit /b
+1 -1
View File
@@ -1,4 +1,4 @@
package test_128
package test_internal
import "core:testing"
+1 -1
View File
@@ -1,5 +1,5 @@
// Intended to contain code that would trigger asan easily if the abi was set up badly.
package test_asan
package test_internal
import "core:testing"
+2 -2
View File
@@ -1,4 +1,4 @@
package test_internal_map
package test_internal
import "core:log"
import "base:intrinsics"
@@ -309,4 +309,4 @@ set_delete_random_key_value :: proc(t: ^testing.T) {
}
seed_incr += 1
}
}
}
+2 -2
View File
@@ -1,4 +1,4 @@
package test_internal_math_pow
package test_internal
@(require) import "core:log"
import "core:math"
@@ -42,4 +42,4 @@ pow_test :: proc(t: ^testing.T) {
testing.expectf(t, _v1 == _v2, "Expected math.pow2_f16(%d) == math.pow(2, %d) (= %04x), got %04x", exp, exp, _v1, _v2)
}
}
}
}
+2 -2
View File
@@ -1,4 +1,4 @@
package test_internal_rtti
package test_internal
import "core:fmt"
import "core:testing"
@@ -43,4 +43,4 @@ rtti_test :: proc(t: ^testing.T) {
l_s := fmt.tprintf("%s", l_buggy)
testing.expectf(t, g_s == EXPECTED_REPR, "Expected fmt.tprintf(\"%%s\", g_s)) to return \"%v\", got \"%v\"", EXPECTED_REPR, g_s)
testing.expectf(t, l_s == EXPECTED_REPR, "Expected fmt.tprintf(\"%%s\", l_s)) to return \"%v\", got \"%v\"", EXPECTED_REPR, l_s)
}
}
+2 -2
View File
@@ -1,4 +1,4 @@
package test_internal_string_compare
package test_internal
import "core:testing"
@@ -54,4 +54,4 @@ string_compare :: proc(t: ^testing.T) {
}
}
}
}
}
-1
View File
@@ -10,7 +10,6 @@ set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style
..\..\..\odin test ..\test_issue_829.odin %COMMON% || exit /b
..\..\..\odin test ..\test_issue_1592.odin %COMMON% || exit /b
..\..\..\odin test ..\test_issue_2056.odin %COMMON% || exit /b
..\..\..\odin test ..\test_issue_2087.odin %COMMON% || exit /b
..\..\..\odin build ..\test_issue_2113.odin %COMMON% -debug || exit /b
..\..\..\odin test ..\test_issue_2466.odin %COMMON% || exit /b
..\..\..\odin test ..\test_issue_2615.odin %COMMON% || exit /b
+2 -4
View File
@@ -6,23 +6,21 @@ pushd build
ODIN=../../../odin
COMMON="-define:ODIN_TEST_FANCY=false -file -vet -strict-style"
NO_NIL_ERR="Error: "
set -x
$ODIN test ../test_issue_829.odin $COMMON
$ODIN test ../test_issue_1592.odin $COMMON
$ODIN test ../test_issue_2056.odin $COMMON
$ODIN test ../test_issue_2087.odin $COMMON
$ODIN build ../test_issue_2113.odin $COMMON -debug
$ODIN test ../test_issue_2466.odin $COMMON
$ODIN test ../test_issue_2615.odin $COMMON
$ODIN test ../test_issue_2637.odin $COMMON
$ODIN test ../test_issue_2666.odin $COMMON
if [[ $($ODIN build ../test_issue_2395.odin $COMMON 2>&1 >/dev/null | grep -c "$NO_NIL_ERR") -eq 2 ]] ; then
if [[ $($ODIN build ../test_issue_2395.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 2 ]] ; then
echo "SUCCESSFUL 1/1"
else
echo "SUCCESSFUL 0/1"
exit 1
fi
set +x
-2
View File
@@ -5,8 +5,6 @@
// exactly 2 errors from the invalid unions
package test_issues
import "core:testing"
ValidUnion :: union($T: typeid) #no_nil {
T,
f32,
-10
View File
@@ -1,10 +0,0 @@
ODIN=../../odin
ODINFLAGS=
OS=$(shell uname)
ifeq ($(OS), OpenBSD)
ODINFLAGS:=$(ODINFLAGS) -extra-linker-flags:-L/usr/local/lib
endif
all:
+3
View File
@@ -0,0 +1,3 @@
package tests_vendor
@(require) import "glfw"
-8
View File
@@ -1,8 +0,0 @@
@echo off
set COMMON=-show-timings -no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false
set PATH_TO_ODIN==..\..\odin
echo ---
echo Running vendor:glfw tests
echo ---
%PATH_TO_ODIN% test glfw %COMMON% -out:vendor_glfw.exe || exit /b
+1
View File
@@ -1,3 +1,4 @@
//+build darwin, windows
package test_vendor_glfw
import "core:testing"
+3 -3
View File
@@ -622,7 +622,7 @@ push_command :: proc(ctx: ^Context, $Type: typeid, extra_size := 0) -> ^Type {
return cmd
}
next_command :: proc(ctx: ^Context, pcmd: ^^Command) -> bool {
next_command :: proc "contextless" (ctx: ^Context, pcmd: ^^Command) -> bool {
cmd := pcmd^
defer pcmd^ = cmd
if cmd != nil {
@@ -630,7 +630,7 @@ next_command :: proc(ctx: ^Context, pcmd: ^^Command) -> bool {
} else {
cmd = (^Command)(&ctx.command_list.items[0])
}
invalid_command :: #force_inline proc(ctx: ^Context) -> ^Command {
invalid_command :: #force_inline proc "contextless" (ctx: ^Context) -> ^Command {
return (^Command)(&ctx.command_list.items[ctx.command_list.idx])
}
for cmd != invalid_command(ctx) {
@@ -643,7 +643,7 @@ next_command :: proc(ctx: ^Context, pcmd: ^^Command) -> bool {
return false
}
next_command_iterator :: proc(ctx: ^Context, pcm: ^^Command) -> (Command_Variant, bool) {
next_command_iterator :: proc "contextless" (ctx: ^Context, pcm: ^^Command) -> (Command_Variant, bool) {
if next_command(ctx, pcm) {
return pcm^.variant, true
}
+2 -2
View File
@@ -118,8 +118,8 @@ when ODIN_OS == .Windows {
} else when ODIN_OS == .Darwin {
foreign import lib {
"macos" +
"-arm64" when ODIN_ARCH == .arm64 else "" +
"/libraylib" + ".500.dylib" when RAYLIB_SHARED else ".a",
("-arm64" when ODIN_ARCH == .arm64 else "") +
"/libraylib" + (".500.dylib" when RAYLIB_SHARED else ".a"),
"system:Cocoa.framework",
"system:OpenGL.framework",
"system:IOKit.framework",
+1 -1
View File
@@ -76,7 +76,7 @@ foreign lib {
GetRenderer :: proc(window: ^Window) -> ^Renderer ---
GetRendererInfo :: proc(renderer: ^Renderer, info: ^RendererInfo) -> c.int ---
GetRendererOutputSize :: proc(renderer: ^Renderer, w, h: ^c.int) -> c.int ---
CreateTexture :: proc(renderer: ^Renderer, format: u32, access: TextureAccess, w, h: c.int) -> ^Texture ---
CreateTexture :: proc(renderer: ^Renderer, format: PixelFormatEnum, access: TextureAccess, w, h: c.int) -> ^Texture ---
CreateTextureFromSurface :: proc(renderer: ^Renderer, surface: ^Surface) -> ^Texture ---
QueryTexture :: proc(texture: ^Texture, format: ^u32, access, w, h: ^c.int) -> c.int ---
SetTextureColorMod :: proc(texture: ^Texture, r, g, b: u8) -> c.int ---
+5
View File
@@ -17,6 +17,11 @@ AllTemporary :: 0
CurrentTime :: 0
NoSymbol :: 0
PropModeReplace :: 0
PropModePrepend :: 1
PropModeAppend :: 2
XA_ATOM :: Atom(4)
XA_WM_CLASS :: Atom(67)
XA_WM_CLIENT_MACHINE :: Atom(36)
XA_WM_COMMAND :: Atom(34)

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