mirror of
https://github.com/Ed94/Odin.git
synced 2026-06-13 09:22:22 -07:00
Merge branch 'master' into prototype-fmt
This commit is contained in:
@@ -7,6 +7,8 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
# PLEASE POST THIS IN THE DISCUSSION TAB UNDER "PROPOSALS" OR "IDEAS/REQUESTS"
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
- name: Download LLVM and setup PATH
|
||||
run: |
|
||||
brew install llvm@11
|
||||
echo "/usr/local/opt/llvm/bin" >> $GITHUB_PATH
|
||||
echo "/usr/local/opt/llvm@11/bin" >> $GITHUB_PATH
|
||||
TMP_PATH=$(xcrun --show-sdk-path)/user/include
|
||||
echo "CPATH=$TMP_PATH" >> $GITHUB_ENV
|
||||
- name: build odin
|
||||
@@ -38,13 +38,6 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Download and unpack LLVM bins
|
||||
shell: powershell
|
||||
run: |
|
||||
cd bin
|
||||
$ProgressPreference = "SilentlyContinue";
|
||||
Invoke-WebRequest -Uri https://github.com/odin-lang/Odin/releases/download/llvm-windows/llvm-binaries.zip -OutFile llvm-binaries.zip
|
||||
7z x llvm-binaries.zip > $null
|
||||
- name: build Odin
|
||||
shell: cmd
|
||||
run: |
|
||||
|
||||
@@ -10,15 +10,6 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Install cURL
|
||||
run: choco install curl
|
||||
- name: Download and unpack LLVM bins
|
||||
shell: cmd
|
||||
run: |
|
||||
cd bin
|
||||
curl -sL https://github.com/odin-lang/Odin/releases/download/llvm-windows/llvm-binaries.zip --output llvm-binaries.zip
|
||||
7z x llvm-binaries.zip > nul
|
||||
rm -f llvm-binaries.zip
|
||||
- name: build Odin
|
||||
shell: cmd
|
||||
run: |
|
||||
@@ -72,8 +63,8 @@ jobs:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Download LLVM and setup PATH
|
||||
run: |
|
||||
brew install llvm
|
||||
echo "/usr/local/opt/llvm/bin" >> $GITHUB_PATH
|
||||
brew install llvm@11
|
||||
echo "/usr/local/opt/llvm@11/bin" >> $GITHUB_PATH
|
||||
TMP_PATH=$(xcrun --show-sdk-path)/user/include
|
||||
echo "CPATH=$TMP_PATH" >> $GITHUB_ENV
|
||||
- name: build odin
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2016-2020 Ginger Bill. All rights reserved.
|
||||
Copyright (c) 2016-2021 Ginger Bill. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
@@ -8,13 +8,31 @@ CC=clang
|
||||
OS=$(shell uname)
|
||||
|
||||
ifeq ($(OS), Darwin)
|
||||
LLVM_CONFIG=llvm-config
|
||||
ifneq ($(shell llvm-config --version | grep '^11\.'),)
|
||||
LLVM_CONFIG=llvm-config
|
||||
else
|
||||
$(error "Requirement: llvm-config must be version 11")
|
||||
endif
|
||||
|
||||
LDFLAGS:=$(LDFLAGS) -liconv
|
||||
CFLAGS:=$(CFLAGS) $(shell llvm-config --cxxflags --ldflags)
|
||||
CFLAGS:=$(CFLAGS) $(shell $(LLVM_CONFIG) --cxxflags --ldflags)
|
||||
LDFLAGS:=$(LDFLAGS) -lLLVM-C
|
||||
endif
|
||||
ifeq ($(OS), Linux)
|
||||
CFLAGS:=$(CFLAGS) $(shell llvm-config-11 --cxxflags --ldflags)
|
||||
LDFLAGS:=$(LDFLAGS) $(shell llvm-config-11 --libs core native --system-libs)
|
||||
LLVM_CONFIG=llvm-config-11
|
||||
ifneq ($(shell which llvm-config-11 2>/dev/null),)
|
||||
LLVM_CONFIG=llvm-config-11
|
||||
else
|
||||
ifneq ($(shell llvm-config --version | grep '^11\.'),)
|
||||
LLVM_CONFIG=llvm-config
|
||||
else
|
||||
$(error "Requirement: llvm-config must be version 11")
|
||||
endif
|
||||
endif
|
||||
|
||||
CFLAGS:=$(CFLAGS) $(shell $(LLVM_CONFIG) --cxxflags --ldflags)
|
||||
LDFLAGS:=$(LDFLAGS) $(shell $(LLVM_CONFIG) --libs core native --system-libs)
|
||||
endif
|
||||
|
||||
all: debug demo
|
||||
|
||||
@@ -100,7 +100,6 @@ In addition, the following platform-specific steps are necessary:
|
||||
|
||||
- Windows
|
||||
* Have Visual Studio installed (MSVC 2010 or later, for the linker)
|
||||
* Have a copy of `opt.exe` and `llc.exe` in `Odin/bin`. Pre-built Windows binaries can be found [here](https://github.com/odin-lang/Odin/releases/tag/llvm-windows) and *must* be explicitly copied
|
||||
* Open a valid command prompt:
|
||||
* **Basic:** run the `x64 Native Tools Command Prompt for VS2017` shortcut bundled with VS 2017, or
|
||||
* **Advanced:** run `vcvarsall.bat x64` from a blank `cmd` session
|
||||
@@ -128,7 +127,6 @@ Please read the [Getting Started Guide](https://github.com/odin-lang/Odin/wiki#g
|
||||
- Windows
|
||||
* x86-64/amd64
|
||||
* MSVC 2010 installed (C++11 support)
|
||||
* [LLVM binaries](https://github.com/odin-lang/Odin/releases/tag/llvm-windows) for `opt.exe`, `llc.exe`, and `lld-link.exe`
|
||||
* Requires MSVC's link.exe as the linker
|
||||
* run `vcvarsall.bat` to setup the path
|
||||
|
||||
|
||||
+3
-4
@@ -2,13 +2,12 @@
|
||||
|
||||
## Setup
|
||||
|
||||
Odin only supports x86-64 at the moment (64-bit), relies on LLVM for code generation and an external linker.
|
||||
Odin currently supports x86-64 and ARM64 at the moment (64-bit), relies on LLVM for code generation and an external linker.
|
||||
|
||||
In addition, the following platform-specific steps are necessary:
|
||||
|
||||
- Windows
|
||||
* Have Visual Studio installed (MSVC 2010 or later, for the linker)
|
||||
* Have a copy of `opt.exe` and `llc.exe` in `Odin/bin`. Pre-built Windows binaries can be found [here](https://github.com/odin-lang/Odin/releases/tag/llvm-windows) and *must* be explicitly copied
|
||||
* Open a valid command prompt:
|
||||
* **Basic:** run the `x64 Native Tools Command Prompt for VS2017` shortcut bundled with VS 2017, or
|
||||
* **Advanced:** run `vcvarsall.bat x64` from a blank `cmd` session
|
||||
@@ -19,12 +18,12 @@ In addition, the following platform-specific steps are necessary:
|
||||
* Make sure the LLVM binaries and the linker are added to your `$PATH` environmental variable
|
||||
|
||||
- GNU/Linux
|
||||
* Have LLVM installed (opt/llc)
|
||||
* Have Clang installed (version X.X or later, for linking)
|
||||
* Make sure the LLVM binaries and the linker are added to your `$PATH` environmental variable
|
||||
|
||||
Then build the compiler by calling `build.bat` (Windows) or `make` (Linux/MacOS). This will automatically run the demo program if successful.
|
||||
|
||||
**Notes for Linux:**: The compiler currently relies on the `core` and `shared` library collection being relative to the compiler executable. Installing the compiler in the usual sense (to `/usr/local/bin` or similar) is therefore not as straight forward as you need to make sure the mentioned libraries are available. As a result, it is recommended to simply explicitly invoke the compiler with `/path/to/odin` in your preferred build system, or add `/path/to/odin` to `$PATH`.
|
||||
**Notes for \*Nix Systems:**: The compiler currently relies on the `core` and `shared` library collection being relative to the compiler executable, by default. Installing the compiler in the usual sense (to `/usr/local/bin` or similar) is therefore not as straight forward as you need to make sure the mentioned libraries are available. As a result, it is recommended to either simply explicitly invoke the compiler with `/path/to/odin` in your preferred build system, or `set ODIN_ROOT=/path/to/odin_root`.
|
||||
|
||||
|
||||
Please read the [Getting Started Guide](https://github.com/odin-lang/Odin/wiki#getting-started-with-odin) for more information.
|
||||
|
||||
@@ -2,8 +2,12 @@
|
||||
|
||||
setlocal EnableDelayedExpansion
|
||||
|
||||
set curr_year=%DATE:~-4%
|
||||
set curr_month=%DATE:~3,2%
|
||||
for /f "usebackq tokens=1,2 delims=,=- " %%i in (`wmic os get LocalDateTime /value`) do @if %%i==LocalDateTime (
|
||||
set CURR_DATE_TIME=%%j
|
||||
)
|
||||
|
||||
set curr_year=%CURR_DATE_TIME:~0,4%
|
||||
set curr_month=%CURR_DATE_TIME:~4,2%
|
||||
|
||||
:: Make sure this is a decent name and not generic
|
||||
set exe_name=odin.exe
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
package compress
|
||||
|
||||
import "core:io"
|
||||
import "core:image"
|
||||
|
||||
Error :: union {
|
||||
General_Error,
|
||||
Deflate_Error,
|
||||
ZLIB_Error,
|
||||
GZIP_Error,
|
||||
ZIP_Error,
|
||||
/*
|
||||
This is here because png.load will return a this type of error union,
|
||||
as it may involve an I/O error, a Deflate error, etc.
|
||||
*/
|
||||
image.Error,
|
||||
}
|
||||
|
||||
General_Error :: enum {
|
||||
File_Not_Found,
|
||||
Cannot_Open_File,
|
||||
File_Too_Short,
|
||||
Stream_Too_Short,
|
||||
Output_Too_Short,
|
||||
Unknown_Compression_Method,
|
||||
Checksum_Failed,
|
||||
Incompatible_Options,
|
||||
Unimplemented,
|
||||
}
|
||||
|
||||
GZIP_Error :: enum {
|
||||
Invalid_GZIP_Signature,
|
||||
Reserved_Flag_Set,
|
||||
Invalid_Extra_Data,
|
||||
Original_Name_Too_Long,
|
||||
Comment_Too_Long,
|
||||
Payload_Length_Invalid,
|
||||
Payload_CRC_Invalid,
|
||||
}
|
||||
|
||||
ZIP_Error :: enum {
|
||||
Invalid_ZIP_File_Signature,
|
||||
Unexpected_Signature,
|
||||
Insert_Next_Disk,
|
||||
Expected_End_of_Central_Directory_Record,
|
||||
}
|
||||
|
||||
ZLIB_Error :: enum {
|
||||
Unsupported_Window_Size,
|
||||
FDICT_Unsupported,
|
||||
Unsupported_Compression_Level,
|
||||
Code_Buffer_Malformed,
|
||||
}
|
||||
|
||||
Deflate_Error :: enum {
|
||||
Huffman_Bad_Sizes,
|
||||
Huffman_Bad_Code_Lengths,
|
||||
Inflate_Error,
|
||||
Bad_Distance,
|
||||
Bad_Huffman_Code,
|
||||
Len_Nlen_Mismatch,
|
||||
BType_3,
|
||||
}
|
||||
|
||||
// General context for ZLIB, LZW, etc.
|
||||
Context :: struct {
|
||||
code_buffer: u32,
|
||||
num_bits: i8,
|
||||
/*
|
||||
num_bits will be set to -100 if the buffer is malformed
|
||||
*/
|
||||
eof: b8,
|
||||
|
||||
input: io.Stream,
|
||||
output: io.Stream,
|
||||
bytes_written: i64,
|
||||
// Used to update hash as we write instead of all at once
|
||||
rolling_hash: u32,
|
||||
|
||||
// Sliding window buffer. Size must be a power of two.
|
||||
window_size: i64,
|
||||
last: ^[dynamic]byte,
|
||||
}
|
||||
|
||||
// Stream helpers
|
||||
/*
|
||||
TODO: These need to be optimized.
|
||||
|
||||
Streams should really only check if a certain method is available once, perhaps even during setup.
|
||||
|
||||
Bit and byte readers may be merged so that reading bytes will grab them from the bit buffer first.
|
||||
This simplifies end-of-stream handling where bits may be left in the bit buffer.
|
||||
*/
|
||||
|
||||
read_data :: #force_inline proc(c: ^Context, $T: typeid) -> (res: T, err: io.Error) {
|
||||
b := make([]u8, size_of(T), context.temp_allocator);
|
||||
r, e1 := io.to_reader(c.input);
|
||||
_, e2 := io.read(r, b);
|
||||
if !e1 || e2 != .None {
|
||||
return T{}, e2;
|
||||
}
|
||||
|
||||
res = (^T)(raw_data(b))^;
|
||||
return res, .None;
|
||||
}
|
||||
|
||||
read_u8 :: #force_inline proc(z: ^Context) -> (res: u8, err: io.Error) {
|
||||
return read_data(z, u8);
|
||||
}
|
||||
|
||||
peek_data :: #force_inline proc(c: ^Context, $T: typeid) -> (res: T, err: io.Error) {
|
||||
// Get current position to read from.
|
||||
curr, e1 := c.input->impl_seek(0, .Current);
|
||||
if e1 != .None {
|
||||
return T{}, e1;
|
||||
}
|
||||
r, e2 := io.to_reader_at(c.input);
|
||||
if !e2 {
|
||||
return T{}, .Empty;
|
||||
}
|
||||
b := make([]u8, size_of(T), context.temp_allocator);
|
||||
_, e3 := io.read_at(r, b, curr);
|
||||
if e3 != .None {
|
||||
return T{}, .Empty;
|
||||
}
|
||||
|
||||
res = (^T)(raw_data(b))^;
|
||||
return res, .None;
|
||||
}
|
||||
|
||||
// Sliding window read back
|
||||
peek_back_byte :: proc(c: ^Context, offset: i64) -> (res: u8, err: io.Error) {
|
||||
// Look back into the sliding window.
|
||||
return c.last[offset % c.window_size], .None;
|
||||
}
|
||||
|
||||
// Generalized bit reader LSB
|
||||
refill_lsb :: proc(z: ^Context, width := i8(24)) {
|
||||
for {
|
||||
if z.num_bits > width {
|
||||
break;
|
||||
}
|
||||
if z.code_buffer == 0 && z.num_bits == -1 {
|
||||
z.num_bits = 0;
|
||||
}
|
||||
if z.code_buffer >= 1 << uint(z.num_bits) {
|
||||
// Code buffer is malformed.
|
||||
z.num_bits = -100;
|
||||
return;
|
||||
}
|
||||
c, err := read_u8(z);
|
||||
if err != .None {
|
||||
// This is fine at the end of the file.
|
||||
z.num_bits = -42;
|
||||
z.eof = true;
|
||||
return;
|
||||
}
|
||||
z.code_buffer |= (u32(c) << u8(z.num_bits));
|
||||
z.num_bits += 8;
|
||||
}
|
||||
}
|
||||
|
||||
consume_bits_lsb :: #force_inline proc(z: ^Context, width: u8) {
|
||||
z.code_buffer >>= width;
|
||||
z.num_bits -= i8(width);
|
||||
}
|
||||
|
||||
peek_bits_lsb :: #force_inline proc(z: ^Context, width: u8) -> u32 {
|
||||
if z.num_bits < i8(width) {
|
||||
refill_lsb(z);
|
||||
}
|
||||
// assert(z.num_bits >= i8(width));
|
||||
return z.code_buffer & ~(~u32(0) << width);
|
||||
}
|
||||
|
||||
peek_bits_no_refill_lsb :: #force_inline proc(z: ^Context, width: u8) -> u32 {
|
||||
assert(z.num_bits >= i8(width));
|
||||
return z.code_buffer & ~(~u32(0) << width);
|
||||
}
|
||||
|
||||
read_bits_lsb :: #force_inline proc(z: ^Context, width: u8) -> u32 {
|
||||
k := peek_bits_lsb(z, width);
|
||||
consume_bits_lsb(z, width);
|
||||
return k;
|
||||
}
|
||||
|
||||
read_bits_no_refill_lsb :: #force_inline proc(z: ^Context, width: u8) -> u32 {
|
||||
k := peek_bits_no_refill_lsb(z, width);
|
||||
consume_bits_lsb(z, width);
|
||||
return k;
|
||||
}
|
||||
|
||||
discard_to_next_byte_lsb :: proc(z: ^Context) {
|
||||
discard := u8(z.num_bits & 7);
|
||||
consume_bits_lsb(z, discard);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
//+ignore
|
||||
package gzip
|
||||
|
||||
import "core:compress/gzip"
|
||||
import "core:bytes"
|
||||
import "core:os"
|
||||
|
||||
// Small GZIP file with fextra, fname and fcomment present.
|
||||
@private
|
||||
TEST: []u8 = {
|
||||
0x1f, 0x8b, 0x08, 0x1c, 0xcb, 0x3b, 0x3a, 0x5a,
|
||||
0x02, 0x03, 0x07, 0x00, 0x61, 0x62, 0x03, 0x00,
|
||||
0x63, 0x64, 0x65, 0x66, 0x69, 0x6c, 0x65, 0x6e,
|
||||
0x61, 0x6d, 0x65, 0x00, 0x54, 0x68, 0x69, 0x73,
|
||||
0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x63, 0x6f,
|
||||
0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0x2b, 0x48,
|
||||
0xac, 0xcc, 0xc9, 0x4f, 0x4c, 0x01, 0x00, 0x15,
|
||||
0x6a, 0x2c, 0x42, 0x07, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
main :: proc() {
|
||||
// Set up output buffer.
|
||||
buf: bytes.Buffer;
|
||||
defer bytes.buffer_destroy(&buf);
|
||||
|
||||
stdout :: proc(s: string) {
|
||||
os.write_string(os.stdout, s);
|
||||
}
|
||||
stderr :: proc(s: string) {
|
||||
os.write_string(os.stderr, s);
|
||||
}
|
||||
|
||||
args := os.args;
|
||||
|
||||
if len(args) < 2 {
|
||||
stderr("No input file specified.\n");
|
||||
err := gzip.load(TEST, &buf);
|
||||
if err != nil {
|
||||
stdout("Displaying test vector: ");
|
||||
stdout(bytes.buffer_to_string(&buf));
|
||||
stdout("\n");
|
||||
}
|
||||
}
|
||||
|
||||
// The rest are all files.
|
||||
args = args[1:];
|
||||
err: gzip.Error;
|
||||
|
||||
for file in args {
|
||||
if file == "-" {
|
||||
// Read from stdin
|
||||
s := os.stream_from_handle(os.stdin);
|
||||
err = gzip.load(s, &buf);
|
||||
} else {
|
||||
err = gzip.load(file, &buf);
|
||||
}
|
||||
if err != nil {
|
||||
if err != E_General.File_Not_Found {
|
||||
stderr("File not found: ");
|
||||
stderr(file);
|
||||
stderr("\n");
|
||||
os.exit(1);
|
||||
}
|
||||
stderr("GZIP returned an error.\n");
|
||||
os.exit(2);
|
||||
}
|
||||
stdout(bytes.buffer_to_string(&buf));
|
||||
}
|
||||
os.exit(0);
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
package gzip
|
||||
|
||||
import "core:compress/zlib"
|
||||
import "core:compress"
|
||||
import "core:os"
|
||||
import "core:io"
|
||||
import "core:bytes"
|
||||
import "core:hash"
|
||||
|
||||
/*
|
||||
|
||||
This package implements support for the GZIP file format v4.3,
|
||||
as specified in RFC 1952.
|
||||
|
||||
It is implemented in such a way that it lends itself naturally
|
||||
to be the input to a complementary TAR implementation.
|
||||
|
||||
*/
|
||||
|
||||
Magic :: enum u16le {
|
||||
GZIP = 0x8b << 8 | 0x1f,
|
||||
}
|
||||
|
||||
Header :: struct #packed {
|
||||
magic: Magic,
|
||||
compression_method: Compression,
|
||||
flags: Header_Flags,
|
||||
modification_time: u32le,
|
||||
xfl: Compression_Flags,
|
||||
os: OS,
|
||||
}
|
||||
#assert(size_of(Header) == 10);
|
||||
|
||||
Header_Flag :: enum u8 {
|
||||
// Order is important
|
||||
text = 0,
|
||||
header_crc = 1,
|
||||
extra = 2,
|
||||
name = 3,
|
||||
comment = 4,
|
||||
reserved_1 = 5,
|
||||
reserved_2 = 6,
|
||||
reserved_3 = 7,
|
||||
}
|
||||
Header_Flags :: distinct bit_set[Header_Flag; u8];
|
||||
|
||||
OS :: enum u8 {
|
||||
FAT = 0,
|
||||
Amiga = 1,
|
||||
VMS = 2,
|
||||
Unix = 3,
|
||||
VM_CMS = 4,
|
||||
Atari_TOS = 5,
|
||||
HPFS = 6,
|
||||
Macintosh = 7,
|
||||
Z_System = 8,
|
||||
CP_M = 9,
|
||||
TOPS_20 = 10,
|
||||
NTFS = 11,
|
||||
QDOS = 12,
|
||||
Acorn_RISCOS = 13,
|
||||
_Unknown = 14,
|
||||
Unknown = 255,
|
||||
}
|
||||
OS_Name :: #partial [OS]string{
|
||||
.FAT = "FAT",
|
||||
.Amiga = "Amiga",
|
||||
.VMS = "VMS/OpenVMS",
|
||||
.Unix = "Unix",
|
||||
.VM_CMS = "VM/CMS",
|
||||
.Atari_TOS = "Atari TOS",
|
||||
.HPFS = "HPFS",
|
||||
.Macintosh = "Macintosh",
|
||||
.Z_System = "Z-System",
|
||||
.CP_M = "CP/M",
|
||||
.TOPS_20 = "TOPS-20",
|
||||
.NTFS = "NTFS",
|
||||
.QDOS = "QDOS",
|
||||
.Acorn_RISCOS = "Acorn RISCOS",
|
||||
.Unknown = "Unknown",
|
||||
};
|
||||
|
||||
Compression :: enum u8 {
|
||||
DEFLATE = 8,
|
||||
}
|
||||
|
||||
Compression_Flags :: enum u8 {
|
||||
Maximum_Compression = 2,
|
||||
Fastest_Compression = 4,
|
||||
}
|
||||
|
||||
Error :: compress.Error;
|
||||
E_General :: compress.General_Error;
|
||||
E_GZIP :: compress.GZIP_Error;
|
||||
E_ZLIB :: compress.ZLIB_Error;
|
||||
E_Deflate :: compress.Deflate_Error;
|
||||
|
||||
load_from_slice :: proc(slice: []u8, buf: ^bytes.Buffer, allocator := context.allocator) -> (err: Error) {
|
||||
|
||||
r := bytes.Reader{};
|
||||
bytes.reader_init(&r, slice);
|
||||
stream := bytes.reader_to_stream(&r);
|
||||
|
||||
err = load_from_stream(stream, buf, allocator);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
load_from_file :: proc(filename: string, buf: ^bytes.Buffer, allocator := context.allocator) -> (err: Error) {
|
||||
data, ok := os.read_entire_file(filename, allocator);
|
||||
defer delete(data);
|
||||
|
||||
err = E_General.File_Not_Found;
|
||||
if ok {
|
||||
err = load_from_slice(data, buf, allocator);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
load_from_stream :: proc(stream: io.Stream, buf: ^bytes.Buffer, allocator := context.allocator) -> (err: Error) {
|
||||
ctx := compress.Context{
|
||||
input = stream,
|
||||
};
|
||||
buf := buf;
|
||||
ws := bytes.buffer_to_stream(buf);
|
||||
ctx.output = ws;
|
||||
|
||||
header, e := compress.read_data(&ctx, Header);
|
||||
if e != .None {
|
||||
return E_General.File_Too_Short;
|
||||
}
|
||||
|
||||
if header.magic != .GZIP {
|
||||
return E_GZIP.Invalid_GZIP_Signature;
|
||||
}
|
||||
if header.compression_method != .DEFLATE {
|
||||
return E_General.Unknown_Compression_Method;
|
||||
}
|
||||
|
||||
if header.os >= ._Unknown {
|
||||
header.os = .Unknown;
|
||||
}
|
||||
|
||||
if .reserved_1 in header.flags || .reserved_2 in header.flags || .reserved_3 in header.flags {
|
||||
return E_GZIP.Reserved_Flag_Set;
|
||||
}
|
||||
|
||||
// printf("signature: %v\n", header.magic);
|
||||
// printf("compression: %v\n", header.compression_method);
|
||||
// printf("flags: %v\n", header.flags);
|
||||
// printf("modification time: %v\n", time.unix(i64(header.modification_time), 0));
|
||||
// printf("xfl: %v (%v)\n", header.xfl, int(header.xfl));
|
||||
// printf("os: %v\n", OS_Name[header.os]);
|
||||
|
||||
if .extra in header.flags {
|
||||
xlen, e_extra := compress.read_data(&ctx, u16le);
|
||||
if e_extra != .None {
|
||||
return E_General.Stream_Too_Short;
|
||||
}
|
||||
// printf("Extra data present (%v bytes)\n", xlen);
|
||||
if xlen < 4 {
|
||||
// Minimum length is 2 for ID + 2 for a field length, if set to zero.
|
||||
return E_GZIP.Invalid_Extra_Data;
|
||||
}
|
||||
|
||||
field_id: [2]u8;
|
||||
field_length: u16le;
|
||||
field_error: io.Error;
|
||||
|
||||
for xlen >= 4 {
|
||||
// println("Parsing Extra field(s).");
|
||||
field_id, field_error = compress.read_data(&ctx, [2]u8);
|
||||
if field_error != .None {
|
||||
// printf("Parsing Extra returned: %v\n", field_error);
|
||||
return E_General.Stream_Too_Short;
|
||||
}
|
||||
xlen -= 2;
|
||||
|
||||
field_length, field_error = compress.read_data(&ctx, u16le);
|
||||
if field_error != .None {
|
||||
// printf("Parsing Extra returned: %v\n", field_error);
|
||||
return E_General.Stream_Too_Short;
|
||||
}
|
||||
xlen -= 2;
|
||||
|
||||
if xlen <= 0 {
|
||||
// We're not going to try and recover by scanning for a ZLIB header.
|
||||
// Who knows what else is wrong with this file.
|
||||
return E_GZIP.Invalid_Extra_Data;
|
||||
}
|
||||
|
||||
// printf(" Field \"%v\" of length %v found: ", string(field_id[:]), field_length);
|
||||
if field_length > 0 {
|
||||
field_data := make([]u8, field_length, context.temp_allocator);
|
||||
_, field_error = ctx.input->impl_read(field_data);
|
||||
if field_error != .None {
|
||||
// printf("Parsing Extra returned: %v\n", field_error);
|
||||
return E_General.Stream_Too_Short;
|
||||
}
|
||||
xlen -= field_length;
|
||||
|
||||
// printf("%v\n", string(field_data));
|
||||
}
|
||||
|
||||
if xlen != 0 {
|
||||
return E_GZIP.Invalid_Extra_Data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if .name in header.flags {
|
||||
// Should be enough.
|
||||
name: [1024]u8;
|
||||
b: [1]u8;
|
||||
i := 0;
|
||||
name_error: io.Error;
|
||||
|
||||
for i < len(name) {
|
||||
_, name_error = ctx.input->impl_read(b[:]);
|
||||
if name_error != .None {
|
||||
return E_General.Stream_Too_Short;
|
||||
}
|
||||
if b == 0 {
|
||||
break;
|
||||
}
|
||||
name[i] = b[0];
|
||||
i += 1;
|
||||
if i >= len(name) {
|
||||
return E_GZIP.Original_Name_Too_Long;
|
||||
}
|
||||
}
|
||||
// printf("Original filename: %v\n", string(name[:i]));
|
||||
}
|
||||
|
||||
if .comment in header.flags {
|
||||
// Should be enough.
|
||||
comment: [1024]u8;
|
||||
b: [1]u8;
|
||||
i := 0;
|
||||
comment_error: io.Error;
|
||||
|
||||
for i < len(comment) {
|
||||
_, comment_error = ctx.input->impl_read(b[:]);
|
||||
if comment_error != .None {
|
||||
return E_General.Stream_Too_Short;
|
||||
}
|
||||
if b == 0 {
|
||||
break;
|
||||
}
|
||||
comment[i] = b[0];
|
||||
i += 1;
|
||||
if i >= len(comment) {
|
||||
return E_GZIP.Comment_Too_Long;
|
||||
}
|
||||
}
|
||||
// printf("Comment: %v\n", string(comment[:i]));
|
||||
}
|
||||
|
||||
if .header_crc in header.flags {
|
||||
crc16: [2]u8;
|
||||
crc_error: io.Error;
|
||||
_, crc_error = ctx.input->impl_read(crc16[:]);
|
||||
if crc_error != .None {
|
||||
return E_General.Stream_Too_Short;
|
||||
}
|
||||
/*
|
||||
We don't actually check the CRC16 (lower 2 bytes of CRC32 of header data until the CRC field).
|
||||
If we find a gzip file in the wild that sets this field, we can add proper support for it.
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
We should have arrived at the ZLIB payload.
|
||||
*/
|
||||
|
||||
zlib_error := zlib.inflate_raw(&ctx);
|
||||
|
||||
// fmt.printf("ZLIB returned: %v\n", zlib_error);
|
||||
|
||||
if zlib_error != nil {
|
||||
return zlib_error;
|
||||
}
|
||||
|
||||
/*
|
||||
Read CRC32 using the ctx bit reader because zlib may leave bytes in there.
|
||||
*/
|
||||
compress.discard_to_next_byte_lsb(&ctx);
|
||||
|
||||
payload_crc_b: [4]u8;
|
||||
payload_len_b: [4]u8;
|
||||
for i in 0..3 {
|
||||
payload_crc_b[i] = u8(compress.read_bits_lsb(&ctx, 8));
|
||||
}
|
||||
payload_crc := transmute(u32le)payload_crc_b;
|
||||
for i in 0..3 {
|
||||
payload_len_b[i] = u8(compress.read_bits_lsb(&ctx, 8));
|
||||
}
|
||||
payload_len := int(transmute(u32le)payload_len_b);
|
||||
|
||||
payload := bytes.buffer_to_bytes(buf);
|
||||
crc32 := u32le(hash.crc32(payload));
|
||||
|
||||
if crc32 != payload_crc {
|
||||
return E_GZIP.Payload_CRC_Invalid;
|
||||
}
|
||||
|
||||
if len(payload) != payload_len {
|
||||
return E_GZIP.Payload_Length_Invalid;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
load :: proc{load_from_file, load_from_slice, load_from_stream};
|
||||
@@ -0,0 +1,42 @@
|
||||
//+ignore
|
||||
package zlib
|
||||
|
||||
import "core:compress/zlib"
|
||||
import "core:bytes"
|
||||
import "core:fmt"
|
||||
|
||||
main :: proc() {
|
||||
|
||||
ODIN_DEMO := []u8{
|
||||
120, 156, 101, 144, 77, 110, 131, 48, 16, 133, 215, 204, 41, 158, 44,
|
||||
69, 73, 32, 148, 182, 75, 35, 14, 208, 125, 47, 96, 185, 195, 143,
|
||||
130, 13, 50, 38, 81, 84, 101, 213, 75, 116, 215, 43, 246, 8, 53,
|
||||
82, 126, 8, 181, 188, 152, 153, 111, 222, 147, 159, 123, 165, 247, 170,
|
||||
98, 24, 213, 88, 162, 198, 244, 157, 243, 16, 186, 115, 44, 75, 227,
|
||||
5, 77, 115, 72, 137, 222, 117, 122, 179, 197, 39, 69, 161, 170, 156,
|
||||
50, 144, 5, 68, 130, 4, 49, 126, 127, 190, 191, 144, 34, 19, 57,
|
||||
69, 74, 235, 209, 140, 173, 242, 157, 155, 54, 158, 115, 162, 168, 12,
|
||||
181, 239, 246, 108, 17, 188, 174, 242, 224, 20, 13, 199, 198, 235, 250,
|
||||
194, 166, 129, 86, 3, 99, 157, 172, 37, 230, 62, 73, 129, 151, 252,
|
||||
70, 211, 5, 77, 31, 104, 188, 160, 113, 129, 215, 59, 205, 22, 52,
|
||||
123, 160, 83, 142, 255, 242, 89, 123, 93, 149, 200, 50, 188, 85, 54,
|
||||
252, 18, 248, 192, 238, 228, 235, 198, 86, 224, 118, 224, 176, 113, 166,
|
||||
112, 67, 106, 227, 159, 122, 215, 88, 95, 110, 196, 123, 205, 183, 224,
|
||||
98, 53, 8, 104, 213, 234, 201, 147, 7, 248, 192, 14, 170, 29, 25,
|
||||
171, 15, 18, 59, 138, 112, 63, 23, 205, 110, 254, 136, 109, 78, 231,
|
||||
63, 234, 138, 133, 204,
|
||||
};
|
||||
|
||||
buf: bytes.Buffer;
|
||||
|
||||
// We can pass ", true" to inflate a raw DEFLATE stream instead of a ZLIB wrapped one.
|
||||
err := zlib.inflate(ODIN_DEMO, &buf);
|
||||
defer bytes.buffer_destroy(&buf);
|
||||
|
||||
if err != nil {
|
||||
fmt.printf("\nError: %v\n", err);
|
||||
}
|
||||
s := bytes.buffer_to_string(&buf);
|
||||
fmt.printf("Input: %v bytes, output (%v bytes):\n%v\n", len(ODIN_DEMO), len(s), s);
|
||||
assert(len(s) == 438);
|
||||
}
|
||||
@@ -0,0 +1,606 @@
|
||||
package zlib
|
||||
|
||||
import "core:compress"
|
||||
|
||||
import "core:mem"
|
||||
import "core:io"
|
||||
import "core:bytes"
|
||||
import "core:hash"
|
||||
/*
|
||||
zlib.inflate decompresses a ZLIB stream passed in as a []u8 or io.Stream.
|
||||
Returns: Error.
|
||||
*/
|
||||
|
||||
Context :: compress.Context;
|
||||
|
||||
Compression_Method :: enum u8 {
|
||||
DEFLATE = 8,
|
||||
Reserved = 15,
|
||||
}
|
||||
|
||||
Compression_Level :: enum u8 {
|
||||
Fastest = 0,
|
||||
Fast = 1,
|
||||
Default = 2,
|
||||
Maximum = 3,
|
||||
}
|
||||
|
||||
Options :: struct {
|
||||
window_size: u16,
|
||||
level: u8,
|
||||
}
|
||||
|
||||
Error :: compress.Error;
|
||||
E_General :: compress.General_Error;
|
||||
E_ZLIB :: compress.ZLIB_Error;
|
||||
E_Deflate :: compress.Deflate_Error;
|
||||
|
||||
DEFLATE_MAX_CHUNK_SIZE :: 65535;
|
||||
DEFLATE_MAX_LITERAL_SIZE :: 65535;
|
||||
DEFLATE_MAX_DISTANCE :: 32768;
|
||||
DEFLATE_MAX_LENGTH :: 258;
|
||||
|
||||
HUFFMAN_MAX_BITS :: 16;
|
||||
HUFFMAN_FAST_BITS :: 9;
|
||||
HUFFMAN_FAST_MASK :: ((1 << HUFFMAN_FAST_BITS) - 1);
|
||||
|
||||
Z_LENGTH_BASE := [31]u16{
|
||||
3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,
|
||||
67,83,99,115,131,163,195,227,258,0,0,
|
||||
};
|
||||
|
||||
Z_LENGTH_EXTRA := [31]u8{
|
||||
0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,
|
||||
};
|
||||
|
||||
Z_DIST_BASE := [32]u16{
|
||||
1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,
|
||||
257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0,
|
||||
};
|
||||
|
||||
Z_DIST_EXTRA := [32]u8{
|
||||
0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0,
|
||||
};
|
||||
|
||||
Z_LENGTH_DEZIGZAG := []u8{
|
||||
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15,
|
||||
};
|
||||
|
||||
Z_FIXED_LENGTH := [288]u8{
|
||||
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
|
||||
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
|
||||
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
|
||||
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
|
||||
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
|
||||
9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
|
||||
9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
|
||||
9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
|
||||
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8,
|
||||
};
|
||||
|
||||
Z_FIXED_DIST := [32]u8{
|
||||
5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
|
||||
};
|
||||
|
||||
/*
|
||||
Accelerate all cases in default tables.
|
||||
*/
|
||||
ZFAST_BITS :: 9;
|
||||
ZFAST_MASK :: ((1 << ZFAST_BITS) - 1);
|
||||
|
||||
/*
|
||||
ZLIB-style Huffman encoding.
|
||||
JPEG packs from left, ZLIB from right. We can't share code.
|
||||
*/
|
||||
Huffman_Table :: struct {
|
||||
fast: [1 << ZFAST_BITS]u16,
|
||||
firstcode: [16]u16,
|
||||
maxcode: [17]int,
|
||||
firstsymbol: [16]u16,
|
||||
size: [288]u8,
|
||||
value: [288]u16,
|
||||
};
|
||||
|
||||
// Implementation starts here
|
||||
|
||||
z_bit_reverse :: #force_inline proc(n: u16, bits: u8) -> (r: u16) {
|
||||
assert(bits <= 16);
|
||||
// NOTE: Can optimize with llvm.bitreverse.i64 or some bit twiddling
|
||||
// by reversing all of the bits and masking out the unneeded ones.
|
||||
r = n;
|
||||
r = ((r & 0xAAAA) >> 1) | ((r & 0x5555) << 1);
|
||||
r = ((r & 0xCCCC) >> 2) | ((r & 0x3333) << 2);
|
||||
r = ((r & 0xF0F0) >> 4) | ((r & 0x0F0F) << 4);
|
||||
r = ((r & 0xFF00) >> 8) | ((r & 0x00FF) << 8);
|
||||
|
||||
r >>= (16 - bits);
|
||||
return;
|
||||
}
|
||||
|
||||
write_byte :: #force_inline proc(z: ^Context, c: u8) -> (err: io.Error) #no_bounds_check {
|
||||
c := c;
|
||||
buf := transmute([]u8)mem.Raw_Slice{data=&c, len=1};
|
||||
z.rolling_hash = hash.adler32(buf, z.rolling_hash);
|
||||
|
||||
_, e := z.output->impl_write(buf);
|
||||
if e != .None {
|
||||
return e;
|
||||
}
|
||||
z.last[z.bytes_written % z.window_size] = c;
|
||||
|
||||
z.bytes_written += 1;
|
||||
return .None;
|
||||
}
|
||||
|
||||
allocate_huffman_table :: proc(allocator := context.allocator) -> (z: ^Huffman_Table, err: Error) {
|
||||
|
||||
z = new(Huffman_Table, allocator);
|
||||
return z, nil;
|
||||
}
|
||||
|
||||
build_huffman :: proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) {
|
||||
sizes: [HUFFMAN_MAX_BITS+1]int;
|
||||
next_code: [HUFFMAN_MAX_BITS]int;
|
||||
|
||||
k := int(0);
|
||||
|
||||
mem.zero_slice(sizes[:]);
|
||||
mem.zero_slice(z.fast[:]);
|
||||
|
||||
for v, _ in code_lengths {
|
||||
sizes[v] += 1;
|
||||
}
|
||||
sizes[0] = 0;
|
||||
|
||||
for i in 1..16 {
|
||||
if sizes[i] > (1 << uint(i)) {
|
||||
return E_Deflate.Huffman_Bad_Sizes;
|
||||
}
|
||||
}
|
||||
code := int(0);
|
||||
|
||||
for i in 1..<16 {
|
||||
next_code[i] = code;
|
||||
z.firstcode[i] = u16(code);
|
||||
z.firstsymbol[i] = u16(k);
|
||||
code = code + sizes[i];
|
||||
if sizes[i] != 0 {
|
||||
if (code - 1 >= (1 << u16(i))) {
|
||||
return E_Deflate.Huffman_Bad_Code_Lengths;
|
||||
}
|
||||
}
|
||||
z.maxcode[i] = code << (16 - uint(i));
|
||||
code <<= 1;
|
||||
k += int(sizes[i]);
|
||||
}
|
||||
|
||||
z.maxcode[16] = 0x10000; // Sentinel
|
||||
c: int;
|
||||
|
||||
for v, ci in code_lengths {
|
||||
if v != 0 {
|
||||
c = next_code[v] - int(z.firstcode[v]) + int(z.firstsymbol[v]);
|
||||
fastv := u16((u16(v) << 9) | u16(ci));
|
||||
z.size[c] = u8(v);
|
||||
z.value[c] = u16(ci);
|
||||
if (v <= ZFAST_BITS) {
|
||||
j := z_bit_reverse(u16(next_code[v]), v);
|
||||
for j < (1 << ZFAST_BITS) {
|
||||
z.fast[j] = fastv;
|
||||
j += (1 << v);
|
||||
}
|
||||
}
|
||||
next_code[v] += 1;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
decode_huffman_slowpath :: proc(z: ^Context, t: ^Huffman_Table) -> (r: u16, err: Error) #no_bounds_check {
|
||||
|
||||
r = 0;
|
||||
err = nil;
|
||||
|
||||
k: int;
|
||||
s: u8;
|
||||
|
||||
code := u16(compress.peek_bits_lsb(z, 16));
|
||||
|
||||
k = int(z_bit_reverse(code, 16));
|
||||
|
||||
#no_bounds_check for s = HUFFMAN_FAST_BITS+1; ; {
|
||||
if k < t.maxcode[s] {
|
||||
break;
|
||||
}
|
||||
s += 1;
|
||||
}
|
||||
if (s >= 16) {
|
||||
return 0, E_Deflate.Bad_Huffman_Code;
|
||||
}
|
||||
// code size is s, so:
|
||||
b := (k >> (16-s)) - int(t.firstcode[s]) + int(t.firstsymbol[s]);
|
||||
if b >= size_of(t.size) {
|
||||
return 0, E_Deflate.Bad_Huffman_Code;
|
||||
}
|
||||
if t.size[b] != s {
|
||||
return 0, E_Deflate.Bad_Huffman_Code;
|
||||
}
|
||||
|
||||
compress.consume_bits_lsb(z, s);
|
||||
|
||||
r = t.value[b];
|
||||
return r, nil;
|
||||
}
|
||||
|
||||
decode_huffman :: proc(z: ^Context, t: ^Huffman_Table) -> (r: u16, err: Error) #no_bounds_check {
|
||||
|
||||
if z.num_bits < 16 {
|
||||
if z.num_bits == -100 {
|
||||
return 0, E_ZLIB.Code_Buffer_Malformed;
|
||||
}
|
||||
compress.refill_lsb(z);
|
||||
if z.eof {
|
||||
return 0, E_General.Stream_Too_Short;
|
||||
}
|
||||
}
|
||||
#no_bounds_check b := t.fast[z.code_buffer & ZFAST_MASK];
|
||||
if b != 0 {
|
||||
s := u8(b >> ZFAST_BITS);
|
||||
compress.consume_bits_lsb(z, s);
|
||||
return b & 511, nil;
|
||||
}
|
||||
return decode_huffman_slowpath(z, t);
|
||||
}
|
||||
|
||||
parse_huffman_block :: proc(z: ^Context, z_repeat, z_offset: ^Huffman_Table) -> (err: Error) #no_bounds_check {
|
||||
#no_bounds_check for {
|
||||
value, e := decode_huffman(z, z_repeat);
|
||||
if e != nil {
|
||||
return err;
|
||||
}
|
||||
if value < 256 {
|
||||
e := write_byte(z, u8(value));
|
||||
if e != .None {
|
||||
return E_General.Output_Too_Short;
|
||||
}
|
||||
} else {
|
||||
if value == 256 {
|
||||
// End of block
|
||||
return nil;
|
||||
}
|
||||
|
||||
value -= 257;
|
||||
length := Z_LENGTH_BASE[value];
|
||||
if Z_LENGTH_EXTRA[value] > 0 {
|
||||
length += u16(compress.read_bits_lsb(z, Z_LENGTH_EXTRA[value]));
|
||||
}
|
||||
|
||||
value, e = decode_huffman(z, z_offset);
|
||||
if e != nil {
|
||||
return E_Deflate.Bad_Huffman_Code;
|
||||
}
|
||||
|
||||
distance := Z_DIST_BASE[value];
|
||||
if Z_DIST_EXTRA[value] > 0 {
|
||||
distance += u16(compress.read_bits_lsb(z, Z_DIST_EXTRA[value]));
|
||||
}
|
||||
|
||||
if z.bytes_written < i64(distance) {
|
||||
// Distance is longer than we've decoded so far.
|
||||
return E_Deflate.Bad_Distance;
|
||||
}
|
||||
|
||||
offset := i64(z.bytes_written - i64(distance));
|
||||
/*
|
||||
These might be sped up with a repl_byte call that copies
|
||||
from the already written output more directly, and that
|
||||
update the Adler checksum once after.
|
||||
|
||||
That way we'd suffer less Stream vtable overhead.
|
||||
*/
|
||||
if distance == 1 {
|
||||
/*
|
||||
Replicate the last outputted byte, length times.
|
||||
*/
|
||||
if length > 0 {
|
||||
b, e := compress.peek_back_byte(z, offset);
|
||||
if e != .None {
|
||||
return E_General.Output_Too_Short;
|
||||
}
|
||||
#no_bounds_check for _ in 0..<length {
|
||||
write_byte(z, b);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if length > 0 {
|
||||
#no_bounds_check for _ in 0..<length {
|
||||
b, e := compress.peek_back_byte(z, offset);
|
||||
if e != .None {
|
||||
return E_General.Output_Too_Short;
|
||||
}
|
||||
write_byte(z, b);
|
||||
offset += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inflate_from_stream :: proc(using ctx: ^Context, raw := false, allocator := context.allocator) -> (err: Error) #no_bounds_check {
|
||||
/*
|
||||
ctx.input must be an io.Stream backed by an implementation that supports:
|
||||
- read
|
||||
- size
|
||||
|
||||
ctx.output must be an io.Stream backed by an implementation that supports:
|
||||
- write
|
||||
|
||||
raw determines whether the ZLIB header is processed, or we're inflating a raw
|
||||
DEFLATE stream.
|
||||
*/
|
||||
|
||||
if !raw {
|
||||
data_size := io.size(ctx.input);
|
||||
if data_size < 6 {
|
||||
return E_General.Stream_Too_Short;
|
||||
}
|
||||
|
||||
cmf, _ := compress.read_u8(ctx);
|
||||
|
||||
method := Compression_Method(cmf & 0xf);
|
||||
if method != .DEFLATE {
|
||||
return E_General.Unknown_Compression_Method;
|
||||
}
|
||||
|
||||
cinfo := (cmf >> 4) & 0xf;
|
||||
if cinfo > 7 {
|
||||
return E_ZLIB.Unsupported_Window_Size;
|
||||
}
|
||||
ctx.window_size = 1 << (cinfo + 8);
|
||||
|
||||
flg, _ := compress.read_u8(ctx);
|
||||
|
||||
fcheck := flg & 0x1f;
|
||||
fcheck_computed := (cmf << 8 | flg) & 0x1f;
|
||||
if fcheck != fcheck_computed {
|
||||
return E_General.Checksum_Failed;
|
||||
}
|
||||
|
||||
fdict := (flg >> 5) & 1;
|
||||
/*
|
||||
We don't handle built-in dictionaries for now.
|
||||
They're application specific and PNG doesn't use them.
|
||||
*/
|
||||
if fdict != 0 {
|
||||
return E_ZLIB.FDICT_Unsupported;
|
||||
}
|
||||
|
||||
// flevel := Compression_Level((flg >> 6) & 3);
|
||||
/*
|
||||
Inflate can consume bits belonging to the Adler checksum.
|
||||
We pass the entire stream to Inflate and will unget bytes if we need to
|
||||
at the end to compare checksums.
|
||||
*/
|
||||
|
||||
// Seed the Adler32 rolling checksum.
|
||||
ctx.rolling_hash = 1;
|
||||
}
|
||||
|
||||
// Parse ZLIB stream without header.
|
||||
err = inflate_raw(ctx);
|
||||
if err != nil {
|
||||
return err;
|
||||
}
|
||||
|
||||
if !raw {
|
||||
compress.discard_to_next_byte_lsb(ctx);
|
||||
|
||||
adler32 := compress.read_bits_lsb(ctx, 8) << 24 | compress.read_bits_lsb(ctx, 8) << 16 | compress.read_bits_lsb(ctx, 8) << 8 | compress.read_bits_lsb(ctx, 8);
|
||||
if ctx.rolling_hash != u32(adler32) {
|
||||
return E_General.Checksum_Failed;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
// @(optimization_mode="speed")
|
||||
inflate_from_stream_raw :: proc(z: ^Context, allocator := context.allocator) -> (err: Error) #no_bounds_check {
|
||||
final := u32(0);
|
||||
type := u32(0);
|
||||
|
||||
z.num_bits = 0;
|
||||
z.code_buffer = 0;
|
||||
|
||||
z_repeat: ^Huffman_Table;
|
||||
z_offset: ^Huffman_Table;
|
||||
codelength_ht: ^Huffman_Table;
|
||||
|
||||
z_repeat, err = allocate_huffman_table(allocator=context.allocator);
|
||||
if err != nil {
|
||||
return err;
|
||||
}
|
||||
z_offset, err = allocate_huffman_table(allocator=context.allocator);
|
||||
if err != nil {
|
||||
return err;
|
||||
}
|
||||
codelength_ht, err = allocate_huffman_table(allocator=context.allocator);
|
||||
if err != nil {
|
||||
return err;
|
||||
}
|
||||
defer free(z_repeat);
|
||||
defer free(z_offset);
|
||||
defer free(codelength_ht);
|
||||
|
||||
if z.window_size == 0 {
|
||||
z.window_size = DEFLATE_MAX_DISTANCE;
|
||||
}
|
||||
|
||||
// Allocate rolling window buffer.
|
||||
last_b := mem.make_dynamic_array_len_cap([dynamic]u8, z.window_size, z.window_size, allocator);
|
||||
z.last = &last_b;
|
||||
defer delete(last_b);
|
||||
|
||||
for {
|
||||
final = compress.read_bits_lsb(z, 1);
|
||||
type = compress.read_bits_lsb(z, 2);
|
||||
|
||||
// fmt.printf("Final: %v | Type: %v\n", final, type);
|
||||
|
||||
switch type {
|
||||
case 0:
|
||||
// Uncompressed block
|
||||
|
||||
// Discard bits until next byte boundary
|
||||
compress.discard_to_next_byte_lsb(z);
|
||||
|
||||
uncompressed_len := i16(compress.read_bits_lsb(z, 16));
|
||||
length_check := i16(compress.read_bits_lsb(z, 16));
|
||||
|
||||
// fmt.printf("LEN: %v, ~LEN: %v, NLEN: %v, ~NLEN: %v\n", uncompressed_len, ~uncompressed_len, length_check, ~length_check);
|
||||
|
||||
|
||||
if ~uncompressed_len != length_check {
|
||||
return E_Deflate.Len_Nlen_Mismatch;
|
||||
}
|
||||
|
||||
/*
|
||||
TODO: Maybe speed this up with a stream-to-stream copy (read_from)
|
||||
and a single Adler32 update after.
|
||||
*/
|
||||
#no_bounds_check for uncompressed_len > 0 {
|
||||
compress.refill_lsb(z);
|
||||
lit := compress.read_bits_lsb(z, 8);
|
||||
write_byte(z, u8(lit));
|
||||
uncompressed_len -= 1;
|
||||
}
|
||||
case 3:
|
||||
return E_Deflate.BType_3;
|
||||
case:
|
||||
// log.debugf("Err: %v | Final: %v | Type: %v\n", err, final, type);
|
||||
if type == 1 {
|
||||
// Use fixed code lengths.
|
||||
err = build_huffman(z_repeat, Z_FIXED_LENGTH[:]);
|
||||
if err != nil {
|
||||
return err;
|
||||
}
|
||||
err = build_huffman(z_offset, Z_FIXED_DIST[:]);
|
||||
if err != nil {
|
||||
return err;
|
||||
}
|
||||
} else {
|
||||
lencodes: [286+32+137]u8;
|
||||
codelength_sizes: [19]u8;
|
||||
|
||||
//i: u32;
|
||||
n: u32;
|
||||
|
||||
compress.refill_lsb(z, 14);
|
||||
hlit := compress.read_bits_no_refill_lsb(z, 5) + 257;
|
||||
hdist := compress.read_bits_no_refill_lsb(z, 5) + 1;
|
||||
hclen := compress.read_bits_no_refill_lsb(z, 4) + 4;
|
||||
ntot := hlit + hdist;
|
||||
|
||||
#no_bounds_check for i in 0..<hclen {
|
||||
s := compress.read_bits_lsb(z, 3);
|
||||
codelength_sizes[Z_LENGTH_DEZIGZAG[i]] = u8(s);
|
||||
}
|
||||
err = build_huffman(codelength_ht, codelength_sizes[:]);
|
||||
if err != nil {
|
||||
return err;
|
||||
}
|
||||
|
||||
n = 0;
|
||||
c: u16;
|
||||
|
||||
for n < ntot {
|
||||
c, err = decode_huffman(z, codelength_ht);
|
||||
if err != nil {
|
||||
return err;
|
||||
}
|
||||
|
||||
if c < 0 || c >= 19 {
|
||||
return E_Deflate.Huffman_Bad_Code_Lengths;
|
||||
}
|
||||
if c < 16 {
|
||||
lencodes[n] = u8(c);
|
||||
n += 1;
|
||||
} else {
|
||||
fill := u8(0);
|
||||
compress.refill_lsb(z, 7);
|
||||
switch c {
|
||||
case 16:
|
||||
c = u16(compress.read_bits_no_refill_lsb(z, 2) + 3);
|
||||
if n == 0 {
|
||||
return E_Deflate.Huffman_Bad_Code_Lengths;
|
||||
}
|
||||
fill = lencodes[n - 1];
|
||||
case 17:
|
||||
c = u16(compress.read_bits_no_refill_lsb(z, 3) + 3);
|
||||
case 18:
|
||||
c = u16(compress.read_bits_no_refill_lsb(z, 7) + 11);
|
||||
case:
|
||||
return E_Deflate.Huffman_Bad_Code_Lengths;
|
||||
}
|
||||
|
||||
if ntot - n < u32(c) {
|
||||
return E_Deflate.Huffman_Bad_Code_Lengths;
|
||||
}
|
||||
|
||||
nc := n + u32(c);
|
||||
#no_bounds_check for ; n < nc; n += 1 {
|
||||
lencodes[n] = fill;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if n != ntot {
|
||||
return E_Deflate.Huffman_Bad_Code_Lengths;
|
||||
}
|
||||
|
||||
err = build_huffman(z_repeat, lencodes[:hlit]);
|
||||
if err != nil {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = build_huffman(z_offset, lencodes[hlit:ntot]);
|
||||
if err != nil {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
err = parse_huffman_block(z, z_repeat, z_offset);
|
||||
// log.debugf("Err: %v | Final: %v | Type: %v\n", err, final, type);
|
||||
if err != nil {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
if final == 1 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
inflate_from_byte_array :: proc(input: []u8, buf: ^bytes.Buffer, raw := false) -> (err: Error) {
|
||||
ctx := Context{};
|
||||
|
||||
r := bytes.Reader{};
|
||||
bytes.reader_init(&r, input);
|
||||
rs := bytes.reader_to_stream(&r);
|
||||
ctx.input = rs;
|
||||
|
||||
buf := buf;
|
||||
ws := bytes.buffer_to_stream(buf);
|
||||
ctx.output = ws;
|
||||
|
||||
err = inflate_from_stream(&ctx, raw);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
inflate_from_byte_array_raw :: proc(input: []u8, buf: ^bytes.Buffer, raw := false) -> (err: Error) {
|
||||
return inflate_from_byte_array(input, buf, true);
|
||||
}
|
||||
|
||||
inflate :: proc{inflate_from_stream, inflate_from_byte_array};
|
||||
inflate_raw :: proc{inflate_from_stream_raw, inflate_from_byte_array_raw};
|
||||
+3
-14
@@ -641,9 +641,9 @@ fmt_write_padding :: proc(fi: ^Info, width: int) {
|
||||
return;
|
||||
}
|
||||
|
||||
pad_byte: byte = '0';
|
||||
if fi.space {
|
||||
pad_byte = ' ';
|
||||
pad_byte: byte = ' ';
|
||||
if !fi.space {
|
||||
pad_byte = '0';
|
||||
}
|
||||
|
||||
for i := 0; i < width; i += 1 {
|
||||
@@ -1908,17 +1908,6 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
handle_relative_pointer :: proc(ptr: ^$T) -> rawptr where intrinsics.type_is_integer(T) {
|
||||
if ptr^ == 0 {
|
||||
return nil;
|
||||
}
|
||||
when intrinsics.type_is_unsigned(T) {
|
||||
return rawptr(uintptr(ptr) + uintptr(ptr^));
|
||||
} else {
|
||||
return rawptr(uintptr(ptr) + uintptr(i64(ptr^)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt_complex :: proc(fi: ^Info, c: complex128, bits: int, verb: rune) {
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
package image
|
||||
|
||||
import "core:bytes"
|
||||
import "core:mem"
|
||||
|
||||
Image :: struct {
|
||||
width: int,
|
||||
height: int,
|
||||
channels: int,
|
||||
depth: u8,
|
||||
pixels: bytes.Buffer,
|
||||
/*
|
||||
Some image loaders/writers can return/take an optional background color.
|
||||
For convenience, we return them as u16 so we don't need to switch on the type
|
||||
in our viewer, and can just test against nil.
|
||||
*/
|
||||
background: Maybe([3]u16),
|
||||
sidecar: any,
|
||||
}
|
||||
|
||||
/*
|
||||
IMPORTANT: `.do_not_expand_*` options currently skip handling of the `alpha_*` options,
|
||||
therefore Gray+Alpha will be returned as such even if you add `.alpha_drop_if_present`,
|
||||
and `.alpha_add_if_missing` and keyed transparency will likewise be ignored.
|
||||
|
||||
The same goes for indexed images. This will be remedied in a near future update.
|
||||
*/
|
||||
|
||||
/*
|
||||
Image_Option:
|
||||
`.info`
|
||||
This option behaves as `.return_ihdr` and `.do_not_decompress_image` and can be used
|
||||
to gather an image's dimensions and color information.
|
||||
|
||||
`.return_header`
|
||||
Fill out img.sidecar.header with the image's format-specific header struct.
|
||||
If we only care about the image specs, we can set `.return_header` +
|
||||
`.do_not_decompress_image`, or `.info`, which works as if both of these were set.
|
||||
|
||||
`.return_metadata`
|
||||
Returns all chunks not needed to decode the data.
|
||||
It also returns the header as if `.return_header` was set.
|
||||
|
||||
`.do_not_decompress_image`
|
||||
Skip decompressing IDAT chunk, defiltering and the rest.
|
||||
|
||||
`.do_not_expand_grayscale`
|
||||
Do not turn grayscale (+ Alpha) images into RGB(A).
|
||||
Returns just the 1 or 2 channels present, although 1, 2 and 4 bit are still scaled to 8-bit.
|
||||
|
||||
`.do_not_expand_indexed`
|
||||
Do not turn indexed (+ Alpha) images into RGB(A).
|
||||
Returns just the 1 or 2 (with `tRNS`) channels present.
|
||||
Make sure to use `return_metadata` to also return the palette chunk so you can recolor it yourself.
|
||||
|
||||
`.do_not_expand_channels`
|
||||
Applies both `.do_not_expand_grayscale` and `.do_not_expand_indexed`.
|
||||
|
||||
`.alpha_add_if_missing`
|
||||
If the image has no alpha channel, it'll add one set to max(type).
|
||||
Turns RGB into RGBA and Gray into Gray+Alpha
|
||||
|
||||
`.alpha_drop_if_present`
|
||||
If the image has an alpha channel, drop it.
|
||||
You may want to use `.alpha_premultiply` in this case.
|
||||
|
||||
NOTE: For PNG, this also skips handling of the tRNS chunk, if present,
|
||||
unless you select `alpha_premultiply`.
|
||||
In this case it'll premultiply the specified pixels in question only,
|
||||
as the others are implicitly fully opaque.
|
||||
|
||||
`.alpha_premultiply`
|
||||
If the image has an alpha channel, returns image data as follows:
|
||||
RGB *= A, Gray = Gray *= A
|
||||
|
||||
`.blend_background`
|
||||
If a bKGD chunk is present in a PNG, we normally just set `img.background`
|
||||
with its value and leave it up to the application to decide how to display the image,
|
||||
as per the PNG specification.
|
||||
|
||||
With `.blend_background` selected, we blend the image against the background
|
||||
color. As this negates the use for an alpha channel, we'll drop it _unless_
|
||||
you also specify `.alpha_add_if_missing`.
|
||||
|
||||
Options that don't apply to an image format will be ignored by their loader.
|
||||
*/
|
||||
|
||||
Option :: enum {
|
||||
info = 0,
|
||||
do_not_decompress_image,
|
||||
return_header,
|
||||
return_metadata,
|
||||
alpha_add_if_missing,
|
||||
alpha_drop_if_present,
|
||||
alpha_premultiply,
|
||||
blend_background,
|
||||
// Unimplemented
|
||||
do_not_expand_grayscale,
|
||||
do_not_expand_indexed,
|
||||
do_not_expand_channels,
|
||||
}
|
||||
Options :: distinct bit_set[Option];
|
||||
|
||||
Error :: enum {
|
||||
Invalid_PNG_Signature,
|
||||
IHDR_Not_First_Chunk,
|
||||
IHDR_Corrupt,
|
||||
IDAT_Missing,
|
||||
IDAT_Must_Be_Contiguous,
|
||||
IDAT_Corrupt,
|
||||
PNG_Does_Not_Adhere_to_Spec,
|
||||
PLTE_Encountered_Unexpectedly,
|
||||
PLTE_Invalid_Length,
|
||||
TRNS_Encountered_Unexpectedly,
|
||||
BKGD_Invalid_Length,
|
||||
Invalid_Image_Dimensions,
|
||||
Unknown_Color_Type,
|
||||
Invalid_Color_Bit_Depth_Combo,
|
||||
Unknown_Filter_Method,
|
||||
Unknown_Interlace_Method,
|
||||
Requested_Channel_Not_Present,
|
||||
Post_Processing_Error,
|
||||
}
|
||||
|
||||
/*
|
||||
Functions to help with image buffer calculations
|
||||
*/
|
||||
|
||||
compute_buffer_size :: proc(width, height, channels, depth: int, extra_row_bytes := int(0)) -> (size: int) {
|
||||
|
||||
size = ((((channels * width * depth) + 7) >> 3) + extra_row_bytes) * height;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
For when you have an RGB(A) image, but want a particular channel.
|
||||
*/
|
||||
|
||||
Channel :: enum u8 {
|
||||
R = 1,
|
||||
G = 2,
|
||||
B = 3,
|
||||
A = 4,
|
||||
}
|
||||
|
||||
return_single_channel :: proc(img: ^Image, channel: Channel) -> (res: ^Image, ok: bool) {
|
||||
|
||||
ok = false;
|
||||
t: bytes.Buffer;
|
||||
|
||||
idx := int(channel);
|
||||
|
||||
if img.channels == 2 && idx == 4 {
|
||||
// Alpha requested, which in a two channel image is index 2: G.
|
||||
idx = 2;
|
||||
}
|
||||
|
||||
if idx > img.channels {
|
||||
return {}, false;
|
||||
}
|
||||
|
||||
switch(img.depth) {
|
||||
case 8:
|
||||
buffer_size := compute_buffer_size(img.width, img.height, 1, 8);
|
||||
t = bytes.Buffer{};
|
||||
resize(&t.buf, buffer_size);
|
||||
|
||||
i := bytes.buffer_to_bytes(&img.pixels);
|
||||
o := bytes.buffer_to_bytes(&t);
|
||||
|
||||
for len(i) > 0 {
|
||||
o[0] = i[idx];
|
||||
i = i[img.channels:];
|
||||
o = o[1:];
|
||||
}
|
||||
case 16:
|
||||
buffer_size := compute_buffer_size(img.width, img.height, 2, 8);
|
||||
t = bytes.Buffer{};
|
||||
resize(&t.buf, buffer_size);
|
||||
|
||||
i := mem.slice_data_cast([]u16, img.pixels.buf[:]);
|
||||
o := mem.slice_data_cast([]u16, t.buf[:]);
|
||||
|
||||
for len(i) > 0 {
|
||||
o[0] = i[idx];
|
||||
i = i[img.channels:];
|
||||
o = o[1:];
|
||||
}
|
||||
case 1, 2, 4:
|
||||
// We shouldn't see this case, as the loader already turns these into 8-bit.
|
||||
return {}, false;
|
||||
}
|
||||
|
||||
res = new(Image);
|
||||
res.width = img.width;
|
||||
res.height = img.height;
|
||||
res.channels = 1;
|
||||
res.depth = img.depth;
|
||||
res.pixels = t;
|
||||
res.background = img.background;
|
||||
res.sidecar = img.sidecar;
|
||||
|
||||
return res, true;
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
//+ignore
|
||||
package png
|
||||
|
||||
import "core:compress"
|
||||
import "core:image"
|
||||
import "core:image/png"
|
||||
import "core:bytes"
|
||||
import "core:fmt"
|
||||
|
||||
// For PPM writer
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
|
||||
main :: proc() {
|
||||
file: string;
|
||||
|
||||
options := image.Options{};
|
||||
err: compress.Error;
|
||||
img: ^image.Image;
|
||||
|
||||
file = "../../../misc/logo-slim.png";
|
||||
|
||||
img, err = png.load(file, options);
|
||||
defer png.destroy(img);
|
||||
|
||||
if err != nil {
|
||||
fmt.printf("Trying to read PNG file %v returned %v\n", file, err);
|
||||
} else {
|
||||
v: png.Info;
|
||||
ok: bool;
|
||||
|
||||
fmt.printf("Image: %vx%vx%v, %v-bit.\n", img.width, img.height, img.channels, img.depth);
|
||||
|
||||
if v, ok = img.sidecar.(png.Info); ok {
|
||||
// Handle ancillary chunks as you wish.
|
||||
// We provide helper functions for a few types.
|
||||
for c in v.chunks {
|
||||
#partial switch (c.header.type) {
|
||||
case .tIME:
|
||||
t, _ := png.core_time(c);
|
||||
fmt.printf("[tIME]: %v\n", t);
|
||||
case .gAMA:
|
||||
fmt.printf("[gAMA]: %v\n", png.gamma(c));
|
||||
case .pHYs:
|
||||
phys := png.phys(c);
|
||||
if phys.unit == .Meter {
|
||||
xm := f32(img.width) / f32(phys.ppu_x);
|
||||
ym := f32(img.height) / f32(phys.ppu_y);
|
||||
dpi_x, dpi_y := png.phys_to_dpi(phys);
|
||||
fmt.printf("[pHYs] Image resolution is %v x %v pixels per meter.\n", phys.ppu_x, phys.ppu_y);
|
||||
fmt.printf("[pHYs] Image resolution is %v x %v DPI.\n", dpi_x, dpi_y);
|
||||
fmt.printf("[pHYs] Image dimensions are %v x %v meters.\n", xm, ym);
|
||||
} else {
|
||||
fmt.printf("[pHYs] x: %v, y: %v pixels per unknown unit.\n", phys.ppu_x, phys.ppu_y);
|
||||
}
|
||||
case .iTXt, .zTXt, .tEXt:
|
||||
res, ok_text := png.text(c);
|
||||
if ok_text {
|
||||
if c.header.type == .iTXt {
|
||||
fmt.printf("[iTXt] %v (%v:%v): %v\n", res.keyword, res.language, res.keyword_localized, res.text);
|
||||
} else {
|
||||
fmt.printf("[tEXt/zTXt] %v: %v\n", res.keyword, res.text);
|
||||
}
|
||||
}
|
||||
defer png.text_destroy(res);
|
||||
case .bKGD:
|
||||
fmt.printf("[bKGD] %v\n", img.background);
|
||||
case .eXIf:
|
||||
res, ok_exif := png.exif(c);
|
||||
if ok_exif {
|
||||
/*
|
||||
Other than checking the signature and byte order, we don't handle Exif data.
|
||||
If you wish to interpret it, pass it to an Exif parser.
|
||||
*/
|
||||
fmt.printf("[eXIf] %v\n", res);
|
||||
}
|
||||
case .PLTE:
|
||||
plte, plte_ok := png.plte(c);
|
||||
if plte_ok {
|
||||
fmt.printf("[PLTE] %v\n", plte);
|
||||
} else {
|
||||
fmt.printf("[PLTE] Error\n");
|
||||
}
|
||||
case .hIST:
|
||||
res, ok_hist := png.hist(c);
|
||||
if ok_hist {
|
||||
fmt.printf("[hIST] %v\n", res);
|
||||
}
|
||||
case .cHRM:
|
||||
res, ok_chrm := png.chrm(c);
|
||||
if ok_chrm {
|
||||
fmt.printf("[cHRM] %v\n", res);
|
||||
}
|
||||
case .sPLT:
|
||||
res, ok_splt := png.splt(c);
|
||||
if ok_splt {
|
||||
fmt.printf("[sPLT] %v\n", res);
|
||||
}
|
||||
png.splt_destroy(res);
|
||||
case .sBIT:
|
||||
if res, ok_sbit := png.sbit(c); ok_sbit {
|
||||
fmt.printf("[sBIT] %v\n", res);
|
||||
}
|
||||
case .iCCP:
|
||||
res, ok_iccp := png.iccp(c);
|
||||
if ok_iccp {
|
||||
fmt.printf("[iCCP] %v\n", res);
|
||||
}
|
||||
png.iccp_destroy(res);
|
||||
case .sRGB:
|
||||
if res, ok_srgb := png.srgb(c); ok_srgb {
|
||||
fmt.printf("[sRGB] Rendering intent: %v\n", res);
|
||||
}
|
||||
case:
|
||||
type := c.header.type;
|
||||
name := png.chunk_type_to_name(&type);
|
||||
fmt.printf("[%v]: %v\n", name, c.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil && .do_not_decompress_image not_in options && .info not_in options {
|
||||
if ok := write_image_as_ppm("out.ppm", img); ok {
|
||||
fmt.println("Saved decoded image.");
|
||||
} else {
|
||||
fmt.println("Error saving out.ppm.");
|
||||
fmt.println(img);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Crappy PPM writer used during testing. Don't use in production.
|
||||
write_image_as_ppm :: proc(filename: string, image: ^image.Image) -> (success: bool) {
|
||||
|
||||
_bg :: proc(bg: Maybe([3]u16), x, y: int, high := true) -> (res: [3]u16) {
|
||||
if v, ok := bg.?; ok {
|
||||
res = v;
|
||||
} else {
|
||||
if high {
|
||||
l := u16(30 * 256 + 30);
|
||||
|
||||
if (x & 4 == 0) ~ (y & 4 == 0) {
|
||||
res = [3]u16{l, 0, l};
|
||||
} else {
|
||||
res = [3]u16{l >> 1, 0, l >> 1};
|
||||
}
|
||||
} else {
|
||||
if (x & 4 == 0) ~ (y & 4 == 0) {
|
||||
res = [3]u16{30, 30, 30};
|
||||
} else {
|
||||
res = [3]u16{15, 15, 15};
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// profiler.timed_proc();
|
||||
using image;
|
||||
using os;
|
||||
|
||||
flags: int = O_WRONLY|O_CREATE|O_TRUNC;
|
||||
|
||||
img := image;
|
||||
|
||||
// PBM 16-bit images are big endian
|
||||
when ODIN_ENDIAN == "little" {
|
||||
if img.depth == 16 {
|
||||
// The pixel components are in Big Endian. Let's byteswap back.
|
||||
input := mem.slice_data_cast([]u16, img.pixels.buf[:]);
|
||||
output := mem.slice_data_cast([]u16be, img.pixels.buf[:]);
|
||||
#no_bounds_check for v, i in input {
|
||||
output[i] = u16be(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pix := bytes.buffer_to_bytes(&img.pixels);
|
||||
|
||||
if len(pix) == 0 || len(pix) < image.width * image.height * int(image.channels) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mode: int = 0;
|
||||
when ODIN_OS == "linux" || ODIN_OS == "darwin" {
|
||||
// NOTE(justasd): 644 (owner read, write; group read; others read)
|
||||
mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
|
||||
}
|
||||
|
||||
fd, err := open(filename, flags, mode);
|
||||
if err != 0 {
|
||||
return false;
|
||||
}
|
||||
defer close(fd);
|
||||
|
||||
write_string(fd,
|
||||
fmt.tprintf("P6\n%v %v\n%v\n", width, height, (1 << depth -1)),
|
||||
);
|
||||
|
||||
if channels == 3 {
|
||||
// We don't handle transparency here...
|
||||
write_ptr(fd, raw_data(pix), len(pix));
|
||||
} else {
|
||||
bpp := depth == 16 ? 2 : 1;
|
||||
bytes_needed := width * height * 3 * bpp;
|
||||
|
||||
op := bytes.Buffer{};
|
||||
bytes.buffer_init_allocator(&op, bytes_needed, bytes_needed);
|
||||
defer bytes.buffer_destroy(&op);
|
||||
|
||||
if channels == 1 {
|
||||
if depth == 16 {
|
||||
assert(len(pix) == width * height * 2);
|
||||
p16 := mem.slice_data_cast([]u16, pix);
|
||||
o16 := mem.slice_data_cast([]u16, op.buf[:]);
|
||||
#no_bounds_check for len(p16) != 0 {
|
||||
r := u16(p16[0]);
|
||||
o16[0] = r;
|
||||
o16[1] = r;
|
||||
o16[2] = r;
|
||||
p16 = p16[1:];
|
||||
o16 = o16[3:];
|
||||
}
|
||||
} else {
|
||||
o := 0;
|
||||
for i := 0; i < len(pix); i += 1 {
|
||||
r := pix[i];
|
||||
op.buf[o ] = r;
|
||||
op.buf[o+1] = r;
|
||||
op.buf[o+2] = r;
|
||||
o += 3;
|
||||
}
|
||||
}
|
||||
write_ptr(fd, raw_data(op.buf), len(op.buf));
|
||||
} else if channels == 2 {
|
||||
if depth == 16 {
|
||||
p16 := mem.slice_data_cast([]u16, pix);
|
||||
o16 := mem.slice_data_cast([]u16, op.buf[:]);
|
||||
|
||||
bgcol := img.background;
|
||||
|
||||
#no_bounds_check for len(p16) != 0 {
|
||||
r := f64(u16(p16[0]));
|
||||
bg: f64;
|
||||
if bgcol != nil {
|
||||
v := bgcol.([3]u16)[0];
|
||||
bg = f64(v);
|
||||
}
|
||||
a := f64(u16(p16[1])) / 65535.0;
|
||||
l := (a * r) + (1 - a) * bg;
|
||||
|
||||
o16[0] = u16(l);
|
||||
o16[1] = u16(l);
|
||||
o16[2] = u16(l);
|
||||
|
||||
p16 = p16[2:];
|
||||
o16 = o16[3:];
|
||||
}
|
||||
} else {
|
||||
o := 0;
|
||||
for i := 0; i < len(pix); i += 2 {
|
||||
r := pix[i]; a := pix[i+1]; a1 := f32(a) / 255.0;
|
||||
c := u8(f32(r) * a1);
|
||||
op.buf[o ] = c;
|
||||
op.buf[o+1] = c;
|
||||
op.buf[o+2] = c;
|
||||
o += 3;
|
||||
}
|
||||
}
|
||||
write_ptr(fd, raw_data(op.buf), len(op.buf));
|
||||
} else if channels == 4 {
|
||||
if depth == 16 {
|
||||
p16 := mem.slice_data_cast([]u16be, pix);
|
||||
o16 := mem.slice_data_cast([]u16be, op.buf[:]);
|
||||
|
||||
#no_bounds_check for len(p16) != 0 {
|
||||
|
||||
bg := _bg(img.background, 0, 0);
|
||||
r := f32(p16[0]);
|
||||
g := f32(p16[1]);
|
||||
b := f32(p16[2]);
|
||||
a := f32(p16[3]) / 65535.0;
|
||||
|
||||
lr := (a * r) + (1 - a) * f32(bg[0]);
|
||||
lg := (a * g) + (1 - a) * f32(bg[1]);
|
||||
lb := (a * b) + (1 - a) * f32(bg[2]);
|
||||
|
||||
o16[0] = u16be(lr);
|
||||
o16[1] = u16be(lg);
|
||||
o16[2] = u16be(lb);
|
||||
|
||||
p16 = p16[4:];
|
||||
o16 = o16[3:];
|
||||
}
|
||||
} else {
|
||||
o := 0;
|
||||
|
||||
for i := 0; i < len(pix); i += 4 {
|
||||
|
||||
x := (i / 4) % width;
|
||||
y := i / width / 4;
|
||||
|
||||
_b := _bg(img.background, x, y, false);
|
||||
bgcol := [3]u8{u8(_b[0]), u8(_b[1]), u8(_b[2])};
|
||||
|
||||
r := f32(pix[i]);
|
||||
g := f32(pix[i+1]);
|
||||
b := f32(pix[i+2]);
|
||||
a := f32(pix[i+3]) / 255.0;
|
||||
|
||||
lr := u8(f32(r) * a + (1 - a) * f32(bgcol[0]));
|
||||
lg := u8(f32(g) * a + (1 - a) * f32(bgcol[1]));
|
||||
lb := u8(f32(b) * a + (1 - a) * f32(bgcol[2]));
|
||||
op.buf[o ] = lr;
|
||||
op.buf[o+1] = lg;
|
||||
op.buf[o+2] = lb;
|
||||
o += 3;
|
||||
}
|
||||
}
|
||||
write_ptr(fd, raw_data(op.buf), len(op.buf));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,516 @@
|
||||
package png
|
||||
|
||||
import "core:image"
|
||||
import "core:compress/zlib"
|
||||
import coretime "core:time"
|
||||
import "core:strings"
|
||||
import "core:bytes"
|
||||
import "core:mem"
|
||||
|
||||
/*
|
||||
These are a few useful utility functions to work with PNG images.
|
||||
*/
|
||||
|
||||
/*
|
||||
Cleanup of image-specific data.
|
||||
There are other helpers for cleanup of PNG-specific data.
|
||||
Those are named *_destroy, where * is the name of the helper.
|
||||
*/
|
||||
|
||||
destroy :: proc(img: ^Image) {
|
||||
if img == nil {
|
||||
/*
|
||||
Nothing to do.
|
||||
Load must've returned with an error.
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
||||
bytes.buffer_destroy(&img.pixels);
|
||||
|
||||
/*
|
||||
We don't need to do anything for the individual chunks.
|
||||
They're allocated on the temp allocator, as is info.chunks
|
||||
|
||||
See read_chunk.
|
||||
*/
|
||||
free(img);
|
||||
}
|
||||
|
||||
/*
|
||||
Chunk helpers
|
||||
*/
|
||||
|
||||
gamma :: proc(c: Chunk) -> f32 {
|
||||
assert(c.header.type == .gAMA);
|
||||
res := (^gAMA)(raw_data(c.data))^;
|
||||
when true {
|
||||
// Returns the wrong result on old backend
|
||||
// Fixed for -llvm-api
|
||||
return f32(res.gamma_100k) / 100_000.0;
|
||||
} else {
|
||||
return f32(u32(res.gamma_100k)) / 100_000.0;
|
||||
}
|
||||
}
|
||||
|
||||
INCHES_PER_METER :: 1000.0 / 25.4;
|
||||
|
||||
phys :: proc(c: Chunk) -> pHYs {
|
||||
assert(c.header.type == .pHYs);
|
||||
res := (^pHYs)(raw_data(c.data))^;
|
||||
return res;
|
||||
}
|
||||
|
||||
phys_to_dpi :: proc(p: pHYs) -> (x_dpi, y_dpi: f32) {
|
||||
return f32(p.ppu_x) / INCHES_PER_METER, f32(p.ppu_y) / INCHES_PER_METER;
|
||||
}
|
||||
|
||||
time :: proc(c: Chunk) -> tIME {
|
||||
assert(c.header.type == .tIME);
|
||||
res := (^tIME)(raw_data(c.data))^;
|
||||
return res;
|
||||
}
|
||||
|
||||
core_time :: proc(c: Chunk) -> (t: coretime.Time, ok: bool) {
|
||||
png_time := time(c);
|
||||
using png_time;
|
||||
return coretime.datetime_to_time(
|
||||
int(year), int(month), int(day),
|
||||
int(hour), int(minute), int(second),
|
||||
);
|
||||
}
|
||||
|
||||
text :: proc(c: Chunk) -> (res: Text, ok: bool) {
|
||||
#partial switch c.header.type {
|
||||
case .tEXt:
|
||||
ok = true;
|
||||
|
||||
fields := bytes.split(s=c.data, sep=[]u8{0}, allocator=context.temp_allocator);
|
||||
if len(fields) == 2 {
|
||||
res.keyword = strings.clone(string(fields[0]));
|
||||
res.text = strings.clone(string(fields[1]));
|
||||
} else {
|
||||
ok = false;
|
||||
}
|
||||
return;
|
||||
case .zTXt:
|
||||
ok = true;
|
||||
|
||||
fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=3, allocator=context.temp_allocator);
|
||||
if len(fields) != 3 || len(fields[1]) != 0 {
|
||||
// Compression method must be 0=Deflate, which thanks to the split above turns
|
||||
// into an empty slice
|
||||
ok = false; return;
|
||||
}
|
||||
|
||||
// Set up ZLIB context and decompress text payload.
|
||||
buf: bytes.Buffer;
|
||||
zlib_error := zlib.inflate_from_byte_array(fields[2], &buf);
|
||||
defer bytes.buffer_destroy(&buf);
|
||||
if zlib_error != nil {
|
||||
ok = false; return;
|
||||
}
|
||||
|
||||
res.keyword = strings.clone(string(fields[0]));
|
||||
res.text = strings.clone(bytes.buffer_to_string(&buf));
|
||||
return;
|
||||
case .iTXt:
|
||||
ok = true;
|
||||
|
||||
s := string(c.data);
|
||||
null := strings.index_byte(s, 0);
|
||||
if null == -1 {
|
||||
ok = false; return;
|
||||
}
|
||||
if len(c.data) < null + 4 {
|
||||
// At a minimum, including the \0 following the keyword, we require 5 more bytes.
|
||||
ok = false; return;
|
||||
}
|
||||
res.keyword = strings.clone(string(c.data[:null]));
|
||||
rest := c.data[null+1:];
|
||||
|
||||
compression_flag := rest[:1][0];
|
||||
if compression_flag > 1 {
|
||||
ok = false; return;
|
||||
}
|
||||
compression_method := rest[1:2][0];
|
||||
if compression_flag == 1 && compression_method > 0 {
|
||||
// Only Deflate is supported
|
||||
ok = false; return;
|
||||
}
|
||||
rest = rest[2:];
|
||||
|
||||
// We now expect an optional language keyword and translated keyword, both followed by a \0
|
||||
null = strings.index_byte(string(rest), 0);
|
||||
if null == -1 {
|
||||
ok = false; return;
|
||||
}
|
||||
res.language = strings.clone(string(rest[:null]));
|
||||
rest = rest[null+1:];
|
||||
|
||||
null = strings.index_byte(string(rest), 0);
|
||||
if null == -1 {
|
||||
ok = false; return;
|
||||
}
|
||||
res.keyword_localized = strings.clone(string(rest[:null]));
|
||||
rest = rest[null+1:];
|
||||
if compression_flag == 0 {
|
||||
res.text = strings.clone(string(rest));
|
||||
} else {
|
||||
// Set up ZLIB context and decompress text payload.
|
||||
buf: bytes.Buffer;
|
||||
zlib_error := zlib.inflate_from_byte_array(rest, &buf);
|
||||
defer bytes.buffer_destroy(&buf);
|
||||
if zlib_error != nil {
|
||||
|
||||
ok = false; return;
|
||||
}
|
||||
|
||||
res.text = strings.clone(bytes.buffer_to_string(&buf));
|
||||
}
|
||||
return;
|
||||
case:
|
||||
// PNG text helper called with an unrecognized chunk type.
|
||||
ok = false; return;
|
||||
}
|
||||
}
|
||||
|
||||
text_destroy :: proc(text: Text) {
|
||||
delete(text.keyword);
|
||||
delete(text.keyword_localized);
|
||||
delete(text.language);
|
||||
delete(text.text);
|
||||
}
|
||||
|
||||
iccp :: proc(c: Chunk) -> (res: iCCP, ok: bool) {
|
||||
ok = true;
|
||||
|
||||
fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=3, allocator=context.temp_allocator);
|
||||
|
||||
if len(fields[0]) < 1 || len(fields[0]) > 79 {
|
||||
// Invalid profile name
|
||||
ok = false; return;
|
||||
}
|
||||
|
||||
if len(fields[1]) != 0 {
|
||||
// Compression method should be a zero, which the split turned into an empty slice.
|
||||
ok = false; return;
|
||||
}
|
||||
|
||||
// Set up ZLIB context and decompress iCCP payload
|
||||
buf: bytes.Buffer;
|
||||
zlib_error := zlib.inflate_from_byte_array(fields[2], &buf);
|
||||
if zlib_error != nil {
|
||||
bytes.buffer_destroy(&buf);
|
||||
ok = false; return;
|
||||
}
|
||||
|
||||
res.name = strings.clone(string(fields[0]));
|
||||
res.profile = bytes.buffer_to_bytes(&buf);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
iccp_destroy :: proc(i: iCCP) {
|
||||
delete(i.name);
|
||||
|
||||
delete(i.profile);
|
||||
|
||||
}
|
||||
|
||||
srgb :: proc(c: Chunk) -> (res: sRGB, ok: bool) {
|
||||
ok = true;
|
||||
|
||||
if c.header.type != .sRGB || len(c.data) != 1 {
|
||||
return {}, false;
|
||||
}
|
||||
|
||||
res.intent = sRGB_Rendering_Intent(c.data[0]);
|
||||
if res.intent > max(sRGB_Rendering_Intent) {
|
||||
ok = false; return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
plte :: proc(c: Chunk) -> (res: PLTE, ok: bool) {
|
||||
if c.header.type != .PLTE {
|
||||
return {}, false;
|
||||
}
|
||||
|
||||
i := 0; j := 0; ok = true;
|
||||
for j < int(c.header.length) {
|
||||
res.entries[i] = {c.data[j], c.data[j+1], c.data[j+2]};
|
||||
i += 1; j += 3;
|
||||
}
|
||||
res.used = u16(i);
|
||||
return;
|
||||
}
|
||||
|
||||
splt :: proc(c: Chunk) -> (res: sPLT, ok: bool) {
|
||||
if c.header.type != .sPLT {
|
||||
return {}, false;
|
||||
}
|
||||
ok = true;
|
||||
|
||||
fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=2, allocator=context.temp_allocator);
|
||||
if len(fields) != 2 {
|
||||
return {}, false;
|
||||
}
|
||||
|
||||
res.depth = fields[1][0];
|
||||
if res.depth != 8 && res.depth != 16 {
|
||||
return {}, false;
|
||||
}
|
||||
|
||||
data := fields[1][1:];
|
||||
count: int;
|
||||
|
||||
if res.depth == 8 {
|
||||
if len(data) % 6 != 0 {
|
||||
return {}, false;
|
||||
}
|
||||
count = len(data) / 6;
|
||||
if count > 256 {
|
||||
return {}, false;
|
||||
}
|
||||
|
||||
res.entries = mem.slice_data_cast([][4]u8, data);
|
||||
} else { // res.depth == 16
|
||||
if len(data) % 10 != 0 {
|
||||
return {}, false;
|
||||
}
|
||||
count = len(data) / 10;
|
||||
if count > 256 {
|
||||
return {}, false;
|
||||
}
|
||||
|
||||
res.entries = mem.slice_data_cast([][4]u16, data);
|
||||
}
|
||||
|
||||
res.name = strings.clone(string(fields[0]));
|
||||
res.used = u16(count);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
splt_destroy :: proc(s: sPLT) {
|
||||
delete(s.name);
|
||||
}
|
||||
|
||||
sbit :: proc(c: Chunk) -> (res: [4]u8, ok: bool) {
|
||||
/*
|
||||
Returns [4]u8 with the significant bits in each channel.
|
||||
A channel will contain zero if not applicable to the PNG color type.
|
||||
*/
|
||||
|
||||
if len(c.data) < 1 || len(c.data) > 4 {
|
||||
ok = false; return;
|
||||
}
|
||||
ok = true;
|
||||
|
||||
for i := 0; i < len(c.data); i += 1 {
|
||||
res[i] = c.data[i];
|
||||
}
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
hist :: proc(c: Chunk) -> (res: hIST, ok: bool) {
|
||||
if c.header.type != .hIST {
|
||||
return {}, false;
|
||||
}
|
||||
if c.header.length & 1 == 1 || c.header.length > 512 {
|
||||
// The entries are u16be, so the length must be even.
|
||||
// At most 256 entries must be present
|
||||
return {}, false;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
data := mem.slice_data_cast([]u16be, c.data);
|
||||
i := 0;
|
||||
for len(data) > 0 {
|
||||
// HIST entries are u16be, we unpack them to machine format
|
||||
res.entries[i] = u16(data[0]);
|
||||
i += 1; data = data[1:];
|
||||
}
|
||||
res.used = u16(i);
|
||||
return;
|
||||
}
|
||||
|
||||
chrm :: proc(c: Chunk) -> (res: cHRM, ok: bool) {
|
||||
ok = true;
|
||||
if c.header.length != size_of(cHRM_Raw) {
|
||||
return {}, false;
|
||||
}
|
||||
chrm := (^cHRM_Raw)(raw_data(c.data))^;
|
||||
|
||||
res.w.x = f32(chrm.w.x) / 100_000.0;
|
||||
res.w.y = f32(chrm.w.y) / 100_000.0;
|
||||
res.r.x = f32(chrm.r.x) / 100_000.0;
|
||||
res.r.y = f32(chrm.r.y) / 100_000.0;
|
||||
res.g.x = f32(chrm.g.x) / 100_000.0;
|
||||
res.g.y = f32(chrm.g.y) / 100_000.0;
|
||||
res.b.x = f32(chrm.b.x) / 100_000.0;
|
||||
res.b.y = f32(chrm.b.y) / 100_000.0;
|
||||
return;
|
||||
}
|
||||
|
||||
exif :: proc(c: Chunk) -> (res: Exif, ok: bool) {
|
||||
|
||||
ok = true;
|
||||
|
||||
if len(c.data) < 4 {
|
||||
ok = false; return;
|
||||
}
|
||||
|
||||
if c.data[0] == 'M' && c.data[1] == 'M' {
|
||||
res.byte_order = .big_endian;
|
||||
if c.data[2] != 0 || c.data[3] != 42 {
|
||||
ok = false; return;
|
||||
}
|
||||
} else if c.data[0] == 'I' && c.data[1] == 'I' {
|
||||
res.byte_order = .little_endian;
|
||||
if c.data[2] != 42 || c.data[3] != 0 {
|
||||
ok = false; return;
|
||||
}
|
||||
} else {
|
||||
ok = false; return;
|
||||
}
|
||||
|
||||
res.data = c.data;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
General helper functions
|
||||
*/
|
||||
|
||||
compute_buffer_size :: image.compute_buffer_size;
|
||||
|
||||
/*
|
||||
PNG save helpers
|
||||
*/
|
||||
|
||||
when false {
|
||||
|
||||
make_chunk :: proc(c: any, t: Chunk_Type) -> (res: Chunk) {
|
||||
|
||||
data: []u8;
|
||||
if v, ok := c.([]u8); ok {
|
||||
data = v;
|
||||
} else {
|
||||
data = mem.any_to_bytes(c);
|
||||
}
|
||||
|
||||
res.header.length = u32be(len(data));
|
||||
res.header.type = t;
|
||||
res.data = data;
|
||||
|
||||
// CRC the type
|
||||
crc := hash.crc32(mem.any_to_bytes(res.header.type));
|
||||
// Extend the CRC with the data
|
||||
res.crc = u32be(hash.crc32(data, crc));
|
||||
return;
|
||||
}
|
||||
|
||||
write_chunk :: proc(fd: os.Handle, chunk: Chunk) {
|
||||
c := chunk;
|
||||
// Write length + type
|
||||
os.write_ptr(fd, &c.header, 8);
|
||||
// Write data
|
||||
os.write_ptr(fd, mem.raw_data(c.data), int(c.header.length));
|
||||
// Write CRC32
|
||||
os.write_ptr(fd, &c.crc, 4);
|
||||
}
|
||||
|
||||
write_image_as_png :: proc(filename: string, image: Image) -> (err: Error) {
|
||||
profiler.timed_proc();
|
||||
using image;
|
||||
using os;
|
||||
flags: int = O_WRONLY|O_CREATE|O_TRUNC;
|
||||
|
||||
if len(image.pixels) == 0 || len(image.pixels) < image.width * image.height * int(image.channels) {
|
||||
return E_PNG.Invalid_Image_Dimensions;
|
||||
}
|
||||
|
||||
mode: int = 0;
|
||||
when ODIN_OS == "linux" || ODIN_OS == "darwin" {
|
||||
// NOTE(justasd): 644 (owner read, write; group read; others read)
|
||||
mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
|
||||
}
|
||||
|
||||
fd, fderr := open(filename, flags, mode);
|
||||
if fderr != 0 {
|
||||
return E_General.Cannot_Open_File;
|
||||
}
|
||||
defer close(fd);
|
||||
|
||||
magic := Signature;
|
||||
|
||||
write_ptr(fd, &magic, 8);
|
||||
|
||||
ihdr := IHDR{
|
||||
width = u32be(width),
|
||||
height = u32be(height),
|
||||
bit_depth = depth,
|
||||
compression_method = 0,
|
||||
filter_method = 0,
|
||||
interlace_method = .None,
|
||||
};
|
||||
|
||||
switch channels {
|
||||
case 1: ihdr.color_type = Color_Type{};
|
||||
case 2: ihdr.color_type = Color_Type{.Alpha};
|
||||
case 3: ihdr.color_type = Color_Type{.Color};
|
||||
case 4: ihdr.color_type = Color_Type{.Color, .Alpha};
|
||||
case:// Unhandled
|
||||
return E_PNG.Unknown_Color_Type;
|
||||
}
|
||||
h := make_chunk(ihdr, .IHDR);
|
||||
write_chunk(fd, h);
|
||||
|
||||
bytes_needed := width * height * int(channels) + height;
|
||||
filter_bytes := mem.make_dynamic_array_len_cap([dynamic]u8, bytes_needed, bytes_needed, context.allocator);
|
||||
defer delete(filter_bytes);
|
||||
|
||||
i := 0; j := 0;
|
||||
// Add a filter byte 0 per pixel row
|
||||
for y := 0; y < height; y += 1 {
|
||||
filter_bytes[j] = 0; j += 1;
|
||||
for x := 0; x < width; x += 1 {
|
||||
for z := 0; z < channels; z += 1 {
|
||||
filter_bytes[j+z] = image.pixels[i+z];
|
||||
}
|
||||
i += channels; j += channels;
|
||||
}
|
||||
}
|
||||
assert(j == bytes_needed);
|
||||
|
||||
a: []u8 = filter_bytes[:];
|
||||
|
||||
out_buf: ^[dynamic]u8;
|
||||
defer free(out_buf);
|
||||
|
||||
ctx := zlib.ZLIB_Context{
|
||||
in_buf = &a,
|
||||
out_buf = out_buf,
|
||||
};
|
||||
err = zlib.write_zlib_stream_from_memory(&ctx);
|
||||
|
||||
b: []u8;
|
||||
if err == nil {
|
||||
b = ctx.out_buf[:];
|
||||
} else {
|
||||
return err;
|
||||
}
|
||||
|
||||
idat := make_chunk(b, .IDAT);
|
||||
|
||||
write_chunk(fd, idat);
|
||||
|
||||
iend := make_chunk([]u8{}, .IEND);
|
||||
write_chunk(fd, iend);
|
||||
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,33 @@ volatile_store :: proc(dst: ^$T, val: T) -> T ---
|
||||
|
||||
// Trapping
|
||||
debug_trap :: proc() ---
|
||||
trap :: proc() -> ! ---
|
||||
trap :: proc() -> ! ---
|
||||
|
||||
// Instructions
|
||||
|
||||
alloca :: proc(size, align: int) -> ^u8 ---
|
||||
cpu_relax :: proc() ---
|
||||
read_cycle_counter :: proc() -> i64 ---
|
||||
|
||||
count_ones :: proc(x: $T) -> T where type_is_integer(T) ---
|
||||
count_zeros :: proc(x: $T) -> T where type_is_integer(T) ---
|
||||
count_trailing_zeros :: proc(x: $T) -> T where type_is_integer(T) ---
|
||||
count_leading_zeros :: proc(x: $T) -> T where type_is_integer(T) ---
|
||||
reverse_bits :: proc(x: $T) -> T where type_is_integer(T) ---
|
||||
byte_swap :: proc(x: $T) -> T where type_is_integer(T) || type_is_float(T) ---
|
||||
|
||||
overflow_add :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok ---
|
||||
overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok ---
|
||||
overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok ---
|
||||
|
||||
fixed_point_mul :: proc(lhs, rhs: $T, #const scale: uint) -> T where type_is_integer(T) ---
|
||||
fixed_point_div :: proc(lhs, rhs: $T, #const scale: uint) -> T where type_is_integer(T) ---
|
||||
fixed_point_mul_sat :: proc(lhs, rhs: $T, #const scale: uint) -> T where type_is_integer(T) ---
|
||||
fixed_point_div_sat :: proc(lhs, rhs: $T, #const scale: uint) -> T where type_is_integer(T) ---
|
||||
|
||||
// Compiler Hints
|
||||
expect :: proc(val, expected_val: T) -> T ---
|
||||
|
||||
|
||||
// Atomics
|
||||
atomic_fence :: proc() ---
|
||||
@@ -67,36 +93,25 @@ atomic_xchg_rel :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_xchg_acqrel :: proc(dst; ^$T, val: T) -> T ---
|
||||
atomic_xchg_relaxed :: proc(dst; ^$T, val: T) -> T ---
|
||||
|
||||
atomic_cxchg :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) ---
|
||||
atomic_cxchg_acq :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) ---
|
||||
atomic_cxchg_rel :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) ---
|
||||
atomic_cxchg_acqrel :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) ---
|
||||
atomic_cxchg_relaxed :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) ---
|
||||
atomic_cxchg_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) ---
|
||||
atomic_cxchg_failacq :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) ---
|
||||
atomic_cxchg_acq_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) ---
|
||||
atomic_cxchg_acqrel_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) ---
|
||||
|
||||
atomic_cxchgweak :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) ---
|
||||
atomic_cxchgweak_acq :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) ---
|
||||
atomic_cxchgweak_rel :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) ---
|
||||
atomic_cxchgweak_acqrel :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) ---
|
||||
atomic_cxchgweak_relaxed :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) ---
|
||||
atomic_cxchgweak_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) ---
|
||||
atomic_cxchgweak_failacq :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) ---
|
||||
atomic_cxchgweak_acq_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) ---
|
||||
atomic_cxchgweak_acqrel_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) ---
|
||||
|
||||
// Instructions
|
||||
|
||||
alloca :: proc(size, align: int) -> ^u8 ---
|
||||
cpu_relax :: proc() ---
|
||||
read_cycle_counter :: proc() -> i64 ---
|
||||
|
||||
|
||||
// Compiler Hints
|
||||
expect :: proc(val, expected_val: T) -> T ---
|
||||
atomic_cxchg :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchg_acq :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchg_rel :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchg_acqrel :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchg_relaxed :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchg_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchg_failacq :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchg_acq_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchg_acqrel_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
|
||||
atomic_cxchgweak :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchgweak_acq :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchgweak_rel :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchgweak_acqrel :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchgweak_relaxed :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchgweak_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchgweak_failacq :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchgweak_acq_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
atomic_cxchgweak_acqrel_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok ---
|
||||
|
||||
// Constant type tests
|
||||
|
||||
@@ -144,6 +159,7 @@ type_is_simd_vector :: proc($T: typeid) -> bool ---
|
||||
type_has_nil :: proc($T: typeid) -> bool ---
|
||||
|
||||
type_is_specialization_of :: proc($T, $S: typeid) -> bool ---
|
||||
type_is_variant_of :: proc($U, $V: typeid) -> bool where type_is_union(U) ---
|
||||
|
||||
type_has_field :: proc($T: typeid, $name: string) -> bool ---
|
||||
|
||||
@@ -159,5 +175,5 @@ type_polymorphic_record_parameter_value :: proc($T: typeid, index: int) -> $V --
|
||||
|
||||
type_field_index_of :: proc($T: typeid, $name: string) -> uintptr ---
|
||||
|
||||
type_equal_proc :: proc($T: typeid) -> (equal: proc "contextless" (rawptr, rawptr) -> bool) ---
|
||||
type_hasher_proc :: proc($T: typeid) -> (hasher: proc "contextless" (data: rawptr, seed: uintptr) -> uintptr) ---
|
||||
type_equal_proc :: proc($T: typeid) -> (equal: proc "contextless" (rawptr, rawptr) -> bool) where type_is_comparable(T) ---
|
||||
type_hasher_proc :: proc($T: typeid) -> (hasher: proc "contextless" (data: rawptr, seed: uintptr) -> uintptr) where type_is_comparable(T) ---
|
||||
|
||||
@@ -6,9 +6,9 @@ Rand :: struct {
|
||||
}
|
||||
|
||||
|
||||
@(private, static)
|
||||
@(private)
|
||||
_GLOBAL_SEED_DATA := 1234567890;
|
||||
@(private, static)
|
||||
@(private)
|
||||
global_rand := create(u64(uintptr(&_GLOBAL_SEED_DATA)));
|
||||
|
||||
set_global_seed :: proc(seed: u64) {
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ Allocator_Mode_Set :: distinct bit_set[Allocator_Mode];
|
||||
Allocator_Query_Info :: runtime.Allocator_Query_Info;
|
||||
/*
|
||||
Allocator_Query_Info :: struct {
|
||||
pointer: Maybe(rawptr),
|
||||
pointer: rawptr,
|
||||
size: Maybe(int),
|
||||
alignment: Maybe(int),
|
||||
}
|
||||
|
||||
@@ -142,6 +142,7 @@ slice_ptr :: proc(ptr: ^$T, len: int) -> []T {
|
||||
byte_slice :: #force_inline proc "contextless" (data: rawptr, len: int) -> []byte {
|
||||
return transmute([]u8)Raw_Slice{data=data, len=max(len, 0)};
|
||||
}
|
||||
@(deprecated="use byte_slice")
|
||||
slice_ptr_to_bytes :: proc(data: rawptr, len: int) -> []byte {
|
||||
return transmute([]u8)Raw_Slice{data=data, len=max(len, 0)};
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ File :: struct {
|
||||
pkg: ^Package,
|
||||
|
||||
fullpath: string,
|
||||
src: []byte,
|
||||
src: string,
|
||||
|
||||
docs: ^Comment_Group,
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ collect_package :: proc(path: string) -> (pkg: ^ast.Package, success: bool) {
|
||||
}
|
||||
file := ast.new(ast.File, NO_POS, NO_POS);
|
||||
file.pkg = pkg;
|
||||
file.src = src;
|
||||
file.src = string(src);
|
||||
file.fullpath = fullpath;
|
||||
pkg.files[fullpath] = file;
|
||||
}
|
||||
|
||||
@@ -8,10 +8,21 @@ import "core:fmt"
|
||||
Warning_Handler :: #type proc(pos: tokenizer.Pos, fmt: string, args: ..any);
|
||||
Error_Handler :: #type proc(pos: tokenizer.Pos, fmt: string, args: ..any);
|
||||
|
||||
Flag :: enum u32 {
|
||||
Optional_Semicolons,
|
||||
}
|
||||
|
||||
Flags :: distinct bit_set[Flag; u32];
|
||||
|
||||
|
||||
Parser :: struct {
|
||||
file: ^ast.File,
|
||||
tok: tokenizer.Tokenizer,
|
||||
|
||||
// If .Optional_Semicolons is true, semicolons are completely as statement terminators
|
||||
// different to .Insert_Semicolon in tok.flags
|
||||
flags: Flags,
|
||||
|
||||
warn: Warning_Handler,
|
||||
err: Error_Handler,
|
||||
|
||||
@@ -100,8 +111,9 @@ end_pos :: proc(tok: tokenizer.Token) -> tokenizer.Pos {
|
||||
return pos;
|
||||
}
|
||||
|
||||
default_parser :: proc() -> Parser {
|
||||
default_parser :: proc(flags := Flags{}) -> Parser {
|
||||
return Parser {
|
||||
flags = flags,
|
||||
err = default_error_handler,
|
||||
warn = default_warning_handler,
|
||||
};
|
||||
@@ -128,6 +140,10 @@ parse_file :: proc(p: ^Parser, file: ^ast.File) -> bool {
|
||||
p.line_comment = nil;
|
||||
}
|
||||
|
||||
if .Optional_Semicolons in p.flags {
|
||||
p.tok.flags += {.Insert_Semicolon};
|
||||
}
|
||||
|
||||
p.file = file;
|
||||
tokenizer.init(&p.tok, file.src, file.fullpath, p.err);
|
||||
if p.tok.ch <= 0 {
|
||||
@@ -400,6 +416,11 @@ is_semicolon_optional_for_node :: proc(p: ^Parser, node: ^ast.Node) -> bool {
|
||||
if node == nil {
|
||||
return false;
|
||||
}
|
||||
|
||||
if .Optional_Semicolons in p.flags {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch n in node.derived {
|
||||
case ast.Empty_Stmt, ast.Block_Stmt:
|
||||
return true;
|
||||
@@ -439,14 +460,34 @@ is_semicolon_optional_for_node :: proc(p: ^Parser, node: ^ast.Node) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
expect_semicolon_newline_error :: proc(p: ^Parser, token: tokenizer.Token, s: ^ast.Node) {
|
||||
if .Optional_Semicolons not_in p.flags && .Insert_Semicolon in p.tok.flags && token.text == "\n" {
|
||||
#partial switch token.kind {
|
||||
case .Close_Brace:
|
||||
case .Close_Paren:
|
||||
case .Else:
|
||||
return;
|
||||
}
|
||||
if is_semicolon_optional_for_node(p, s) {
|
||||
return;
|
||||
}
|
||||
|
||||
tok := token;
|
||||
tok.pos.column -= 1;
|
||||
error(p, tok.pos, "expected ';', got newline");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
expect_semicolon :: proc(p: ^Parser, node: ^ast.Node) -> bool {
|
||||
if allow_token(p, .Semicolon) {
|
||||
expect_semicolon_newline_error(p, p.prev_tok, node);
|
||||
return true;
|
||||
}
|
||||
|
||||
prev := p.prev_tok;
|
||||
if prev.kind == .Semicolon {
|
||||
expect_semicolon_newline_error(p, p.prev_tok, node);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -615,7 +656,7 @@ parse_if_stmt :: proc(p: ^Parser) -> ^ast.If_Stmt {
|
||||
cond = parse_expr(p, false);
|
||||
} else {
|
||||
init = parse_simple_stmt(p, nil);
|
||||
if allow_token(p, .Semicolon) {
|
||||
if parse_control_statement_semicolon_separator(p) {
|
||||
cond = parse_expr(p, false);
|
||||
} else {
|
||||
cond = convert_stmt_to_expr(p, init, "boolean expression");
|
||||
@@ -668,6 +709,18 @@ parse_if_stmt :: proc(p: ^Parser) -> ^ast.If_Stmt {
|
||||
return if_stmt;
|
||||
}
|
||||
|
||||
parse_control_statement_semicolon_separator :: proc(p: ^Parser) -> bool {
|
||||
tok := peek_token(p);
|
||||
if tok.kind != .Open_Brace {
|
||||
return allow_token(p, .Semicolon);
|
||||
}
|
||||
if tok.text == ";" {
|
||||
return allow_token(p, .Semicolon);
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
parse_for_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
|
||||
if p.curr_proc == nil {
|
||||
error(p, p.curr_tok.pos, "you cannot use a for statement in the file scope");
|
||||
@@ -716,7 +769,7 @@ parse_for_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
|
||||
}
|
||||
}
|
||||
|
||||
if !is_range && allow_token(p, .Semicolon) {
|
||||
if !is_range && parse_control_statement_semicolon_separator(p) {
|
||||
init = cond;
|
||||
cond = nil;
|
||||
if p.curr_tok.kind != .Semicolon {
|
||||
@@ -820,7 +873,7 @@ parse_switch_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
|
||||
tag = parse_simple_stmt(p, {Stmt_Allow_Flag.In});
|
||||
if as, ok := tag.derived.(ast.Assign_Stmt); ok && as.op.kind == .In {
|
||||
is_type_switch = true;
|
||||
} else if allow_token(p, .Semicolon) {
|
||||
} else if parse_control_statement_semicolon_separator(p) {
|
||||
init = tag;
|
||||
tag = nil;
|
||||
if p.curr_tok.kind != .Open_Brace {
|
||||
@@ -831,6 +884,7 @@ parse_switch_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
|
||||
}
|
||||
|
||||
|
||||
skip_possible_newline(p);
|
||||
open := expect_token(p, .Open_Brace);
|
||||
|
||||
for p.curr_tok.kind == .Case {
|
||||
@@ -958,6 +1012,7 @@ parse_foreign_block :: proc(p: ^Parser, tok: tokenizer.Token) -> ^ast.Foreign_Bl
|
||||
defer p.in_foreign_block = prev_in_foreign_block;
|
||||
p.in_foreign_block = true;
|
||||
|
||||
skip_possible_newline_for_literal(p);
|
||||
open := expect_token(p, .Open_Brace);
|
||||
for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF {
|
||||
decl := parse_foreign_block_decl(p);
|
||||
@@ -1287,7 +1342,7 @@ token_precedence :: proc(p: ^Parser, kind: tokenizer.Token_Kind) -> int {
|
||||
#partial switch kind {
|
||||
case .Question, .If, .When:
|
||||
return 1;
|
||||
case .Ellipsis, .Range_Half:
|
||||
case .Ellipsis, .Range_Half, .Range_Full:
|
||||
if !p.allow_range {
|
||||
return 0;
|
||||
}
|
||||
@@ -2234,6 +2289,8 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
|
||||
}
|
||||
body: ^ast.Stmt;
|
||||
|
||||
skip_possible_newline_for_literal(p);
|
||||
|
||||
if allow_token(p, .Undef) {
|
||||
body = nil;
|
||||
if where_token.kind != .Invalid {
|
||||
@@ -2406,6 +2463,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
|
||||
p.expr_level = where_prev_level;
|
||||
}
|
||||
|
||||
skip_possible_newline_for_literal(p);
|
||||
expect_token(p, .Open_Brace);
|
||||
fields, name_count = parse_field_list(p, .Close_Brace, ast.Field_Flags_Struct);
|
||||
close := expect_token(p, .Close_Brace);
|
||||
@@ -2474,6 +2532,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
|
||||
|
||||
variants: [dynamic]^ast.Expr;
|
||||
|
||||
skip_possible_newline_for_literal(p);
|
||||
expect_token_after(p, .Open_Brace, "union");
|
||||
|
||||
for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF {
|
||||
@@ -2504,6 +2563,8 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
|
||||
if p.curr_tok.kind != .Open_Brace {
|
||||
base_type = parse_type(p);
|
||||
}
|
||||
|
||||
skip_possible_newline_for_literal(p);
|
||||
open := expect_token(p, .Open_Brace);
|
||||
fields := parse_elem_list(p);
|
||||
close := expect_token(p, .Close_Brace);
|
||||
@@ -2602,6 +2663,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
|
||||
}
|
||||
}
|
||||
|
||||
skip_possible_newline_for_literal(p);
|
||||
open := expect_token(p, .Open_Brace);
|
||||
asm_string := parse_expr(p, false);
|
||||
expect_token(p, .Comma);
|
||||
@@ -2812,7 +2874,7 @@ parse_atom_expr :: proc(p: ^Parser, value: ^ast.Expr, lhs: bool) -> (operand: ^a
|
||||
open := expect_token(p, .Open_Bracket);
|
||||
|
||||
#partial switch p.curr_tok.kind {
|
||||
case .Colon, .Ellipsis, .Range_Half:
|
||||
case .Colon, .Ellipsis, .Range_Half, .Range_Full:
|
||||
// NOTE(bill): Do not err yet
|
||||
break;
|
||||
case:
|
||||
@@ -2820,7 +2882,7 @@ parse_atom_expr :: proc(p: ^Parser, value: ^ast.Expr, lhs: bool) -> (operand: ^a
|
||||
}
|
||||
|
||||
#partial switch p.curr_tok.kind {
|
||||
case .Ellipsis, .Range_Half:
|
||||
case .Ellipsis, .Range_Half, .Range_Full:
|
||||
error(p, p.curr_tok.pos, "expected a colon, not a range");
|
||||
fallthrough;
|
||||
case .Colon:
|
||||
|
||||
@@ -107,6 +107,7 @@ Token_Kind :: enum u32 {
|
||||
Comma, // ,
|
||||
Ellipsis, // ..
|
||||
Range_Half, // ..<
|
||||
Range_Full, // ..=
|
||||
Back_Slash, // \
|
||||
B_Operator_End,
|
||||
|
||||
@@ -233,6 +234,7 @@ tokens := [Token_Kind.COUNT]string {
|
||||
",",
|
||||
"..",
|
||||
"..<",
|
||||
"..=",
|
||||
"\\",
|
||||
"",
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ Flags :: distinct bit_set[Flag; u32];
|
||||
Tokenizer :: struct {
|
||||
// Immutable data
|
||||
path: string,
|
||||
src: []byte,
|
||||
src: string,
|
||||
err: Error_Handler,
|
||||
|
||||
flags: Flags,
|
||||
@@ -31,7 +31,7 @@ Tokenizer :: struct {
|
||||
error_count: int,
|
||||
}
|
||||
|
||||
init :: proc(t: ^Tokenizer, src: []byte, path: string, err: Error_Handler = default_error_handler) {
|
||||
init :: proc(t: ^Tokenizer, src: string, path: string, err: Error_Handler = default_error_handler) {
|
||||
t.src = src;
|
||||
t.err = err;
|
||||
t.ch = ' ';
|
||||
@@ -87,7 +87,7 @@ advance_rune :: proc(using t: ^Tokenizer) {
|
||||
case r == 0:
|
||||
error(t, t.offset, "illegal character NUL");
|
||||
case r >= utf8.RUNE_SELF:
|
||||
r, w = utf8.decode_rune(src[read_offset:]);
|
||||
r, w = utf8.decode_rune_in_string(src[read_offset:]);
|
||||
if r == utf8.RUNE_ERROR && w == 1 {
|
||||
error(t, t.offset, "illegal UTF-8 encoding");
|
||||
} else if r == utf8.RUNE_BOM && offset > 0 {
|
||||
@@ -623,6 +623,9 @@ scan :: proc(t: ^Tokenizer) -> Token {
|
||||
if t.ch == '<' {
|
||||
advance_rune(t);
|
||||
kind = .Range_Half;
|
||||
} else if t.ch == '=' {
|
||||
advance_rune(t);
|
||||
kind = .Range_Full;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+18
-47
@@ -1,11 +1,8 @@
|
||||
package os2
|
||||
|
||||
Platform_Error_Min_Bits :: 32;
|
||||
import "core:io"
|
||||
|
||||
Error :: enum u64 {
|
||||
None = 0,
|
||||
|
||||
// General Errors
|
||||
General_Error :: enum u32 {
|
||||
Invalid_Argument,
|
||||
|
||||
Permission_Denied,
|
||||
@@ -13,43 +10,20 @@ Error :: enum u64 {
|
||||
Not_Exist,
|
||||
Closed,
|
||||
|
||||
// Timeout Errors
|
||||
Timeout,
|
||||
|
||||
// I/O Errors
|
||||
// EOF is the error returned by `read` when no more input is available
|
||||
EOF,
|
||||
|
||||
// Unexpected_EOF means that EOF was encountered in the middle of reading a fixed-sized block of data
|
||||
Unexpected_EOF,
|
||||
|
||||
// Short_Write means that a write accepted fewer bytes than requested but failed to return an explicit error
|
||||
Short_Write,
|
||||
|
||||
// Invalid_Write means that a write returned an impossible count
|
||||
Invalid_Write,
|
||||
|
||||
// Short_Buffer means that a read required a longer buffer than was provided
|
||||
Short_Buffer,
|
||||
|
||||
// No_Progress is returned by some implementations of `io.Reader` when many calls
|
||||
// to `read` have failed to return any data or error.
|
||||
// This is usually a signed of a broken `io.Reader` implementation
|
||||
No_Progress,
|
||||
|
||||
Invalid_Whence,
|
||||
Invalid_Offset,
|
||||
Invalid_Unread,
|
||||
|
||||
Negative_Read,
|
||||
Negative_Write,
|
||||
Negative_Count,
|
||||
Buffer_Full,
|
||||
|
||||
// Platform Specific Errors
|
||||
Platform_Minimum = 1<<Platform_Error_Min_Bits,
|
||||
}
|
||||
|
||||
Platform_Error :: struct {
|
||||
err: i32,
|
||||
}
|
||||
|
||||
Error :: union {
|
||||
General_Error,
|
||||
io.Error,
|
||||
Platform_Error,
|
||||
}
|
||||
#assert(size_of(Error) == size_of(u64));
|
||||
|
||||
Path_Error :: struct {
|
||||
op: string,
|
||||
path: string,
|
||||
@@ -83,20 +57,17 @@ link_error_delete :: proc(lerr: Maybe(Link_Error)) {
|
||||
|
||||
|
||||
is_platform_error :: proc(ferr: Error) -> (err: i32, ok: bool) {
|
||||
if ferr >= .Platform_Minimum {
|
||||
err = i32(u64(ferr)>>Platform_Error_Min_Bits);
|
||||
ok = true;
|
||||
v: Platform_Error;
|
||||
if v, ok = ferr.(Platform_Error); ok {
|
||||
err = v.err;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
error_from_platform_error :: proc(errno: i32) -> Error {
|
||||
return Error(u64(errno) << Platform_Error_Min_Bits);
|
||||
}
|
||||
|
||||
error_string :: proc(ferr: Error) -> string {
|
||||
#partial switch ferr {
|
||||
case .None: return "";
|
||||
switch ferr {
|
||||
case nil: return "";
|
||||
case .Invalid_Argument: return "invalid argument";
|
||||
case .Permission_Denied: return "permission denied";
|
||||
case .Exist: return "file already exists";
|
||||
|
||||
@@ -10,23 +10,14 @@ file_to_stream :: proc(fd: Handle) -> (s: io.Stream) {
|
||||
|
||||
@(private)
|
||||
error_to_io_error :: proc(ferr: Error) -> io.Error {
|
||||
#partial switch ferr {
|
||||
case .None: return .None;
|
||||
case .EOF: return .EOF;
|
||||
case .Unexpected_EOF: return .Unexpected_EOF;
|
||||
case .Short_Write: return .Short_Write;
|
||||
case .Invalid_Write: return .Invalid_Write;
|
||||
case .Short_Buffer: return .Short_Buffer;
|
||||
case .No_Progress: return .No_Progress;
|
||||
case .Invalid_Whence: return .Invalid_Whence;
|
||||
case .Invalid_Offset: return .Invalid_Offset;
|
||||
case .Invalid_Unread: return .Invalid_Unread;
|
||||
case .Negative_Read: return .Negative_Read;
|
||||
case .Negative_Write: return .Negative_Write;
|
||||
case .Negative_Count: return .Negative_Count;
|
||||
case .Buffer_Full: return .Buffer_Full;
|
||||
if ferr == nil {
|
||||
return .None;
|
||||
}
|
||||
return .Unknown;
|
||||
err, ok := ferr.(io.Error);
|
||||
if !ok {
|
||||
err = .Unknown;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package os2
|
||||
|
||||
import "core:mem"
|
||||
import "core:io"
|
||||
import "core:strconv"
|
||||
import "core:unicode/utf8"
|
||||
|
||||
|
||||
@@ -5,19 +5,19 @@ import "core:io"
|
||||
import "core:time"
|
||||
|
||||
_create :: proc(name: string) -> (Handle, Error) {
|
||||
return 0, .None;
|
||||
return 0, nil;
|
||||
}
|
||||
|
||||
_open :: proc(name: string) -> (Handle, Error) {
|
||||
return 0, .None;
|
||||
return 0, nil;
|
||||
}
|
||||
|
||||
_open_file :: proc(name: string, flag: int, perm: File_Mode) -> (Handle, Error) {
|
||||
return 0, .None;
|
||||
return 0, nil;
|
||||
}
|
||||
|
||||
_close :: proc(fd: Handle) -> Error {
|
||||
return .None;
|
||||
return nil;
|
||||
}
|
||||
|
||||
_name :: proc(fd: Handle, allocator := context.allocator) -> string {
|
||||
@@ -58,11 +58,11 @@ _file_size :: proc(fd: Handle) -> (n: i64, err: Error) {
|
||||
|
||||
|
||||
_sync :: proc(fd: Handle) -> Error {
|
||||
return .None;
|
||||
return nil;
|
||||
}
|
||||
|
||||
_flush :: proc(fd: Handle) -> Error {
|
||||
return .None;
|
||||
return nil;
|
||||
}
|
||||
|
||||
_truncate :: proc(fd: Handle, size: i64) -> Maybe(Path_Error) {
|
||||
@@ -92,20 +92,20 @@ _read_link :: proc(name: string) -> (string, Maybe(Path_Error)) {
|
||||
|
||||
|
||||
_chdir :: proc(fd: Handle) -> Error {
|
||||
return .None;
|
||||
return nil;
|
||||
}
|
||||
|
||||
_chmod :: proc(fd: Handle, mode: File_Mode) -> Error {
|
||||
return .None;
|
||||
return nil;
|
||||
}
|
||||
|
||||
_chown :: proc(fd: Handle, uid, gid: int) -> Error {
|
||||
return .None;
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
_lchown :: proc(name: string, uid, gid: int) -> Error {
|
||||
return .None;
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import win32 "core:sys/windows"
|
||||
_pipe :: proc() -> (r, w: Handle, err: Error) {
|
||||
p: [2]win32.HANDLE;
|
||||
if !win32.CreatePipe(&p[0], &p[1], nil, 0) {
|
||||
return 0, 0, error_from_platform_error(i32(win32.GetLastError()));
|
||||
return 0, 0, Platform_Error{i32(win32.GetLastError())};
|
||||
}
|
||||
return Handle(p[0]), Handle(p[1]), nil;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ _same_file :: proc(fi1, fi2: File_Info) -> bool {
|
||||
|
||||
|
||||
_stat_errno :: proc(errno: win32.DWORD) -> Path_Error {
|
||||
return Path_Error{err = error_from_platform_error(i32(errno))};
|
||||
return Path_Error{err = Platform_Error{i32(errno)}};
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator := co
|
||||
fd: win32.WIN32_FIND_DATAW;
|
||||
sh := win32.FindFirstFileW(wname, &fd);
|
||||
if sh == win32.INVALID_HANDLE_VALUE {
|
||||
e = Path_Error{err = error_from_platform_error(i32(win32.GetLastError()))};
|
||||
e = Path_Error{err = Platform_Error{i32(win32.GetLastError())}};
|
||||
return;
|
||||
}
|
||||
win32.FindClose(sh);
|
||||
@@ -99,7 +99,7 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator := co
|
||||
|
||||
h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil);
|
||||
if h == win32.INVALID_HANDLE_VALUE {
|
||||
e = Path_Error{err = error_from_platform_error(i32(win32.GetLastError()))};
|
||||
e = Path_Error{err = Platform_Error{i32(win32.GetLastError())}};
|
||||
return;
|
||||
}
|
||||
defer win32.CloseHandle(h);
|
||||
|
||||
@@ -4,11 +4,11 @@ package os2
|
||||
import win32 "core:sys/windows"
|
||||
|
||||
_create_temp :: proc(dir, pattern: string) -> (Handle, Error) {
|
||||
return 0, .None;
|
||||
return 0, nil;
|
||||
}
|
||||
|
||||
_mkdir_temp :: proc(dir, pattern: string, allocator := context.allocator) -> (string, Error) {
|
||||
return "", .None;
|
||||
return "", nil;
|
||||
}
|
||||
|
||||
_temp_dir :: proc(allocator := context.allocator) -> string {
|
||||
|
||||
@@ -10,7 +10,7 @@ import "core:c"
|
||||
Handle :: distinct i32;
|
||||
File_Time :: distinct u64;
|
||||
Errno :: distinct i32;
|
||||
Syscall :: distinct int;
|
||||
Syscall :: distinct i32;
|
||||
|
||||
INVALID_HANDLE :: ~Handle(0);
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import "core:strconv"
|
||||
Handle :: distinct i32;
|
||||
File_Time :: distinct u64;
|
||||
Errno :: distinct i32;
|
||||
Syscall :: distinct int;
|
||||
Syscall :: distinct i32;
|
||||
|
||||
INVALID_HANDLE :: ~Handle(0);
|
||||
|
||||
@@ -269,7 +269,7 @@ SYS_GETTID: Syscall : 186;
|
||||
|
||||
foreign libc {
|
||||
@(link_name="__errno_location") __errno_location :: proc() -> ^int ---;
|
||||
@(link_name="syscall") syscall :: proc(number: Syscall, #c_vararg args: ..any) -> int ---;
|
||||
@(link_name="syscall") syscall :: proc(number: Syscall, #c_vararg args: ..any) -> i32 ---;
|
||||
|
||||
@(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, mode: c.int) -> Handle ---;
|
||||
@(link_name="close") _unix_close :: proc(fd: Handle) -> c.int ---;
|
||||
@@ -595,7 +595,7 @@ exit :: proc "contextless" (code: int) -> ! {
|
||||
}
|
||||
|
||||
current_thread_id :: proc "contextless" () -> int {
|
||||
return syscall(SYS_GETTID);
|
||||
return cast(int)syscall(SYS_GETTID);
|
||||
}
|
||||
|
||||
dlopen :: proc(filename: string, flags: int) -> rawptr {
|
||||
|
||||
@@ -32,6 +32,7 @@ Calling_Convention :: enum u8 {
|
||||
Fast_Call = 5,
|
||||
|
||||
None = 6,
|
||||
Naked = 7,
|
||||
}
|
||||
|
||||
Type_Info_Enum_Value :: distinct i64;
|
||||
@@ -120,6 +121,9 @@ Type_Info_Union :: struct {
|
||||
variants: []^Type_Info,
|
||||
tag_offset: uintptr,
|
||||
tag_type: ^Type_Info,
|
||||
|
||||
equal: Equal_Proc, // set only when the struct has .Comparable set but does not have .Simple_Compare set
|
||||
|
||||
custom_align: bool,
|
||||
no_nil: bool,
|
||||
maybe: bool,
|
||||
|
||||
+10
-31
@@ -105,17 +105,9 @@ mem_copy :: proc "contextless" (dst, src: rawptr, len: int) -> rawptr {
|
||||
if src == nil {
|
||||
return dst;
|
||||
}
|
||||
|
||||
// NOTE(bill): This _must_ be implemented like C's memmove
|
||||
foreign _ {
|
||||
when size_of(rawptr) == 8 {
|
||||
@(link_name="llvm.memmove.p0i8.p0i8.i64")
|
||||
llvm_memmove :: proc "none" (dst, src: rawptr, len: int, is_volatile: bool = false) ---;
|
||||
} else {
|
||||
@(link_name="llvm.memmove.p0i8.p0i8.i32")
|
||||
llvm_memmove :: proc "none" (dst, src: rawptr, len: int, is_volatile: bool = false) ---;
|
||||
}
|
||||
}
|
||||
llvm_memmove(dst, src, len);
|
||||
intrinsics.mem_copy(dst, src, len);
|
||||
return dst;
|
||||
}
|
||||
|
||||
@@ -123,17 +115,9 @@ mem_copy_non_overlapping :: proc "contextless" (dst, src: rawptr, len: int) -> r
|
||||
if src == nil {
|
||||
return dst;
|
||||
}
|
||||
|
||||
// NOTE(bill): This _must_ be implemented like C's memcpy
|
||||
foreign _ {
|
||||
when size_of(rawptr) == 8 {
|
||||
@(link_name="llvm.memcpy.p0i8.p0i8.i64")
|
||||
llvm_memcpy :: proc "none" (dst, src: rawptr, len: int, is_volatile: bool = false) ---;
|
||||
} else {
|
||||
@(link_name="llvm.memcpy.p0i8.p0i8.i32")
|
||||
llvm_memcpy :: proc "none" (dst, src: rawptr, len: int, is_volatile: bool = false) ---;
|
||||
}
|
||||
}
|
||||
llvm_memcpy(dst, src, len);
|
||||
intrinsics.mem_copy_non_overlapping(dst, src, len);
|
||||
return dst;
|
||||
}
|
||||
|
||||
@@ -409,11 +393,6 @@ string_decode_rune :: #force_inline proc "contextless" (s: string) -> (rune, int
|
||||
return rune(s0&MASK4)<<18 | rune(b1&MASKX)<<12 | rune(b2&MASKX)<<6 | rune(b3&MASKX), 4;
|
||||
}
|
||||
|
||||
@(default_calling_convention = "none")
|
||||
foreign {
|
||||
@(link_name="llvm.sqrt.f32") _sqrt_f32 :: proc(x: f32) -> f32 ---
|
||||
@(link_name="llvm.sqrt.f64") _sqrt_f64 :: proc(x: f64) -> f64 ---
|
||||
}
|
||||
abs_f16 :: #force_inline proc "contextless" (x: f16) -> f16 {
|
||||
return -x if x < 0 else x;
|
||||
}
|
||||
@@ -445,27 +424,27 @@ max_f64 :: proc(a, b: f64) -> f64 {
|
||||
|
||||
abs_complex32 :: #force_inline proc "contextless" (x: complex32) -> f16 {
|
||||
r, i := real(x), imag(x);
|
||||
return f16(_sqrt_f32(f32(r*r + i*i)));
|
||||
return f16(intrinsics.sqrt(f32(r*r + i*i)));
|
||||
}
|
||||
abs_complex64 :: #force_inline proc "contextless" (x: complex64) -> f32 {
|
||||
r, i := real(x), imag(x);
|
||||
return _sqrt_f32(r*r + i*i);
|
||||
return intrinsics.sqrt(r*r + i*i);
|
||||
}
|
||||
abs_complex128 :: #force_inline proc "contextless" (x: complex128) -> f64 {
|
||||
r, i := real(x), imag(x);
|
||||
return _sqrt_f64(r*r + i*i);
|
||||
return intrinsics.sqrt(r*r + i*i);
|
||||
}
|
||||
abs_quaternion64 :: #force_inline proc "contextless" (x: quaternion64) -> f16 {
|
||||
r, i, j, k := real(x), imag(x), jmag(x), kmag(x);
|
||||
return f16(_sqrt_f32(f32(r*r + i*i + j*j + k*k)));
|
||||
return f16(intrinsics.sqrt(f32(r*r + i*i + j*j + k*k)));
|
||||
}
|
||||
abs_quaternion128 :: #force_inline proc "contextless" (x: quaternion128) -> f32 {
|
||||
r, i, j, k := real(x), imag(x), jmag(x), kmag(x);
|
||||
return _sqrt_f32(r*r + i*i + j*j + k*k);
|
||||
return intrinsics.sqrt(r*r + i*i + j*j + k*k);
|
||||
}
|
||||
abs_quaternion256 :: #force_inline proc "contextless" (x: quaternion256) -> f64 {
|
||||
r, i, j, k := real(x), imag(x), jmag(x), kmag(x);
|
||||
return _sqrt_f64(r*r + i*i + j*j + k*k);
|
||||
return intrinsics.sqrt(r*r + i*i + j*j + k*k);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ udivmod128 :: proc "c" (a, b: u128, rem: ^u128) -> u128 {
|
||||
q, r: [2]u64 = ---, ---;
|
||||
sr: u32 = 0;
|
||||
|
||||
low :: ODIN_ENDIAN == "big" ? 1 : 0;
|
||||
low :: 1 when ODIN_ENDIAN == "big" else 0;
|
||||
high :: 1 - low;
|
||||
U64_BITS :: 8*size_of(u64);
|
||||
U128_BITS :: 8*size_of(u128);
|
||||
|
||||
@@ -221,7 +221,7 @@ pop_rune :: proc(b: ^Builder) -> (r: rune, width: int) {
|
||||
}
|
||||
|
||||
|
||||
@(private, static)
|
||||
@(private)
|
||||
DIGITS_LOWER := "0123456789abcdefx";
|
||||
|
||||
write_quoted_string :: proc{
|
||||
|
||||
+41
-43
@@ -2,78 +2,76 @@ package sync2
|
||||
|
||||
import "intrinsics"
|
||||
|
||||
// TODO(bill): Is this even a good design? The intrinsics seem to be more than good enough and just as clean
|
||||
|
||||
cpu_relax :: intrinsics.cpu_relax;
|
||||
|
||||
atomic_fence :: intrinsics.atomic_fence;
|
||||
atomic_fence_acq :: intrinsics.atomic_fence_acq;
|
||||
atomic_fence_rel :: intrinsics.atomic_fence_rel;
|
||||
atomic_fence_acqrel :: intrinsics.atomic_fence_acqrel;
|
||||
atomic_fence :: intrinsics.atomic_fence;
|
||||
atomic_fence_acquire :: intrinsics.atomic_fence_acq;
|
||||
atomic_fence_release :: intrinsics.atomic_fence_rel;
|
||||
atomic_fence_acqrel :: intrinsics.atomic_fence_acqrel;
|
||||
|
||||
atomic_store :: intrinsics.atomic_store;
|
||||
atomic_store_rel :: intrinsics.atomic_store_rel;
|
||||
atomic_store_release :: intrinsics.atomic_store_rel;
|
||||
atomic_store_relaxed :: intrinsics.atomic_store_relaxed;
|
||||
atomic_store_unordered :: intrinsics.atomic_store_unordered;
|
||||
|
||||
atomic_load :: intrinsics.atomic_load;
|
||||
atomic_load_acq :: intrinsics.atomic_load_acq;
|
||||
atomic_load_acquire :: intrinsics.atomic_load_acq;
|
||||
atomic_load_relaxed :: intrinsics.atomic_load_relaxed;
|
||||
atomic_load_unordered :: intrinsics.atomic_load_unordered;
|
||||
|
||||
atomic_add :: intrinsics.atomic_add;
|
||||
atomic_add_acq :: intrinsics.atomic_add_acq;
|
||||
atomic_add_rel :: intrinsics.atomic_add_rel;
|
||||
atomic_add_acquire :: intrinsics.atomic_add_acq;
|
||||
atomic_add_release :: intrinsics.atomic_add_rel;
|
||||
atomic_add_acqrel :: intrinsics.atomic_add_acqrel;
|
||||
atomic_add_relaxed :: intrinsics.atomic_add_relaxed;
|
||||
atomic_sub :: intrinsics.atomic_sub;
|
||||
atomic_sub_acq :: intrinsics.atomic_sub_acq;
|
||||
atomic_sub_rel :: intrinsics.atomic_sub_rel;
|
||||
atomic_sub_acquire :: intrinsics.atomic_sub_acq;
|
||||
atomic_sub_release :: intrinsics.atomic_sub_rel;
|
||||
atomic_sub_acqrel :: intrinsics.atomic_sub_acqrel;
|
||||
atomic_sub_relaxed :: intrinsics.atomic_sub_relaxed;
|
||||
atomic_and :: intrinsics.atomic_and;
|
||||
atomic_and_acq :: intrinsics.atomic_and_acq;
|
||||
atomic_and_rel :: intrinsics.atomic_and_rel;
|
||||
atomic_and_acquire :: intrinsics.atomic_and_acq;
|
||||
atomic_and_release :: intrinsics.atomic_and_rel;
|
||||
atomic_and_acqrel :: intrinsics.atomic_and_acqrel;
|
||||
atomic_and_relaxed :: intrinsics.atomic_and_relaxed;
|
||||
atomic_nand :: intrinsics.atomic_nand;
|
||||
atomic_nand_acq :: intrinsics.atomic_nand_acq;
|
||||
atomic_nand_rel :: intrinsics.atomic_nand_rel;
|
||||
atomic_nand_acquire :: intrinsics.atomic_nand_acq;
|
||||
atomic_nand_release :: intrinsics.atomic_nand_rel;
|
||||
atomic_nand_acqrel :: intrinsics.atomic_nand_acqrel;
|
||||
atomic_nand_relaxed :: intrinsics.atomic_nand_relaxed;
|
||||
atomic_or :: intrinsics.atomic_or;
|
||||
atomic_or_acq :: intrinsics.atomic_or_acq;
|
||||
atomic_or_rel :: intrinsics.atomic_or_rel;
|
||||
atomic_or_acquire :: intrinsics.atomic_or_acq;
|
||||
atomic_or_release :: intrinsics.atomic_or_rel;
|
||||
atomic_or_acqrel :: intrinsics.atomic_or_acqrel;
|
||||
atomic_or_relaxed :: intrinsics.atomic_or_relaxed;
|
||||
atomic_xor :: intrinsics.atomic_xor;
|
||||
atomic_xor_acq :: intrinsics.atomic_xor_acq;
|
||||
atomic_xor_rel :: intrinsics.atomic_xor_rel;
|
||||
atomic_xor_acquire :: intrinsics.atomic_xor_acq;
|
||||
atomic_xor_release :: intrinsics.atomic_xor_rel;
|
||||
atomic_xor_acqrel :: intrinsics.atomic_xor_acqrel;
|
||||
atomic_xor_relaxed :: intrinsics.atomic_xor_relaxed;
|
||||
|
||||
atomic_xchg :: intrinsics.atomic_xchg;
|
||||
atomic_xchg_acq :: intrinsics.atomic_xchg_acq;
|
||||
atomic_xchg_rel :: intrinsics.atomic_xchg_rel;
|
||||
atomic_xchg_acqrel :: intrinsics.atomic_xchg_acqrel;
|
||||
atomic_xchg_relaxed :: intrinsics.atomic_xchg_relaxed;
|
||||
atomic_exchange :: intrinsics.atomic_xchg;
|
||||
atomic_exchange_acquire :: intrinsics.atomic_xchg_acq;
|
||||
atomic_exchange_release :: intrinsics.atomic_xchg_rel;
|
||||
atomic_exchange_acqrel :: intrinsics.atomic_xchg_acqrel;
|
||||
atomic_exchange_relaxed :: intrinsics.atomic_xchg_relaxed;
|
||||
|
||||
atomic_cxchg :: intrinsics.atomic_cxchg;
|
||||
atomic_cxchg_acq :: intrinsics.atomic_cxchg_acq;
|
||||
atomic_cxchg_rel :: intrinsics.atomic_cxchg_rel;
|
||||
atomic_cxchg_acqrel :: intrinsics.atomic_cxchg_acqrel;
|
||||
atomic_cxchg_relaxed :: intrinsics.atomic_cxchg_relaxed;
|
||||
atomic_cxchg_failrelaxed :: intrinsics.atomic_cxchg_failrelaxed;
|
||||
atomic_cxchg_failacq :: intrinsics.atomic_cxchg_failacq;
|
||||
atomic_cxchg_acq_failrelaxed :: intrinsics.atomic_cxchg_acq_failrelaxed;
|
||||
atomic_cxchg_acqrel_failrelaxed :: intrinsics.atomic_cxchg_acqrel_failrelaxed;
|
||||
atomic_compare_exchange_strong :: intrinsics.atomic_cxchg;
|
||||
atomic_compare_exchange_strong_acquire :: intrinsics.atomic_cxchg_acq;
|
||||
atomic_compare_exchange_strong_release :: intrinsics.atomic_cxchg_rel;
|
||||
atomic_compare_exchange_strong_acqrel :: intrinsics.atomic_cxchg_acqrel;
|
||||
atomic_compare_exchange_strong_relaxed :: intrinsics.atomic_cxchg_relaxed;
|
||||
atomic_compare_exchange_strong_failrelaxed :: intrinsics.atomic_cxchg_failrelaxed;
|
||||
atomic_compare_exchange_strong_failacquire :: intrinsics.atomic_cxchg_failacq;
|
||||
atomic_compare_exchange_strong_acquire_failrelaxed :: intrinsics.atomic_cxchg_acq_failrelaxed;
|
||||
atomic_compare_exchange_strong_acqrel_failrelaxed :: intrinsics.atomic_cxchg_acqrel_failrelaxed;
|
||||
|
||||
atomic_cxchgweak :: intrinsics.atomic_cxchgweak;
|
||||
atomic_cxchgweak_acq :: intrinsics.atomic_cxchgweak_acq;
|
||||
atomic_cxchgweak_rel :: intrinsics.atomic_cxchgweak_rel;
|
||||
atomic_cxchgweak_acqrel :: intrinsics.atomic_cxchgweak_acqrel;
|
||||
atomic_cxchgweak_relaxed :: intrinsics.atomic_cxchgweak_relaxed;
|
||||
atomic_cxchgweak_failrelaxed :: intrinsics.atomic_cxchgweak_failrelaxed;
|
||||
atomic_cxchgweak_failacq :: intrinsics.atomic_cxchgweak_failacq;
|
||||
atomic_cxchgweak_acq_failrelaxed :: intrinsics.atomic_cxchgweak_acq_failrelaxed;
|
||||
atomic_cxchgweak_acqrel_failrelaxed :: intrinsics.atomic_cxchgweak_acqrel_failrelaxed;
|
||||
atomic_compare_exchange_weak :: intrinsics.atomic_cxchgweak;
|
||||
atomic_compare_exchange_weak_acquire :: intrinsics.atomic_cxchgweak_acq;
|
||||
atomic_compare_exchange_weak_release :: intrinsics.atomic_cxchgweak_rel;
|
||||
atomic_compare_exchange_weak_acqrel :: intrinsics.atomic_cxchgweak_acqrel;
|
||||
atomic_compare_exchange_weak_relaxed :: intrinsics.atomic_cxchgweak_relaxed;
|
||||
atomic_compare_exchange_weak_failrelaxed :: intrinsics.atomic_cxchgweak_failrelaxed;
|
||||
atomic_compare_exchange_weak_failacquire :: intrinsics.atomic_cxchgweak_failacq;
|
||||
atomic_compare_exchange_weak_acquire_failrelaxed :: intrinsics.atomic_cxchgweak_acq_failrelaxed;
|
||||
atomic_compare_exchange_weak_acqrel_failrelaxed :: intrinsics.atomic_cxchgweak_acqrel_failrelaxed;
|
||||
|
||||
@@ -1,886 +0,0 @@
|
||||
package sync2
|
||||
|
||||
// TODO(bill): The Channel implementation needs a complete rewrite for this new package sync design
|
||||
// Especially how the `select` things work
|
||||
|
||||
import "core:mem"
|
||||
import "core:time"
|
||||
import "core:math/rand"
|
||||
|
||||
_, _ :: time, rand;
|
||||
|
||||
Channel_Direction :: enum i8 {
|
||||
Both = 0,
|
||||
Send = +1,
|
||||
Recv = -1,
|
||||
}
|
||||
|
||||
Channel :: struct(T: typeid, Direction := Channel_Direction.Both) {
|
||||
using _internal: ^Raw_Channel,
|
||||
}
|
||||
|
||||
channel_init :: proc(ch: ^$C/Channel($T, $D), cap := 0, allocator := context.allocator) {
|
||||
context.allocator = allocator;
|
||||
ch._internal = raw_channel_create(size_of(T), align_of(T), cap);
|
||||
return;
|
||||
}
|
||||
|
||||
channel_make :: proc($T: typeid, cap := 0, allocator := context.allocator) -> (ch: Channel(T, .Both)) {
|
||||
context.allocator = allocator;
|
||||
ch._internal = raw_channel_create(size_of(T), align_of(T), cap);
|
||||
return;
|
||||
}
|
||||
|
||||
channel_make_send :: proc($T: typeid, cap := 0, allocator := context.allocator) -> (ch: Channel(T, .Send)) {
|
||||
context.allocator = allocator;
|
||||
ch._internal = raw_channel_create(size_of(T), align_of(T), cap);
|
||||
return;
|
||||
}
|
||||
channel_make_recv :: proc($T: typeid, cap := 0, allocator := context.allocator) -> (ch: Channel(T, .Recv)) {
|
||||
context.allocator = allocator;
|
||||
ch._internal = raw_channel_create(size_of(T), align_of(T), cap);
|
||||
return;
|
||||
}
|
||||
|
||||
channel_destroy :: proc(ch: $C/Channel($T, $D)) {
|
||||
raw_channel_destroy(ch._internal);
|
||||
}
|
||||
|
||||
channel_as_send :: proc(ch: $C/Channel($T, .Both)) -> (res: Channel(T, .Send)) {
|
||||
res._internal = ch._internal;
|
||||
return;
|
||||
}
|
||||
|
||||
channel_as_recv :: proc(ch: $C/Channel($T, .Both)) -> (res: Channel(T, .Recv)) {
|
||||
res._internal = ch._internal;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
channel_len :: proc(ch: $C/Channel($T, $D)) -> int {
|
||||
return ch._internal.len if ch._internal != nil else 0;
|
||||
}
|
||||
channel_cap :: proc(ch: $C/Channel($T, $D)) -> int {
|
||||
return ch._internal.cap if ch._internal != nil else 0;
|
||||
}
|
||||
|
||||
|
||||
channel_send :: proc(ch: $C/Channel($T, $D), msg: T, loc := #caller_location) where D >= .Both {
|
||||
msg := msg;
|
||||
_ = raw_channel_send_impl(ch._internal, &msg, /*block*/true, loc);
|
||||
}
|
||||
channel_try_send :: proc(ch: $C/Channel($T, $D), msg: T, loc := #caller_location) -> bool where D >= .Both {
|
||||
msg := msg;
|
||||
return raw_channel_send_impl(ch._internal, &msg, /*block*/false, loc);
|
||||
}
|
||||
|
||||
channel_recv :: proc(ch: $C/Channel($T, $D), loc := #caller_location) -> (msg: T) where D <= .Both {
|
||||
c := ch._internal;
|
||||
if c == nil {
|
||||
panic(message="cannot recv message; channel is nil", loc=loc);
|
||||
}
|
||||
mutex_lock(&c.mutex);
|
||||
raw_channel_recv_impl(c, &msg, loc);
|
||||
mutex_unlock(&c.mutex);
|
||||
return;
|
||||
}
|
||||
channel_try_recv :: proc(ch: $C/Channel($T, $D), loc := #caller_location) -> (msg: T, ok: bool) where D <= .Both {
|
||||
c := ch._internal;
|
||||
if c != nil && mutex_try_lock(&c.mutex) {
|
||||
if c.len > 0 {
|
||||
raw_channel_recv_impl(c, &msg, loc);
|
||||
ok = true;
|
||||
}
|
||||
mutex_unlock(&c.mutex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
channel_try_recv_ptr :: proc(ch: $C/Channel($T, $D), msg: ^T, loc := #caller_location) -> (ok: bool) where D <= .Both {
|
||||
res: T;
|
||||
res, ok = channel_try_recv(ch, loc);
|
||||
if ok && msg != nil {
|
||||
msg^ = res;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
channel_is_nil :: proc(ch: $C/Channel($T, $D)) -> bool {
|
||||
return ch._internal == nil;
|
||||
}
|
||||
channel_is_open :: proc(ch: $C/Channel($T, $D)) -> bool {
|
||||
c := ch._internal;
|
||||
return c != nil && !c.closed;
|
||||
}
|
||||
|
||||
|
||||
channel_eq :: proc(a, b: $C/Channel($T, $D)) -> bool {
|
||||
return a._internal == b._internal;
|
||||
}
|
||||
channel_ne :: proc(a, b: $C/Channel($T, $D)) -> bool {
|
||||
return a._internal != b._internal;
|
||||
}
|
||||
|
||||
|
||||
channel_can_send :: proc(ch: $C/Channel($T, $D)) -> (ok: bool) where D >= .Both {
|
||||
return raw_channel_can_send(ch._internal);
|
||||
}
|
||||
channel_can_recv :: proc(ch: $C/Channel($T, $D)) -> (ok: bool) where D <= .Both {
|
||||
return raw_channel_can_recv(ch._internal);
|
||||
}
|
||||
|
||||
|
||||
channel_peek :: proc(ch: $C/Channel($T, $D)) -> int {
|
||||
c := ch._internal;
|
||||
if c == nil {
|
||||
return -1;
|
||||
}
|
||||
if atomic_load(&c.closed) {
|
||||
return -1;
|
||||
}
|
||||
return atomic_load(&c.len);
|
||||
}
|
||||
|
||||
|
||||
channel_close :: proc(ch: $C/Channel($T, $D), loc := #caller_location) {
|
||||
raw_channel_close(ch._internal, loc);
|
||||
}
|
||||
|
||||
|
||||
channel_iterator :: proc(ch: $C/Channel($T, $D)) -> (msg: T, ok: bool) where D <= .Both {
|
||||
c := ch._internal;
|
||||
if c == nil {
|
||||
return;
|
||||
}
|
||||
|
||||
if !c.closed || c.len > 0 {
|
||||
msg, ok = channel_recv(ch), true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
channel_drain :: proc(ch: $C/Channel($T, $D)) where D >= .Both {
|
||||
raw_channel_drain(ch._internal);
|
||||
}
|
||||
|
||||
|
||||
channel_move :: proc(dst: $C1/Channel($T, $D1) src: $C2/Channel(T, $D2)) where D1 <= .Both, D2 >= .Both {
|
||||
for msg in channel_iterator(src) {
|
||||
channel_send(dst, msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Raw_Channel_Wait_Queue :: struct {
|
||||
next: ^Raw_Channel_Wait_Queue,
|
||||
state: ^uintptr,
|
||||
}
|
||||
|
||||
|
||||
Raw_Channel :: struct {
|
||||
closed: bool,
|
||||
ready: bool, // ready to recv
|
||||
data_offset: u16, // data is stored at the end of this data structure
|
||||
elem_size: u32,
|
||||
len, cap: int,
|
||||
read, write: int,
|
||||
mutex: Mutex,
|
||||
cond: Cond,
|
||||
allocator: mem.Allocator,
|
||||
|
||||
sendq: ^Raw_Channel_Wait_Queue,
|
||||
recvq: ^Raw_Channel_Wait_Queue,
|
||||
}
|
||||
|
||||
raw_channel_wait_queue_insert :: proc(head: ^^Raw_Channel_Wait_Queue, val: ^Raw_Channel_Wait_Queue) {
|
||||
val.next = head^;
|
||||
head^ = val;
|
||||
}
|
||||
raw_channel_wait_queue_remove :: proc(head: ^^Raw_Channel_Wait_Queue, val: ^Raw_Channel_Wait_Queue) {
|
||||
p := head;
|
||||
for p^ != nil && p^ != val {
|
||||
p = &p^.next;
|
||||
}
|
||||
if p != nil {
|
||||
p^ = p^.next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
raw_channel_create :: proc(elem_size, elem_align: int, cap := 0) -> ^Raw_Channel {
|
||||
assert(int(u32(elem_size)) == elem_size);
|
||||
|
||||
s := size_of(Raw_Channel);
|
||||
s = mem.align_forward_int(s, elem_align);
|
||||
data_offset := uintptr(s);
|
||||
s += elem_size * max(cap, 1);
|
||||
|
||||
a := max(elem_align, align_of(Raw_Channel));
|
||||
|
||||
c := (^Raw_Channel)(mem.alloc(s, a));
|
||||
if c == nil {
|
||||
return nil;
|
||||
}
|
||||
|
||||
c.data_offset = u16(data_offset);
|
||||
c.elem_size = u32(elem_size);
|
||||
c.len, c.cap = 0, max(cap, 0);
|
||||
c.read, c.write = 0, 0;
|
||||
c.allocator = context.allocator;
|
||||
c.closed = false;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
raw_channel_destroy :: proc(c: ^Raw_Channel) {
|
||||
if c == nil {
|
||||
return;
|
||||
}
|
||||
context.allocator = c.allocator;
|
||||
atomic_store(&c.closed, true);
|
||||
free(c);
|
||||
}
|
||||
|
||||
raw_channel_close :: proc(c: ^Raw_Channel, loc := #caller_location) {
|
||||
if c == nil {
|
||||
panic(message="cannot close nil channel", loc=loc);
|
||||
}
|
||||
mutex_lock(&c.mutex);
|
||||
defer mutex_unlock(&c.mutex);
|
||||
atomic_store(&c.closed, true);
|
||||
|
||||
// Release readers and writers
|
||||
raw_channel_wait_queue_broadcast(c.recvq);
|
||||
raw_channel_wait_queue_broadcast(c.sendq);
|
||||
cond_broadcast(&c.cond);
|
||||
}
|
||||
|
||||
|
||||
|
||||
raw_channel_send_impl :: proc(c: ^Raw_Channel, msg: rawptr, block: bool, loc := #caller_location) -> bool {
|
||||
send :: proc(c: ^Raw_Channel, src: rawptr) {
|
||||
data := uintptr(c) + uintptr(c.data_offset);
|
||||
dst := data + uintptr(c.write * int(c.elem_size));
|
||||
mem.copy(rawptr(dst), src, int(c.elem_size));
|
||||
c.len += 1;
|
||||
c.write = (c.write + 1) % max(c.cap, 1);
|
||||
}
|
||||
|
||||
switch {
|
||||
case c == nil:
|
||||
panic(message="cannot send message; channel is nil", loc=loc);
|
||||
case c.closed:
|
||||
panic(message="cannot send message; channel is closed", loc=loc);
|
||||
}
|
||||
|
||||
mutex_lock(&c.mutex);
|
||||
defer mutex_unlock(&c.mutex);
|
||||
|
||||
if c.cap > 0 {
|
||||
if !block && c.len >= c.cap {
|
||||
return false;
|
||||
}
|
||||
|
||||
for c.len >= c.cap {
|
||||
cond_wait(&c.cond, &c.mutex);
|
||||
}
|
||||
} else if c.len > 0 { // TODO(bill): determine correct behaviour
|
||||
if !block {
|
||||
return false;
|
||||
}
|
||||
cond_wait(&c.cond, &c.mutex);
|
||||
} else if c.len == 0 && !block {
|
||||
return false;
|
||||
}
|
||||
|
||||
send(c, msg);
|
||||
cond_signal(&c.cond);
|
||||
raw_channel_wait_queue_signal(c.recvq);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
raw_channel_recv_impl :: proc(c: ^Raw_Channel, res: rawptr, loc := #caller_location) {
|
||||
recv :: proc(c: ^Raw_Channel, dst: rawptr, loc := #caller_location) {
|
||||
if c.len < 1 {
|
||||
panic(message="cannot recv message; channel is empty", loc=loc);
|
||||
}
|
||||
c.len -= 1;
|
||||
|
||||
data := uintptr(c) + uintptr(c.data_offset);
|
||||
src := data + uintptr(c.read * int(c.elem_size));
|
||||
mem.copy(dst, rawptr(src), int(c.elem_size));
|
||||
c.read = (c.read + 1) % max(c.cap, 1);
|
||||
}
|
||||
|
||||
if c == nil {
|
||||
panic(message="cannot recv message; channel is nil", loc=loc);
|
||||
}
|
||||
atomic_store(&c.ready, true);
|
||||
for c.len < 1 {
|
||||
raw_channel_wait_queue_signal(c.sendq);
|
||||
cond_wait(&c.cond, &c.mutex);
|
||||
}
|
||||
atomic_store(&c.ready, false);
|
||||
recv(c, res, loc);
|
||||
if c.cap > 0 {
|
||||
if c.len == c.cap - 1 {
|
||||
// NOTE(bill): Only signal on the last one
|
||||
cond_signal(&c.cond);
|
||||
}
|
||||
} else {
|
||||
cond_signal(&c.cond);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
raw_channel_can_send :: proc(c: ^Raw_Channel) -> (ok: bool) {
|
||||
if c == nil {
|
||||
return false;
|
||||
}
|
||||
mutex_lock(&c.mutex);
|
||||
switch {
|
||||
case c.closed:
|
||||
ok = false;
|
||||
case c.cap > 0:
|
||||
ok = c.ready && c.len < c.cap;
|
||||
case:
|
||||
ok = c.ready && c.len == 0;
|
||||
}
|
||||
mutex_unlock(&c.mutex);
|
||||
return;
|
||||
}
|
||||
raw_channel_can_recv :: proc(c: ^Raw_Channel) -> (ok: bool) {
|
||||
if c == nil {
|
||||
return false;
|
||||
}
|
||||
mutex_lock(&c.mutex);
|
||||
ok = c.len > 0;
|
||||
mutex_unlock(&c.mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
raw_channel_drain :: proc(c: ^Raw_Channel) {
|
||||
if c == nil {
|
||||
return;
|
||||
}
|
||||
mutex_lock(&c.mutex);
|
||||
c.len = 0;
|
||||
c.read = 0;
|
||||
c.write = 0;
|
||||
mutex_unlock(&c.mutex);
|
||||
}
|
||||
|
||||
|
||||
|
||||
MAX_SELECT_CHANNELS :: 64;
|
||||
SELECT_MAX_TIMEOUT :: max(time.Duration);
|
||||
|
||||
Select_Command :: enum {
|
||||
Recv,
|
||||
Send,
|
||||
}
|
||||
|
||||
Select_Channel :: struct {
|
||||
channel: ^Raw_Channel,
|
||||
command: Select_Command,
|
||||
}
|
||||
|
||||
|
||||
|
||||
select :: proc(channels: ..Select_Channel) -> (index: int) {
|
||||
return select_timeout(SELECT_MAX_TIMEOUT, ..channels);
|
||||
}
|
||||
select_timeout :: proc(timeout: time.Duration, channels: ..Select_Channel) -> (index: int) {
|
||||
switch len(channels) {
|
||||
case 0:
|
||||
panic("sync: select with no channels");
|
||||
}
|
||||
|
||||
assert(len(channels) <= MAX_SELECT_CHANNELS);
|
||||
|
||||
backing: [MAX_SELECT_CHANNELS]int;
|
||||
queues: [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue;
|
||||
candidates := backing[:];
|
||||
cap := len(channels);
|
||||
candidates = candidates[:cap];
|
||||
|
||||
count := u32(0);
|
||||
for c, i in channels {
|
||||
if c.channel == nil {
|
||||
continue;
|
||||
}
|
||||
switch c.command {
|
||||
case .Recv:
|
||||
if raw_channel_can_recv(c.channel) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
case .Send:
|
||||
if raw_channel_can_send(c.channel) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
wait_state: uintptr = 0;
|
||||
for _, i in channels {
|
||||
q := &queues[i];
|
||||
q.state = &wait_state;
|
||||
}
|
||||
|
||||
for c, i in channels {
|
||||
if c.channel == nil {
|
||||
continue;
|
||||
}
|
||||
q := &queues[i];
|
||||
switch c.command {
|
||||
case .Recv: raw_channel_wait_queue_insert(&c.channel.recvq, q);
|
||||
case .Send: raw_channel_wait_queue_insert(&c.channel.sendq, q);
|
||||
}
|
||||
}
|
||||
raw_channel_wait_queue_wait_on(&wait_state, timeout);
|
||||
for c, i in channels {
|
||||
if c.channel == nil {
|
||||
continue;
|
||||
}
|
||||
q := &queues[i];
|
||||
switch c.command {
|
||||
case .Recv: raw_channel_wait_queue_remove(&c.channel.recvq, q);
|
||||
case .Send: raw_channel_wait_queue_remove(&c.channel.sendq, q);
|
||||
}
|
||||
}
|
||||
|
||||
for c, i in channels {
|
||||
switch c.command {
|
||||
case .Recv:
|
||||
if raw_channel_can_recv(c.channel) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
case .Send:
|
||||
if raw_channel_can_send(c.channel) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if count == 0 && timeout == SELECT_MAX_TIMEOUT {
|
||||
index = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(count != 0);
|
||||
}
|
||||
|
||||
t := time.now();
|
||||
r := rand.create(transmute(u64)t);
|
||||
i := rand.uint32(&r);
|
||||
|
||||
index = candidates[i % count];
|
||||
return;
|
||||
}
|
||||
|
||||
select_recv :: proc(channels: ..^Raw_Channel) -> (index: int) {
|
||||
switch len(channels) {
|
||||
case 0:
|
||||
panic("sync: select with no channels");
|
||||
}
|
||||
|
||||
assert(len(channels) <= MAX_SELECT_CHANNELS);
|
||||
|
||||
backing: [MAX_SELECT_CHANNELS]int;
|
||||
queues: [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue;
|
||||
candidates := backing[:];
|
||||
cap := len(channels);
|
||||
candidates = candidates[:cap];
|
||||
|
||||
count := u32(0);
|
||||
for c, i in channels {
|
||||
if raw_channel_can_recv(c) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
state: uintptr;
|
||||
for c, i in channels {
|
||||
q := &queues[i];
|
||||
q.state = &state;
|
||||
raw_channel_wait_queue_insert(&c.recvq, q);
|
||||
}
|
||||
raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT);
|
||||
for c, i in channels {
|
||||
q := &queues[i];
|
||||
raw_channel_wait_queue_remove(&c.recvq, q);
|
||||
}
|
||||
|
||||
for c, i in channels {
|
||||
if raw_channel_can_recv(c) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
assert(count != 0);
|
||||
}
|
||||
|
||||
t := time.now();
|
||||
r := rand.create(transmute(u64)t);
|
||||
i := rand.uint32(&r);
|
||||
|
||||
index = candidates[i % count];
|
||||
return;
|
||||
}
|
||||
|
||||
select_recv_msg :: proc(channels: ..$C/Channel($T, $D)) -> (msg: T, index: int) {
|
||||
switch len(channels) {
|
||||
case 0:
|
||||
panic("sync: select with no channels");
|
||||
}
|
||||
|
||||
assert(len(channels) <= MAX_SELECT_CHANNELS);
|
||||
|
||||
queues: [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue;
|
||||
candidates: [MAX_SELECT_CHANNELS]int;
|
||||
|
||||
count := u32(0);
|
||||
for c, i in channels {
|
||||
if raw_channel_can_recv(c) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
state: uintptr;
|
||||
for c, i in channels {
|
||||
q := &queues[i];
|
||||
q.state = &state;
|
||||
raw_channel_wait_queue_insert(&c.recvq, q);
|
||||
}
|
||||
raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT);
|
||||
for c, i in channels {
|
||||
q := &queues[i];
|
||||
raw_channel_wait_queue_remove(&c.recvq, q);
|
||||
}
|
||||
|
||||
for c, i in channels {
|
||||
if raw_channel_can_recv(c) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
assert(count != 0);
|
||||
}
|
||||
|
||||
t := time.now();
|
||||
r := rand.create(transmute(u64)t);
|
||||
i := rand.uint32(&r);
|
||||
|
||||
index = candidates[i % count];
|
||||
msg = channel_recv(channels[index]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
select_send_msg :: proc(msg: $T, channels: ..$C/Channel(T, $D)) -> (index: int) {
|
||||
switch len(channels) {
|
||||
case 0:
|
||||
panic("sync: select with no channels");
|
||||
}
|
||||
|
||||
assert(len(channels) <= MAX_SELECT_CHANNELS);
|
||||
|
||||
backing: [MAX_SELECT_CHANNELS]int;
|
||||
queues: [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue;
|
||||
candidates := backing[:];
|
||||
cap := len(channels);
|
||||
candidates = candidates[:cap];
|
||||
|
||||
count := u32(0);
|
||||
for c, i in channels {
|
||||
if raw_channel_can_recv(c) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
state: uintptr;
|
||||
for c, i in channels {
|
||||
q := &queues[i];
|
||||
q.state = &state;
|
||||
raw_channel_wait_queue_insert(&c.recvq, q);
|
||||
}
|
||||
raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT);
|
||||
for c, i in channels {
|
||||
q := &queues[i];
|
||||
raw_channel_wait_queue_remove(&c.recvq, q);
|
||||
}
|
||||
|
||||
for c, i in channels {
|
||||
if raw_channel_can_recv(c) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
assert(count != 0);
|
||||
}
|
||||
|
||||
t := time.now();
|
||||
r := rand.create(transmute(u64)t);
|
||||
i := rand.uint32(&r);
|
||||
|
||||
index = candidates[i % count];
|
||||
|
||||
if msg != nil {
|
||||
channel_send(channels[index], msg);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
select_send :: proc(channels: ..^Raw_Channel) -> (index: int) {
|
||||
switch len(channels) {
|
||||
case 0:
|
||||
panic("sync: select with no channels");
|
||||
}
|
||||
|
||||
assert(len(channels) <= MAX_SELECT_CHANNELS);
|
||||
candidates: [MAX_SELECT_CHANNELS]int;
|
||||
queues: [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue;
|
||||
|
||||
count := u32(0);
|
||||
for c, i in channels {
|
||||
if raw_channel_can_send(c) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
state: uintptr;
|
||||
for c, i in channels {
|
||||
q := &queues[i];
|
||||
q.state = &state;
|
||||
raw_channel_wait_queue_insert(&c.sendq, q);
|
||||
}
|
||||
raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT);
|
||||
for c, i in channels {
|
||||
q := &queues[i];
|
||||
raw_channel_wait_queue_remove(&c.sendq, q);
|
||||
}
|
||||
|
||||
for c, i in channels {
|
||||
if raw_channel_can_send(c) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
assert(count != 0);
|
||||
}
|
||||
|
||||
t := time.now();
|
||||
r := rand.create(transmute(u64)t);
|
||||
i := rand.uint32(&r);
|
||||
|
||||
index = candidates[i % count];
|
||||
return;
|
||||
}
|
||||
|
||||
select_try :: proc(channels: ..Select_Channel) -> (index: int) {
|
||||
switch len(channels) {
|
||||
case 0:
|
||||
panic("sync: select with no channels");
|
||||
}
|
||||
|
||||
assert(len(channels) <= MAX_SELECT_CHANNELS);
|
||||
|
||||
backing: [MAX_SELECT_CHANNELS]int;
|
||||
candidates := backing[:];
|
||||
cap := len(channels);
|
||||
candidates = candidates[:cap];
|
||||
|
||||
count := u32(0);
|
||||
for c, i in channels {
|
||||
switch c.command {
|
||||
case .Recv:
|
||||
if raw_channel_can_recv(c.channel) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
case .Send:
|
||||
if raw_channel_can_send(c.channel) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
index = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
t := time.now();
|
||||
r := rand.create(transmute(u64)t);
|
||||
i := rand.uint32(&r);
|
||||
|
||||
index = candidates[i % count];
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
select_try_recv :: proc(channels: ..^Raw_Channel) -> (index: int) {
|
||||
switch len(channels) {
|
||||
case 0:
|
||||
index = -1;
|
||||
return;
|
||||
case 1:
|
||||
index = -1;
|
||||
if raw_channel_can_recv(channels[0]) {
|
||||
index = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
assert(len(channels) <= MAX_SELECT_CHANNELS);
|
||||
candidates: [MAX_SELECT_CHANNELS]int;
|
||||
|
||||
count := u32(0);
|
||||
for c, i in channels {
|
||||
if raw_channel_can_recv(c) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
index = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
t := time.now();
|
||||
r := rand.create(transmute(u64)t);
|
||||
i := rand.uint32(&r);
|
||||
|
||||
index = candidates[i % count];
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
select_try_send :: proc(channels: ..^Raw_Channel) -> (index: int) #no_bounds_check {
|
||||
switch len(channels) {
|
||||
case 0:
|
||||
return -1;
|
||||
case 1:
|
||||
if raw_channel_can_send(channels[0]) {
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
assert(len(channels) <= MAX_SELECT_CHANNELS);
|
||||
candidates: [MAX_SELECT_CHANNELS]int;
|
||||
|
||||
count := u32(0);
|
||||
for c, i in channels {
|
||||
if raw_channel_can_send(c) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
index = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
t := time.now();
|
||||
r := rand.create(transmute(u64)t);
|
||||
i := rand.uint32(&r);
|
||||
|
||||
index = candidates[i % count];
|
||||
return;
|
||||
}
|
||||
|
||||
select_try_recv_msg :: proc(channels: ..$C/Channel($T, $D)) -> (msg: T, index: int) {
|
||||
switch len(channels) {
|
||||
case 0:
|
||||
index = -1;
|
||||
return;
|
||||
case 1:
|
||||
ok: bool;
|
||||
if msg, ok = channel_try_recv(channels[0]); ok {
|
||||
index = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
assert(len(channels) <= MAX_SELECT_CHANNELS);
|
||||
candidates: [MAX_SELECT_CHANNELS]int;
|
||||
|
||||
count := u32(0);
|
||||
for c, i in channels {
|
||||
if channel_can_recv(c) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
index = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
t := time.now();
|
||||
r := rand.create(transmute(u64)t);
|
||||
i := rand.uint32(&r);
|
||||
|
||||
index = candidates[i % count];
|
||||
msg = channel_recv(channels[index]);
|
||||
return;
|
||||
}
|
||||
|
||||
select_try_send_msg :: proc(msg: $T, channels: ..$C/Channel(T, $D)) -> (index: int) {
|
||||
index = -1;
|
||||
switch len(channels) {
|
||||
case 0:
|
||||
return;
|
||||
case 1:
|
||||
if channel_try_send(channels[0], msg) {
|
||||
index = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
assert(len(channels) <= MAX_SELECT_CHANNELS);
|
||||
candidates: [MAX_SELECT_CHANNELS]int;
|
||||
|
||||
count := u32(0);
|
||||
for c, i in channels {
|
||||
if raw_channel_can_send(c) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
index = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
t := time.now();
|
||||
r := rand.create(transmute(u64)t);
|
||||
i := rand.uint32(&r);
|
||||
|
||||
index = candidates[i % count];
|
||||
channel_send(channels[index], msg);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
//+build linux, darwin, freebsd
|
||||
//+private
|
||||
package sync2
|
||||
|
||||
import "core:time"
|
||||
|
||||
raw_channel_wait_queue_wait_on :: proc(state: ^uintptr, timeout: time.Duration) {
|
||||
// stub
|
||||
}
|
||||
|
||||
raw_channel_wait_queue_signal :: proc(q: ^Raw_Channel_Wait_Queue) {
|
||||
// stub
|
||||
}
|
||||
|
||||
raw_channel_wait_queue_broadcast :: proc(q: ^Raw_Channel_Wait_Queue) {
|
||||
// stub
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
//+build windows
|
||||
//+private
|
||||
package sync2
|
||||
|
||||
import win32 "core:sys/windows"
|
||||
import "core:time"
|
||||
|
||||
raw_channel_wait_queue_wait_on :: proc(state: ^uintptr, timeout: time.Duration) {
|
||||
ms: win32.DWORD = win32.INFINITE;
|
||||
if max(time.Duration) != SELECT_MAX_TIMEOUT {
|
||||
ms = win32.DWORD((max(time.duration_nanoseconds(timeout), 0) + 999999)/1000000);
|
||||
}
|
||||
|
||||
v := atomic_load(state);
|
||||
for v == 0 {
|
||||
win32.WaitOnAddress(state, &v, size_of(state^), ms);
|
||||
v = atomic_load(state);
|
||||
}
|
||||
atomic_store(state, 0);
|
||||
}
|
||||
|
||||
raw_channel_wait_queue_signal :: proc(q: ^Raw_Channel_Wait_Queue) {
|
||||
for x := q; x != nil; x = x.next {
|
||||
atomic_add(x.state, 1);
|
||||
win32.WakeByAddressSingle(x.state);
|
||||
}
|
||||
}
|
||||
|
||||
raw_channel_wait_queue_broadcast :: proc(q: ^Raw_Channel_Wait_Queue) {
|
||||
for x := q; x != nil; x = x.next {
|
||||
atomic_add(x.state, 1);
|
||||
win32.WakeByAddressAll(x.state);
|
||||
}
|
||||
}
|
||||
@@ -122,6 +122,36 @@ barrier_wait :: proc(b: ^Barrier) -> (is_leader: bool) {
|
||||
}
|
||||
|
||||
|
||||
Auto_Reset_Event :: struct {
|
||||
// status == 0: Event is reset and no threads are waiting
|
||||
// status == 1: Event is signaled
|
||||
// status == -N: Event is reset and N threads are waiting
|
||||
status: i32,
|
||||
sema: Sema,
|
||||
}
|
||||
|
||||
auto_reset_event_signal :: proc(e: ^Auto_Reset_Event) {
|
||||
old_status := atomic_load_relaxed(&e.status);
|
||||
for {
|
||||
new_status := old_status + 1 if old_status < 1 else 1;
|
||||
if _, ok := atomic_compare_exchange_weak_release(&e.status, old_status, new_status); ok {
|
||||
break;
|
||||
}
|
||||
|
||||
if old_status < 0 {
|
||||
sema_post(&e.sema);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto_reset_event_wait :: proc(e: ^Auto_Reset_Event) {
|
||||
old_status := atomic_sub_acquire(&e.status, 1);
|
||||
if old_status < 1 {
|
||||
sema_wait(&e.sema);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Ticket_Mutex :: struct {
|
||||
ticket: uint,
|
||||
@@ -130,7 +160,7 @@ Ticket_Mutex :: struct {
|
||||
|
||||
ticket_mutex_lock :: #force_inline proc(m: ^Ticket_Mutex) {
|
||||
ticket := atomic_add_relaxed(&m.ticket, 1);
|
||||
for ticket != atomic_load_acq(&m.serving) {
|
||||
for ticket != atomic_load_acquire(&m.serving) {
|
||||
cpu_relax();
|
||||
}
|
||||
}
|
||||
@@ -142,23 +172,23 @@ ticket_mutex_unlock :: #force_inline proc(m: ^Ticket_Mutex) {
|
||||
|
||||
|
||||
Benaphore :: struct {
|
||||
counter: int,
|
||||
counter: i32,
|
||||
sema: Sema,
|
||||
}
|
||||
|
||||
benaphore_lock :: proc(b: ^Benaphore) {
|
||||
if atomic_add_acq(&b.counter, 1) > 1 {
|
||||
if atomic_add_acquire(&b.counter, 1) > 1 {
|
||||
sema_wait(&b.sema);
|
||||
}
|
||||
}
|
||||
|
||||
benaphore_try_lock :: proc(b: ^Benaphore) -> bool {
|
||||
v, _ := atomic_cxchg_acq(&b.counter, 1, 0);
|
||||
v, _ := atomic_compare_exchange_strong_acquire(&b.counter, 1, 0);
|
||||
return v == 0;
|
||||
}
|
||||
|
||||
benaphore_unlock :: proc(b: ^Benaphore) {
|
||||
if atomic_sub_rel(&b.counter, 1) > 0 {
|
||||
if atomic_sub_release(&b.counter, 1) > 0 {
|
||||
sema_post(&b.sema);
|
||||
}
|
||||
}
|
||||
@@ -166,13 +196,13 @@ benaphore_unlock :: proc(b: ^Benaphore) {
|
||||
Recursive_Benaphore :: struct {
|
||||
counter: int,
|
||||
owner: int,
|
||||
recursion: int,
|
||||
recursion: i32,
|
||||
sema: Sema,
|
||||
}
|
||||
|
||||
recursive_benaphore_lock :: proc(b: ^Recursive_Benaphore) {
|
||||
tid := runtime.current_thread_id();
|
||||
if atomic_add_acq(&b.counter, 1) > 1 {
|
||||
if atomic_add_acquire(&b.counter, 1) > 1 {
|
||||
if tid != b.owner {
|
||||
sema_wait(&b.sema);
|
||||
}
|
||||
@@ -185,10 +215,10 @@ recursive_benaphore_lock :: proc(b: ^Recursive_Benaphore) {
|
||||
recursive_benaphore_try_lock :: proc(b: ^Recursive_Benaphore) -> bool {
|
||||
tid := runtime.current_thread_id();
|
||||
if b.owner == tid {
|
||||
atomic_add_acq(&b.counter, 1);
|
||||
atomic_add_acquire(&b.counter, 1);
|
||||
}
|
||||
|
||||
if v, _ := atomic_cxchg_acq(&b.counter, 1, 0); v != 0 {
|
||||
if v, _ := atomic_compare_exchange_strong_acquire(&b.counter, 1, 0); v != 0 {
|
||||
return false;
|
||||
}
|
||||
// inside the lock
|
||||
@@ -205,7 +235,7 @@ recursive_benaphore_unlock :: proc(b: ^Recursive_Benaphore) {
|
||||
if recursion == 0 {
|
||||
b.owner = 0;
|
||||
}
|
||||
if atomic_sub_rel(&b.counter, 1) > 0 {
|
||||
if atomic_sub_release(&b.counter, 1) > 0 {
|
||||
if recursion == 0 {
|
||||
sema_post(&b.sema);
|
||||
}
|
||||
@@ -223,7 +253,7 @@ Once :: struct {
|
||||
}
|
||||
|
||||
once_do :: proc(o: ^Once, fn: proc()) {
|
||||
if atomic_load_acq(&o.done) == false {
|
||||
if atomic_load_acquire(&o.done) == false {
|
||||
_once_do_slow(o, fn);
|
||||
}
|
||||
}
|
||||
@@ -234,6 +264,6 @@ _once_do_slow :: proc(o: ^Once, fn: proc()) {
|
||||
defer mutex_unlock(&o.m);
|
||||
if !o.done {
|
||||
fn();
|
||||
atomic_store_rel(&o.done, true);
|
||||
atomic_store_release(&o.done, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package sync2
|
||||
|
||||
import "core:time"
|
||||
import "core:runtime"
|
||||
|
||||
// A Mutex is a mutual exclusion lock
|
||||
// The zero value for a Mutex is an unlocked mutex
|
||||
@@ -26,6 +25,18 @@ mutex_try_lock :: proc(m: ^Mutex) -> bool {
|
||||
return _mutex_try_lock(m);
|
||||
}
|
||||
|
||||
// Example:
|
||||
//
|
||||
// if mutex_guard(&m) {
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
@(deferred_in=mutex_unlock)
|
||||
mutex_guard :: proc(m: ^Mutex) -> bool {
|
||||
mutex_lock(m);
|
||||
return true;
|
||||
}
|
||||
|
||||
// A RW_Mutex is a reader/writer mutual exclusion lock
|
||||
// The lock can be held by any arbitrary number of readers or a single writer
|
||||
// The zero value for a RW_Mutex is an unlocked mutex
|
||||
@@ -66,61 +77,65 @@ rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool {
|
||||
return _rw_mutex_try_shared_lock(rw);
|
||||
}
|
||||
|
||||
// Example:
|
||||
//
|
||||
// if rw_mutex_guard(&m) {
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
@(deferred_in=rw_mutex_unlock)
|
||||
rw_mutex_guard :: proc(m: ^RW_Mutex) -> bool {
|
||||
rw_mutex_lock(m);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Example:
|
||||
//
|
||||
// if rw_mutex_shared_guard(&m) {
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
@(deferred_in=rw_mutex_shared_unlock)
|
||||
rw_mutex_shared_guard :: proc(m: ^RW_Mutex) -> bool {
|
||||
rw_mutex_shared_lock(m);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// A Recusrive_Mutex is a recursive mutual exclusion lock
|
||||
// The zero value for a Recursive_Mutex is an unlocked mutex
|
||||
//
|
||||
// A Recursive_Mutex must not be copied after first use
|
||||
Recursive_Mutex :: struct {
|
||||
// TODO(bill): Is this implementation too lazy?
|
||||
// Can this be made to work on all OSes without construction and destruction, i.e. Zero is Initialized
|
||||
// CRITICAL_SECTION would be a perfect candidate for this on Windows but that cannot be "dumb"
|
||||
|
||||
owner: int,
|
||||
recursion: int,
|
||||
mutex: Mutex,
|
||||
impl: _Recursive_Mutex,
|
||||
}
|
||||
|
||||
recursive_mutex_lock :: proc(m: ^Recursive_Mutex) {
|
||||
tid := runtime.current_thread_id();
|
||||
if tid != m.owner {
|
||||
mutex_lock(&m.mutex);
|
||||
}
|
||||
// inside the lock
|
||||
m.owner = tid;
|
||||
m.recursion += 1;
|
||||
_recursive_mutex_lock(m);
|
||||
}
|
||||
|
||||
recursive_mutex_unlock :: proc(m: ^Recursive_Mutex) {
|
||||
tid := runtime.current_thread_id();
|
||||
assert(tid == m.owner);
|
||||
m.recursion -= 1;
|
||||
recursion := m.recursion;
|
||||
if recursion == 0 {
|
||||
m.owner = 0;
|
||||
}
|
||||
if recursion == 0 {
|
||||
mutex_unlock(&m.mutex);
|
||||
}
|
||||
// outside the lock
|
||||
|
||||
_recursive_mutex_unlock(m);
|
||||
}
|
||||
|
||||
recursive_mutex_try_lock :: proc(m: ^Recursive_Mutex) -> bool {
|
||||
tid := runtime.current_thread_id();
|
||||
if m.owner == tid {
|
||||
return mutex_try_lock(&m.mutex);
|
||||
}
|
||||
if !mutex_try_lock(&m.mutex) {
|
||||
return false;
|
||||
}
|
||||
// inside the lock
|
||||
m.owner = tid;
|
||||
m.recursion += 1;
|
||||
return true;
|
||||
return _recursive_mutex_try_lock(m);
|
||||
}
|
||||
|
||||
|
||||
// Example:
|
||||
//
|
||||
// if recursive_mutex_guard(&m) {
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
@(deferred_in=recursive_mutex_unlock)
|
||||
recursive_mutex_guard :: proc(m: ^Recursive_Mutex) -> bool {
|
||||
recursive_mutex_lock(m);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Cond implements a condition variable, a rendezvous point for threads
|
||||
// waiting for signalling the occurence of an event
|
||||
@@ -153,33 +168,14 @@ cond_broadcast :: proc(c: ^Cond) {
|
||||
//
|
||||
// A Sema must not be copied after first use
|
||||
Sema :: struct {
|
||||
// TODO(bill): Is this implementation too lazy?
|
||||
// Can this be made to work on all OSes without construction and destruction, i.e. Zero is Initialized
|
||||
|
||||
mutex: Mutex,
|
||||
cond: Cond,
|
||||
count: int,
|
||||
impl: _Sema,
|
||||
}
|
||||
|
||||
|
||||
sema_wait :: proc(s: ^Sema) {
|
||||
mutex_lock(&s.mutex);
|
||||
defer mutex_unlock(&s.mutex);
|
||||
|
||||
for s.count == 0 {
|
||||
cond_wait(&s.cond, &s.mutex);
|
||||
}
|
||||
|
||||
s.count -= 1;
|
||||
if s.count > 0 {
|
||||
cond_signal(&s.cond);
|
||||
}
|
||||
_sema_wait(s);
|
||||
}
|
||||
|
||||
sema_post :: proc(s: ^Sema, count := 1) {
|
||||
mutex_lock(&s.mutex);
|
||||
defer mutex_unlock(&s.mutex);
|
||||
|
||||
s.count += count;
|
||||
cond_signal(&s.cond);
|
||||
_sema_post(s, count);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package sync2
|
||||
when !#config(ODIN_SYNC_USE_PTHREADS, true) {
|
||||
|
||||
import "core:time"
|
||||
import "core:runtime"
|
||||
|
||||
_Mutex_State :: enum i32 {
|
||||
Unlocked = 0,
|
||||
@@ -160,6 +161,54 @@ _rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool {
|
||||
}
|
||||
|
||||
|
||||
_Recursive_Mutex :: struct {
|
||||
owner: int,
|
||||
recursion: int,
|
||||
mutex: Mutex,
|
||||
}
|
||||
|
||||
_recursive_mutex_lock :: proc(m: ^Recursive_Mutex) {
|
||||
tid := runtime.current_thread_id();
|
||||
if tid != m.impl.owner {
|
||||
mutex_lock(&m.impl.mutex);
|
||||
}
|
||||
// inside the lock
|
||||
m.impl.owner = tid;
|
||||
m.impl.recursion += 1;
|
||||
}
|
||||
|
||||
_recursive_mutex_unlock :: proc(m: ^Recursive_Mutex) {
|
||||
tid := runtime.current_thread_id();
|
||||
assert(tid == m.impl.owner);
|
||||
m.impl.recursion -= 1;
|
||||
recursion := m.impl.recursion;
|
||||
if recursion == 0 {
|
||||
m.impl.owner = 0;
|
||||
}
|
||||
if recursion == 0 {
|
||||
mutex_unlock(&m.impl.mutex);
|
||||
}
|
||||
// outside the lock
|
||||
|
||||
}
|
||||
|
||||
_recursive_mutex_try_lock :: proc(m: ^Recursive_Mutex) -> bool {
|
||||
tid := runtime.current_thread_id();
|
||||
if m.impl.owner == tid {
|
||||
return mutex_try_lock(&m.impl.mutex);
|
||||
}
|
||||
if !mutex_try_lock(&m.impl.mutex) {
|
||||
return false;
|
||||
}
|
||||
// inside the lock
|
||||
m.impl.owner = tid;
|
||||
m.impl.recursion += 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Queue_Item :: struct {
|
||||
next: ^Queue_Item,
|
||||
@@ -240,5 +289,35 @@ _cond_broadcast :: proc(c: ^Cond) {
|
||||
}
|
||||
}
|
||||
|
||||
_Sema :: struct {
|
||||
mutex: Mutex,
|
||||
cond: Cond,
|
||||
count: int,
|
||||
}
|
||||
|
||||
_sema_wait :: proc(s: ^Sema) {
|
||||
mutex_lock(&s.impl.mutex);
|
||||
defer mutex_unlock(&s.impl.mutex);
|
||||
|
||||
for s.impl.count == 0 {
|
||||
cond_wait(&s.impl.cond, &s.impl.mutex);
|
||||
}
|
||||
|
||||
s.impl.count -= 1;
|
||||
if s.impl.count > 0 {
|
||||
cond_signal(&s.impl.cond);
|
||||
}
|
||||
}
|
||||
|
||||
_sema_post :: proc(s: ^Sema, count := 1) {
|
||||
mutex_lock(&s.impl.mutex);
|
||||
defer mutex_unlock(&s.impl.mutex);
|
||||
|
||||
s.impl.count += count;
|
||||
cond_signal(&s.impl.cond);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
} // !ODIN_SYNC_USE_PTHREADS
|
||||
|
||||
@@ -5,6 +5,7 @@ package sync2
|
||||
when #config(ODIN_SYNC_USE_PTHREADS, true) {
|
||||
|
||||
import "core:time"
|
||||
import "core:runtime"
|
||||
import "core:sys/unix"
|
||||
|
||||
_Mutex_State :: enum i32 {
|
||||
@@ -83,7 +84,7 @@ _rw_mutex_shared_lock :: proc(rw: ^RW_Mutex) {
|
||||
state := atomic_load(&rw.impl.state);
|
||||
for state & (RW_Mutex_State_Is_Writing|RW_Mutex_State_Writer_Mask) == 0 {
|
||||
ok: bool;
|
||||
state, ok = atomic_cxchgweak(&rw.impl.state, state, state + RW_Mutex_State_Reader);
|
||||
state, ok = atomic_compare_exchange_weak(&rw.impl.state, state, state + RW_Mutex_State_Reader);
|
||||
if ok {
|
||||
return;
|
||||
}
|
||||
@@ -106,7 +107,7 @@ _rw_mutex_shared_unlock :: proc(rw: ^RW_Mutex) {
|
||||
_rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool {
|
||||
state := atomic_load(&rw.impl.state);
|
||||
if state & (RW_Mutex_State_Is_Writing|RW_Mutex_State_Writer_Mask) == 0 {
|
||||
_, ok := atomic_cxchg(&rw.impl.state, state, state + RW_Mutex_State_Reader);
|
||||
_, ok := atomic_compare_exchange_strong(&rw.impl.state, state, state + RW_Mutex_State_Reader);
|
||||
if ok {
|
||||
return true;
|
||||
}
|
||||
@@ -120,6 +121,53 @@ _rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
_Recursive_Mutex :: struct {
|
||||
owner: int,
|
||||
recursion: int,
|
||||
mutex: Mutex,
|
||||
}
|
||||
|
||||
_recursive_mutex_lock :: proc(m: ^Recursive_Mutex) {
|
||||
tid := runtime.current_thread_id();
|
||||
if tid != m.impl.owner {
|
||||
mutex_lock(&m.impl.mutex);
|
||||
}
|
||||
// inside the lock
|
||||
m.impl.owner = tid;
|
||||
m.impl.recursion += 1;
|
||||
}
|
||||
|
||||
_recursive_mutex_unlock :: proc(m: ^Recursive_Mutex) {
|
||||
tid := runtime.current_thread_id();
|
||||
assert(tid == m.impl.owner);
|
||||
m.impl.recursion -= 1;
|
||||
recursion := m.impl.recursion;
|
||||
if recursion == 0 {
|
||||
m.impl.owner = 0;
|
||||
}
|
||||
if recursion == 0 {
|
||||
mutex_unlock(&m.impl.mutex);
|
||||
}
|
||||
// outside the lock
|
||||
|
||||
}
|
||||
|
||||
_recursive_mutex_try_lock :: proc(m: ^Recursive_Mutex) -> bool {
|
||||
tid := runtime.current_thread_id();
|
||||
if m.impl.owner == tid {
|
||||
return mutex_try_lock(&m.impl.mutex);
|
||||
}
|
||||
if !mutex_try_lock(&m.impl.mutex) {
|
||||
return false;
|
||||
}
|
||||
// inside the lock
|
||||
m.impl.owner = tid;
|
||||
m.impl.recursion += 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
_Cond :: struct {
|
||||
pthread_cond: unix.pthread_cond_t,
|
||||
}
|
||||
@@ -150,5 +198,34 @@ _cond_broadcast :: proc(c: ^Cond) {
|
||||
assert(err == 0);
|
||||
}
|
||||
|
||||
_Sema :: struct {
|
||||
mutex: Mutex,
|
||||
cond: Cond,
|
||||
count: int,
|
||||
}
|
||||
|
||||
_sema_wait :: proc(s: ^Sema) {
|
||||
mutex_lock(&s.impl.mutex);
|
||||
defer mutex_unlock(&s.impl.mutex);
|
||||
|
||||
for s.impl.count == 0 {
|
||||
cond_wait(&s.impl.cond, &s.impl.mutex);
|
||||
}
|
||||
|
||||
s.impl.count -= 1;
|
||||
if s.impl.count > 0 {
|
||||
cond_signal(&s.impl.cond);
|
||||
}
|
||||
}
|
||||
|
||||
_sema_post :: proc(s: ^Sema, count := 1) {
|
||||
mutex_lock(&s.impl.mutex);
|
||||
defer mutex_unlock(&s.impl.mutex);
|
||||
|
||||
s.impl.count += count;
|
||||
cond_signal(&s.impl.cond);
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // ODIN_SYNC_USE_PTHREADS
|
||||
|
||||
@@ -50,6 +50,56 @@ _rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool {
|
||||
}
|
||||
|
||||
|
||||
_Recursive_Mutex :: struct {
|
||||
owner: u32,
|
||||
claim_count: i32,
|
||||
}
|
||||
|
||||
_recursive_mutex_lock :: proc(m: ^Recursive_Mutex) {
|
||||
tid := win32.GetCurrentThreadId();
|
||||
for {
|
||||
prev_owner := atomic_compare_exchange_strong_acquire(&m.impl.owner, tid, 0);
|
||||
switch prev_owner {
|
||||
case 0, tid:
|
||||
m.impl.claim_count += 1;
|
||||
// inside the lock
|
||||
return;
|
||||
}
|
||||
|
||||
win32.WaitOnAddress(
|
||||
&m.impl.owner,
|
||||
&prev_owner,
|
||||
size_of(prev_owner),
|
||||
win32.INFINITE,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_recursive_mutex_unlock :: proc(m: ^Recursive_Mutex) {
|
||||
m.impl.claim_count -= 1;
|
||||
if m.impl.claim_count != 0 {
|
||||
return;
|
||||
}
|
||||
atomic_exchange_release(&m.impl.owner, 0);
|
||||
win32.WakeByAddressSingle(&m.impl.owner);
|
||||
// outside the lock
|
||||
|
||||
}
|
||||
|
||||
_recursive_mutex_try_lock :: proc(m: ^Recursive_Mutex) -> bool {
|
||||
tid := win32.GetCurrentThreadId();
|
||||
prev_owner := atomic_compare_exchange_strong_acquire(&m.impl.owner, tid, 0);
|
||||
switch prev_owner {
|
||||
case 0, tid:
|
||||
m.impl.claim_count += 1;
|
||||
// inside the lock
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
_Cond :: struct {
|
||||
cond: win32.CONDITION_VARIABLE,
|
||||
@@ -71,3 +121,35 @@ _cond_signal :: proc(c: ^Cond) {
|
||||
_cond_broadcast :: proc(c: ^Cond) {
|
||||
win32.WakeAllConditionVariable(&c.impl.cond);
|
||||
}
|
||||
|
||||
|
||||
_Sema :: struct {
|
||||
count: i32,
|
||||
}
|
||||
|
||||
_sema_wait :: proc(s: ^Sema) {
|
||||
for {
|
||||
original_count := s.impl.count;
|
||||
for original_count == 0 {
|
||||
win32.WaitOnAddress(
|
||||
&s.impl.count,
|
||||
&original_count,
|
||||
size_of(original_count),
|
||||
win32.INFINITE,
|
||||
);
|
||||
original_count = s.impl.count;
|
||||
}
|
||||
if original_count == atomic_compare_exchange_strong(&s.impl.count, original_count-1, original_count) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_sema_post :: proc(s: ^Sema, count := 1) {
|
||||
atomic_add(&s.impl.count, i32(count));
|
||||
if count == 1 {
|
||||
win32.WakeByAddressSingle(&s.impl.count);
|
||||
} else {
|
||||
win32.WakeByAddressAll(&s.impl.count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package testing
|
||||
|
||||
import "core:io"
|
||||
import "core:os"
|
||||
import "core:strings"
|
||||
import "core:slice"
|
||||
|
||||
reset_t :: proc(t: ^T) {
|
||||
@@ -55,12 +54,9 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
||||
|
||||
logf(t, "[Test: %s]", it.name);
|
||||
|
||||
// TODO(bill): Catch panics
|
||||
{
|
||||
it.p(t);
|
||||
}
|
||||
run_internal_test(t, it);
|
||||
|
||||
if t.error_count != 0 {
|
||||
if failed(t) {
|
||||
logf(t, "[%s : FAILURE]", it.name);
|
||||
} else {
|
||||
logf(t, "[%s : SUCCESS]", it.name);
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
//+private
|
||||
//+build !windows
|
||||
package testing
|
||||
|
||||
run_internal_test :: proc(t: ^T, it: Internal_Test) {
|
||||
// TODO(bill): Catch panics on other platforms
|
||||
it.p(t);
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
//+private
|
||||
//+build windows
|
||||
package testing
|
||||
|
||||
import win32 "core:sys/windows"
|
||||
import "core:runtime"
|
||||
import "intrinsics"
|
||||
|
||||
|
||||
Sema :: struct {
|
||||
count: i32,
|
||||
}
|
||||
|
||||
sema_reset :: proc "contextless" (s: ^Sema) {
|
||||
intrinsics.atomic_store(&s.count, 0);
|
||||
}
|
||||
sema_wait :: proc "contextless" (s: ^Sema) {
|
||||
for {
|
||||
original_count := s.count;
|
||||
for original_count == 0 {
|
||||
win32.WaitOnAddress(
|
||||
&s.count,
|
||||
&original_count,
|
||||
size_of(original_count),
|
||||
win32.INFINITE,
|
||||
);
|
||||
original_count = s.count;
|
||||
}
|
||||
if original_count == intrinsics.atomic_cxchg(&s.count, original_count-1, original_count) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sema_post :: proc "contextless" (s: ^Sema, count := 1) {
|
||||
intrinsics.atomic_add(&s.count, i32(count));
|
||||
if count == 1 {
|
||||
win32.WakeByAddressSingle(&s.count);
|
||||
} else {
|
||||
win32.WakeByAddressAll(&s.count);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Thread_Proc :: #type proc(^Thread);
|
||||
|
||||
MAX_USER_ARGUMENTS :: 8;
|
||||
|
||||
Thread :: struct {
|
||||
using specific: Thread_Os_Specific,
|
||||
procedure: Thread_Proc,
|
||||
|
||||
t: ^T,
|
||||
it: Internal_Test,
|
||||
success: bool,
|
||||
|
||||
init_context: Maybe(runtime.Context),
|
||||
|
||||
creation_allocator: runtime.Allocator,
|
||||
}
|
||||
|
||||
Thread_Os_Specific :: struct {
|
||||
win32_thread: win32.HANDLE,
|
||||
win32_thread_id: win32.DWORD,
|
||||
done: bool, // see note in `is_done`
|
||||
}
|
||||
|
||||
thread_create :: proc(procedure: Thread_Proc) -> ^Thread {
|
||||
__windows_thread_entry_proc :: proc "stdcall" (t_: rawptr) -> win32.DWORD {
|
||||
t := (^Thread)(t_);
|
||||
context = runtime.default_context();
|
||||
c := context;
|
||||
if ic, ok := t.init_context.?; ok {
|
||||
c = ic;
|
||||
}
|
||||
context = c;
|
||||
|
||||
t.procedure(t);
|
||||
|
||||
if t.init_context == nil {
|
||||
if context.temp_allocator.data == &runtime.global_default_temp_allocator_data {
|
||||
runtime.default_temp_allocator_destroy(auto_cast context.temp_allocator.data);
|
||||
}
|
||||
}
|
||||
|
||||
intrinsics.atomic_store(&t.done, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
thread := new(Thread);
|
||||
if thread == nil {
|
||||
return nil;
|
||||
}
|
||||
thread.creation_allocator = context.allocator;
|
||||
|
||||
win32_thread_id: win32.DWORD;
|
||||
win32_thread := win32.CreateThread(nil, 0, __windows_thread_entry_proc, thread, win32.CREATE_SUSPENDED, &win32_thread_id);
|
||||
if win32_thread == nil {
|
||||
free(thread, thread.creation_allocator);
|
||||
return nil;
|
||||
}
|
||||
thread.procedure = procedure;
|
||||
thread.win32_thread = win32_thread;
|
||||
thread.win32_thread_id = win32_thread_id;
|
||||
thread.init_context = context;
|
||||
|
||||
return thread;
|
||||
}
|
||||
|
||||
thread_start :: proc "contextless" (thread: ^Thread) {
|
||||
win32.ResumeThread(thread.win32_thread);
|
||||
}
|
||||
|
||||
thread_join_and_destroy :: proc(thread: ^Thread) {
|
||||
if thread.win32_thread != win32.INVALID_HANDLE {
|
||||
win32.WaitForSingleObject(thread.win32_thread, win32.INFINITE);
|
||||
win32.CloseHandle(thread.win32_thread);
|
||||
thread.win32_thread = win32.INVALID_HANDLE;
|
||||
}
|
||||
free(thread, thread.creation_allocator);
|
||||
}
|
||||
|
||||
thread_terminate :: proc "contextless" (thread: ^Thread, exit_code: int) {
|
||||
win32.TerminateThread(thread.win32_thread, u32(exit_code));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
global_threaded_runner_semaphore: Sema;
|
||||
global_exception_handler: rawptr;
|
||||
global_current_thread: ^Thread;
|
||||
global_current_t: ^T;
|
||||
|
||||
run_internal_test :: proc(t: ^T, it: Internal_Test) {
|
||||
thread := thread_create(proc(thread: ^Thread) {
|
||||
exception_handler_proc :: proc "stdcall" (ExceptionInfo: ^win32.EXCEPTION_POINTERS) -> win32.LONG {
|
||||
switch ExceptionInfo.ExceptionRecord.ExceptionCode {
|
||||
case
|
||||
win32.EXCEPTION_DATATYPE_MISALIGNMENT,
|
||||
win32.EXCEPTION_BREAKPOINT,
|
||||
win32.EXCEPTION_ACCESS_VIOLATION,
|
||||
win32.EXCEPTION_ILLEGAL_INSTRUCTION,
|
||||
win32.EXCEPTION_ARRAY_BOUNDS_EXCEEDED,
|
||||
win32.EXCEPTION_STACK_OVERFLOW:
|
||||
|
||||
sema_post(&global_threaded_runner_semaphore);
|
||||
return win32.EXCEPTION_EXECUTE_HANDLER;
|
||||
}
|
||||
|
||||
return win32.EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
global_exception_handler = win32.AddVectoredExceptionHandler(0, exception_handler_proc);
|
||||
|
||||
context.assertion_failure_proc = proc(prefix, message: string, loc: runtime.Source_Code_Location) {
|
||||
errorf(t=global_current_t, format="%s %s", args={prefix, message}, loc=loc);
|
||||
intrinsics.trap();
|
||||
};
|
||||
|
||||
thread.it.p(thread.t);
|
||||
|
||||
thread.success = true;
|
||||
sema_post(&global_threaded_runner_semaphore);
|
||||
});
|
||||
|
||||
sema_reset(&global_threaded_runner_semaphore);
|
||||
global_current_t = t;
|
||||
|
||||
t._fail_now = proc() -> ! {
|
||||
intrinsics.trap();
|
||||
};
|
||||
|
||||
thread.t = t;
|
||||
thread.it = it;
|
||||
thread.success = false;
|
||||
|
||||
thread_start(thread);
|
||||
|
||||
sema_wait(&global_threaded_runner_semaphore);
|
||||
thread_terminate(thread, int(!thread.success));
|
||||
thread_join_and_destroy(thread);
|
||||
|
||||
win32.RemoveVectoredExceptionHandler(global_exception_handler);
|
||||
|
||||
if !thread.success && t.error_count == 0 {
|
||||
t.error_count += 1;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -25,16 +25,21 @@ T :: struct {
|
||||
w: io.Writer,
|
||||
|
||||
cleanups: [dynamic]Internal_Cleanup,
|
||||
|
||||
_fail_now: proc() -> !,
|
||||
}
|
||||
|
||||
|
||||
error :: proc(t: ^T, args: ..any, loc := #caller_location) {
|
||||
log(t=t, args=args, loc=loc);
|
||||
fmt.wprintf(t.w, "%v: ", loc);
|
||||
fmt.wprintln(t.w, ..args);
|
||||
t.error_count += 1;
|
||||
}
|
||||
|
||||
errorf :: proc(t: ^T, format: string, args: ..any, loc := #caller_location) {
|
||||
logf(t=t, format=format, args=args, loc=loc);
|
||||
fmt.wprintf(t.w, "%v: ", loc);
|
||||
fmt.wprintf(t.w, format, ..args);
|
||||
fmt.wprintln(t.w);
|
||||
t.error_count += 1;
|
||||
}
|
||||
|
||||
@@ -43,6 +48,13 @@ fail :: proc(t: ^T) {
|
||||
t.error_count += 1;
|
||||
}
|
||||
|
||||
fail_now :: proc(t: ^T) {
|
||||
fail(t);
|
||||
if t._fail_now != nil {
|
||||
t._fail_now();
|
||||
}
|
||||
}
|
||||
|
||||
failed :: proc(t: ^T) -> bool {
|
||||
return t.error_count != 0;
|
||||
}
|
||||
|
||||
+4
-5
@@ -262,19 +262,18 @@ datetime_to_time :: proc(year, month, day, hour, minute, second: int, nsec := in
|
||||
return;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
|
||||
_y := year - 1970;
|
||||
_m := month - 1;
|
||||
_d := day - 1;
|
||||
|
||||
if _m < 0 || _m > 11 {
|
||||
if month < 1 || month > 12 {
|
||||
_m %= 12; ok = false;
|
||||
}
|
||||
if _d < 0 || _m > 30 {
|
||||
if day < 1 || day > 31 {
|
||||
_d %= 31; ok = false;
|
||||
}
|
||||
if _m < 0 || _m > 11 {
|
||||
_m %= 12; ok = false;
|
||||
}
|
||||
|
||||
s := i64(0);
|
||||
div, mod := divmod(_y, 400);
|
||||
|
||||
@@ -12,7 +12,6 @@ package unicode
|
||||
@(private) pLo :: pLl | pLu; // a letter that is neither upper nor lower case.
|
||||
@(private) pLmask :: pLo;
|
||||
|
||||
@(static)
|
||||
char_properties := [MAX_LATIN1+1]u8{
|
||||
0x00 = pC, // '\x00'
|
||||
0x01 = pC, // '\x01'
|
||||
@@ -273,7 +272,6 @@ char_properties := [MAX_LATIN1+1]u8{
|
||||
};
|
||||
|
||||
|
||||
@(static)
|
||||
alpha_ranges := [?]i32{
|
||||
0x00d8, 0x00f6,
|
||||
0x00f8, 0x01f5,
|
||||
@@ -429,7 +427,6 @@ alpha_ranges := [?]i32{
|
||||
0xffda, 0xffdc,
|
||||
};
|
||||
|
||||
@(static)
|
||||
alpha_singlets := [?]i32{
|
||||
0x00aa,
|
||||
0x00b5,
|
||||
@@ -465,7 +462,6 @@ alpha_singlets := [?]i32{
|
||||
0xfe74,
|
||||
};
|
||||
|
||||
@(static)
|
||||
space_ranges := [?]i32{
|
||||
0x0009, 0x000d, // tab and newline
|
||||
0x0020, 0x0020, // space
|
||||
@@ -481,7 +477,6 @@ space_ranges := [?]i32{
|
||||
0xfeff, 0xfeff,
|
||||
};
|
||||
|
||||
@(static)
|
||||
unicode_spaces := [?]i32{
|
||||
0x0009, // tab
|
||||
0x000a, // LF
|
||||
@@ -499,7 +494,6 @@ unicode_spaces := [?]i32{
|
||||
0xfeff, // unknown
|
||||
};
|
||||
|
||||
@(static)
|
||||
to_upper_ranges := [?]i32{
|
||||
0x0061, 0x007a, 468, // a-z A-Z
|
||||
0x00e0, 0x00f6, 468,
|
||||
@@ -538,7 +532,6 @@ to_upper_ranges := [?]i32{
|
||||
0xff41, 0xff5a, 468,
|
||||
};
|
||||
|
||||
@(static)
|
||||
to_upper_singlets := [?]i32{
|
||||
0x00ff, 621,
|
||||
0x0101, 499,
|
||||
@@ -882,7 +875,6 @@ to_upper_singlets := [?]i32{
|
||||
0x1ff3, 509,
|
||||
};
|
||||
|
||||
@(static)
|
||||
to_lower_ranges := [?]i32{
|
||||
0x0041, 0x005a, 532, // A-Z a-z
|
||||
0x00c0, 0x00d6, 532, // - -
|
||||
@@ -922,7 +914,6 @@ to_lower_ranges := [?]i32{
|
||||
0xff21, 0xff3a, 532, // - -
|
||||
};
|
||||
|
||||
@(static)
|
||||
to_lower_singlets := [?]i32{
|
||||
0x0100, 501,
|
||||
0x0102, 501,
|
||||
@@ -1259,7 +1250,6 @@ to_lower_singlets := [?]i32{
|
||||
0x1ffc, 491,
|
||||
};
|
||||
|
||||
@(static)
|
||||
to_title_singlets := [?]i32{
|
||||
0x01c4, 501,
|
||||
0x01c6, 499,
|
||||
|
||||
+29
-7
@@ -1352,8 +1352,8 @@ bit_set_type :: proc() {
|
||||
|
||||
d: Days;
|
||||
d = {Sunday, Monday};
|
||||
e := d | WEEKEND;
|
||||
e |= {Monday};
|
||||
e := d + WEEKEND;
|
||||
e += {Monday};
|
||||
fmt.println(d, e);
|
||||
|
||||
ok := Saturday in e; // `in` is only allowed for `map` and `bit_set` types
|
||||
@@ -1372,12 +1372,12 @@ bit_set_type :: proc() {
|
||||
fmt.println(typeid_of(type_of(x))); // bit_set[A..Z]
|
||||
fmt.println(typeid_of(type_of(y))); // bit_set[0..8; u16]
|
||||
|
||||
incl(&x, 'F');
|
||||
x += {'F'};
|
||||
assert('F' in x);
|
||||
excl(&x, 'F');
|
||||
x -= {'F'};
|
||||
assert('F' not_in x);
|
||||
|
||||
y |= {1, 4, 2};
|
||||
y += {1, 4, 2};
|
||||
assert(2 in y);
|
||||
}
|
||||
{
|
||||
@@ -1760,8 +1760,6 @@ range_statements_with_multiple_return_values :: proc() {
|
||||
|
||||
|
||||
soa_struct_layout :: proc() {
|
||||
// IMPORTANT NOTE(bill, 2019-11-03): This feature is subject to be changed/removed
|
||||
// NOTE(bill): Most likely #soa [N]T
|
||||
fmt.println("\n#SOA Struct Layout");
|
||||
|
||||
{
|
||||
@@ -1858,6 +1856,30 @@ soa_struct_layout :: proc() {
|
||||
fmt.println(cap(d));
|
||||
fmt.println(d[:]);
|
||||
}
|
||||
{ // soa_zip and soa_unzip
|
||||
fmt.println("\nsoa_zip and soa_unzip");
|
||||
|
||||
x := []i32{1, 3, 9};
|
||||
y := []f32{2, 4, 16};
|
||||
z := []b32{true, false, true};
|
||||
|
||||
// produce an #soa slice the normal slices passed
|
||||
s := soa_zip(a=x, b=y, c=z);
|
||||
|
||||
// iterate over the #soa slice
|
||||
for v, i in s {
|
||||
fmt.println(v, i); // exactly the same as s[i]
|
||||
// NOTE: 'v' is NOT a temporary value but has a specialized addressing mode
|
||||
// which means that when accessing v.a etc, it does the correct transformation
|
||||
// internally:
|
||||
// s[i].a === s.a[i]
|
||||
fmt.println(v.a, v.b, v.c);
|
||||
}
|
||||
|
||||
// Recover the slices from the #soa slice
|
||||
a, b, c := soa_unzip(s);
|
||||
fmt.println(a, b, c);
|
||||
}
|
||||
}
|
||||
|
||||
constant_literal_expressions :: proc() {
|
||||
|
||||
@@ -1347,8 +1347,8 @@ bit_set_type :: proc() {
|
||||
|
||||
d: Days
|
||||
d = {Sunday, Monday}
|
||||
e := d | WEEKEND
|
||||
e |= {Monday}
|
||||
e := d + WEEKEND
|
||||
e += {Monday}
|
||||
fmt.println(d, e)
|
||||
|
||||
ok := Saturday in e // `in` is only allowed for `map` and `bit_set` types
|
||||
@@ -1367,12 +1367,12 @@ bit_set_type :: proc() {
|
||||
fmt.println(typeid_of(type_of(x))) // bit_set[A..Z]
|
||||
fmt.println(typeid_of(type_of(y))) // bit_set[0..8; u16]
|
||||
|
||||
incl(&x, 'F')
|
||||
x += {'F'};
|
||||
assert('F' in x)
|
||||
excl(&x, 'F')
|
||||
x -= {'F'};
|
||||
assert('F' not_in x)
|
||||
|
||||
y |= {1, 4, 2}
|
||||
y += {1, 4, 2}
|
||||
assert(2 in y)
|
||||
}
|
||||
{
|
||||
|
||||
+9
-61
@@ -173,8 +173,6 @@ struct BuildContext {
|
||||
String resource_filepath;
|
||||
String pdb_filepath;
|
||||
bool has_resource;
|
||||
String opt_flags;
|
||||
String llc_flags;
|
||||
String link_flags;
|
||||
String extra_linker_flags;
|
||||
String microarch;
|
||||
@@ -202,18 +200,24 @@ struct BuildContext {
|
||||
bool disallow_do;
|
||||
bool insert_semicolon;
|
||||
|
||||
|
||||
bool ignore_warnings;
|
||||
bool warnings_as_errors;
|
||||
bool show_error_line;
|
||||
|
||||
bool use_subsystem_windows;
|
||||
bool ignore_microsoft_magic;
|
||||
bool linker_map_file;
|
||||
|
||||
bool use_separate_modules;
|
||||
|
||||
u32 cmd_doc_flags;
|
||||
Array<String> extra_packages;
|
||||
|
||||
QueryDataSetSettings query_data_set_settings;
|
||||
|
||||
StringSet test_names;
|
||||
|
||||
gbAffinity affinity;
|
||||
isize thread_count;
|
||||
|
||||
@@ -744,6 +748,9 @@ String get_fullpath_core(gbAllocator a, String path) {
|
||||
return path_to_fullpath(a, res);
|
||||
}
|
||||
|
||||
bool show_error_line(void) {
|
||||
return build_context.show_error_line;
|
||||
}
|
||||
|
||||
|
||||
void init_build_context(TargetMetrics *cross_target) {
|
||||
@@ -806,22 +813,12 @@ void init_build_context(TargetMetrics *cross_target) {
|
||||
bc->word_size = metrics->word_size;
|
||||
bc->max_align = metrics->max_align;
|
||||
bc->link_flags = str_lit(" ");
|
||||
bc->opt_flags = str_lit(" ");
|
||||
|
||||
|
||||
gbString llc_flags = gb_string_make_reserve(heap_allocator(), 64);
|
||||
if (bc->ODIN_DEBUG) {
|
||||
// llc_flags = gb_string_appendc(llc_flags, "-debug-compile ");
|
||||
}
|
||||
|
||||
// NOTE(zangent): The linker flags to set the build architecture are different
|
||||
// across OSs. It doesn't make sense to allocate extra data on the heap
|
||||
// here, so I just #defined the linker flags to keep things concise.
|
||||
if (bc->metrics.arch == TargetArch_amd64) {
|
||||
if (bc->microarch.len == 0) {
|
||||
llc_flags = gb_string_appendc(llc_flags, "-march=x86-64 ");
|
||||
}
|
||||
|
||||
switch (bc->metrics.os) {
|
||||
case TargetOs_windows:
|
||||
bc->link_flags = str_lit("/machine:x64 ");
|
||||
@@ -836,10 +833,6 @@ void init_build_context(TargetMetrics *cross_target) {
|
||||
break;
|
||||
}
|
||||
} else if (bc->metrics.arch == TargetArch_386) {
|
||||
if (bc->microarch.len == 0) {
|
||||
llc_flags = gb_string_appendc(llc_flags, "-march=x86 ");
|
||||
}
|
||||
|
||||
switch (bc->metrics.os) {
|
||||
case TargetOs_windows:
|
||||
bc->link_flags = str_lit("/machine:x86 ");
|
||||
@@ -856,10 +849,6 @@ void init_build_context(TargetMetrics *cross_target) {
|
||||
break;
|
||||
}
|
||||
} else if (bc->metrics.arch == TargetArch_arm64) {
|
||||
if (bc->microarch.len == 0) {
|
||||
llc_flags = gb_string_appendc(llc_flags, "-march=arm64 ");
|
||||
}
|
||||
|
||||
switch (bc->metrics.os) {
|
||||
case TargetOs_darwin:
|
||||
bc->link_flags = str_lit("-arch arm64 ");
|
||||
@@ -872,50 +861,9 @@ void init_build_context(TargetMetrics *cross_target) {
|
||||
gb_printf_err("Compiler Error: Unsupported architecture\n");;
|
||||
gb_exit(1);
|
||||
}
|
||||
llc_flags = gb_string_appendc(llc_flags, " ");
|
||||
|
||||
|
||||
bc->optimization_level = gb_clamp(bc->optimization_level, 0, 3);
|
||||
|
||||
gbString opt_flags = gb_string_make_reserve(heap_allocator(), 64);
|
||||
|
||||
if (bc->microarch.len != 0) {
|
||||
opt_flags = gb_string_appendc(opt_flags, "-march=");
|
||||
opt_flags = gb_string_append_length(opt_flags, bc->microarch.text, bc->microarch.len);
|
||||
opt_flags = gb_string_appendc(opt_flags, " ");
|
||||
|
||||
// llc_flags = gb_string_appendc(opt_flags, "-march=");
|
||||
// llc_flags = gb_string_append_length(llc_flags, bc->microarch.text, bc->microarch.len);
|
||||
// llc_flags = gb_string_appendc(llc_flags, " ");
|
||||
}
|
||||
|
||||
|
||||
if (bc->optimization_level != 0) {
|
||||
opt_flags = gb_string_append_fmt(opt_flags, "-O%d ", bc->optimization_level);
|
||||
// NOTE(lachsinc): The following options were previously passed during call
|
||||
// to opt in main.cpp:exec_llvm_opt().
|
||||
// -die: Dead instruction elimination
|
||||
// -memcpyopt: MemCpy optimization
|
||||
}
|
||||
if (bc->ODIN_DEBUG == false) {
|
||||
opt_flags = gb_string_appendc(opt_flags, "-mem2reg -die ");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// NOTE(lachsinc): This optimization option was previously required to get
|
||||
// around an issue in fmt.odin. Thank bp for tracking it down! Leaving for now until the issue
|
||||
// is resolved and confirmed by Bill. Maybe it should be readded in non-debug builds.
|
||||
// if (bc->ODIN_DEBUG == false) {
|
||||
// opt_flags = gb_string_appendc(opt_flags, "-mem2reg ");
|
||||
// }
|
||||
|
||||
bc->opt_flags = make_string_c(opt_flags);
|
||||
bc->llc_flags = make_string_c(llc_flags);
|
||||
|
||||
|
||||
#undef LINK_FLAG_X64
|
||||
#undef LINK_FLAG_386
|
||||
}
|
||||
|
||||
+131
-6
@@ -87,7 +87,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
|
||||
|
||||
case BuiltinProc_DIRECTIVE: {
|
||||
ast_node(bd, BasicDirective, ce->proc);
|
||||
String name = bd->name;
|
||||
String name = bd->name.string;
|
||||
if (name == "defined") {
|
||||
break;
|
||||
}
|
||||
@@ -124,7 +124,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
|
||||
|
||||
case BuiltinProc_DIRECTIVE: {
|
||||
ast_node(bd, BasicDirective, ce->proc);
|
||||
String name = bd->name;
|
||||
String name = bd->name.string;
|
||||
if (name == "location") {
|
||||
if (ce->args.count > 1) {
|
||||
error(ce->args[0], "'#location' expects either 0 or 1 arguments, got %td", ce->args.count);
|
||||
@@ -1509,6 +1509,10 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
|
||||
|
||||
{
|
||||
Type *bt = base_type(x.type);
|
||||
if (are_types_identical(bt, t_f16)) {
|
||||
add_package_dependency(c, "runtime", "min_f16");
|
||||
add_package_dependency(c, "runtime", "max_f16");
|
||||
}
|
||||
if (are_types_identical(bt, t_f32)) {
|
||||
add_package_dependency(c, "runtime", "min_f32");
|
||||
add_package_dependency(c, "runtime", "max_f32");
|
||||
@@ -2017,11 +2021,92 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
|
||||
}
|
||||
}
|
||||
|
||||
operand->mode = Addressing_Value;
|
||||
operand->type = make_optional_ok_type(default_type(x.type), false); // Just reusing this procedure, it's not optional
|
||||
operand->mode = Addressing_OptionalOk;
|
||||
operand->type = default_type(x.type);
|
||||
}
|
||||
break;
|
||||
|
||||
case BuiltinProc_sqrt:
|
||||
{
|
||||
Operand x = {};
|
||||
check_expr(c, &x, ce->args[0]);
|
||||
if (x.mode == Addressing_Invalid) {
|
||||
return false;
|
||||
}
|
||||
if (!is_type_float(x.type)) {
|
||||
gbString xts = type_to_string(x.type);
|
||||
error(x.expr, "Expected a floating point value for '%.*s', got %s", LIT(builtin_procs[id].name), xts);
|
||||
gb_string_free(xts);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (x.mode == Addressing_Constant) {
|
||||
f64 v = exact_value_to_f64(x.value);
|
||||
|
||||
operand->mode = Addressing_Constant;
|
||||
operand->type = x.type;
|
||||
operand->value = exact_value_float(gb_sqrt(v));
|
||||
break;
|
||||
}
|
||||
operand->mode = Addressing_Value;
|
||||
operand->type = default_type(x.type);
|
||||
}
|
||||
break;
|
||||
|
||||
case BuiltinProc_mem_copy:
|
||||
case BuiltinProc_mem_copy_non_overlapping:
|
||||
{
|
||||
operand->mode = Addressing_NoValue;
|
||||
operand->type = t_invalid;
|
||||
|
||||
Operand dst = {};
|
||||
Operand src = {};
|
||||
Operand len = {};
|
||||
check_expr(c, &dst, ce->args[0]);
|
||||
check_expr(c, &src, ce->args[1]);
|
||||
check_expr(c, &len, ce->args[2]);
|
||||
if (dst.mode == Addressing_Invalid) {
|
||||
return false;
|
||||
}
|
||||
if (src.mode == Addressing_Invalid) {
|
||||
return false;
|
||||
}
|
||||
if (len.mode == Addressing_Invalid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!is_type_pointer(dst.type)) {
|
||||
gbString str = type_to_string(dst.type);
|
||||
error(dst.expr, "Expected a pointer value for '%.*s', got %s", LIT(builtin_procs[id].name), str);
|
||||
gb_string_free(str);
|
||||
return false;
|
||||
}
|
||||
if (!is_type_pointer(src.type)) {
|
||||
gbString str = type_to_string(src.type);
|
||||
error(src.expr, "Expected a pointer value for '%.*s', got %s", LIT(builtin_procs[id].name), str);
|
||||
gb_string_free(str);
|
||||
return false;
|
||||
}
|
||||
if (!is_type_integer(len.type)) {
|
||||
gbString str = type_to_string(len.type);
|
||||
error(len.expr, "Expected an integer value for the number of bytes for '%.*s', got %s", LIT(builtin_procs[id].name), str);
|
||||
gb_string_free(str);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (len.mode == Addressing_Constant) {
|
||||
i64 n = exact_value_to_i64(len.value);
|
||||
if (n < 0) {
|
||||
gbString str = expr_to_string(len.expr);
|
||||
error(len.expr, "Expected a non-negative integer value for the number of bytes for '%.*s', got %s", LIT(builtin_procs[id].name), str);
|
||||
gb_string_free(str);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case BuiltinProc_atomic_fence:
|
||||
case BuiltinProc_atomic_fence_acq:
|
||||
case BuiltinProc_atomic_fence_rel:
|
||||
@@ -2149,8 +2234,8 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
|
||||
check_assignment(c, &x, elem, builtin_name);
|
||||
check_assignment(c, &y, elem, builtin_name);
|
||||
|
||||
operand->mode = Addressing_Value;
|
||||
operand->type = make_optional_ok_type(elem, /*typed*/false);
|
||||
operand->mode = Addressing_OptionalOk;
|
||||
operand->type = elem;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@@ -2425,6 +2510,46 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
|
||||
}
|
||||
break;
|
||||
|
||||
case BuiltinProc_type_is_variant_of:
|
||||
{
|
||||
if (operand->mode != Addressing_Type) {
|
||||
error(operand->expr, "Expected a type for '%.*s'", LIT(builtin_name));
|
||||
operand->mode = Addressing_Invalid;
|
||||
operand->type = t_invalid;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Type *u = operand->type;
|
||||
|
||||
if (!is_type_union(u)) {
|
||||
error(operand->expr, "Expected a union type for '%.*s'", LIT(builtin_name));
|
||||
operand->mode = Addressing_Invalid;
|
||||
operand->type = t_invalid;
|
||||
return false;
|
||||
}
|
||||
|
||||
Type *v = check_type(c, ce->args[1]);
|
||||
|
||||
u = base_type(u);
|
||||
GB_ASSERT(u->kind == Type_Union);
|
||||
|
||||
bool is_variant = false;
|
||||
|
||||
for_array(i, u->Union.variants) {
|
||||
Type *vt = u->Union.variants[i];
|
||||
if (are_types_identical(v, vt)) {
|
||||
is_variant = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
operand->mode = Addressing_Constant;
|
||||
operand->type = t_untyped_bool;
|
||||
operand->value = exact_value_bool(is_variant);
|
||||
}
|
||||
break;
|
||||
|
||||
case BuiltinProc_type_struct_field_count:
|
||||
operand->value = exact_value_i64(0);
|
||||
if (operand->mode != Addressing_Type) {
|
||||
|
||||
+18
-16
@@ -289,17 +289,6 @@ void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr, Type *def)
|
||||
if (decl != nullptr) {
|
||||
AttributeContext ac = {};
|
||||
check_decl_attributes(ctx, decl->attributes, type_decl_attribute, &ac);
|
||||
if (ac.atom_op_table != nullptr) {
|
||||
Type *bt = base_type(e->type);
|
||||
switch (bt->kind) {
|
||||
case Type_Struct:
|
||||
bt->Struct.atom_op_table = ac.atom_op_table;
|
||||
break;
|
||||
default:
|
||||
error(e->token, "Only struct types can have custom atom operations");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -352,16 +341,17 @@ void override_entity_in_scope(Entity *original_entity, Entity *new_entity) {
|
||||
|
||||
string_map_set(&found_scope->elements, original_name, new_entity);
|
||||
|
||||
original_entity->flags |= EntityFlag_Overridden;
|
||||
original_entity->type = new_entity->type;
|
||||
original_entity->aliased_of = new_entity;
|
||||
|
||||
if (original_entity->identifier == nullptr) {
|
||||
original_entity->identifier = new_entity->identifier;
|
||||
}
|
||||
if (original_entity->identifier != nullptr &&
|
||||
original_entity->identifier->kind == Ast_Ident) {
|
||||
original_entity->identifier->Ident.entity = nullptr;
|
||||
original_entity->identifier->Ident.entity = new_entity;
|
||||
}
|
||||
original_entity->flags |= EntityFlag_Overridden;
|
||||
|
||||
// IMPORTANT NOTE(bill, 2021-04-10): copy only the variants
|
||||
// This is most likely NEVER required, but it does not at all hurt to keep
|
||||
@@ -375,6 +365,7 @@ void override_entity_in_scope(Entity *original_entity, Entity *new_entity) {
|
||||
void check_const_decl(CheckerContext *ctx, Entity *e, Ast *type_expr, Ast *init, Type *named_type) {
|
||||
GB_ASSERT(e->type == nullptr);
|
||||
GB_ASSERT(e->kind == Entity_Constant);
|
||||
init = unparen_expr(init);
|
||||
|
||||
if (e->flags & EntityFlag_Visited) {
|
||||
e->type = t_invalid;
|
||||
@@ -408,6 +399,18 @@ void check_const_decl(CheckerContext *ctx, Entity *e, Ast *type_expr, Ast *init,
|
||||
e->kind = Entity_TypeName;
|
||||
e->type = nullptr;
|
||||
|
||||
if (entity != nullptr && entity->type != nullptr &&
|
||||
is_type_polymorphic_record_unspecialized(entity->type)) {
|
||||
DeclInfo *decl = decl_info_of_entity(e);
|
||||
if (decl != nullptr) {
|
||||
if (decl->attributes.count > 0) {
|
||||
error(decl->attributes[0], "Constant alias declarations cannot have attributes");
|
||||
}
|
||||
}
|
||||
|
||||
override_entity_in_scope(e, entity);
|
||||
return;
|
||||
}
|
||||
check_type_decl(ctx, e, ctx->decl->init_expr, named_type);
|
||||
return;
|
||||
}
|
||||
@@ -896,10 +899,9 @@ void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast *type_expr,
|
||||
|
||||
e->Variable.thread_local_model = ac.thread_local_model;
|
||||
e->Variable.is_export = ac.is_export;
|
||||
e->flags &= ~EntityFlag_Static;
|
||||
if (ac.is_static) {
|
||||
e->flags |= EntityFlag_Static;
|
||||
} else {
|
||||
e->flags &= ~EntityFlag_Static;
|
||||
error(e->token, "@(static) is not supported for global variables, nor required");
|
||||
}
|
||||
ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix);
|
||||
|
||||
|
||||
+283
-265
@@ -621,7 +621,9 @@ i64 check_distance_between_types(CheckerContext *c, Operand *operand, Type *type
|
||||
}
|
||||
PolyProcData poly_proc_data = {};
|
||||
if (check_polymorphic_procedure_assignment(c, operand, type, operand->expr, &poly_proc_data)) {
|
||||
add_entity_use(c, operand->expr, poly_proc_data.gen_entity);
|
||||
Entity *e = poly_proc_data.gen_entity;
|
||||
add_type_and_value(c->info, operand->expr, Addressing_Value, e->type, {});
|
||||
add_entity_use(c, operand->expr, e);
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
@@ -1113,6 +1115,7 @@ bool check_cycle(CheckerContext *c, Entity *curr, bool report) {
|
||||
error(curr->token, "\t%.*s refers to", LIT(curr->token.string));
|
||||
}
|
||||
error(curr->token, "\t%.*s", LIT(curr->token.string));
|
||||
curr->type = t_invalid;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1130,7 +1133,7 @@ Entity *check_ident(CheckerContext *c, Operand *o, Ast *n, Type *named_type, Typ
|
||||
Entity *e = scope_lookup(c->scope, name);
|
||||
if (e == nullptr) {
|
||||
if (is_blank_ident(name)) {
|
||||
error(n, "'_' cannot be used as a value type");
|
||||
error(n, "'_' cannot be used as a value");
|
||||
} else {
|
||||
error(n, "Undeclared name: %.*s", LIT(name));
|
||||
}
|
||||
@@ -1141,6 +1144,9 @@ Entity *check_ident(CheckerContext *c, Operand *o, Ast *n, Type *named_type, Typ
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GB_ASSERT((e->flags & EntityFlag_Overridden) == 0);
|
||||
|
||||
if (e->parent_proc_decl != nullptr &&
|
||||
e->parent_proc_decl != c->curr_proc_decl) {
|
||||
if (e->kind == Entity_Variable) {
|
||||
@@ -1195,8 +1201,6 @@ Entity *check_ident(CheckerContext *c, Operand *o, Ast *n, Type *named_type, Typ
|
||||
if (e->state == EntityState_Unresolved) {
|
||||
check_entity_decl(c, e, nullptr, named_type);
|
||||
}
|
||||
|
||||
|
||||
if (e->type == nullptr) {
|
||||
// TODO(bill): Which is correct? return or compiler_error?
|
||||
// compiler_error("How did this happen? type: %s; identifier: %.*s\n", type_to_string(e->type), LIT(name));
|
||||
@@ -2212,6 +2216,10 @@ void check_shift(CheckerContext *c, Operand *x, Operand *y, Ast *node, Type *typ
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_type_untyped(y->type)) {
|
||||
convert_to_typed(c, y, t_uint);
|
||||
}
|
||||
|
||||
x->mode = Addressing_Value;
|
||||
}
|
||||
|
||||
@@ -2379,9 +2387,15 @@ bool check_cast_internal(CheckerContext *c, Operand *x, Type *type) {
|
||||
if (core_type(bt)->kind == Type_Basic) {
|
||||
if (check_representable_as_constant(c, x->value, bt, &x->value)) {
|
||||
return true;
|
||||
} else if (is_type_pointer(type) && check_is_castable_to(c, x, type)) {
|
||||
return true;
|
||||
} else if (check_is_castable_to(c, x, type)) {
|
||||
if (is_type_pointer(type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (check_is_castable_to(c, x, type)) {
|
||||
x->value = {};
|
||||
x->mode = Addressing_Value;
|
||||
return true;
|
||||
}
|
||||
} else if (check_is_castable_to(c, x, type)) {
|
||||
if (x->mode != Addressing_Constant) {
|
||||
@@ -2391,6 +2405,9 @@ bool check_cast_internal(CheckerContext *c, Operand *x, Type *type) {
|
||||
} else if (is_type_union(type)) {
|
||||
x->mode = Addressing_Value;
|
||||
}
|
||||
if (x->mode == Addressing_Value) {
|
||||
x->value = {};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -2503,6 +2520,10 @@ bool check_binary_array_expr(CheckerContext *c, Token op, Operand *x, Operand *y
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_ise_expr(Ast *node) {
|
||||
node = unparen_expr(node);
|
||||
return node->kind == Ast_ImplicitSelectorExpr;
|
||||
}
|
||||
|
||||
void check_binary_expr(CheckerContext *c, Operand *x, Ast *node, Type *type_hint, bool use_lhs_as_type_hint=false) {
|
||||
GB_ASSERT(node->kind == Ast_BinaryExpr);
|
||||
@@ -2520,8 +2541,14 @@ void check_binary_expr(CheckerContext *c, Operand *x, Ast *node, Type *type_hint
|
||||
case Token_CmpEq:
|
||||
case Token_NotEq: {
|
||||
// NOTE(bill): Allow comparisons between types
|
||||
check_expr_or_type(c, x, be->left, type_hint);
|
||||
check_expr_or_type(c, y, be->right, x->type);
|
||||
if (is_ise_expr(be->left)) {
|
||||
// Evalute the right before the left for an '.X' expression
|
||||
check_expr_or_type(c, y, be->right, type_hint);
|
||||
check_expr_or_type(c, x, be->left, y->type);
|
||||
} else {
|
||||
check_expr_or_type(c, x, be->left, type_hint);
|
||||
check_expr_or_type(c, y, be->right, x->type);
|
||||
}
|
||||
bool xt = x->mode == Addressing_Type;
|
||||
bool yt = y->mode == Addressing_Type;
|
||||
// If only one is a type, this is an error
|
||||
@@ -2628,11 +2655,22 @@ void check_binary_expr(CheckerContext *c, Operand *x, Ast *node, Type *type_hint
|
||||
return;
|
||||
|
||||
default:
|
||||
check_expr_with_type_hint(c, x, be->left, type_hint);
|
||||
if (use_lhs_as_type_hint) {
|
||||
check_expr_with_type_hint(c, y, be->right, x->type);
|
||||
if (is_ise_expr(be->left)) {
|
||||
// Evalute the right before the left for an '.X' expression
|
||||
check_expr_or_type(c, y, be->right, type_hint);
|
||||
|
||||
if (use_lhs_as_type_hint) { // RHS in this case
|
||||
check_expr_or_type(c, x, be->left, y->type);
|
||||
} else {
|
||||
check_expr_with_type_hint(c, x, be->left, type_hint);
|
||||
}
|
||||
} else {
|
||||
check_expr_with_type_hint(c, y, be->right, type_hint);
|
||||
check_expr_with_type_hint(c, x, be->left, type_hint);
|
||||
if (use_lhs_as_type_hint) {
|
||||
check_expr_with_type_hint(c, y, be->right, x->type);
|
||||
} else {
|
||||
check_expr_with_type_hint(c, y, be->right, type_hint);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -2852,13 +2890,33 @@ void update_expr_type(CheckerContext *c, Ast *e, Type *type, bool final) {
|
||||
if (token_is_comparison(be->op.kind)) {
|
||||
// NOTE(bill): Do nothing as the types are fine
|
||||
} else if (token_is_shift(be->op.kind)) {
|
||||
update_expr_type(c, be->left, type, final);
|
||||
update_expr_type(c, be->left, type, final);
|
||||
} else {
|
||||
update_expr_type(c, be->left, type, final);
|
||||
update_expr_type(c, be->right, type, final);
|
||||
}
|
||||
case_end;
|
||||
|
||||
case_ast_node(te, TernaryIfExpr, e);
|
||||
if (old.value.kind != ExactValue_Invalid) {
|
||||
// See above note in UnaryExpr case
|
||||
break;
|
||||
}
|
||||
|
||||
update_expr_type(c, te->x, type, final);
|
||||
update_expr_type(c, te->y, type, final);
|
||||
case_end;
|
||||
|
||||
case_ast_node(te, TernaryWhenExpr, e);
|
||||
if (old.value.kind != ExactValue_Invalid) {
|
||||
// See above note in UnaryExpr case
|
||||
break;
|
||||
}
|
||||
|
||||
update_expr_type(c, te->x, type, final);
|
||||
update_expr_type(c, te->y, type, final);
|
||||
case_end;
|
||||
|
||||
case_ast_node(pe, ParenExpr, e);
|
||||
update_expr_type(c, pe->expr, type, final);
|
||||
case_end;
|
||||
@@ -3146,8 +3204,8 @@ void convert_to_typed(CheckerContext *c, Operand *operand, Type *target_type) {
|
||||
break;
|
||||
}
|
||||
|
||||
operand->type = target_type;
|
||||
update_expr_type(c, operand->expr, target_type, true);
|
||||
operand->type = target_type;
|
||||
}
|
||||
|
||||
bool check_index_value(CheckerContext *c, bool open_range, Ast *index_value, i64 max_count, i64 *value, Type *type_hint=nullptr) {
|
||||
@@ -3896,6 +3954,16 @@ bool check_assignment_arguments(CheckerContext *ctx, Array<Operand> const &lhs,
|
||||
add_type_and_value(&c->checker->info, o.expr, o.mode, tuple, o.value);
|
||||
}
|
||||
|
||||
if (o.mode == Addressing_OptionalOk && expr->kind == Ast_TypeAssertion) {
|
||||
// NOTE(bill): Used only for optimizations in the backend
|
||||
if (is_blank_ident(lhs[0].expr)) {
|
||||
expr->TypeAssertion.ignores[0] = true;
|
||||
}
|
||||
if (is_blank_ident(lhs[1].expr)) {
|
||||
expr->TypeAssertion.ignores[1] = true;
|
||||
}
|
||||
}
|
||||
|
||||
array_add(operands, val0);
|
||||
array_add(operands, val1);
|
||||
optional_ok = true;
|
||||
@@ -4010,6 +4078,16 @@ bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize lhs_count,
|
||||
add_type_and_value(&c->checker->info, o.expr, o.mode, tuple, o.value);
|
||||
}
|
||||
|
||||
if (o.mode == Addressing_OptionalOk && expr->kind == Ast_TypeAssertion) {
|
||||
// NOTE(bill): Used only for optimizations in the backend
|
||||
if (is_blank_ident(lhs[0]->token)) {
|
||||
expr->TypeAssertion.ignores[0] = true;
|
||||
}
|
||||
if (is_blank_ident(lhs[1]->token)) {
|
||||
expr->TypeAssertion.ignores[1] = true;
|
||||
}
|
||||
}
|
||||
|
||||
array_add(operands, val0);
|
||||
array_add(operands, val1);
|
||||
optional_ok = true;
|
||||
@@ -4034,6 +4112,16 @@ bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize lhs_count,
|
||||
}
|
||||
|
||||
|
||||
bool is_expr_constant_zero(Ast *expr) {
|
||||
GB_ASSERT(expr != nullptr);
|
||||
auto v = exact_value_to_integer(expr->tav.value);
|
||||
if (v.kind == ExactValue_Integer) {
|
||||
return big_int_cmp_zero(&v.value_integer) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
CALL_ARGUMENT_CHECKER(check_call_arguments_internal) {
|
||||
ast_node(ce, CallExpr, call);
|
||||
GB_ASSERT(is_type_proc(proc_type));
|
||||
@@ -4203,8 +4291,13 @@ CALL_ARGUMENT_CHECKER(check_call_arguments_internal) {
|
||||
if (show_error) {
|
||||
check_assignment(c, &o, t, str_lit("argument"));
|
||||
}
|
||||
err = CallArgumentError_WrongTypes;
|
||||
// TODO(bill, 2021-05-05): Is this incorrect logic to only fail if there is ambiguity for definite?
|
||||
if (o.mode == Addressing_Invalid) {
|
||||
err = CallArgumentError_WrongTypes;
|
||||
}
|
||||
}
|
||||
} else if (show_error) {
|
||||
check_assignment(c, &o, t, str_lit("argument"));
|
||||
}
|
||||
score += s;
|
||||
|
||||
@@ -4220,7 +4313,10 @@ CALL_ARGUMENT_CHECKER(check_call_arguments_internal) {
|
||||
if (o.mode == Addressing_Type && is_type_typeid(e->type)) {
|
||||
add_type_info_type(c, o.type);
|
||||
add_type_and_value(c->info, o.expr, Addressing_Value, e->type, exact_value_typeid(o.type));
|
||||
} else if (show_error && is_type_untyped(o.type)) {
|
||||
update_expr_type(c, o.expr, t, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (variadic) {
|
||||
@@ -4258,6 +4354,8 @@ CALL_ARGUMENT_CHECKER(check_call_arguments_internal) {
|
||||
check_assignment(c, &o, t, str_lit("argument"));
|
||||
}
|
||||
err = CallArgumentError_WrongTypes;
|
||||
} else if (show_error) {
|
||||
check_assignment(c, &o, t, str_lit("argument"));
|
||||
}
|
||||
score += s;
|
||||
if (is_type_any(elem)) {
|
||||
@@ -4266,6 +4364,8 @@ CALL_ARGUMENT_CHECKER(check_call_arguments_internal) {
|
||||
if (o.mode == Addressing_Type && is_type_typeid(t)) {
|
||||
add_type_info_type(c, o.type);
|
||||
add_type_and_value(c->info, o.expr, Addressing_Value, t, exact_value_typeid(o.type));
|
||||
} else if (show_error && is_type_untyped(o.type)) {
|
||||
update_expr_type(c, o.expr, t, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4480,6 +4580,8 @@ CALL_ARGUMENT_CHECKER(check_named_call_arguments) {
|
||||
err = CallArgumentError_NoneConstantParameter;
|
||||
}
|
||||
}
|
||||
} else if (show_error) {
|
||||
check_assignment(c, o, e->type, str_lit("procedure argument"));
|
||||
}
|
||||
score += s;
|
||||
}
|
||||
@@ -5414,7 +5516,7 @@ ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *pr
|
||||
if (proc != nullptr &&
|
||||
proc->kind == Ast_BasicDirective) {
|
||||
ast_node(bd, BasicDirective, proc);
|
||||
String name = bd->name;
|
||||
String name = bd->name.string;
|
||||
if (name == "location" || name == "assert" || name == "panic" || name == "defined" || name == "config" || name == "load") {
|
||||
operand->mode = Addressing_Builtin;
|
||||
operand->builtin_id = BuiltinProc_DIRECTIVE;
|
||||
@@ -5475,6 +5577,15 @@ ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *pr
|
||||
if (operand->mode == Addressing_Type) {
|
||||
Type *t = operand->type;
|
||||
if (is_type_polymorphic_record(t)) {
|
||||
if (!is_type_named(t)) {
|
||||
gbString s = expr_to_string(operand->expr);
|
||||
error(call, "Illegal use of an unnamed polymorphic record, %s", s);
|
||||
gb_string_free(s);
|
||||
operand->mode = Addressing_Invalid;
|
||||
operand->type = t_invalid;;
|
||||
return Expr_Expr;
|
||||
}
|
||||
|
||||
auto err = check_polymorphic_record_type(c, operand, call);
|
||||
if (err == 0) {
|
||||
Ast *ident = operand->expr;
|
||||
@@ -5509,9 +5620,15 @@ ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *pr
|
||||
}
|
||||
check_expr(c, operand, arg);
|
||||
if (operand->mode != Addressing_Invalid) {
|
||||
check_cast(c, operand, t);
|
||||
if (is_type_polymorphic(t)) {
|
||||
error(call, "A polymorphic type cannot be used in a type conversion");
|
||||
} else {
|
||||
// NOTE(bill): Otherwise the compiler can override the polymorphic type
|
||||
// as it assumes it is determining the type
|
||||
check_cast(c, operand, t);
|
||||
}
|
||||
}
|
||||
|
||||
operand->type = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -5661,7 +5778,7 @@ void check_expr_with_type_hint(CheckerContext *c, Operand *o, Ast *e, Type *t) {
|
||||
break;
|
||||
case Addressing_Type:
|
||||
if (t == nullptr || !is_type_typeid(t)) {
|
||||
err_str = "is not an expression but a type";
|
||||
err_str = "is not an expression but a type, in this context it is ambiguous";
|
||||
}
|
||||
break;
|
||||
case Addressing_Builtin:
|
||||
@@ -5849,8 +5966,9 @@ bool check_range(CheckerContext *c, Ast *node, Operand *x, Operand *y, ExactValu
|
||||
|
||||
TokenKind op = Token_Lt;
|
||||
switch (ie->op.kind) {
|
||||
case Token_Ellipsis: op = Token_LtEq; break;
|
||||
case Token_RangeHalf: op = Token_Lt; break;
|
||||
case Token_Ellipsis: op = Token_LtEq; break; // ..
|
||||
case Token_RangeFull: op = Token_LtEq; break; // ..=
|
||||
case Token_RangeHalf: op = Token_Lt; break; // ..<
|
||||
default: error(ie->op, "Invalid range operator"); break;
|
||||
}
|
||||
bool ok = compare_exact_values(op, a, b);
|
||||
@@ -5861,7 +5979,7 @@ bool check_range(CheckerContext *c, Ast *node, Operand *x, Operand *y, ExactValu
|
||||
}
|
||||
|
||||
ExactValue inline_for_depth = exact_value_sub(b, a);
|
||||
if (ie->op.kind == Token_Ellipsis) {
|
||||
if (ie->op.kind != Token_RangeHalf) {
|
||||
inline_for_depth = exact_value_increment_one(inline_for_depth);
|
||||
}
|
||||
|
||||
@@ -5896,6 +6014,88 @@ bool check_is_operand_compound_lit_constant(CheckerContext *c, Operand *o) {
|
||||
}
|
||||
|
||||
|
||||
bool attempt_implicit_selector_expr(CheckerContext *c, Operand *o, AstImplicitSelectorExpr *ise, Type *th) {
|
||||
if (is_type_enum(th)) {
|
||||
Type *enum_type = base_type(th);
|
||||
GB_ASSERT(enum_type->kind == Type_Enum);
|
||||
|
||||
String name = ise->selector->Ident.token.string;
|
||||
|
||||
Entity *e = scope_lookup_current(enum_type->Enum.scope, name);
|
||||
if (e == nullptr) {
|
||||
return false;
|
||||
}
|
||||
GB_ASSERT(are_types_identical(base_type(e->type), enum_type));
|
||||
GB_ASSERT(e->kind == Entity_Constant);
|
||||
o->value = e->Constant.value;
|
||||
o->mode = Addressing_Constant;
|
||||
o->type = e->type;
|
||||
return true;
|
||||
}
|
||||
bool show_error = true;
|
||||
if (is_type_union(th)) {
|
||||
Type *union_type = base_type(th);
|
||||
isize enum_count = 0;
|
||||
Type *et = nullptr;
|
||||
|
||||
auto operands = array_make<Operand>(temporary_allocator(), 0, union_type->Union.variants.count);
|
||||
|
||||
for_array(i, union_type->Union.variants) {
|
||||
Type *vt = union_type->Union.variants[i];
|
||||
|
||||
Operand x = {};
|
||||
if (attempt_implicit_selector_expr(c, &x, ise, vt)) {
|
||||
array_add(&operands, x);
|
||||
}
|
||||
}
|
||||
|
||||
if (operands.count == 1) {
|
||||
*o = operands[0];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ExprKind check_implicit_selector_expr(CheckerContext *c, Operand *o, Ast *node, Type *type_hint) {
|
||||
ast_node(ise, ImplicitSelectorExpr, node);
|
||||
|
||||
o->type = t_invalid;
|
||||
o->expr = node;
|
||||
o->mode = Addressing_Invalid;
|
||||
|
||||
Type *th = type_hint;
|
||||
|
||||
if (th == nullptr) {
|
||||
gbString str = expr_to_string(node);
|
||||
error(node, "Cannot determine type for implicit selector expression '%s'", str);
|
||||
gb_string_free(str);
|
||||
return Expr_Expr;
|
||||
}
|
||||
o->type = th;
|
||||
Type *enum_type = th;
|
||||
|
||||
bool ok = attempt_implicit_selector_expr(c, o, ise, th);
|
||||
if (!ok) {
|
||||
String name = ise->selector->Ident.token.string;
|
||||
|
||||
if (is_type_enum(th)) {
|
||||
gbString typ = type_to_string(th);
|
||||
error(node, "Undeclared name %.*s for type '%s'", LIT(name), typ);
|
||||
gb_string_free(typ);
|
||||
} else {
|
||||
gbString typ = type_to_string(th);
|
||||
gbString str = expr_to_string(node);
|
||||
error(node, "Invalid type '%s' for implicit selector expression '%s'", typ, str);
|
||||
gb_string_free(str);
|
||||
gb_string_free(typ);
|
||||
}
|
||||
}
|
||||
|
||||
o->expr = node;
|
||||
return Expr_Expr;
|
||||
}
|
||||
|
||||
ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type *type_hint) {
|
||||
u32 prev_state_flags = c->state_flags;
|
||||
defer (c->state_flags = prev_state_flags);
|
||||
@@ -5991,13 +6191,14 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
|
||||
|
||||
case_ast_node(bd, BasicDirective, node);
|
||||
o->mode = Addressing_Constant;
|
||||
if (bd->name == "file") {
|
||||
String name = bd->name.string;
|
||||
if (name == "file") {
|
||||
o->type = t_untyped_string;
|
||||
o->value = exact_value_string(get_file_path_string(bd->token.pos.file_id));
|
||||
} else if (bd->name == "line") {
|
||||
} else if (name == "line") {
|
||||
o->type = t_untyped_integer;
|
||||
o->value = exact_value_i64(bd->token.pos.line);
|
||||
} else if (bd->name == "procedure") {
|
||||
} else if (name == "procedure") {
|
||||
if (c->curr_proc_decl == nullptr) {
|
||||
error(node, "#procedure may only be used within procedures");
|
||||
o->type = t_untyped_string;
|
||||
@@ -6006,7 +6207,7 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
|
||||
o->type = t_untyped_string;
|
||||
o->value = exact_value_string(c->proc_name);
|
||||
}
|
||||
} else if (bd->name == "caller_location") {
|
||||
} else if (name == "caller_location") {
|
||||
init_core_source_code_location(c->checker);
|
||||
error(node, "#caller_location may only be used as a default argument parameter");
|
||||
o->type = t_source_code_location;
|
||||
@@ -6061,88 +6262,6 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
|
||||
o->type = type;
|
||||
case_end;
|
||||
|
||||
case_ast_node(te, TernaryExpr, node);
|
||||
Operand cond = {Addressing_Invalid};
|
||||
check_expr(c, &cond, te->cond);
|
||||
node->viral_state_flags |= te->cond->viral_state_flags;
|
||||
|
||||
if (cond.mode != Addressing_Invalid && !is_type_boolean(cond.type)) {
|
||||
error(te->cond, "Non-boolean condition in if expression");
|
||||
}
|
||||
|
||||
Operand x = {Addressing_Invalid};
|
||||
Operand y = {Addressing_Invalid};
|
||||
check_expr_or_type(c, &x, te->x, type_hint);
|
||||
node->viral_state_flags |= te->x->viral_state_flags;
|
||||
|
||||
if (te->y != nullptr) {
|
||||
check_expr_or_type(c, &y, te->y, type_hint);
|
||||
node->viral_state_flags |= te->y->viral_state_flags;
|
||||
} else {
|
||||
error(node, "A ternary expression must have an else clause");
|
||||
return kind;
|
||||
}
|
||||
|
||||
if (x.type == nullptr || x.type == t_invalid ||
|
||||
y.type == nullptr || y.type == t_invalid) {
|
||||
return kind;
|
||||
}
|
||||
|
||||
if (x.mode == Addressing_Type && y.mode == Addressing_Type &&
|
||||
cond.mode == Addressing_Constant && is_type_boolean(cond.type)) {
|
||||
o->mode = Addressing_Type;
|
||||
if (cond.value.value_bool) {
|
||||
o->type = x.type;
|
||||
o->expr = x.expr;
|
||||
} else {
|
||||
o->type = y.type;
|
||||
o->expr = y.expr;
|
||||
}
|
||||
return Expr_Expr;
|
||||
}
|
||||
|
||||
convert_to_typed(c, &x, y.type);
|
||||
if (x.mode == Addressing_Invalid) {
|
||||
return kind;
|
||||
}
|
||||
convert_to_typed(c, &y, x.type);
|
||||
if (y.mode == Addressing_Invalid) {
|
||||
x.mode = Addressing_Invalid;
|
||||
return kind;
|
||||
}
|
||||
|
||||
if (!ternary_compare_types(x.type, y.type)) {
|
||||
gbString its = type_to_string(x.type);
|
||||
gbString ets = type_to_string(y.type);
|
||||
error(node, "Mismatched types in ternary expression, %s vs %s", its, ets);
|
||||
gb_string_free(ets);
|
||||
gb_string_free(its);
|
||||
return kind;
|
||||
}
|
||||
|
||||
Type *type = x.type;
|
||||
if (is_type_untyped_nil(type) || is_type_untyped_undef(type)) {
|
||||
type = y.type;
|
||||
}
|
||||
|
||||
o->type = type;
|
||||
o->mode = Addressing_Value;
|
||||
|
||||
if (cond.mode == Addressing_Constant && is_type_boolean(cond.type) &&
|
||||
x.mode == Addressing_Constant &&
|
||||
y.mode == Addressing_Constant) {
|
||||
|
||||
o->mode = Addressing_Constant;
|
||||
|
||||
if (cond.value.value_bool) {
|
||||
o->value = x.value;
|
||||
} else {
|
||||
o->value = y.value;
|
||||
}
|
||||
}
|
||||
|
||||
case_end;
|
||||
|
||||
case_ast_node(te, TernaryIfExpr, node);
|
||||
Operand cond = {Addressing_Invalid};
|
||||
check_expr(c, &cond, te->cond);
|
||||
@@ -6255,7 +6374,7 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
|
||||
if (cl->type->ArrayType.tag != nullptr) {
|
||||
Ast *tag = cl->type->ArrayType.tag;
|
||||
GB_ASSERT(tag->kind == Ast_BasicDirective);
|
||||
String name = tag->BasicDirective.name;
|
||||
String name = tag->BasicDirective.name.string;
|
||||
if (name == "soa") {
|
||||
error(node, "#soa arrays are not supported for compound literals");
|
||||
return kind;
|
||||
@@ -6267,7 +6386,7 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
|
||||
if (cl->elems.count > 0) {
|
||||
Ast *tag = cl->type->DynamicArrayType.tag;
|
||||
GB_ASSERT(tag->kind == Ast_BasicDirective);
|
||||
String name = tag->BasicDirective.name;
|
||||
String name = tag->BasicDirective.name.string;
|
||||
if (name == "soa") {
|
||||
error(node, "#soa arrays are not supported for compound literals");
|
||||
return kind;
|
||||
@@ -7279,10 +7398,11 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
|
||||
case_end;
|
||||
|
||||
case_ast_node(ue, UnaryExpr, node);
|
||||
Ast *prev_unary_address_hint = c->unary_address_hint;
|
||||
c->unary_address_hint = unparen_expr(node);
|
||||
check_expr_base(c, o, ue->expr, type_hint);
|
||||
c->unary_address_hint = prev_unary_address_hint;
|
||||
Type *th = type_hint;
|
||||
if (ue->op.kind == Token_And) {
|
||||
th = type_deref(th);
|
||||
}
|
||||
check_expr_base(c, o, ue->expr, th);
|
||||
node->viral_state_flags |= ue->expr->viral_state_flags;
|
||||
|
||||
if (o->mode == Addressing_Invalid) {
|
||||
@@ -7444,68 +7564,7 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
|
||||
|
||||
|
||||
case_ast_node(ise, ImplicitSelectorExpr, node);
|
||||
o->type = t_invalid;
|
||||
o->expr = node;
|
||||
o->mode = Addressing_Invalid;
|
||||
|
||||
Type *th = type_hint;
|
||||
|
||||
if (th == nullptr) {
|
||||
gbString str = expr_to_string(node);
|
||||
error(node, "Cannot determine type for implicit selector expression '%s'", str);
|
||||
gb_string_free(str);
|
||||
return Expr_Expr;
|
||||
}
|
||||
o->type = th;
|
||||
Type *enum_type = th;
|
||||
|
||||
if (!is_type_enum(th)) {
|
||||
bool show_error = true;
|
||||
if (is_type_union(th)) {
|
||||
Type *union_type = base_type(th);
|
||||
isize enum_count = 0;
|
||||
Type *et = nullptr;
|
||||
for_array(i, union_type->Union.variants) {
|
||||
Type *vt = union_type->Union.variants[i];
|
||||
if (is_type_enum(vt)) {
|
||||
enum_count += 1;
|
||||
et = vt;
|
||||
}
|
||||
}
|
||||
if (enum_count == 1) {
|
||||
show_error = false;
|
||||
enum_type = et;
|
||||
}
|
||||
}
|
||||
|
||||
if (show_error) {
|
||||
gbString typ = type_to_string(th);
|
||||
gbString str = expr_to_string(node);
|
||||
error(node, "Invalid type '%s' for implicit selector expression '%s'", typ, str);
|
||||
gb_string_free(str);
|
||||
gb_string_free(typ);
|
||||
return Expr_Expr;
|
||||
}
|
||||
}
|
||||
GB_ASSERT(ise->selector->kind == Ast_Ident);
|
||||
String name = ise->selector->Ident.token.string;
|
||||
|
||||
enum_type = base_type(enum_type);
|
||||
GB_ASSERT(enum_type->kind == Type_Enum);
|
||||
Entity *e = scope_lookup_current(enum_type->Enum.scope, name);
|
||||
if (e == nullptr) {
|
||||
gbString typ = type_to_string(th);
|
||||
error(node, "Undeclared name %.*s for type '%s'", LIT(name), typ);
|
||||
gb_string_free(typ);
|
||||
return Expr_Expr;
|
||||
}
|
||||
GB_ASSERT(are_types_identical(base_type(e->type), enum_type));
|
||||
GB_ASSERT(e->kind == Entity_Constant);
|
||||
o->value = e->Constant.value;
|
||||
o->mode = Addressing_Constant;
|
||||
o->type = e->type;
|
||||
|
||||
return Expr_Expr;
|
||||
return check_implicit_selector_expr(c, o, node, type_hint);
|
||||
case_end;
|
||||
|
||||
case_ast_node(ie, IndexExpr, node);
|
||||
@@ -7542,47 +7601,6 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
|
||||
return Expr_Expr;
|
||||
}
|
||||
|
||||
if (t->kind == Type_Struct) {
|
||||
TypeAtomOpTable *atom_op_table = t->Struct.atom_op_table;
|
||||
if (atom_op_table != nullptr) {
|
||||
if (atom_op_table->op[TypeAtomOp_index_set]) {
|
||||
if (c->assignment_lhs_hint == node) {
|
||||
o->mode = Addressing_AtomOpAssign;
|
||||
o->type = o->type;
|
||||
o->expr = node;
|
||||
return kind;
|
||||
}
|
||||
}
|
||||
if (atom_op_table->op[TypeAtomOp_index_get]) {
|
||||
Entity *e = atom_op_table->op[TypeAtomOp_index_get];
|
||||
if (ie->index == nullptr) {
|
||||
gbString str = expr_to_string(o->expr);
|
||||
error(o->expr, "Missing index for '%s'", str);
|
||||
gb_string_free(str);
|
||||
o->mode = Addressing_Invalid;
|
||||
o->expr = node;
|
||||
return kind;
|
||||
}
|
||||
|
||||
GB_ASSERT(e->identifier != nullptr);
|
||||
Ast *proc_ident = clone_ast(e->identifier);
|
||||
|
||||
auto args = array_make<Ast *>(heap_allocator(), 2);
|
||||
args[0] = ie->expr;
|
||||
args[1] = ie->index;
|
||||
|
||||
GB_ASSERT(c->file != nullptr);
|
||||
Ast *fake_call = ast_call_expr(c->file, proc_ident, args, ie->open, ie->close, {});
|
||||
check_expr_base(c, o, fake_call, type_hint);
|
||||
AtomOpMapEntry entry = {TypeAtomOp_index_get, fake_call};
|
||||
map_set(&c->info->atom_op_map, hash_pointer(node), entry);
|
||||
o->expr = node;
|
||||
return kind;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
i64 max_count = -1;
|
||||
bool valid = check_set_index_data(o, t, is_ptr, &max_count, o->type);
|
||||
|
||||
@@ -7721,37 +7739,6 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
|
||||
if (is_type_soa_struct(t)) {
|
||||
valid = true;
|
||||
o->type = make_soa_struct_slice(c, nullptr, nullptr, t->Struct.soa_elem);
|
||||
} else {
|
||||
TypeAtomOpTable *atom_op_table = t->Struct.atom_op_table;
|
||||
if (atom_op_table != nullptr && atom_op_table->op[TypeAtomOp_slice]) {
|
||||
Entity *e = atom_op_table->op[TypeAtomOp_slice];
|
||||
GB_ASSERT(e->identifier != nullptr);
|
||||
Ast *proc_ident = clone_ast(e->identifier);
|
||||
|
||||
Ast *expr = se->expr;
|
||||
if (o->mode == Addressing_Variable) {
|
||||
expr = ast_unary_expr(c->file, {Token_And, STR_LIT("&")}, expr);
|
||||
} else if (is_type_pointer(o->type)) {
|
||||
// Okay
|
||||
} else {
|
||||
gbString str = expr_to_string(node);
|
||||
error(node, "Cannot slice '%s', value is not addressable", str);
|
||||
gb_string_free(str);
|
||||
o->mode = Addressing_Invalid;
|
||||
o->expr = node;
|
||||
return kind;
|
||||
}
|
||||
auto args = array_make<Ast *>(heap_allocator(), 1);
|
||||
args[0] = expr;
|
||||
|
||||
|
||||
GB_ASSERT(c->file != nullptr);
|
||||
Ast *fake_call = ast_call_expr(c->file, proc_ident, args, se->open, se->close, {});
|
||||
check_expr_base(c, o, fake_call, type_hint);
|
||||
AtomOpMapEntry entry = {TypeAtomOp_slice, fake_call};
|
||||
map_set(&c->info->atom_op_map, hash_pointer(node), entry);
|
||||
valid = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -7780,10 +7767,7 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
|
||||
return kind;
|
||||
}
|
||||
|
||||
o->mode = Addressing_Value;
|
||||
|
||||
if (se->low == nullptr && se->high != nullptr) {
|
||||
// error(se->interval0, "1st index is required if a 2nd index is specified");
|
||||
// It is okay to continue as it will assume the 1st index is zero
|
||||
}
|
||||
|
||||
@@ -7818,6 +7802,16 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
|
||||
}
|
||||
}
|
||||
|
||||
if (max_count < 0) {
|
||||
if (o->mode == Addressing_Constant) {
|
||||
gbString s = expr_to_string(se->expr);
|
||||
error(se->expr, "Cannot slice constant value '%s'", s);
|
||||
gb_string_free(s);
|
||||
}
|
||||
}
|
||||
|
||||
o->mode = Addressing_Value;
|
||||
|
||||
if (is_type_string(t) && max_count >= 0) {
|
||||
bool all_constant = true;
|
||||
for (isize i = 0; i < gb_count_of(nodes); i++) {
|
||||
@@ -8158,7 +8152,7 @@ gbString write_expr_to_string(gbString str, Ast *node, bool shorthand) {
|
||||
|
||||
case_ast_node(bd, BasicDirective, node);
|
||||
str = gb_string_append_rune(str, '#');
|
||||
str = string_append_string(str, bd->name);
|
||||
str = string_append_string(str, bd->name.string);
|
||||
case_end;
|
||||
|
||||
case_ast_node(ud, Undef, node);
|
||||
@@ -8222,20 +8216,22 @@ gbString write_expr_to_string(gbString str, Ast *node, bool shorthand) {
|
||||
str = write_expr_to_string(str, be->right, shorthand);
|
||||
case_end;
|
||||
|
||||
case_ast_node(te, TernaryExpr, node);
|
||||
str = write_expr_to_string(str, te->cond, shorthand);
|
||||
str = gb_string_appendc(str, " ? ");
|
||||
str = write_expr_to_string(str, te->x, shorthand);
|
||||
str = gb_string_appendc(str, " : ");
|
||||
str = write_expr_to_string(str, te->y, shorthand);
|
||||
case_end;
|
||||
|
||||
case_ast_node(te, TernaryIfExpr, node);
|
||||
str = write_expr_to_string(str, te->x, shorthand);
|
||||
str = gb_string_appendc(str, " if ");
|
||||
str = write_expr_to_string(str, te->cond, shorthand);
|
||||
str = gb_string_appendc(str, " else ");
|
||||
str = write_expr_to_string(str, te->y, shorthand);
|
||||
TokenPos x = ast_token(te->x).pos;
|
||||
TokenPos cond = ast_token(te->cond).pos;
|
||||
if (x < cond) {
|
||||
str = write_expr_to_string(str, te->x, shorthand);
|
||||
str = gb_string_appendc(str, " if ");
|
||||
str = write_expr_to_string(str, te->cond, shorthand);
|
||||
str = gb_string_appendc(str, " else ");
|
||||
str = write_expr_to_string(str, te->y, shorthand);
|
||||
} else {
|
||||
str = write_expr_to_string(str, te->cond, shorthand);
|
||||
str = gb_string_appendc(str, " ? ");
|
||||
str = write_expr_to_string(str, te->x, shorthand);
|
||||
str = gb_string_appendc(str, " : ");
|
||||
str = write_expr_to_string(str, te->y, shorthand);
|
||||
}
|
||||
case_end;
|
||||
|
||||
case_ast_node(te, TernaryWhenExpr, node);
|
||||
@@ -8525,8 +8521,18 @@ gbString write_expr_to_string(gbString str, Ast *node, bool shorthand) {
|
||||
|
||||
case_ast_node(st, StructType, node);
|
||||
str = gb_string_appendc(str, "struct ");
|
||||
if (st->polymorphic_params) {
|
||||
str = gb_string_append_rune(str, '(');
|
||||
str = write_expr_to_string(str, st->polymorphic_params, shorthand);
|
||||
str = gb_string_appendc(str, ") ");
|
||||
}
|
||||
if (st->is_packed) str = gb_string_appendc(str, "#packed ");
|
||||
if (st->is_raw_union) str = gb_string_appendc(str, "#raw_union ");
|
||||
if (st->align) {
|
||||
str = gb_string_appendc(str, "#align ");
|
||||
str = write_expr_to_string(str, st->align, shorthand);
|
||||
str = gb_string_append_rune(str, ' ');
|
||||
}
|
||||
str = gb_string_append_rune(str, '{');
|
||||
if (shorthand) {
|
||||
str = gb_string_appendc(str, "...");
|
||||
@@ -8539,6 +8545,18 @@ gbString write_expr_to_string(gbString str, Ast *node, bool shorthand) {
|
||||
|
||||
case_ast_node(st, UnionType, node);
|
||||
str = gb_string_appendc(str, "union ");
|
||||
if (st->polymorphic_params) {
|
||||
str = gb_string_append_rune(str, '(');
|
||||
str = write_expr_to_string(str, st->polymorphic_params, shorthand);
|
||||
str = gb_string_appendc(str, ") ");
|
||||
}
|
||||
if (st->no_nil) str = gb_string_appendc(str, "#no_nil ");
|
||||
if (st->maybe) str = gb_string_appendc(str, "#maybe ");
|
||||
if (st->align) {
|
||||
str = gb_string_appendc(str, "#align ");
|
||||
str = write_expr_to_string(str, st->align, shorthand);
|
||||
str = gb_string_append_rune(str, ' ');
|
||||
}
|
||||
str = gb_string_append_rune(str, '{');
|
||||
if (shorthand) {
|
||||
str = gb_string_appendc(str, "...");
|
||||
|
||||
+70
-55
@@ -7,7 +7,7 @@ bool is_diverging_stmt(Ast *stmt) {
|
||||
return false;
|
||||
}
|
||||
if (expr->CallExpr.proc->kind == Ast_BasicDirective) {
|
||||
String name = expr->CallExpr.proc->BasicDirective.name;
|
||||
String name = expr->CallExpr.proc->BasicDirective.name.string;
|
||||
return name == "panic";
|
||||
}
|
||||
Ast *proc = unparen_expr(expr->CallExpr.proc);
|
||||
@@ -939,6 +939,7 @@ void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {
|
||||
TokenKind upper_op = Token_Invalid;
|
||||
switch (be->op.kind) {
|
||||
case Token_Ellipsis: upper_op = Token_GtEq; break;
|
||||
case Token_RangeFull: upper_op = Token_GtEq; break;
|
||||
case Token_RangeHalf: upper_op = Token_Gt; break;
|
||||
default: GB_PANIC("Invalid range operator"); break;
|
||||
}
|
||||
@@ -960,9 +961,44 @@ void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {
|
||||
Operand b1 = rhs;
|
||||
check_comparison(ctx, &a1, &b1, Token_LtEq);
|
||||
|
||||
add_constant_switch_case(ctx, &seen, lhs);
|
||||
if (upper_op == Token_GtEq) {
|
||||
add_constant_switch_case(ctx, &seen, rhs);
|
||||
if (is_type_enum(x.type)) {
|
||||
// TODO(bill): Fix this logic so it's fast!!!
|
||||
|
||||
i64 v0 = exact_value_to_i64(lhs.value);
|
||||
i64 v1 = exact_value_to_i64(rhs.value);
|
||||
Operand v = {};
|
||||
v.mode = Addressing_Constant;
|
||||
v.type = x.type;
|
||||
v.expr = x.expr;
|
||||
|
||||
Type *bt = base_type(x.type);
|
||||
GB_ASSERT(bt->kind == Type_Enum);
|
||||
for (i64 vi = v0; vi <= v1; vi++) {
|
||||
if (upper_op != Token_GtEq && vi == v1) {
|
||||
break;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
for_array(j, bt->Enum.fields) {
|
||||
Entity *f = bt->Enum.fields[j];
|
||||
GB_ASSERT(f->kind == Entity_Constant);
|
||||
|
||||
i64 fv = exact_value_to_i64(f->Constant.value);
|
||||
if (fv == vi) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
v.value = exact_value_i64(vi);
|
||||
add_constant_switch_case(ctx, &seen, v);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
add_constant_switch_case(ctx, &seen, lhs);
|
||||
if (upper_op == Token_GtEq) {
|
||||
add_constant_switch_case(ctx, &seen, rhs);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_type_string(x.type)) {
|
||||
@@ -1400,6 +1436,28 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
|
||||
gbString expr_str = expr_to_string(operand.expr);
|
||||
error(node, "Expression is not used: '%s'", expr_str);
|
||||
gb_string_free(expr_str);
|
||||
if (operand.expr->kind == Ast_BinaryExpr) {
|
||||
ast_node(be, BinaryExpr, operand.expr);
|
||||
if (be->op.kind != Token_CmpEq) {
|
||||
break;
|
||||
}
|
||||
|
||||
switch (be->left->tav.mode) {
|
||||
case Addressing_Context:
|
||||
case Addressing_Variable:
|
||||
case Addressing_MapIndex:
|
||||
case Addressing_SoaVariable:
|
||||
{
|
||||
gbString lhs = expr_to_string(be->left);
|
||||
gbString rhs = expr_to_string(be->right);
|
||||
error_line("\tSuggestion: Did you mean to do an assignment?\n", lhs, rhs);
|
||||
error_line("\t '%s = %s;'\n", lhs, rhs);
|
||||
gb_string_free(rhs);
|
||||
gb_string_free(lhs);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -1454,53 +1512,6 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
|
||||
auto lhs_to_ignore = array_make<bool>(temporary_allocator(), lhs_count);
|
||||
|
||||
isize max = gb_min(lhs_count, rhs_count);
|
||||
// NOTE(bill, 2020-05-02): This is an utter hack to get these custom atom operations working
|
||||
// correctly for assignments
|
||||
for (isize i = 0; i < max; i++) {
|
||||
if (lhs_operands[i].mode == Addressing_AtomOpAssign) {
|
||||
Operand lhs = lhs_operands[i];
|
||||
|
||||
Type *t = base_type(lhs.type);
|
||||
GB_ASSERT(t->kind == Type_Struct);
|
||||
ast_node(ie, IndexExpr, unparen_expr(lhs.expr));
|
||||
|
||||
TypeAtomOpTable *atom_op_table = t->Struct.atom_op_table;
|
||||
GB_ASSERT(atom_op_table->op[TypeAtomOp_index_set] != nullptr);
|
||||
Entity *e = atom_op_table->op[TypeAtomOp_index_set];
|
||||
|
||||
GB_ASSERT(e->identifier != nullptr);
|
||||
Ast *proc_ident = clone_ast(e->identifier);
|
||||
GB_ASSERT(ctx->file != nullptr);
|
||||
|
||||
|
||||
TypeAndValue tv = type_and_value_of_expr(ie->expr);
|
||||
Ast *expr = ie->expr;
|
||||
if (is_type_pointer(tv.type)) {
|
||||
// Okay
|
||||
} else if (tv.mode == Addressing_Variable) {
|
||||
// NOTE(bill): Hack it to take the address instead
|
||||
expr = ast_unary_expr(ctx->file, {Token_And, STR_LIT("&")}, ie->expr);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto args = array_make<Ast *>(heap_allocator(), 3);
|
||||
args[0] = expr;
|
||||
args[1] = ie->index;
|
||||
args[2] = rhs_operands[i].expr;
|
||||
|
||||
Ast *fake_call = ast_call_expr(ctx->file, proc_ident, args, ie->open, ie->close, {});
|
||||
Operand fake_operand = {};
|
||||
fake_operand.expr = lhs.expr;
|
||||
check_expr_base(ctx, &fake_operand, fake_call, nullptr);
|
||||
AtomOpMapEntry entry = {TypeAtomOp_index_set, fake_call};
|
||||
map_set(&ctx->info->atom_op_map, hash_pointer(lhs.expr), entry);
|
||||
|
||||
lhs_to_ignore[i] = true;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
for (isize i = 0; i < max; i++) {
|
||||
if (lhs_to_ignore[i]) {
|
||||
continue;
|
||||
@@ -1526,8 +1537,8 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
|
||||
}
|
||||
Operand lhs = {Addressing_Invalid};
|
||||
Operand rhs = {Addressing_Invalid};
|
||||
Ast binary_expr = {Ast_BinaryExpr};
|
||||
ast_node(be, BinaryExpr, &binary_expr);
|
||||
Ast *binary_expr = alloc_ast_node(node->file, Ast_BinaryExpr);
|
||||
ast_node(be, BinaryExpr, binary_expr);
|
||||
be->op = op;
|
||||
be->op.kind = cast(TokenKind)(cast(i32)be->op.kind - (Token_AddEq - Token_Add));
|
||||
// NOTE(bill): Only use the first one will be used
|
||||
@@ -1535,7 +1546,7 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
|
||||
be->right = as->rhs[0];
|
||||
|
||||
check_expr(ctx, &lhs, as->lhs[0]);
|
||||
check_binary_expr(ctx, &rhs, &binary_expr, nullptr, true);
|
||||
check_binary_expr(ctx, &rhs, binary_expr, nullptr, true);
|
||||
if (rhs.mode == Addressing_Invalid) {
|
||||
return;
|
||||
}
|
||||
@@ -1632,7 +1643,11 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
|
||||
} else {
|
||||
for (isize i = 0; i < result_count; i++) {
|
||||
Entity *e = pt->results->Tuple.variables[i];
|
||||
check_assignment(ctx, &operands[i], e->type, str_lit("return statement"));
|
||||
Operand *o = &operands[i];
|
||||
check_assignment(ctx, o, e->type, str_lit("return statement"));
|
||||
if (is_type_untyped(o->type)) {
|
||||
update_expr_type(ctx, o->expr, e->type, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
case_end;
|
||||
|
||||
+51
-53
@@ -322,19 +322,6 @@ void add_polymorphic_record_entity(CheckerContext *ctx, Ast *node, Type *named_t
|
||||
array_add(&array, e);
|
||||
map_set(&ctx->checker->info.gen_types, hash_pointer(original_type), array);
|
||||
}
|
||||
|
||||
{
|
||||
Type *dst_bt = base_type(named_type);
|
||||
Type *src_bt = base_type(original_type);
|
||||
if ((dst_bt != nullptr && src_bt != nullptr) &&
|
||||
(dst_bt->kind == src_bt->kind)){
|
||||
if (dst_bt->kind == Type_Struct) {
|
||||
if (dst_bt->Struct.atom_op_table == nullptr) {
|
||||
dst_bt->Struct.atom_op_table = src_bt->Struct.atom_op_table;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Type *check_record_polymorphic_params(CheckerContext *ctx, Ast *polymorphic_params,
|
||||
@@ -944,6 +931,7 @@ void check_bit_set_type(CheckerContext *c, Type *type, Type *named_type, Ast *no
|
||||
|
||||
switch (be->op.kind) {
|
||||
case Token_Ellipsis:
|
||||
case Token_RangeFull:
|
||||
if (upper - lower >= bits) {
|
||||
error(bs->elem, "bit_set range is greater than %lld bits, %lld bits are required", bits, (upper-lower+1));
|
||||
}
|
||||
@@ -1203,10 +1191,15 @@ ParameterValue handle_parameter_value(CheckerContext *ctx, Type *in_type, Type *
|
||||
|
||||
if (allow_caller_location &&
|
||||
expr->kind == Ast_BasicDirective &&
|
||||
expr->BasicDirective.name == "caller_location") {
|
||||
expr->BasicDirective.name.string == "caller_location") {
|
||||
init_core_source_code_location(ctx->checker);
|
||||
param_value.kind = ParameterValue_Location;
|
||||
o.type = t_source_code_location;
|
||||
|
||||
if (in_type) {
|
||||
check_assignment(ctx, &o, in_type, str_lit("parameter value"));
|
||||
}
|
||||
|
||||
} else {
|
||||
if (in_type) {
|
||||
check_expr_with_type_hint(ctx, &o, expr, in_type);
|
||||
@@ -1214,6 +1207,11 @@ ParameterValue handle_parameter_value(CheckerContext *ctx, Type *in_type, Type *
|
||||
check_expr(ctx, &o, expr);
|
||||
}
|
||||
|
||||
if (in_type) {
|
||||
check_assignment(ctx, &o, in_type, str_lit("parameter value"));
|
||||
}
|
||||
|
||||
|
||||
if (is_operand_nil(o)) {
|
||||
param_value.kind = ParameterValue_Nil;
|
||||
} else if (o.mode != Addressing_Constant) {
|
||||
@@ -1221,16 +1219,7 @@ ParameterValue handle_parameter_value(CheckerContext *ctx, Type *in_type, Type *
|
||||
param_value.kind = ParameterValue_Constant;
|
||||
param_value.value = exact_value_procedure(expr);
|
||||
} else {
|
||||
Entity *e = nullptr;
|
||||
// if (o.mode == Addressing_Value && is_type_proc(o.type)) {
|
||||
if (o.mode == Addressing_Value || o.mode == Addressing_Variable) {
|
||||
Operand x = {};
|
||||
if (expr->kind == Ast_Ident) {
|
||||
e = check_ident(ctx, &x, expr, nullptr, nullptr, false);
|
||||
} else if (expr->kind == Ast_SelectorExpr) {
|
||||
e = check_selector(ctx, &x, expr, nullptr);
|
||||
}
|
||||
}
|
||||
Entity *e = entity_from_expr(o.expr);
|
||||
|
||||
if (e != nullptr) {
|
||||
if (e->kind == Entity_Procedure) {
|
||||
@@ -1253,8 +1242,11 @@ ParameterValue handle_parameter_value(CheckerContext *ctx, Type *in_type, Type *
|
||||
} else if (allow_caller_location && o.mode == Addressing_Context) {
|
||||
param_value.kind = ParameterValue_Value;
|
||||
param_value.ast_value = expr;
|
||||
} else if (o.value.kind != ExactValue_Invalid) {
|
||||
param_value.kind = ParameterValue_Constant;
|
||||
param_value.value = o.value;
|
||||
} else {
|
||||
error(expr, "Default parameter must be a constant");
|
||||
error(expr, "Default parameter must be a constant, %d", o.mode);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1267,12 +1259,14 @@ ParameterValue handle_parameter_value(CheckerContext *ctx, Type *in_type, Type *
|
||||
}
|
||||
}
|
||||
|
||||
if (in_type) {
|
||||
check_assignment(ctx, &o, in_type, str_lit("parameter value"));
|
||||
if (out_type_) {
|
||||
if (in_type != nullptr) {
|
||||
*out_type_ = in_type;
|
||||
} else {
|
||||
*out_type_ = default_type(o.type);
|
||||
}
|
||||
}
|
||||
|
||||
if (out_type_) *out_type_ = default_type(o.type);
|
||||
|
||||
return param_value;
|
||||
}
|
||||
|
||||
@@ -1389,6 +1383,9 @@ Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_params, bool *is
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (type == nullptr) {
|
||||
error(param, "Invalid parameter type");
|
||||
type = t_invalid;
|
||||
@@ -1408,6 +1405,21 @@ Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_params, bool *is
|
||||
type = t_invalid;
|
||||
}
|
||||
|
||||
if (is_type_polymorphic(type)) {
|
||||
switch (param_value.kind) {
|
||||
case ParameterValue_Invalid:
|
||||
case ParameterValue_Constant:
|
||||
case ParameterValue_Nil:
|
||||
break;
|
||||
case ParameterValue_Location:
|
||||
case ParameterValue_Value:
|
||||
gbString str = type_to_string(type);
|
||||
error(params[i], "A default value for a parameter must not be a polymorphic constant type, got %s", str);
|
||||
gb_string_free(str);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (p->flags&FieldFlag_c_vararg) {
|
||||
if (p->type == nullptr ||
|
||||
@@ -2109,6 +2121,12 @@ void add_map_key_type_dependencies(CheckerContext *ctx, Type *key) {
|
||||
Entity *field = key->Struct.fields[i];
|
||||
add_map_key_type_dependencies(ctx, field->type);
|
||||
}
|
||||
} else if (key->kind == Type_Union) {
|
||||
add_package_dependency(ctx, "runtime", "default_hasher_n");
|
||||
for_array(i, key->Union.variants) {
|
||||
Type *v = key->Union.variants[i];
|
||||
add_map_key_type_dependencies(ctx, v);
|
||||
}
|
||||
} else if (key->kind == Type_EnumeratedArray) {
|
||||
add_package_dependency(ctx, "runtime", "default_hasher_n");
|
||||
add_map_key_type_dependencies(ctx, key->EnumeratedArray.elem);
|
||||
@@ -2511,16 +2529,6 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// if (ctx->type_level == 0 && entity->state == EntityState_InProgress) {
|
||||
// error(entity->token, "Illegal declaration cycle of `%.*s`", LIT(entity->token.string));
|
||||
// for_array(j, *ctx->type_path) {
|
||||
// Entity *k = (*ctx->type_path)[j];
|
||||
// error(k->token, "\t%.*s refers to", LIT(k->token.string));
|
||||
// }
|
||||
// error(entity->token, "\t%.*s", LIT(entity->token.string));
|
||||
// *type = t_invalid;
|
||||
// }
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2703,7 +2711,7 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t
|
||||
bool is_partial = false;
|
||||
if (at->tag != nullptr) {
|
||||
GB_ASSERT(at->tag->kind == Ast_BasicDirective);
|
||||
String name = at->tag->BasicDirective.name;
|
||||
String name = at->tag->BasicDirective.name.string;
|
||||
if (name == "partial") {
|
||||
is_partial = true;
|
||||
} else {
|
||||
@@ -2737,7 +2745,7 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t
|
||||
|
||||
if (at->tag != nullptr) {
|
||||
GB_ASSERT(at->tag->kind == Ast_BasicDirective);
|
||||
String name = at->tag->BasicDirective.name;
|
||||
String name = at->tag->BasicDirective.name.string;
|
||||
if (name == "soa") {
|
||||
*type = make_soa_struct_fixed(ctx, e, at->elem, elem, count, generic_type);
|
||||
} else if (name == "simd") {
|
||||
@@ -2762,7 +2770,7 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t
|
||||
|
||||
if (at->tag != nullptr) {
|
||||
GB_ASSERT(at->tag->kind == Ast_BasicDirective);
|
||||
String name = at->tag->BasicDirective.name;
|
||||
String name = at->tag->BasicDirective.name.string;
|
||||
if (name == "soa") {
|
||||
*type = make_soa_struct_slice(ctx, e, at->elem, elem);
|
||||
} else {
|
||||
@@ -2782,7 +2790,7 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t
|
||||
Type *elem = check_type(ctx, dat->elem);
|
||||
if (dat->tag != nullptr) {
|
||||
GB_ASSERT(dat->tag->kind == Ast_BasicDirective);
|
||||
String name = dat->tag->BasicDirective.name;
|
||||
String name = dat->tag->BasicDirective.name.string;
|
||||
if (name == "soa") {
|
||||
*type = make_soa_struct_dynamic_array(ctx, e, dat->elem, elem);
|
||||
} else {
|
||||
@@ -2883,16 +2891,6 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t
|
||||
}
|
||||
case_end;
|
||||
|
||||
case_ast_node(te, TernaryExpr, e);
|
||||
Operand o = {};
|
||||
check_expr_or_type(ctx, &o, e);
|
||||
if (o.mode == Addressing_Type) {
|
||||
*type = o.type;
|
||||
set_base_type(named_type, *type);
|
||||
return true;
|
||||
}
|
||||
case_end;
|
||||
|
||||
case_ast_node(te, TernaryIfExpr, e);
|
||||
Operand o = {};
|
||||
check_expr_or_type(ctx, &o, e);
|
||||
|
||||
+75
-249
@@ -675,55 +675,44 @@ Entity *add_global_entity(Entity *entity, Scope *scope=builtin_pkg->scope) {
|
||||
return entity;
|
||||
}
|
||||
|
||||
void add_global_constant(String name, Type *type, ExactValue value) {
|
||||
void add_global_constant(char const *name, Type *type, ExactValue value) {
|
||||
Entity *entity = alloc_entity(Entity_Constant, nullptr, make_token_ident(name), type);
|
||||
entity->Constant.value = value;
|
||||
add_global_entity(entity);
|
||||
}
|
||||
|
||||
|
||||
void add_global_string_constant(String name, String value) {
|
||||
void add_global_string_constant(char const *name, String const &value) {
|
||||
add_global_constant(name, t_untyped_string, exact_value_string(value));
|
||||
}
|
||||
|
||||
void add_global_bool_constant(char const *name, bool value) {
|
||||
add_global_constant(name, t_untyped_bool, exact_value_bool(value));
|
||||
}
|
||||
|
||||
void add_global_type_entity(String name, Type *type) {
|
||||
add_global_entity(alloc_entity_type_name(nullptr, make_token_ident(name), type));
|
||||
}
|
||||
|
||||
|
||||
AstPackage *create_builtin_package(char const *name) {
|
||||
gbAllocator a = permanent_allocator();
|
||||
AstPackage *pkg = gb_alloc_item(a, AstPackage);
|
||||
pkg->name = make_string_c(name);
|
||||
pkg->kind = Package_Normal;
|
||||
|
||||
pkg->scope = create_scope(nullptr);
|
||||
pkg->scope->flags |= ScopeFlag_Pkg | ScopeFlag_Global | ScopeFlag_Builtin;
|
||||
pkg->scope->pkg = pkg;
|
||||
return pkg;
|
||||
}
|
||||
|
||||
void init_universal(void) {
|
||||
BuildContext *bc = &build_context;
|
||||
|
||||
// NOTE(bill): No need to free these
|
||||
// gbAllocator a = heap_allocator();
|
||||
gbAllocator a = permanent_allocator();
|
||||
|
||||
builtin_pkg = gb_alloc_item(a, AstPackage);
|
||||
builtin_pkg->name = str_lit("builtin");
|
||||
builtin_pkg->kind = Package_Normal;
|
||||
|
||||
builtin_pkg->scope = create_scope(nullptr);
|
||||
builtin_pkg->scope->flags |= ScopeFlag_Pkg | ScopeFlag_Global | ScopeFlag_Builtin;
|
||||
builtin_pkg->scope->pkg = builtin_pkg;
|
||||
|
||||
intrinsics_pkg = gb_alloc_item(a, AstPackage);
|
||||
intrinsics_pkg->name = str_lit("intrinsics");
|
||||
intrinsics_pkg->kind = Package_Normal;
|
||||
|
||||
intrinsics_pkg->scope = create_scope(nullptr);
|
||||
intrinsics_pkg->scope->flags |= ScopeFlag_Pkg | ScopeFlag_Global | ScopeFlag_Builtin;
|
||||
intrinsics_pkg->scope->pkg = intrinsics_pkg;
|
||||
|
||||
config_pkg = gb_alloc_item(a, AstPackage);
|
||||
config_pkg->name = str_lit("config");
|
||||
config_pkg->kind = Package_Normal;
|
||||
|
||||
config_pkg->scope = create_scope(nullptr);
|
||||
config_pkg->scope->flags |= ScopeFlag_Pkg | ScopeFlag_Global | ScopeFlag_Builtin;
|
||||
config_pkg->scope->pkg = config_pkg;
|
||||
|
||||
builtin_pkg = create_builtin_package("builtin");
|
||||
intrinsics_pkg = create_builtin_package("intrinsics");
|
||||
config_pkg = create_builtin_package("config");
|
||||
|
||||
// Types
|
||||
for (isize i = 0; i < gb_count_of(basic_types); i++) {
|
||||
@@ -740,23 +729,23 @@ void init_universal(void) {
|
||||
}
|
||||
|
||||
// Constants
|
||||
add_global_constant(str_lit("true"), t_untyped_bool, exact_value_bool(true));
|
||||
add_global_constant(str_lit("false"), t_untyped_bool, exact_value_bool(false));
|
||||
|
||||
add_global_entity(alloc_entity_nil(str_lit("nil"), t_untyped_nil));
|
||||
|
||||
add_global_bool_constant("true", true);
|
||||
add_global_bool_constant("false", false);
|
||||
|
||||
// TODO(bill): Set through flags in the compiler
|
||||
add_global_string_constant(str_lit("ODIN_OS"), bc->ODIN_OS);
|
||||
add_global_string_constant(str_lit("ODIN_ARCH"), bc->ODIN_ARCH);
|
||||
add_global_string_constant(str_lit("ODIN_ENDIAN"), bc->ODIN_ENDIAN);
|
||||
add_global_string_constant(str_lit("ODIN_VENDOR"), bc->ODIN_VENDOR);
|
||||
add_global_string_constant(str_lit("ODIN_VERSION"), bc->ODIN_VERSION);
|
||||
add_global_string_constant(str_lit("ODIN_ROOT"), bc->ODIN_ROOT);
|
||||
add_global_constant(str_lit("ODIN_DEBUG"), t_untyped_bool, exact_value_bool(bc->ODIN_DEBUG));
|
||||
add_global_constant(str_lit("ODIN_DISABLE_ASSERT"), t_untyped_bool, exact_value_bool(bc->ODIN_DISABLE_ASSERT));
|
||||
add_global_constant(str_lit("ODIN_DEFAULT_TO_NIL_ALLOCATOR"), t_untyped_bool, exact_value_bool(bc->ODIN_DEFAULT_TO_NIL_ALLOCATOR));
|
||||
add_global_constant(str_lit("ODIN_NO_DYNAMIC_LITERALS"), t_untyped_bool, exact_value_bool(bc->no_dynamic_literals));
|
||||
add_global_constant(str_lit("ODIN_TEST"), t_untyped_bool, exact_value_bool(bc->command_kind == Command_test));
|
||||
add_global_string_constant("ODIN_OS", bc->ODIN_OS);
|
||||
add_global_string_constant("ODIN_ARCH", bc->ODIN_ARCH);
|
||||
add_global_string_constant("ODIN_ENDIAN", bc->ODIN_ENDIAN);
|
||||
add_global_string_constant("ODIN_VENDOR", bc->ODIN_VENDOR);
|
||||
add_global_string_constant("ODIN_VERSION", bc->ODIN_VERSION);
|
||||
add_global_string_constant("ODIN_ROOT", bc->ODIN_ROOT);
|
||||
add_global_bool_constant("ODIN_DEBUG", bc->ODIN_DEBUG);
|
||||
add_global_bool_constant("ODIN_DISABLE_ASSERT", bc->ODIN_DISABLE_ASSERT);
|
||||
add_global_bool_constant("ODIN_DEFAULT_TO_NIL_ALLOCATOR", bc->ODIN_DEFAULT_TO_NIL_ALLOCATOR);
|
||||
add_global_bool_constant("ODIN_NO_DYNAMIC_LITERALS", bc->no_dynamic_literals);
|
||||
add_global_bool_constant("ODIN_TEST", bc->command_kind == Command_test);
|
||||
|
||||
|
||||
// Builtin Procedures
|
||||
@@ -801,8 +790,6 @@ void init_universal(void) {
|
||||
}
|
||||
GB_ASSERT(type != nullptr);
|
||||
|
||||
|
||||
|
||||
Entity *entity = alloc_entity_constant(nullptr, make_token_ident(name), type, value);
|
||||
entity->state = EntityState_Resolved;
|
||||
if (scope_insert(config_pkg->scope, entity)) {
|
||||
@@ -960,7 +947,11 @@ Entity *entity_of_node(Ast *expr) {
|
||||
expr = unparen_expr(expr);
|
||||
switch (expr->kind) {
|
||||
case_ast_node(ident, Ident, expr);
|
||||
return ident->entity;
|
||||
Entity *e = ident->entity;
|
||||
if (e && e->flags & EntityFlag_Overridden) {
|
||||
// GB_PANIC("use of an overriden entity: %.*s", LIT(e->token.string));
|
||||
}
|
||||
return e;
|
||||
case_end;
|
||||
case_ast_node(se, SelectorExpr, expr);
|
||||
Ast *s = unselector_expr(se->selector);
|
||||
@@ -973,7 +964,6 @@ Entity *entity_of_node(Ast *expr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
DeclInfo *decl_info_of_entity(Entity *e) {
|
||||
if (e != nullptr) {
|
||||
return e->decl_info;
|
||||
@@ -1870,7 +1860,8 @@ void generate_minimum_dependency_set(Checker *c, Entity *start) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (e->file == nullptr || !e->file->is_test) {
|
||||
// if (e->file == nullptr || !e->file->is_test) {
|
||||
if (e->file == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2675,206 +2666,6 @@ DECL_ATTRIBUTE_PROC(type_decl_attribute) {
|
||||
if (name == "private") {
|
||||
// NOTE(bill): Handled elsewhere `check_collect_value_decl`
|
||||
return true;
|
||||
} else if (name == "index_get") {
|
||||
if (value != nullptr) {
|
||||
Operand o = {};
|
||||
check_expr_or_type(c, &o, value);
|
||||
Entity *e = entity_of_node(value);
|
||||
if (e != nullptr && e->kind == Entity_Procedure) {
|
||||
if (ac->deferred_procedure.entity != nullptr) {
|
||||
error(elem, "Previous usage of the '%.*s' attribute", LIT(name));
|
||||
}
|
||||
|
||||
bool valid = true;
|
||||
|
||||
{
|
||||
Type *pt = base_type(e->type);
|
||||
GB_ASSERT(pt->kind == Type_Proc);
|
||||
|
||||
if (pt->Proc.result_count == 0) {
|
||||
error(value, "'%s' attribute must return something", LIT(name));
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (pt->Proc.param_count < 2) {
|
||||
error(value, "'%s' attribute must allow for 2 parameters", LIT(name));
|
||||
valid = false;
|
||||
} else {
|
||||
isize minimum_param_count = 0;
|
||||
for_array(i, pt->Proc.params->Tuple.variables) {
|
||||
Entity *param = pt->Proc.params->Tuple.variables[i];
|
||||
if (param->kind == Entity_Variable) {
|
||||
if (param->Variable.param_value.kind == ParameterValue_Invalid) {
|
||||
minimum_param_count += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else if (param->kind == Entity_Constant) {
|
||||
minimum_param_count += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (minimum_param_count > 2) {
|
||||
error(value, "'%s' attribute must allow for at a minimum 2 parameters", LIT(name));
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
if (ac->atom_op_table == nullptr) {
|
||||
ac->atom_op_table = gb_alloc_item(permanent_allocator(), TypeAtomOpTable);
|
||||
}
|
||||
ac->atom_op_table->op[TypeAtomOp_index_get] = e;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
error(elem, "Expected a procedure entity for '%.*s'", LIT(name));
|
||||
return false;
|
||||
} else if (name == "index_set") {
|
||||
if (value != nullptr) {
|
||||
Operand o = {};
|
||||
check_expr_or_type(c, &o, value);
|
||||
Entity *e = entity_of_node(value);
|
||||
if (e != nullptr && e->kind == Entity_Procedure) {
|
||||
if (ac->deferred_procedure.entity != nullptr) {
|
||||
error(elem, "Previous usage of the '%.*s' attribute", LIT(name));
|
||||
}
|
||||
|
||||
bool valid = true;
|
||||
|
||||
{
|
||||
Type *pt = base_type(e->type);
|
||||
GB_ASSERT(pt->kind == Type_Proc);
|
||||
|
||||
if (pt->Proc.param_count < 3) {
|
||||
error(value, "'%s' attribute must allow for 3 parameters", LIT(name));
|
||||
valid = false;
|
||||
} else {
|
||||
isize minimum_param_count = 0;
|
||||
for_array(i, pt->Proc.params->Tuple.variables) {
|
||||
Entity *param = pt->Proc.params->Tuple.variables[i];
|
||||
if (param->kind == Entity_Variable) {
|
||||
if (param->Variable.param_value.kind == ParameterValue_Invalid) {
|
||||
minimum_param_count += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else if (param->kind == Entity_Constant) {
|
||||
minimum_param_count += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (minimum_param_count > 3) {
|
||||
error(value, "'%s' attribute must allow for at a minimum 3 parameters", LIT(name));
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (pt->Proc.variadic || pt->Proc.c_vararg) {
|
||||
error(value, "'%s' attribute does not allow variadic procedures", LIT(name));
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
if (ac->atom_op_table == nullptr) {
|
||||
ac->atom_op_table = gb_alloc_item(permanent_allocator(), TypeAtomOpTable);
|
||||
}
|
||||
ac->atom_op_table->op[TypeAtomOp_index_set] = e;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
error(elem, "Expected a procedure entity for '%.*s'", LIT(name));
|
||||
return false;
|
||||
} else if (name == "slice") {
|
||||
if (value != nullptr) {
|
||||
Operand o = {};
|
||||
check_expr_or_type(c, &o, value);
|
||||
Entity *e = entity_of_node(value);
|
||||
if (e != nullptr && e->kind == Entity_Procedure) {
|
||||
if (ac->deferred_procedure.entity != nullptr) {
|
||||
error(elem, "Previous usage of the '%.*s' attribute", LIT(name));
|
||||
}
|
||||
|
||||
bool valid = true;
|
||||
|
||||
{
|
||||
Type *pt = base_type(e->type);
|
||||
GB_ASSERT(pt->kind == Type_Proc);
|
||||
|
||||
if (pt->Proc.param_count < 1) {
|
||||
error(value, "'%s' attribute must allow for 1 parameter", LIT(name));
|
||||
valid = false;
|
||||
} else {
|
||||
isize minimum_param_count = 0;
|
||||
for_array(i, pt->Proc.params->Tuple.variables) {
|
||||
Entity *param = pt->Proc.params->Tuple.variables[i];
|
||||
if (param->kind == Entity_Variable) {
|
||||
if (param->Variable.param_value.kind == ParameterValue_Invalid) {
|
||||
minimum_param_count += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else if (param->kind == Entity_Constant) {
|
||||
minimum_param_count += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (minimum_param_count > 1) {
|
||||
error(value, "'%s' attribute must allow for at a minimum 1 parameter", LIT(name));
|
||||
valid = false;
|
||||
}
|
||||
{
|
||||
Entity *param = pt->Proc.params->Tuple.variables[0];
|
||||
Type *param_type = base_type(param->type);
|
||||
if (is_type_pointer(param_type) && !is_type_rawptr(param_type)) {
|
||||
// okay
|
||||
} else {
|
||||
error(value, "'%s' attribute's first parameter must be a pointer", LIT(name));
|
||||
valid = false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (pt->Proc.variadic || pt->Proc.c_vararg) {
|
||||
error(value, "'%s' attribute does not allow variadic procedures", LIT(name));
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (pt->Proc.result_count != 1) {
|
||||
error(value, "'%s' attribute must return 1 result", LIT(name));
|
||||
valid = false;
|
||||
} else {
|
||||
Type *rt = pt->Proc.results->Tuple.variables[0]->type;
|
||||
rt = base_type(rt);
|
||||
if (!is_type_slice(rt)) {
|
||||
error(value, "'%s' attribute must return a slice", LIT(name));
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
if (ac->atom_op_table == nullptr) {
|
||||
ac->atom_op_table = gb_alloc_item(permanent_allocator(), TypeAtomOpTable);
|
||||
}
|
||||
ac->atom_op_table->op[TypeAtomOp_slice] = e;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
error(elem, "Expected a procedure entity for '%.*s'", LIT(name));
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -4498,6 +4289,38 @@ void check_unchecked_bodies(Checker *c) {
|
||||
}
|
||||
}
|
||||
|
||||
void check_test_names(Checker *c) {
|
||||
if (build_context.test_names.entries.count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
AstPackage *pkg = c->info.init_package;
|
||||
Scope *s = pkg->scope;
|
||||
|
||||
for_array(i, build_context.test_names.entries) {
|
||||
String name = build_context.test_names.entries[i].value;
|
||||
Entity *e = scope_lookup(s, name);
|
||||
if (e == nullptr) {
|
||||
Token tok = {};
|
||||
if (pkg->files.count != 0) {
|
||||
tok = pkg->files[0]->tokens[0];
|
||||
}
|
||||
error(tok, "Unable to find the test '%.*s' in 'package %.*s' ", LIT(name), LIT(pkg->name));
|
||||
}
|
||||
}
|
||||
|
||||
for (isize i = 0; i < c->info.testing_procedures.count; /**/) {
|
||||
Entity *e = c->info.testing_procedures[i];
|
||||
String name = e->token.string;
|
||||
if (!string_set_exists(&build_context.test_names, name)) {
|
||||
array_ordered_remove(&c->info.testing_procedures, i);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void check_parsed_files(Checker *c) {
|
||||
#define TIME_SECTION(str) do { if (build_context.show_more_timings) timings_start_section(&global_timings, str_lit(str)); } while (0)
|
||||
|
||||
@@ -4572,6 +4395,9 @@ void check_parsed_files(Checker *c) {
|
||||
TIME_SECTION("generate minimum dependency set");
|
||||
generate_minimum_dependency_set(c, c->info.entry_point);
|
||||
|
||||
TIME_SECTION("check test names");
|
||||
check_test_names(c);
|
||||
|
||||
TIME_SECTION("calculate global init order");
|
||||
// Calculate initialization order of global variables
|
||||
calculate_global_init_order(c);
|
||||
|
||||
@@ -114,7 +114,6 @@ struct AttributeContext {
|
||||
String deprecated_message;
|
||||
DeferredProcedure deferred_procedure;
|
||||
u32 optimization_mode; // ProcedureOptimizationMode
|
||||
struct TypeAtomOpTable *atom_op_table;
|
||||
};
|
||||
|
||||
AttributeContext make_attribute_context(String link_prefix) {
|
||||
@@ -335,7 +334,6 @@ struct CheckerContext {
|
||||
Scope * polymorphic_scope;
|
||||
|
||||
Ast *assignment_lhs_hint;
|
||||
Ast *unary_address_hint;
|
||||
};
|
||||
|
||||
struct Checker {
|
||||
|
||||
@@ -56,6 +56,11 @@ enum BuiltinProcId {
|
||||
BuiltinProc_overflow_sub,
|
||||
BuiltinProc_overflow_mul,
|
||||
|
||||
BuiltinProc_sqrt,
|
||||
|
||||
BuiltinProc_mem_copy,
|
||||
BuiltinProc_mem_copy_non_overlapping,
|
||||
|
||||
BuiltinProc_volatile_store,
|
||||
BuiltinProc_volatile_load,
|
||||
|
||||
@@ -197,6 +202,8 @@ BuiltinProc__type_simple_boolean_end,
|
||||
|
||||
BuiltinProc_type_is_specialization_of,
|
||||
|
||||
BuiltinProc_type_is_variant_of,
|
||||
|
||||
BuiltinProc_type_struct_field_count,
|
||||
|
||||
BuiltinProc_type_proc_parameter_count,
|
||||
@@ -276,6 +283,11 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
|
||||
{STR_LIT("overflow_sub"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||
{STR_LIT("overflow_mul"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||
|
||||
{STR_LIT("sqrt"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||
|
||||
{STR_LIT("mem_copy"), 3, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
|
||||
{STR_LIT("mem_copy_non_overlapping"), 3, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
|
||||
|
||||
{STR_LIT("volatile_store"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
|
||||
{STR_LIT("volatile_load"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||
|
||||
@@ -415,6 +427,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
|
||||
|
||||
{STR_LIT("type_is_specialization_of"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||
|
||||
{STR_LIT("type_is_variant_of"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||
|
||||
{STR_LIT("type_struct_field_count"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||
|
||||
{STR_LIT("type_proc_parameter_count"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||
|
||||
@@ -697,6 +697,9 @@ OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type) {
|
||||
case ProcCC_None:
|
||||
calling_convention = str_lit("none");
|
||||
break;
|
||||
case ProcCC_Naked:
|
||||
calling_convention = str_lit("naked");
|
||||
break;
|
||||
case ProcCC_InlineAsm:
|
||||
calling_convention = str_lit("inline-assembly");
|
||||
break;
|
||||
|
||||
@@ -126,6 +126,8 @@ struct Entity {
|
||||
Entity * using_parent;
|
||||
Ast * using_expr;
|
||||
|
||||
Entity * aliased_of;
|
||||
|
||||
lbModule * code_gen_module;
|
||||
lbProcedure *code_gen_procedure;
|
||||
|
||||
|
||||
+69
-134
@@ -10,21 +10,35 @@ struct lbArgType {
|
||||
LLVMTypeRef cast_type; // Optional
|
||||
LLVMTypeRef pad_type; // Optional
|
||||
LLVMAttributeRef attribute; // Optional
|
||||
LLVMAttributeRef align_attribute; // Optional
|
||||
};
|
||||
|
||||
|
||||
i64 lb_sizeof(LLVMTypeRef type);
|
||||
i64 lb_alignof(LLVMTypeRef type);
|
||||
|
||||
lbArgType lb_arg_type_direct(LLVMTypeRef type, LLVMTypeRef cast_type, LLVMTypeRef pad_type, LLVMAttributeRef attr) {
|
||||
return lbArgType{lbArg_Direct, type, cast_type, pad_type, attr};
|
||||
return lbArgType{lbArg_Direct, type, cast_type, pad_type, attr, nullptr};
|
||||
}
|
||||
lbArgType lb_arg_type_direct(LLVMTypeRef type) {
|
||||
return lb_arg_type_direct(type, nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
lbArgType lb_arg_type_indirect(LLVMTypeRef type, LLVMAttributeRef attr) {
|
||||
return lbArgType{lbArg_Indirect, type, nullptr, nullptr, attr};
|
||||
return lbArgType{lbArg_Indirect, type, nullptr, nullptr, attr, nullptr};
|
||||
}
|
||||
|
||||
lbArgType lb_arg_type_indirect_byval(LLVMContextRef c, LLVMTypeRef type) {
|
||||
i64 alignment = lb_alignof(type);
|
||||
alignment = gb_max(alignment, 8);
|
||||
|
||||
LLVMAttributeRef byval_attr = lb_create_enum_attribute_with_type(c, "byval", type);
|
||||
LLVMAttributeRef align_attr = lb_create_enum_attribute(c, "align", alignment);
|
||||
return lbArgType{lbArg_Indirect, type, nullptr, nullptr, byval_attr, align_attr};
|
||||
}
|
||||
|
||||
lbArgType lb_arg_type_ignore(LLVMTypeRef type) {
|
||||
return lbArgType{lbArg_Ignore, type, nullptr, nullptr, nullptr};
|
||||
return lbArgType{lbArg_Ignore, type, nullptr, nullptr, nullptr, nullptr};
|
||||
}
|
||||
|
||||
struct lbFunctionType {
|
||||
@@ -121,6 +135,9 @@ void lb_add_function_type_attributes(LLVMValueRef fn, lbFunctionType *ft, ProcCa
|
||||
if (arg->attribute) {
|
||||
LLVMAddAttributeAtIndex(fn, arg_index+1, arg->attribute);
|
||||
}
|
||||
if (arg->align_attribute) {
|
||||
LLVMAddAttributeAtIndex(fn, arg_index+1, arg->align_attribute);
|
||||
}
|
||||
|
||||
arg_index++;
|
||||
}
|
||||
@@ -145,8 +162,6 @@ void lb_add_function_type_attributes(LLVMValueRef fn, lbFunctionType *ft, ProcCa
|
||||
|
||||
}
|
||||
|
||||
i64 lb_sizeof(LLVMTypeRef type);
|
||||
i64 lb_alignof(LLVMTypeRef type);
|
||||
|
||||
i64 lb_sizeof(LLVMTypeRef type) {
|
||||
LLVMTypeKind kind = LLVMGetTypeKind(type);
|
||||
@@ -271,110 +286,6 @@ i64 lb_alignof(LLVMTypeRef type) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if 0
|
||||
Type *lb_abi_to_odin_type(lbModule *m, LLVMTypeRef type, bool is_return, u32 level = 0) {
|
||||
Type **found = map_get(&m->llvm_types, hash_pointer(type));
|
||||
if (found) {
|
||||
return *found;
|
||||
}
|
||||
GB_ASSERT_MSG(level < 64, "%s %d", LLVMPrintTypeToString(type), is_return);
|
||||
|
||||
LLVMTypeKind kind = LLVMGetTypeKind(type);
|
||||
switch (kind) {
|
||||
case LLVMVoidTypeKind:
|
||||
return nullptr;
|
||||
case LLVMIntegerTypeKind:
|
||||
{
|
||||
unsigned w = LLVMGetIntTypeWidth(type);
|
||||
if (w == 1) {
|
||||
return t_llvm_bool;
|
||||
}
|
||||
unsigned bytes = (w + 7)/8;
|
||||
switch (bytes) {
|
||||
case 1: return t_u8;
|
||||
case 2: return t_u16;
|
||||
case 4: return t_u32;
|
||||
case 8: return t_u64;
|
||||
case 16: return t_u128;
|
||||
}
|
||||
GB_PANIC("Unhandled integer type");
|
||||
}
|
||||
case LLVMFloatTypeKind:
|
||||
return t_f32;
|
||||
case LLVMDoubleTypeKind:
|
||||
return t_f64;
|
||||
case LLVMPointerTypeKind:
|
||||
{
|
||||
LLVMTypeRef elem = LLVMGetElementType(type);
|
||||
if (lb_is_type_kind(elem, LLVMFunctionTypeKind)) {
|
||||
unsigned param_count = LLVMCountParamTypes(elem);
|
||||
LLVMTypeRef *params = gb_alloc_array(heap_allocator(), LLVMTypeRef, param_count);
|
||||
defer (gb_free(heap_allocator(), params));
|
||||
LLVMGetParamTypes(elem, params);
|
||||
|
||||
Type **param_types = gb_alloc_array(heap_allocator(), Type *, param_count);
|
||||
defer (gb_free(heap_allocator(), param_types));
|
||||
|
||||
for (unsigned i = 0; i < param_count; i++) {
|
||||
param_types[i] = lb_abi_to_odin_type(m, params[i], false, level+1);
|
||||
}
|
||||
|
||||
LLVMTypeRef ret = LLVMGetReturnType(elem);
|
||||
Type *ret_type = lb_abi_to_odin_type(m, ret, true, level+1);
|
||||
|
||||
bool is_c_vararg = !!LLVMIsFunctionVarArg(elem);
|
||||
return alloc_type_proc_from_types(param_types, param_count, ret_type, is_c_vararg);
|
||||
}
|
||||
return alloc_type_pointer(lb_abi_to_odin_type(m, elem, false, level+1));
|
||||
}
|
||||
case LLVMFunctionTypeKind:
|
||||
GB_PANIC("LLVMFunctionTypeKind should not be seen on its own");
|
||||
break;
|
||||
|
||||
case LLVMStructTypeKind:
|
||||
{
|
||||
unsigned field_count = LLVMCountStructElementTypes(type);
|
||||
Type **fields = gb_alloc_array(heap_allocator(), Type *, field_count);
|
||||
for (unsigned i = 0; i < field_count; i++) {
|
||||
LLVMTypeRef field_type = LLVMStructGetTypeAtIndex(type, i);
|
||||
if (lb_is_type_kind(field_type, LLVMPointerTypeKind) && level > 0) {
|
||||
fields[i] = t_rawptr;
|
||||
} else {
|
||||
fields[i] = lb_abi_to_odin_type(m, field_type, false, level+1);
|
||||
}
|
||||
}
|
||||
if (is_return) {
|
||||
return alloc_type_tuple_from_field_types(fields, field_count, !!LLVMIsPackedStruct(type), false);
|
||||
} else {
|
||||
return alloc_type_struct_from_field_types(fields, field_count, !!LLVMIsPackedStruct(type));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case LLVMArrayTypeKind:
|
||||
{
|
||||
|
||||
i64 count = LLVMGetArrayLength(type);
|
||||
Type *elem = lb_abi_to_odin_type(m, LLVMGetElementType(type), false, level+1);
|
||||
return alloc_type_array(elem, count);
|
||||
}
|
||||
break;
|
||||
|
||||
case LLVMX86_MMXTypeKind:
|
||||
return t_vector_x86_mmx;
|
||||
case LLVMVectorTypeKind:
|
||||
{
|
||||
i64 count = LLVMGetVectorSize(type);
|
||||
Type *elem = lb_abi_to_odin_type(m, LLVMGetElementType(type), false, level+1);
|
||||
return alloc_type_simd_vector(count, elem);
|
||||
}
|
||||
|
||||
}
|
||||
GB_PANIC("Unhandled type for lb_abi_to_odin_type -> %s", LLVMPrintTypeToString(type));
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#define LB_ABI_INFO(name) lbFunctionType *name(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, LLVMTypeRef return_type, bool return_is_defined, ProcCallingConvention calling_convention)
|
||||
typedef LB_ABI_INFO(lbAbiInfoType);
|
||||
@@ -432,7 +343,7 @@ namespace lbAbi386 {
|
||||
if (sz == 0) {
|
||||
args[i] = lb_arg_type_ignore(t);
|
||||
} else {
|
||||
args[i] = lb_arg_type_indirect(t, lb_create_enum_attribute(c, "byval"));
|
||||
args[i] = lb_arg_type_indirect(t, nullptr);
|
||||
}
|
||||
} else {
|
||||
args[i] = non_struct(c, t, false);
|
||||
@@ -452,7 +363,7 @@ namespace lbAbi386 {
|
||||
case 4: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 32), nullptr, nullptr);
|
||||
case 8: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 64), nullptr, nullptr);
|
||||
}
|
||||
LLVMAttributeRef attr = lb_create_enum_attribute(c, "sret");
|
||||
LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type);
|
||||
return lb_arg_type_indirect(return_type, attr);
|
||||
}
|
||||
return non_struct(c, return_type, true);
|
||||
@@ -523,8 +434,14 @@ namespace lbAbiAmd64SysV {
|
||||
switch (reg_class) {
|
||||
case RegClass_SSEFs:
|
||||
case RegClass_SSEFv:
|
||||
case RegClass_SSEDs:
|
||||
case RegClass_SSEDv:
|
||||
return true;
|
||||
case RegClass_SSEInt8:
|
||||
case RegClass_SSEInt16:
|
||||
case RegClass_SSEInt32:
|
||||
case RegClass_SSEInt64:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -610,9 +527,9 @@ namespace lbAbiAmd64SysV {
|
||||
if (is_mem_cls(cls, attribute_kind)) {
|
||||
LLVMAttributeRef attribute = nullptr;
|
||||
if (attribute_kind == Amd64TypeAttribute_ByVal) {
|
||||
attribute = lb_create_enum_attribute(c, "byval");
|
||||
return lb_arg_type_indirect_byval(c, type);
|
||||
} else if (attribute_kind == Amd64TypeAttribute_StructRect) {
|
||||
attribute = lb_create_enum_attribute(c, "sret");
|
||||
attribute = lb_create_enum_attribute_with_type(c, "sret", type);
|
||||
}
|
||||
return lb_arg_type_indirect(type, attribute);
|
||||
} else {
|
||||
@@ -642,30 +559,48 @@ namespace lbAbiAmd64SysV {
|
||||
return reg_classes;
|
||||
}
|
||||
|
||||
void unify(Array<RegClass> *cls, i64 i, RegClass newv) {
|
||||
RegClass &oldv = (*cls)[i];
|
||||
void unify(Array<RegClass> *cls, i64 i, RegClass const newv) {
|
||||
RegClass const oldv = (*cls)[i];
|
||||
if (oldv == newv) {
|
||||
return;
|
||||
} else if (oldv == RegClass_NoClass) {
|
||||
oldv = newv;
|
||||
}
|
||||
|
||||
RegClass to_write = newv;
|
||||
if (oldv == RegClass_NoClass) {
|
||||
to_write = newv;
|
||||
} else if (newv == RegClass_NoClass) {
|
||||
return;
|
||||
} else if (oldv == RegClass_Memory || newv == RegClass_Memory) {
|
||||
return;
|
||||
} else if (oldv == RegClass_Int || newv == RegClass_Int) {
|
||||
return;
|
||||
} else if (oldv == RegClass_X87 || oldv == RegClass_X87Up || oldv == RegClass_ComplexX87 ||
|
||||
newv == RegClass_X87 || newv == RegClass_X87Up || newv == RegClass_ComplexX87) {
|
||||
oldv = RegClass_Memory;
|
||||
} else {
|
||||
oldv = newv;
|
||||
to_write = RegClass_Memory;
|
||||
} else if (oldv == RegClass_Int || newv == RegClass_Int) {
|
||||
to_write = RegClass_Int;
|
||||
} else if (oldv == RegClass_X87 || oldv == RegClass_X87Up || oldv == RegClass_ComplexX87) {
|
||||
to_write = RegClass_Memory;
|
||||
} else if (newv == RegClass_X87 || newv == RegClass_X87Up || newv == RegClass_ComplexX87) {
|
||||
to_write = RegClass_Memory;
|
||||
} else if (newv == RegClass_SSEUp) {
|
||||
switch (oldv) {
|
||||
case RegClass_SSEFv:
|
||||
case RegClass_SSEFs:
|
||||
case RegClass_SSEDv:
|
||||
case RegClass_SSEDs:
|
||||
case RegClass_SSEInt8:
|
||||
case RegClass_SSEInt16:
|
||||
case RegClass_SSEInt32:
|
||||
case RegClass_SSEInt64:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
(*cls)[i] = to_write;
|
||||
}
|
||||
|
||||
void fixup(LLVMTypeRef t, Array<RegClass> *cls) {
|
||||
i64 i = 0;
|
||||
i64 e = cls->count;
|
||||
if (e > 2 && (lb_is_type_kind(t, LLVMStructTypeKind) || lb_is_type_kind(t, LLVMArrayTypeKind))) {
|
||||
if (e > 2 && (lb_is_type_kind(t, LLVMStructTypeKind) ||
|
||||
lb_is_type_kind(t, LLVMArrayTypeKind) ||
|
||||
lb_is_type_kind(t, LLVMVectorTypeKind))) {
|
||||
RegClass &oldv = (*cls)[i];
|
||||
if (is_sse(oldv)) {
|
||||
for (i++; i < e; i++) {
|
||||
@@ -709,8 +644,8 @@ namespace lbAbiAmd64SysV {
|
||||
|
||||
unsigned llvec_len(Array<RegClass> const ®_classes, isize offset) {
|
||||
unsigned len = 1;
|
||||
for (isize i = offset+1; i < reg_classes.count; i++) {
|
||||
if (reg_classes[offset] != RegClass_SSEFv && reg_classes[i] != RegClass_SSEUp) {
|
||||
for (isize i = offset; i < reg_classes.count; i++) {
|
||||
if (reg_classes[i] != RegClass_SSEUp) {
|
||||
break;
|
||||
}
|
||||
len++;
|
||||
@@ -721,7 +656,7 @@ namespace lbAbiAmd64SysV {
|
||||
|
||||
LLVMTypeRef llreg(LLVMContextRef c, Array<RegClass> const ®_classes) {
|
||||
auto types = array_make<LLVMTypeRef>(heap_allocator(), 0, reg_classes.count);
|
||||
for_array(i, reg_classes) {
|
||||
for (isize i = 0; i < reg_classes.count; /**/) {
|
||||
RegClass reg_class = reg_classes[i];
|
||||
switch (reg_class) {
|
||||
case RegClass_Int:
|
||||
@@ -763,7 +698,7 @@ namespace lbAbiAmd64SysV {
|
||||
break;
|
||||
}
|
||||
|
||||
unsigned vec_len = llvec_len(reg_classes, i);
|
||||
unsigned vec_len = llvec_len(reg_classes, i+1);
|
||||
LLVMTypeRef vec_type = LLVMVectorType(elem_type, vec_len * elems_per_word);
|
||||
array_add(&types, vec_type);
|
||||
i += vec_len;
|
||||
@@ -779,9 +714,9 @@ namespace lbAbiAmd64SysV {
|
||||
default:
|
||||
GB_PANIC("Unhandled RegClass");
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
GB_ASSERT(types.count != 0);
|
||||
if (types.count == 1) {
|
||||
return types[0];
|
||||
}
|
||||
@@ -893,7 +828,7 @@ namespace lbAbiAmd64SysV {
|
||||
if (sz == 0) {
|
||||
args[i] = lb_arg_type_ignore(t);
|
||||
} else {
|
||||
args[i] = lb_arg_type_indirect(t, lb_create_enum_attribute(c, "byval"));
|
||||
args[i] = lb_arg_type_indirect_byval(c, t);
|
||||
}
|
||||
} else {
|
||||
args[i] = non_struct(c, t);
|
||||
@@ -913,7 +848,7 @@ namespace lbAbiAmd64SysV {
|
||||
case 4: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 32), nullptr, nullptr);
|
||||
case 8: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 64), nullptr, nullptr);
|
||||
}
|
||||
LLVMAttributeRef attr = lb_create_enum_attribute(c, "sret");
|
||||
LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type);
|
||||
return lb_arg_type_indirect(return_type, attr);
|
||||
} else if (build_context.metrics.os == TargetOs_windows && lb_is_type_kind(return_type, LLVMIntegerTypeKind) && lb_sizeof(return_type) == 16) {
|
||||
return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 128), nullptr, nullptr);
|
||||
@@ -1063,7 +998,7 @@ namespace lbAbiArm64 {
|
||||
}
|
||||
return lb_arg_type_direct(type, cast_type, nullptr, nullptr);
|
||||
} else {
|
||||
LLVMAttributeRef attr = lb_create_enum_attribute(c, "sret");
|
||||
LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", type);
|
||||
return lb_arg_type_indirect(type, attr);
|
||||
}
|
||||
}
|
||||
|
||||
+1569
-928
File diff suppressed because it is too large
Load Diff
+19
-7
@@ -85,11 +85,10 @@ struct lbModule {
|
||||
LLVMModuleRef mod;
|
||||
LLVMContextRef ctx;
|
||||
|
||||
u64 state_flags;
|
||||
struct lbGenerator *gen;
|
||||
|
||||
CheckerInfo *info;
|
||||
|
||||
gbMutex mutex;
|
||||
AstPackage *pkg; // associated
|
||||
|
||||
Map<LLVMTypeRef> types; // Key: Type *
|
||||
Map<Type *> llvm_types; // Key: LLVMTypeRef
|
||||
@@ -109,8 +108,6 @@ struct lbModule {
|
||||
Map<lbProcedure *> equal_procs; // Key: Type *
|
||||
Map<lbProcedure *> hasher_procs; // Key: Type *
|
||||
|
||||
u32 global_array_index;
|
||||
u32 global_generated_index;
|
||||
u32 nested_type_name_guid;
|
||||
|
||||
Array<lbProcedure *> procedures_to_generate;
|
||||
@@ -126,12 +123,22 @@ struct lbModule {
|
||||
};
|
||||
|
||||
struct lbGenerator {
|
||||
lbModule module;
|
||||
CheckerInfo *info;
|
||||
|
||||
gbMutex mutex;
|
||||
|
||||
Array<String> output_object_paths;
|
||||
Array<String> output_temp_paths;
|
||||
String output_base;
|
||||
String output_name;
|
||||
Map<lbModule *> modules; // Key: AstPackage *
|
||||
Map<lbModule *> modules_through_ctx; // Key: LLVMContextRef *
|
||||
lbModule default_module;
|
||||
|
||||
Map<lbProcedure *> anonymous_proc_lits; // Key: Ast *
|
||||
|
||||
gbAtomic32 global_array_index;
|
||||
gbAtomic32 global_generated_index;
|
||||
};
|
||||
|
||||
|
||||
@@ -210,6 +217,7 @@ enum lbProcedureFlag : u32 {
|
||||
|
||||
struct lbProcedure {
|
||||
u32 flags;
|
||||
u16 state_flags;
|
||||
|
||||
lbProcedure *parent;
|
||||
Array<lbProcedure *> children;
|
||||
@@ -268,9 +276,10 @@ String lb_mangle_name(lbModule *m, Entity *e);
|
||||
String lb_get_entity_name(lbModule *m, Entity *e, String name = {});
|
||||
|
||||
LLVMAttributeRef lb_create_enum_attribute(LLVMContextRef ctx, char const *name, u64 value=0);
|
||||
LLVMAttributeRef lb_create_enum_attribute_with_type(LLVMContextRef ctx, char const *name, LLVMTypeRef type);
|
||||
void lb_add_proc_attribute_at_index(lbProcedure *p, isize index, char const *name, u64 value);
|
||||
void lb_add_proc_attribute_at_index(lbProcedure *p, isize index, char const *name);
|
||||
lbProcedure *lb_create_procedure(lbModule *module, Entity *entity);
|
||||
lbProcedure *lb_create_procedure(lbModule *module, Entity *entity, bool ignore_body=false);
|
||||
void lb_end_procedure(lbProcedure *p);
|
||||
|
||||
|
||||
@@ -381,6 +390,8 @@ lbValue lb_gen_map_header(lbProcedure *p, lbValue map_val_ptr, Type *map_type);
|
||||
lbValue lb_gen_map_hash(lbProcedure *p, lbValue key, Type *key_type);
|
||||
void lb_insert_dynamic_map_key_and_value(lbProcedure *p, lbAddr addr, Type *map_type, lbValue map_key, lbValue map_value, Ast *node);
|
||||
|
||||
lbValue lb_find_procedure_value_from_entity(lbModule *m, Entity *e);
|
||||
lbValue lb_find_value_from_entity(lbModule *m, Entity *e);
|
||||
|
||||
void lb_store_type_case_implicit(lbProcedure *p, Ast *clause, lbValue value);
|
||||
lbAddr lb_store_range_stmt_val(lbProcedure *p, Ast *stmt_val, lbValue value);
|
||||
@@ -472,6 +483,7 @@ lbCallingConventionKind const lb_calling_convention_map[ProcCC_MAX] = {
|
||||
lbCallingConvention_X86_FastCall, // ProcCC_FastCall,
|
||||
|
||||
lbCallingConvention_C, // ProcCC_None,
|
||||
lbCallingConvention_C, // ProcCC_Naked,
|
||||
lbCallingConvention_C, // ProcCC_InlineAsm,
|
||||
};
|
||||
|
||||
|
||||
+127
-4
@@ -1,7 +1,25 @@
|
||||
void lb_populate_function_pass_manager(LLVMPassManagerRef fpm, bool ignore_memcpy_pass, i32 optimization_level);
|
||||
void lb_populate_function_pass_manager(lbModule *m, LLVMPassManagerRef fpm, bool ignore_memcpy_pass, i32 optimization_level);
|
||||
void lb_add_function_simplifcation_passes(LLVMPassManagerRef mpm, i32 optimization_level);
|
||||
void lb_populate_module_pass_manager(LLVMTargetMachineRef target_machine, LLVMPassManagerRef mpm, i32 optimization_level);
|
||||
void lb_populate_function_pass_manager_specific(LLVMPassManagerRef fpm, i32 optimization_level);
|
||||
void lb_populate_function_pass_manager_specific(lbModule *m, LLVMPassManagerRef fpm, i32 optimization_level);
|
||||
|
||||
LLVMBool lb_must_preserve_predicate_callback(LLVMValueRef value, void *user_data) {
|
||||
lbModule *m = cast(lbModule *)user_data;
|
||||
if (m == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (value == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return LLVMIsAAllocaInst(value) != nullptr;
|
||||
}
|
||||
|
||||
void lb_add_must_preserve_predicate_pass(lbModule *m, LLVMPassManagerRef fpm, i32 optimization_level) {
|
||||
if (false && optimization_level == 0 && m->debug_builder) {
|
||||
// LLVMAddInternalizePassWithMustPreservePredicate(fpm, m, lb_must_preserve_predicate_callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void lb_basic_populate_function_pass_manager(LLVMPassManagerRef fpm) {
|
||||
LLVMAddPromoteMemoryToRegisterPass(fpm);
|
||||
@@ -15,11 +33,13 @@ void lb_basic_populate_function_pass_manager(LLVMPassManagerRef fpm) {
|
||||
LLVMAddCFGSimplificationPass(fpm);
|
||||
}
|
||||
|
||||
void lb_populate_function_pass_manager(LLVMPassManagerRef fpm, bool ignore_memcpy_pass, i32 optimization_level) {
|
||||
void lb_populate_function_pass_manager(lbModule *m, LLVMPassManagerRef fpm, bool ignore_memcpy_pass, i32 optimization_level) {
|
||||
// NOTE(bill): Treat -opt:3 as if it was -opt:2
|
||||
// TODO(bill): Determine which opt definitions should exist in the first place
|
||||
optimization_level = gb_clamp(optimization_level, 0, 2);
|
||||
|
||||
lb_add_must_preserve_predicate_pass(m, fpm, optimization_level);
|
||||
|
||||
if (ignore_memcpy_pass) {
|
||||
lb_basic_populate_function_pass_manager(fpm);
|
||||
return;
|
||||
@@ -57,11 +77,13 @@ void lb_populate_function_pass_manager(LLVMPassManagerRef fpm, bool ignore_memcp
|
||||
#endif
|
||||
}
|
||||
|
||||
void lb_populate_function_pass_manager_specific(LLVMPassManagerRef fpm, i32 optimization_level) {
|
||||
void lb_populate_function_pass_manager_specific(lbModule *m, LLVMPassManagerRef fpm, i32 optimization_level) {
|
||||
// NOTE(bill): Treat -opt:3 as if it was -opt:2
|
||||
// TODO(bill): Determine which opt definitions should exist in the first place
|
||||
optimization_level = gb_clamp(optimization_level, 0, 2);
|
||||
|
||||
lb_add_must_preserve_predicate_pass(m, fpm, optimization_level);
|
||||
|
||||
if (optimization_level == 0) {
|
||||
LLVMAddMemCpyOptPass(fpm);
|
||||
lb_basic_populate_function_pass_manager(fpm);
|
||||
@@ -226,3 +248,104 @@ void lb_populate_module_pass_manager(LLVMTargetMachineRef target_machine, LLVMPa
|
||||
|
||||
LLVMAddCFGSimplificationPass(mpm);
|
||||
}
|
||||
|
||||
void lb_run_remove_dead_instruction_pass(lbProcedure *p) {
|
||||
isize removal_count = 0;
|
||||
isize pass_count = 0;
|
||||
isize const max_pass_count = 10;
|
||||
isize original_instruction_count = 0;
|
||||
// Custom remove dead instruction pass
|
||||
for (; pass_count < max_pass_count; pass_count++) {
|
||||
bool was_dead_instructions = false;
|
||||
|
||||
// NOTE(bill): Iterate backwards
|
||||
// reduces the number of passes as things later on will depend on things previously
|
||||
for (LLVMBasicBlockRef block = LLVMGetLastBasicBlock(p->value);
|
||||
block != nullptr;
|
||||
block = LLVMGetPreviousBasicBlock(block)) {
|
||||
// NOTE(bill): Iterate backwards
|
||||
// reduces the number of passes as things later on will depend on things previously
|
||||
for (LLVMValueRef instr = LLVMGetLastInstruction(block);
|
||||
instr != nullptr;
|
||||
/**/) {
|
||||
if (pass_count == 0) {
|
||||
original_instruction_count += 1;
|
||||
}
|
||||
|
||||
LLVMValueRef curr_instr = instr;
|
||||
instr = LLVMGetPreviousInstruction(instr);
|
||||
|
||||
LLVMUseRef first_use = LLVMGetFirstUse(curr_instr);
|
||||
if (first_use != nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (LLVMTypeOf(curr_instr) == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// NOTE(bill): Explicit instructions are set here because some instructions could have side effects
|
||||
switch (LLVMGetInstructionOpcode(curr_instr)) {
|
||||
case LLVMFNeg:
|
||||
case LLVMAdd:
|
||||
case LLVMFAdd:
|
||||
case LLVMSub:
|
||||
case LLVMFSub:
|
||||
case LLVMMul:
|
||||
case LLVMFMul:
|
||||
case LLVMUDiv:
|
||||
case LLVMSDiv:
|
||||
case LLVMFDiv:
|
||||
case LLVMURem:
|
||||
case LLVMSRem:
|
||||
case LLVMFRem:
|
||||
case LLVMShl:
|
||||
case LLVMLShr:
|
||||
case LLVMAShr:
|
||||
case LLVMAnd:
|
||||
case LLVMOr:
|
||||
case LLVMXor:
|
||||
case LLVMAlloca:
|
||||
case LLVMLoad:
|
||||
case LLVMGetElementPtr:
|
||||
case LLVMTrunc:
|
||||
case LLVMZExt:
|
||||
case LLVMSExt:
|
||||
case LLVMFPToUI:
|
||||
case LLVMFPToSI:
|
||||
case LLVMUIToFP:
|
||||
case LLVMSIToFP:
|
||||
case LLVMFPTrunc:
|
||||
case LLVMFPExt:
|
||||
case LLVMPtrToInt:
|
||||
case LLVMIntToPtr:
|
||||
case LLVMBitCast:
|
||||
case LLVMAddrSpaceCast:
|
||||
case LLVMICmp:
|
||||
case LLVMFCmp:
|
||||
case LLVMSelect:
|
||||
case LLVMExtractElement:
|
||||
case LLVMShuffleVector:
|
||||
case LLVMExtractValue:
|
||||
removal_count += 1;
|
||||
LLVMInstructionEraseFromParent(curr_instr);
|
||||
was_dead_instructions = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!was_dead_instructions) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void lb_run_function_pass_manager(LLVMPassManagerRef fpm, lbProcedure *p) {
|
||||
LLVMRunFunctionPassManager(fpm, p->value);
|
||||
// NOTE(bill): LLVMAddDCEPass doesn't seem to be exported in the official DLL's for LLVM
|
||||
// which means we cannot rely upon it
|
||||
// This is also useful for read the .ll for debug purposes because a lot of instructions
|
||||
// are not removed
|
||||
lb_run_remove_dead_instruction_pass(p);
|
||||
}
|
||||
|
||||
+104
-109
@@ -218,8 +218,19 @@ i32 linker_stage(lbGenerator *gen) {
|
||||
add_path(find_result.vs_library_path);
|
||||
}
|
||||
|
||||
for_array(i, gen->module.foreign_library_paths) {
|
||||
String lib = gen->module.foreign_library_paths[i];
|
||||
for_array(j, gen->modules.entries) {
|
||||
lbModule *m = gen->modules.entries[j].value;
|
||||
for_array(i, m->foreign_library_paths) {
|
||||
String lib = m->foreign_library_paths[i];
|
||||
GB_ASSERT(lib.len < gb_count_of(lib_str_buf)-1);
|
||||
isize len = gb_snprintf(lib_str_buf, gb_size_of(lib_str_buf),
|
||||
" \"%.*s\"", LIT(lib));
|
||||
lib_str = gb_string_appendc(lib_str, lib_str_buf);
|
||||
}
|
||||
}
|
||||
|
||||
for_array(i, gen->default_module.foreign_library_paths) {
|
||||
String lib = gen->default_module.foreign_library_paths[i];
|
||||
GB_ASSERT(lib.len < gb_count_of(lib_str_buf)-1);
|
||||
isize len = gb_snprintf(lib_str_buf, gb_size_of(lib_str_buf),
|
||||
" \"%.*s\"", LIT(lib));
|
||||
@@ -265,22 +276,22 @@ i32 linker_stage(lbGenerator *gen) {
|
||||
LIT(build_context.resource_filepath)
|
||||
);
|
||||
|
||||
if(result == 0) {
|
||||
result = system_exec_command_line_app("msvc-link",
|
||||
"\"%.*slink.exe\" %s \"%.*s.res\" -OUT:\"%.*s.%s\" %s "
|
||||
"/nologo /incremental:no /opt:ref /subsystem:%s "
|
||||
" %.*s "
|
||||
" %.*s "
|
||||
" %s "
|
||||
"",
|
||||
LIT(find_result.vs_exe_path), object_files, LIT(output_base), LIT(output_base), output_ext,
|
||||
link_settings,
|
||||
subsystem_str,
|
||||
LIT(build_context.link_flags),
|
||||
LIT(build_context.extra_linker_flags),
|
||||
lib_str
|
||||
);
|
||||
}
|
||||
if (result == 0) {
|
||||
result = system_exec_command_line_app("msvc-link",
|
||||
"\"%.*slink.exe\" %s \"%.*s.res\" -OUT:\"%.*s.%s\" %s "
|
||||
"/nologo /incremental:no /opt:ref /subsystem:%s "
|
||||
" %.*s "
|
||||
" %.*s "
|
||||
" %s "
|
||||
"",
|
||||
LIT(find_result.vs_exe_path), object_files, LIT(output_base), LIT(output_base), output_ext,
|
||||
link_settings,
|
||||
subsystem_str,
|
||||
LIT(build_context.link_flags),
|
||||
LIT(build_context.extra_linker_flags),
|
||||
lib_str
|
||||
);
|
||||
}
|
||||
} else {
|
||||
result = system_exec_command_line_app("msvc-link",
|
||||
"\"%.*slink.exe\" %s -OUT:\"%.*s.%s\" %s "
|
||||
@@ -327,8 +338,8 @@ i32 linker_stage(lbGenerator *gen) {
|
||||
gbString lib_str = gb_string_make(heap_allocator(), "-L/");
|
||||
defer (gb_string_free(lib_str));
|
||||
|
||||
for_array(i, gen->module.foreign_library_paths) {
|
||||
String lib = gen->module.foreign_library_paths[i];
|
||||
for_array(i, gen->default_module.foreign_library_paths) {
|
||||
String lib = gen->default_module.foreign_library_paths[i];
|
||||
|
||||
// NOTE(zangent): Sometimes, you have to use -framework on MacOS.
|
||||
// This allows you to specify '-f' in a #foreign_system_library,
|
||||
@@ -409,6 +420,11 @@ i32 linker_stage(lbGenerator *gen) {
|
||||
#endif
|
||||
}
|
||||
|
||||
if (build_context.metrics.os == TargetOs_linux) {
|
||||
link_settings = gb_string_appendc(link_settings, "-no-pie ");
|
||||
}
|
||||
|
||||
|
||||
if (build_context.out_filepath.len > 0) {
|
||||
//NOTE(thebirk): We have a custom -out arguments, so we should use the extension from that
|
||||
isize pos = string_extension_position(build_context.out_filepath);
|
||||
@@ -580,6 +596,7 @@ enum BuildFlagKind {
|
||||
BuildFlag_NoCRT,
|
||||
BuildFlag_NoEntryPoint,
|
||||
BuildFlag_UseLLD,
|
||||
BuildFlag_UseSeparateModules,
|
||||
BuildFlag_Vet,
|
||||
BuildFlag_VetExtra,
|
||||
BuildFlag_UseLLVMApi,
|
||||
@@ -587,6 +604,8 @@ enum BuildFlagKind {
|
||||
BuildFlag_ExtraLinkerFlags,
|
||||
BuildFlag_Microarch,
|
||||
|
||||
BuildFlag_TestName,
|
||||
|
||||
BuildFlag_DisallowDo,
|
||||
BuildFlag_DefaultToNilAllocator,
|
||||
BuildFlag_InsertSemicolon,
|
||||
@@ -602,6 +621,7 @@ enum BuildFlagKind {
|
||||
|
||||
BuildFlag_IgnoreWarnings,
|
||||
BuildFlag_WarningsAsErrors,
|
||||
BuildFlag_VerboseErrors,
|
||||
|
||||
#if defined(GB_SYSTEM_WINDOWS)
|
||||
BuildFlag_IgnoreVsSearch,
|
||||
@@ -698,6 +718,7 @@ bool parse_build_flags(Array<String> args) {
|
||||
add_flag(&build_flags, BuildFlag_NoCRT, str_lit("no-crt"), BuildFlagParam_None, Command__does_build);
|
||||
add_flag(&build_flags, BuildFlag_NoEntryPoint, str_lit("no-entry-point"), BuildFlagParam_None, Command__does_check &~ Command_test);
|
||||
add_flag(&build_flags, BuildFlag_UseLLD, str_lit("lld"), BuildFlagParam_None, Command__does_build);
|
||||
add_flag(&build_flags, BuildFlag_UseSeparateModules,str_lit("use-separate-modules"),BuildFlagParam_None, Command__does_build);
|
||||
add_flag(&build_flags, BuildFlag_Vet, str_lit("vet"), BuildFlagParam_None, Command__does_check);
|
||||
add_flag(&build_flags, BuildFlag_VetExtra, str_lit("vet-extra"), BuildFlagParam_None, Command__does_check);
|
||||
add_flag(&build_flags, BuildFlag_UseLLVMApi, str_lit("llvm-api"), BuildFlagParam_None, Command__does_build);
|
||||
@@ -705,6 +726,8 @@ bool parse_build_flags(Array<String> args) {
|
||||
add_flag(&build_flags, BuildFlag_ExtraLinkerFlags, str_lit("extra-linker-flags"), BuildFlagParam_String, Command__does_build);
|
||||
add_flag(&build_flags, BuildFlag_Microarch, str_lit("microarch"), BuildFlagParam_String, Command__does_build);
|
||||
|
||||
add_flag(&build_flags, BuildFlag_TestName, str_lit("test-name"), BuildFlagParam_String, Command_test);
|
||||
|
||||
add_flag(&build_flags, BuildFlag_DisallowDo, str_lit("disallow-do"), BuildFlagParam_None, Command__does_check);
|
||||
add_flag(&build_flags, BuildFlag_DefaultToNilAllocator, str_lit("default-to-nil-allocator"), BuildFlagParam_None, Command__does_check);
|
||||
add_flag(&build_flags, BuildFlag_InsertSemicolon, str_lit("insert-semicolon"), BuildFlagParam_None, Command__does_check);
|
||||
@@ -719,6 +742,7 @@ bool parse_build_flags(Array<String> args) {
|
||||
|
||||
add_flag(&build_flags, BuildFlag_IgnoreWarnings, str_lit("ignore-warnings"), BuildFlagParam_None, Command_all);
|
||||
add_flag(&build_flags, BuildFlag_WarningsAsErrors, str_lit("warnings-as-errors"), BuildFlagParam_None, Command_all);
|
||||
add_flag(&build_flags, BuildFlag_VerboseErrors, str_lit("verbose-errors"), BuildFlagParam_None, Command_all);
|
||||
|
||||
#if defined(GB_SYSTEM_WINDOWS)
|
||||
add_flag(&build_flags, BuildFlag_IgnoreVsSearch, str_lit("ignore-vs-search"), BuildFlagParam_None, Command__does_build);
|
||||
@@ -1175,6 +1199,10 @@ bool parse_build_flags(Array<String> args) {
|
||||
build_context.use_lld = true;
|
||||
break;
|
||||
|
||||
case BuildFlag_UseSeparateModules:
|
||||
build_context.use_separate_modules = true;
|
||||
break;
|
||||
|
||||
case BuildFlag_Vet:
|
||||
build_context.vet = true;
|
||||
break;
|
||||
@@ -1203,6 +1231,21 @@ bool parse_build_flags(Array<String> args) {
|
||||
string_to_lower(&build_context.microarch);
|
||||
break;
|
||||
|
||||
case BuildFlag_TestName:
|
||||
GB_ASSERT(value.kind == ExactValue_String);
|
||||
{
|
||||
String name = value.value_string;
|
||||
if (!string_is_valid_identifier(name)) {
|
||||
gb_printf_err("Test name '%.*s' must be a valid identifier\n", LIT(name));
|
||||
bad_flags = true;
|
||||
break;
|
||||
}
|
||||
string_set_add(&build_context.test_names, name);
|
||||
|
||||
// NOTE(bill): Allow for multiple -test-name
|
||||
continue;
|
||||
}
|
||||
|
||||
case BuildFlag_DisallowDo:
|
||||
build_context.disallow_do = true;
|
||||
break;
|
||||
@@ -1279,6 +1322,10 @@ bool parse_build_flags(Array<String> args) {
|
||||
}
|
||||
break;
|
||||
|
||||
case BuildFlag_VerboseErrors:
|
||||
build_context.show_error_line = true;
|
||||
break;
|
||||
|
||||
#if defined(GB_SYSTEM_WINDOWS)
|
||||
case BuildFlag_IgnoreVsSearch:
|
||||
GB_ASSERT(value.kind == ExactValue_Invalid);
|
||||
@@ -1507,97 +1554,23 @@ void show_timings(Checker *c, Timings *t) {
|
||||
}
|
||||
}
|
||||
|
||||
void remove_temp_files(String output_base) {
|
||||
void remove_temp_files(lbGenerator *gen) {
|
||||
if (build_context.keep_temp_files) return;
|
||||
|
||||
auto data = array_make<u8>(heap_allocator(), output_base.len + 30);
|
||||
defer (array_free(&data));
|
||||
|
||||
isize n = output_base.len;
|
||||
gb_memmove(data.data, output_base.text, n);
|
||||
#define EXT_REMOVE(s) do { \
|
||||
gb_memmove(data.data+n, s, gb_size_of(s)); \
|
||||
gb_file_remove(cast(char const *)data.data); \
|
||||
} while (0)
|
||||
EXT_REMOVE(".ll");
|
||||
EXT_REMOVE(".bc");
|
||||
EXT_REMOVE("_memcpy_pass.bc");
|
||||
if (build_context.build_mode != BuildMode_Object && !build_context.keep_object_files) {
|
||||
#if defined(GB_SYSTEM_WINDOWS)
|
||||
EXT_REMOVE(".obj");
|
||||
EXT_REMOVE(".res");
|
||||
#else
|
||||
EXT_REMOVE(".o");
|
||||
#endif
|
||||
for_array(i, gen->output_temp_paths) {
|
||||
String path = gen->output_temp_paths[i];
|
||||
gb_file_remove(cast(char const *)path.text);
|
||||
}
|
||||
|
||||
#undef EXT_REMOVE
|
||||
if (build_context.build_mode != BuildMode_Object && !build_context.keep_object_files) {
|
||||
for_array(i, gen->output_object_paths) {
|
||||
String path = gen->output_object_paths[i];
|
||||
gb_file_remove(cast(char const *)path.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
i32 exec_llvm_opt(String output_base) {
|
||||
#if defined(GB_SYSTEM_WINDOWS)
|
||||
// For more passes arguments: http://llvm.org/docs/Passes.html
|
||||
|
||||
return system_exec_command_line_app("llvm-opt",
|
||||
"\"%.*sbin/opt\" \"%.*s.ll\" -o \"%.*s_memcpy_pass.bc\" -memcpyopt"
|
||||
"",
|
||||
LIT(build_context.ODIN_ROOT),
|
||||
LIT(output_base), LIT(output_base))
|
||||
|
||||
|| system_exec_command_line_app("llvm-opt",
|
||||
"\"%.*sbin/opt\" \"%.*s_memcpy_pass.bc\" -o \"%.*s.bc\" %.*s "
|
||||
"",
|
||||
LIT(build_context.ODIN_ROOT),
|
||||
LIT(output_base), LIT(output_base),
|
||||
LIT(build_context.opt_flags));
|
||||
#else
|
||||
// NOTE(zangent): This is separate because it seems that LLVM tools are packaged
|
||||
// with the Windows version, while they will be system-provided on MacOS and GNU/Linux
|
||||
|
||||
return system_exec_command_line_app("llvm-opt",
|
||||
"opt \"%.*s.ll\" -o \"%.*s_memcpy_pass.bc\" -memcpyopt"
|
||||
"",
|
||||
LIT(output_base), LIT(output_base))
|
||||
|
||||
|| system_exec_command_line_app("llvm-opt",
|
||||
"opt \"%.*s_memcpy_pass.bc\" -o \"%.*s.bc\" %.*s "
|
||||
"",
|
||||
LIT(output_base), LIT(output_base),
|
||||
LIT(build_context.opt_flags));
|
||||
#endif
|
||||
}
|
||||
|
||||
i32 exec_llvm_llc(String output_base) {
|
||||
// For more arguments: http://llvm.org/docs/CommandGuide/llc.html
|
||||
#if defined(GB_SYSTEM_WINDOWS)
|
||||
return system_exec_command_line_app("llvm-llc",
|
||||
"\"%.*sbin\\llc\" \"%.*s.bc\" -filetype=obj -O%d "
|
||||
"-o \"%.*s.obj\" "
|
||||
"%.*s"
|
||||
"",
|
||||
LIT(build_context.ODIN_ROOT),
|
||||
LIT(output_base),
|
||||
build_context.optimization_level,
|
||||
LIT(output_base),
|
||||
LIT(build_context.llc_flags));
|
||||
#else
|
||||
// NOTE(zangent): Linux / Unix is unfinished and not tested very well.
|
||||
return system_exec_command_line_app("llc",
|
||||
"llc \"%.*s.bc\" -filetype=obj -relocation-model=pic -O%d "
|
||||
"%.*s "
|
||||
"%s%.*s",
|
||||
LIT(output_base),
|
||||
build_context.optimization_level,
|
||||
LIT(build_context.llc_flags),
|
||||
build_context.cross_compiling ? "-mtriple=" : "",
|
||||
cast(int)(build_context.cross_compiling ? build_context.metrics.target_triplet.len : 0),
|
||||
build_context.metrics.target_triplet.text);
|
||||
#endif
|
||||
}
|
||||
|
||||
void print_show_help(String const arg0, String const &command) {
|
||||
print_usage_line(0, "%.*s is a tool for managing Odin source code", LIT(arg0));
|
||||
print_usage_line(0, "Usage");
|
||||
@@ -1612,7 +1585,7 @@ void print_show_help(String const arg0, String const &command) {
|
||||
} else if (command == "check") {
|
||||
print_usage_line(1, "check parse and type check .odin file");
|
||||
} else if (command == "test") {
|
||||
print_usage_line(1, "test build ands runs 'test_*' procedures in the initial package");
|
||||
print_usage_line(1, "test build ands runs procedures with the attribute @(test) in the initial package");
|
||||
} else if (command == "query") {
|
||||
print_usage_line(1, "query [experimental] parse, type check, and output a .json file containing information about the program");
|
||||
} else if (command == "doc") {
|
||||
@@ -1627,6 +1600,7 @@ void print_show_help(String const arg0, String const &command) {
|
||||
bool doc = command == "doc";
|
||||
bool build = command == "build";
|
||||
bool run_or_build = command == "run" || command == "build" || command == "test";
|
||||
bool test_only = command == "test";
|
||||
bool check_only = command == "check";
|
||||
bool check = run_or_build || command == "check";
|
||||
|
||||
@@ -1720,6 +1694,11 @@ void print_show_help(String const arg0, String const &command) {
|
||||
print_usage_line(3, "-build-mode:shared Build as a dynamically linked library");
|
||||
print_usage_line(3, "-build-mode:obj Build as an object file");
|
||||
print_usage_line(3, "-build-mode:object Build as an object file");
|
||||
print_usage_line(3, "-build-mode:assembly Build as an object file");
|
||||
print_usage_line(3, "-build-mode:assembler Build as an assembly file");
|
||||
print_usage_line(3, "-build-mode:asm Build as an assembly file");
|
||||
print_usage_line(3, "-build-mode:llvm-ir Build as an LLVM IR file");
|
||||
print_usage_line(3, "-build-mode:llvm Build as an LLVM IR file");
|
||||
print_usage_line(0, "");
|
||||
}
|
||||
|
||||
@@ -1746,9 +1725,16 @@ void print_show_help(String const arg0, String const &command) {
|
||||
print_usage_line(2, "Disables automatic linking with the C Run Time");
|
||||
print_usage_line(0, "");
|
||||
|
||||
print_usage_line(1, "-use-lld");
|
||||
print_usage_line(1, "-lld");
|
||||
print_usage_line(2, "Use the LLD linker rather than the default");
|
||||
print_usage_line(0, "");
|
||||
|
||||
print_usage_line(1, "-use-separate-modules");
|
||||
print_usage_line(1, "[EXPERIMENTAL]");
|
||||
print_usage_line(2, "The backend generates multiple build units which are then linked together");
|
||||
print_usage_line(2, "Normally, a single build unit is generated for a standard project");
|
||||
print_usage_line(0, "");
|
||||
|
||||
}
|
||||
|
||||
if (check) {
|
||||
@@ -1776,6 +1762,12 @@ void print_show_help(String const arg0, String const &command) {
|
||||
}
|
||||
}
|
||||
|
||||
if (test_only) {
|
||||
print_usage_line(1, "-test-name:<string>");
|
||||
print_usage_line(2, "Run specific test only by name");
|
||||
print_usage_line(0, "");
|
||||
}
|
||||
|
||||
if (run_or_build) {
|
||||
print_usage_line(1, "-extra-linker-flags:<string>");
|
||||
print_usage_line(2, "Adds extra linker specific flags in a string");
|
||||
@@ -1943,7 +1935,7 @@ int main(int arg_count, char const **arg_ptr) {
|
||||
|
||||
Timings *timings = &global_timings;
|
||||
|
||||
timings_init(timings, str_lit("Total Time"), 128);
|
||||
timings_init(timings, str_lit("Total Time"), 2048);
|
||||
defer (timings_destroy(timings));
|
||||
|
||||
arena_init(&permanent_arena, heap_allocator());
|
||||
@@ -1967,7 +1959,7 @@ int main(int arg_count, char const **arg_ptr) {
|
||||
|
||||
map_init(&build_context.defined_values, heap_allocator());
|
||||
build_context.extra_packages.allocator = heap_allocator();
|
||||
|
||||
string_set_init(&build_context.test_names, heap_allocator());
|
||||
|
||||
Array<String> args = setup_args(arg_count, arg_ptr);
|
||||
|
||||
@@ -2190,6 +2182,9 @@ int main(int arg_count, char const **arg_ptr) {
|
||||
case BuildMode_DynamicLibrary:
|
||||
i32 result = linker_stage(&gen);
|
||||
if (result != 0) {
|
||||
if (build_context.show_timings) {
|
||||
show_timings(&checker, timings);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
@@ -2199,7 +2194,7 @@ int main(int arg_count, char const **arg_ptr) {
|
||||
show_timings(&checker, timings);
|
||||
}
|
||||
|
||||
remove_temp_files(gen.output_base);
|
||||
remove_temp_files(&gen);
|
||||
|
||||
if (run_output) {
|
||||
#if defined(GB_SYSTEM_WINDOWS)
|
||||
|
||||
+159
-175
@@ -1,110 +1,4 @@
|
||||
Token ast_token(Ast *node) {
|
||||
switch (node->kind) {
|
||||
case Ast_Ident: return node->Ident.token;
|
||||
case Ast_Implicit: return node->Implicit;
|
||||
case Ast_Undef: return node->Undef;
|
||||
case Ast_BasicLit: return node->BasicLit.token;
|
||||
case Ast_BasicDirective: return node->BasicDirective.token;
|
||||
case Ast_ProcGroup: return node->ProcGroup.token;
|
||||
case Ast_ProcLit: return ast_token(node->ProcLit.type);
|
||||
case Ast_CompoundLit:
|
||||
if (node->CompoundLit.type != nullptr) {
|
||||
return ast_token(node->CompoundLit.type);
|
||||
}
|
||||
return node->CompoundLit.open;
|
||||
|
||||
case Ast_TagExpr: return node->TagExpr.token;
|
||||
case Ast_BadExpr: return node->BadExpr.begin;
|
||||
case Ast_UnaryExpr: return node->UnaryExpr.op;
|
||||
case Ast_BinaryExpr: return ast_token(node->BinaryExpr.left);
|
||||
case Ast_ParenExpr: return node->ParenExpr.open;
|
||||
case Ast_CallExpr: return ast_token(node->CallExpr.proc);
|
||||
case Ast_SelectorExpr:
|
||||
if (node->SelectorExpr.selector != nullptr) {
|
||||
return ast_token(node->SelectorExpr.selector);
|
||||
}
|
||||
return node->SelectorExpr.token;
|
||||
case Ast_SelectorCallExpr:
|
||||
if (node->SelectorCallExpr.expr != nullptr) {
|
||||
return ast_token(node->SelectorCallExpr.expr);
|
||||
}
|
||||
return node->SelectorCallExpr.token;
|
||||
case Ast_ImplicitSelectorExpr:
|
||||
if (node->ImplicitSelectorExpr.selector != nullptr) {
|
||||
return ast_token(node->ImplicitSelectorExpr.selector);
|
||||
}
|
||||
return node->ImplicitSelectorExpr.token;
|
||||
case Ast_IndexExpr: return node->IndexExpr.open;
|
||||
case Ast_SliceExpr: return node->SliceExpr.open;
|
||||
case Ast_Ellipsis: return node->Ellipsis.token;
|
||||
case Ast_FieldValue: return node->FieldValue.eq;
|
||||
case Ast_DerefExpr: return node->DerefExpr.op;
|
||||
case Ast_TernaryExpr: return ast_token(node->TernaryExpr.cond);
|
||||
case Ast_TernaryIfExpr: return ast_token(node->TernaryIfExpr.x);
|
||||
case Ast_TernaryWhenExpr: return ast_token(node->TernaryWhenExpr.x);
|
||||
case Ast_TypeAssertion: return ast_token(node->TypeAssertion.expr);
|
||||
case Ast_TypeCast: return node->TypeCast.token;
|
||||
case Ast_AutoCast: return node->AutoCast.token;
|
||||
case Ast_InlineAsmExpr: return node->InlineAsmExpr.token;
|
||||
|
||||
case Ast_BadStmt: return node->BadStmt.begin;
|
||||
case Ast_EmptyStmt: return node->EmptyStmt.token;
|
||||
case Ast_ExprStmt: return ast_token(node->ExprStmt.expr);
|
||||
case Ast_TagStmt: return node->TagStmt.token;
|
||||
case Ast_AssignStmt: return node->AssignStmt.op;
|
||||
case Ast_BlockStmt: return node->BlockStmt.open;
|
||||
case Ast_IfStmt: return node->IfStmt.token;
|
||||
case Ast_WhenStmt: return node->WhenStmt.token;
|
||||
case Ast_ReturnStmt: return node->ReturnStmt.token;
|
||||
case Ast_ForStmt: return node->ForStmt.token;
|
||||
case Ast_RangeStmt: return node->RangeStmt.token;
|
||||
case Ast_UnrollRangeStmt: return node->UnrollRangeStmt.unroll_token;
|
||||
case Ast_CaseClause: return node->CaseClause.token;
|
||||
case Ast_SwitchStmt: return node->SwitchStmt.token;
|
||||
case Ast_TypeSwitchStmt: return node->TypeSwitchStmt.token;
|
||||
case Ast_DeferStmt: return node->DeferStmt.token;
|
||||
case Ast_BranchStmt: return node->BranchStmt.token;
|
||||
case Ast_UsingStmt: return node->UsingStmt.token;
|
||||
|
||||
case Ast_BadDecl: return node->BadDecl.begin;
|
||||
case Ast_Label: return node->Label.token;
|
||||
|
||||
case Ast_ValueDecl: return ast_token(node->ValueDecl.names[0]);
|
||||
case Ast_PackageDecl: return node->PackageDecl.token;
|
||||
case Ast_ImportDecl: return node->ImportDecl.token;
|
||||
case Ast_ForeignImportDecl: return node->ForeignImportDecl.token;
|
||||
|
||||
case Ast_ForeignBlockDecl: return node->ForeignBlockDecl.token;
|
||||
|
||||
case Ast_Attribute:
|
||||
return node->Attribute.token;
|
||||
|
||||
case Ast_Field:
|
||||
if (node->Field.names.count > 0) {
|
||||
return ast_token(node->Field.names[0]);
|
||||
}
|
||||
return ast_token(node->Field.type);
|
||||
case Ast_FieldList:
|
||||
return node->FieldList.token;
|
||||
|
||||
case Ast_TypeidType: return node->TypeidType.token;
|
||||
case Ast_HelperType: return node->HelperType.token;
|
||||
case Ast_DistinctType: return node->DistinctType.token;
|
||||
case Ast_PolyType: return node->PolyType.token;
|
||||
case Ast_ProcType: return node->ProcType.token;
|
||||
case Ast_RelativeType: return ast_token(node->RelativeType.tag);
|
||||
case Ast_PointerType: return node->PointerType.token;
|
||||
case Ast_ArrayType: return node->ArrayType.token;
|
||||
case Ast_DynamicArrayType: return node->DynamicArrayType.token;
|
||||
case Ast_StructType: return node->StructType.token;
|
||||
case Ast_UnionType: return node->UnionType.token;
|
||||
case Ast_EnumType: return node->EnumType.token;
|
||||
case Ast_BitSetType: return node->BitSetType.token;
|
||||
case Ast_MapType: return node->MapType.token;
|
||||
}
|
||||
|
||||
return empty_token;
|
||||
}
|
||||
#include "parser_pos.cpp"
|
||||
|
||||
Token token_end_of_line(AstFile *f, Token tok) {
|
||||
u8 const *start = f->tokenizer.start + tok.pos.offset;
|
||||
@@ -116,6 +10,48 @@ Token token_end_of_line(AstFile *f, Token tok) {
|
||||
return tok;
|
||||
}
|
||||
|
||||
gbString get_file_line_as_string(TokenPos const &pos, i32 *offset_) {
|
||||
AstFile *file = get_ast_file_from_id(pos.file_id);
|
||||
if (file == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
isize offset = pos.offset;
|
||||
|
||||
u8 *start = file->tokenizer.start;
|
||||
u8 *end = file->tokenizer.end;
|
||||
isize len = end-start;
|
||||
if (len < offset) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
u8 *pos_offset = start+offset;
|
||||
|
||||
u8 *line_start = pos_offset;
|
||||
u8 *line_end = pos_offset;
|
||||
while (line_start >= start) {
|
||||
if (*line_start == '\n') {
|
||||
line_start += 1;
|
||||
break;
|
||||
}
|
||||
line_start -= 1;
|
||||
}
|
||||
|
||||
while (line_end < end) {
|
||||
if (*line_end == '\n') {
|
||||
line_end -= 1;
|
||||
break;
|
||||
}
|
||||
line_end += 1;
|
||||
}
|
||||
String the_line = make_string(line_start, line_end-line_start);
|
||||
the_line = string_trim_whitespace(the_line);
|
||||
|
||||
if (offset_) *offset_ = cast(i32)(pos_offset - the_line.text);
|
||||
|
||||
return gb_string_make_length(heap_allocator(), the_line.text, the_line.len);
|
||||
}
|
||||
|
||||
|
||||
|
||||
isize ast_node_size(AstKind kind) {
|
||||
return align_formula_isize(gb_size_of(AstCommonStuff) + ast_variant_sizes[kind], gb_align_of(void *));
|
||||
@@ -241,11 +177,6 @@ Ast *clone_ast(Ast *node) {
|
||||
n->FieldValue.value = clone_ast(n->FieldValue.value);
|
||||
break;
|
||||
|
||||
case Ast_TernaryExpr:
|
||||
n->TernaryExpr.cond = clone_ast(n->TernaryExpr.cond);
|
||||
n->TernaryExpr.x = clone_ast(n->TernaryExpr.x);
|
||||
n->TernaryExpr.y = clone_ast(n->TernaryExpr.y);
|
||||
break;
|
||||
case Ast_TernaryIfExpr:
|
||||
n->TernaryIfExpr.x = clone_ast(n->TernaryIfExpr.x);
|
||||
n->TernaryIfExpr.cond = clone_ast(n->TernaryIfExpr.cond);
|
||||
@@ -438,12 +369,15 @@ Ast *clone_ast(Ast *node) {
|
||||
|
||||
void error(Ast *node, char const *fmt, ...) {
|
||||
Token token = {};
|
||||
TokenPos end_pos = {};
|
||||
if (node != nullptr) {
|
||||
token = ast_token(node);
|
||||
end_pos = ast_end_pos(node);
|
||||
}
|
||||
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
error_va(token, fmt, va);
|
||||
error_va(token.pos, end_pos, fmt, va);
|
||||
va_end(va);
|
||||
if (node != nullptr && node->file != nullptr) {
|
||||
node->file->error_count += 1;
|
||||
@@ -457,7 +391,7 @@ void error_no_newline(Ast *node, char const *fmt, ...) {
|
||||
}
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
error_no_newline_va(token, fmt, va);
|
||||
error_no_newline_va(token.pos, fmt, va);
|
||||
va_end(va);
|
||||
if (node != nullptr && node->file != nullptr) {
|
||||
node->file->error_count += 1;
|
||||
@@ -465,16 +399,28 @@ void error_no_newline(Ast *node, char const *fmt, ...) {
|
||||
}
|
||||
|
||||
void warning(Ast *node, char const *fmt, ...) {
|
||||
Token token = {};
|
||||
TokenPos end_pos = {};
|
||||
if (node != nullptr) {
|
||||
token = ast_token(node);
|
||||
end_pos = ast_end_pos(node);
|
||||
}
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
warning_va(ast_token(node), fmt, va);
|
||||
warning_va(token.pos, end_pos, fmt, va);
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
void syntax_error(Ast *node, char const *fmt, ...) {
|
||||
Token token = {};
|
||||
TokenPos end_pos = {};
|
||||
if (node != nullptr) {
|
||||
token = ast_token(node);
|
||||
end_pos = ast_end_pos(node);
|
||||
}
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
syntax_error_va(ast_token(node), fmt, va);
|
||||
syntax_error_va(token.pos, end_pos, fmt, va);
|
||||
va_end(va);
|
||||
if (node != nullptr && node->file != nullptr) {
|
||||
node->file->error_count += 1;
|
||||
@@ -646,7 +592,7 @@ Ast *ast_basic_lit(AstFile *f, Token basic_lit) {
|
||||
return result;
|
||||
}
|
||||
|
||||
Ast *ast_basic_directive(AstFile *f, Token token, String name) {
|
||||
Ast *ast_basic_directive(AstFile *f, Token token, Token name) {
|
||||
Ast *result = alloc_ast_node(f, Ast_BasicDirective);
|
||||
result->BasicDirective.token = token;
|
||||
result->BasicDirective.name = name;
|
||||
@@ -698,13 +644,6 @@ Ast *ast_compound_lit(AstFile *f, Ast *type, Array<Ast *> const &elems, Token op
|
||||
}
|
||||
|
||||
|
||||
Ast *ast_ternary_expr(AstFile *f, Ast *cond, Ast *x, Ast *y) {
|
||||
Ast *result = alloc_ast_node(f, Ast_TernaryExpr);
|
||||
result->TernaryExpr.cond = cond;
|
||||
result->TernaryExpr.x = x;
|
||||
result->TernaryExpr.y = y;
|
||||
return result;
|
||||
}
|
||||
Ast *ast_ternary_if_expr(AstFile *f, Ast *x, Ast *cond, Ast *y) {
|
||||
Ast *result = alloc_ast_node(f, Ast_TernaryIfExpr);
|
||||
result->TernaryIfExpr.x = x;
|
||||
@@ -1357,6 +1296,7 @@ Token expect_token_after(AstFile *f, TokenKind kind, char const *msg) {
|
||||
bool is_token_range(TokenKind kind) {
|
||||
switch (kind) {
|
||||
case Token_Ellipsis:
|
||||
case Token_RangeFull:
|
||||
case Token_RangeHalf:
|
||||
return true;
|
||||
}
|
||||
@@ -1587,6 +1527,10 @@ void expect_semicolon(AstFile *f, Ast *s) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (f->curr_token.kind == Token_EOF) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (s != nullptr) {
|
||||
bool insert_semi = (f->tokenizer.flags & TokenizerFlag_InsertSemicolon) != 0;
|
||||
if (insert_semi) {
|
||||
@@ -2007,35 +1951,28 @@ Ast *parse_operand(AstFile *f, bool lhs) {
|
||||
Token name = expect_token(f, Token_Ident);
|
||||
if (name.string == "type") {
|
||||
return ast_helper_type(f, token, parse_type(f));
|
||||
} /* else if (name.string == "no_deferred") {
|
||||
operand = parse_expr(f, false);
|
||||
if (unparen_expr(operand)->kind != Ast_CallExpr) {
|
||||
syntax_error(operand, "#no_deferred can only be applied to procedure calls");
|
||||
operand = ast_bad_expr(f, token, f->curr_token);
|
||||
}
|
||||
operand->state_flags |= StateFlag_no_deferred;
|
||||
} */ else if (name.string == "file") {
|
||||
return ast_basic_directive(f, token, name.string);
|
||||
} else if (name.string == "line") { return ast_basic_directive(f, token, name.string);
|
||||
} else if (name.string == "procedure") { return ast_basic_directive(f, token, name.string);
|
||||
} else if (name.string == "caller_location") { return ast_basic_directive(f, token, name.string);
|
||||
} else if (name.string == "file") {
|
||||
return ast_basic_directive(f, token, name);
|
||||
} else if (name.string == "line") { return ast_basic_directive(f, token, name);
|
||||
} else if (name.string == "procedure") { return ast_basic_directive(f, token, name);
|
||||
} else if (name.string == "caller_location") { return ast_basic_directive(f, token, name);
|
||||
} else if (name.string == "location") {
|
||||
Ast *tag = ast_basic_directive(f, token, name.string);
|
||||
Ast *tag = ast_basic_directive(f, token, name);
|
||||
return parse_call_expr(f, tag);
|
||||
} else if (name.string == "load") {
|
||||
Ast *tag = ast_basic_directive(f, token, name.string);
|
||||
Ast *tag = ast_basic_directive(f, token, name);
|
||||
return parse_call_expr(f, tag);
|
||||
} else if (name.string == "assert") {
|
||||
Ast *tag = ast_basic_directive(f, token, name.string);
|
||||
Ast *tag = ast_basic_directive(f, token, name);
|
||||
return parse_call_expr(f, tag);
|
||||
} else if (name.string == "defined") {
|
||||
Ast *tag = ast_basic_directive(f, token, name.string);
|
||||
Ast *tag = ast_basic_directive(f, token, name);
|
||||
return parse_call_expr(f, tag);
|
||||
} else if (name.string == "config") {
|
||||
Ast *tag = ast_basic_directive(f, token, name.string);
|
||||
Ast *tag = ast_basic_directive(f, token, name);
|
||||
return parse_call_expr(f, tag);
|
||||
} else if (name.string == "soa" || name.string == "simd") {
|
||||
Ast *tag = ast_basic_directive(f, token, name.string);
|
||||
Ast *tag = ast_basic_directive(f, token, name);
|
||||
Ast *original_type = parse_type(f);
|
||||
Ast *type = unparen_expr(original_type);
|
||||
switch (type->kind) {
|
||||
@@ -2047,7 +1984,7 @@ Ast *parse_operand(AstFile *f, bool lhs) {
|
||||
}
|
||||
return original_type;
|
||||
} else if (name.string == "partial") {
|
||||
Ast *tag = ast_basic_directive(f, token, name.string);
|
||||
Ast *tag = ast_basic_directive(f, token, name);
|
||||
Ast *original_type = parse_type(f);
|
||||
Ast *type = unparen_expr(original_type);
|
||||
switch (type->kind) {
|
||||
@@ -2059,6 +1996,10 @@ Ast *parse_operand(AstFile *f, bool lhs) {
|
||||
return original_type;
|
||||
} else if (name.string == "bounds_check") {
|
||||
Ast *operand = parse_expr(f, lhs);
|
||||
if (operand == nullptr) {
|
||||
syntax_error(token, "Invalid expresssion for #%.*s", LIT(name.string));
|
||||
return nullptr;
|
||||
}
|
||||
operand->state_flags |= StateFlag_bounds_check;
|
||||
if ((operand->state_flags & StateFlag_no_bounds_check) != 0) {
|
||||
syntax_error(token, "#bounds_check and #no_bounds_check cannot be applied together");
|
||||
@@ -2066,13 +2007,17 @@ Ast *parse_operand(AstFile *f, bool lhs) {
|
||||
return operand;
|
||||
} else if (name.string == "no_bounds_check") {
|
||||
Ast *operand = parse_expr(f, lhs);
|
||||
if (operand == nullptr) {
|
||||
syntax_error(token, "Invalid expresssion for #%.*s", LIT(name.string));
|
||||
return nullptr;
|
||||
}
|
||||
operand->state_flags |= StateFlag_no_bounds_check;
|
||||
if ((operand->state_flags & StateFlag_bounds_check) != 0) {
|
||||
syntax_error(token, "#bounds_check and #no_bounds_check cannot be applied together");
|
||||
}
|
||||
return operand;
|
||||
} else if (name.string == "relative") {
|
||||
Ast *tag = ast_basic_directive(f, token, name.string);
|
||||
Ast *tag = ast_basic_directive(f, token, name);
|
||||
tag = parse_call_expr(f, tag);
|
||||
Ast *type = parse_type(f);
|
||||
return ast_relative_type(f, tag, type);
|
||||
@@ -2158,6 +2103,8 @@ Ast *parse_operand(AstFile *f, bool lhs) {
|
||||
return type;
|
||||
}
|
||||
|
||||
skip_possible_newline_for_literal(f);
|
||||
|
||||
if (allow_token(f, Token_Undef)) {
|
||||
if (where_token.kind != Token_Invalid) {
|
||||
syntax_error(where_token, "'where' clauses are not allowed on procedure literals without a defined body (replaced with ---)");
|
||||
@@ -2170,6 +2117,14 @@ Ast *parse_operand(AstFile *f, bool lhs) {
|
||||
body = parse_body(f);
|
||||
f->curr_proc = curr_proc;
|
||||
|
||||
// Apply the tags directly to the body rather than the type
|
||||
if (tags & ProcTag_no_bounds_check) {
|
||||
body->state_flags |= StateFlag_no_bounds_check;
|
||||
}
|
||||
if (tags & ProcTag_bounds_check) {
|
||||
body->state_flags |= StateFlag_bounds_check;
|
||||
}
|
||||
|
||||
return ast_proc_lit(f, type, body, tags, where_token, where_clauses);
|
||||
} else if (allow_token(f, Token_do)) {
|
||||
Ast *curr_proc = f->curr_proc;
|
||||
@@ -2317,7 +2272,7 @@ Ast *parse_operand(AstFile *f, bool lhs) {
|
||||
f->expr_level = prev_level;
|
||||
}
|
||||
|
||||
|
||||
skip_possible_newline_for_literal(f);
|
||||
Token open = expect_token_after(f, Token_OpenBrace, "struct");
|
||||
|
||||
isize name_count = 0;
|
||||
@@ -2394,6 +2349,7 @@ Ast *parse_operand(AstFile *f, bool lhs) {
|
||||
}
|
||||
|
||||
|
||||
skip_possible_newline_for_literal(f);
|
||||
Token open = expect_token_after(f, Token_OpenBrace, "union");
|
||||
|
||||
while (f->curr_token.kind != Token_CloseBrace &&
|
||||
@@ -2418,6 +2374,8 @@ Ast *parse_operand(AstFile *f, bool lhs) {
|
||||
if (f->curr_token.kind != Token_OpenBrace) {
|
||||
base_type = parse_type(f);
|
||||
}
|
||||
|
||||
skip_possible_newline_for_literal(f);
|
||||
Token open = expect_token(f, Token_OpenBrace);
|
||||
|
||||
Array<Ast *> values = parse_element_list(f);
|
||||
@@ -2509,6 +2467,7 @@ Ast *parse_operand(AstFile *f, bool lhs) {
|
||||
}
|
||||
}
|
||||
|
||||
skip_possible_newline_for_literal(f);
|
||||
Token open = expect_token(f, Token_OpenBrace);
|
||||
Ast *asm_string = parse_expr(f, false);
|
||||
expect_token(f, Token_Comma);
|
||||
@@ -2673,6 +2632,7 @@ Ast *parse_atom_expr(AstFile *f, Ast *operand, bool lhs) {
|
||||
|
||||
switch (f->curr_token.kind) {
|
||||
case Token_Ellipsis:
|
||||
case Token_RangeFull:
|
||||
case Token_RangeHalf:
|
||||
// NOTE(bill): Do not err yet
|
||||
case Token_Colon:
|
||||
@@ -2684,6 +2644,7 @@ Ast *parse_atom_expr(AstFile *f, Ast *operand, bool lhs) {
|
||||
|
||||
switch (f->curr_token.kind) {
|
||||
case Token_Ellipsis:
|
||||
case Token_RangeFull:
|
||||
case Token_RangeHalf:
|
||||
syntax_error(f->curr_token, "Expected a colon, not a range");
|
||||
/* fallthrough */
|
||||
@@ -2722,6 +2683,16 @@ Ast *parse_atom_expr(AstFile *f, Ast *operand, bool lhs) {
|
||||
}
|
||||
break;
|
||||
|
||||
case Token_Increment:
|
||||
case Token_Decrement:
|
||||
if (!lhs) {
|
||||
Token token = advance_token(f);
|
||||
syntax_error(token, "Postfix '%.*s' operator is not supported", LIT(token.string));
|
||||
} else {
|
||||
loop = false;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
loop = false;
|
||||
break;
|
||||
@@ -2752,16 +2723,26 @@ Ast *parse_unary_expr(AstFile *f, bool lhs) {
|
||||
return ast_auto_cast(f, token, expr);
|
||||
}
|
||||
|
||||
|
||||
case Token_Add:
|
||||
case Token_Sub:
|
||||
case Token_Not:
|
||||
case Token_Xor:
|
||||
case Token_And: {
|
||||
case Token_And:
|
||||
case Token_Not: {
|
||||
Token token = advance_token(f);
|
||||
Ast *expr = parse_unary_expr(f, lhs);
|
||||
return ast_unary_expr(f, token, expr);
|
||||
}
|
||||
|
||||
case Token_Increment:
|
||||
case Token_Decrement: {
|
||||
Token token = advance_token(f);
|
||||
syntax_error(token, "Unary '%.*s' operator is not supported", LIT(token.string));
|
||||
Ast *expr = parse_unary_expr(f, lhs);
|
||||
return ast_unary_expr(f, token, expr);
|
||||
}
|
||||
|
||||
|
||||
case Token_Period: {
|
||||
Token token = expect_token(f, Token_Period);
|
||||
Ast *ident = parse_ident(f);
|
||||
@@ -2790,6 +2771,7 @@ i32 token_precedence(AstFile *f, TokenKind t) {
|
||||
case Token_when:
|
||||
return 1;
|
||||
case Token_Ellipsis:
|
||||
case Token_RangeFull:
|
||||
case Token_RangeHalf:
|
||||
if (!f->allow_range) {
|
||||
return 0;
|
||||
@@ -2857,7 +2839,7 @@ Ast *parse_binary_expr(AstFile *f, bool lhs, i32 prec_in) {
|
||||
Ast *x = parse_expr(f, lhs);
|
||||
Token token_c = expect_token(f, Token_Colon);
|
||||
Ast *y = parse_expr(f, lhs);
|
||||
expr = ast_ternary_expr(f, cond, x, y);
|
||||
expr = ast_ternary_if_expr(f, x, cond, y);
|
||||
} else if (op.kind == Token_if) {
|
||||
Ast *x = expr;
|
||||
// Token_if
|
||||
@@ -2979,7 +2961,7 @@ Ast *parse_foreign_block(AstFile *f, Token token) {
|
||||
defer (f->in_foreign_block = prev_in_foreign_block);
|
||||
f->in_foreign_block = true;
|
||||
|
||||
|
||||
skip_possible_newline_for_literal(f);
|
||||
open = expect_token(f, Token_OpenBrace);
|
||||
|
||||
while (f->curr_token.kind != Token_CloseBrace &&
|
||||
@@ -3151,6 +3133,13 @@ Ast *parse_simple_stmt(AstFile *f, u32 flags) {
|
||||
return ast_bad_stmt(f, token, f->curr_token);
|
||||
}
|
||||
|
||||
switch (token.kind) {
|
||||
case Token_Increment:
|
||||
case Token_Decrement:
|
||||
advance_token(f);
|
||||
syntax_error(token, "Postfix '%.*s' statement is not supported", LIT(token.string));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
#if 0
|
||||
@@ -3220,6 +3209,7 @@ ProcCallingConvention string_to_calling_convention(String s) {
|
||||
if (s == "fastcall") return ProcCC_FastCall;
|
||||
if (s == "fast") return ProcCC_FastCall;
|
||||
if (s == "none") return ProcCC_None;
|
||||
if (s == "naked") return ProcCC_Naked;
|
||||
return ProcCC_Invalid;
|
||||
}
|
||||
|
||||
@@ -3896,12 +3886,6 @@ Ast *parse_return_stmt(AstFile *f) {
|
||||
|
||||
while (f->curr_token.kind != Token_Semicolon) {
|
||||
Ast *arg = parse_expr(f, false);
|
||||
// if (f->curr_token.kind == Token_Eq) {
|
||||
// Token eq = expect_token(f, Token_Eq);
|
||||
// Ast *value = parse_value(f);
|
||||
// arg = ast_field_value(f, arg, value, eq);
|
||||
// }
|
||||
|
||||
array_add(&results, arg);
|
||||
if (f->curr_token.kind != Token_Comma ||
|
||||
f->curr_token.kind == Token_EOF) {
|
||||
@@ -3966,7 +3950,7 @@ Ast *parse_for_stmt(AstFile *f) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_range && allow_token(f, Token_Semicolon)) {
|
||||
if (!is_range && parse_control_statement_semicolon_separator(f)) {
|
||||
init = cond;
|
||||
cond = nullptr;
|
||||
if (f->curr_token.kind != Token_Semicolon) {
|
||||
@@ -4022,7 +4006,7 @@ Ast *parse_case_clause(AstFile *f, bool is_type) {
|
||||
}
|
||||
f->allow_range = prev_allow_range;
|
||||
f->allow_in_expr = prev_allow_in_expr;
|
||||
expect_token(f, Token_Colon); // TODO(bill): Is this the best syntax?
|
||||
expect_token(f, Token_Colon);
|
||||
Array<Ast *> stmts = parse_stmt_list(f);
|
||||
|
||||
return ast_case_clause(f, token, list, stmts);
|
||||
@@ -4332,6 +4316,16 @@ Ast *parse_unrolled_for_loop(AstFile *f, Token unroll_token) {
|
||||
return ast_unroll_range_stmt(f, unroll_token, for_token, val0, val1, in_token, expr, body);
|
||||
}
|
||||
|
||||
void parse_check_directive_for_empty_statement(Ast *s, Token const &name) {
|
||||
if (s != nullptr && s->kind == Ast_EmptyStmt) {
|
||||
if (s->EmptyStmt.token.string == "\n") {
|
||||
syntax_error(name, "#%.*s cannot be followed by a newline", LIT(name.string));
|
||||
} else {
|
||||
syntax_error(name, "#%.*s cannot be applied to an empty statement ';'", LIT(name.string));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ast *parse_stmt(AstFile *f) {
|
||||
Ast *s = nullptr;
|
||||
Token token = f->curr_token;
|
||||
@@ -4438,6 +4432,7 @@ Ast *parse_stmt(AstFile *f) {
|
||||
|
||||
if (tag == "bounds_check") {
|
||||
s = parse_stmt(f);
|
||||
parse_check_directive_for_empty_statement(s, name);
|
||||
s->state_flags |= StateFlag_bounds_check;
|
||||
if ((s->state_flags & StateFlag_no_bounds_check) != 0) {
|
||||
syntax_error(token, "#bounds_check and #no_bounds_check cannot be applied together");
|
||||
@@ -4445,27 +4440,12 @@ Ast *parse_stmt(AstFile *f) {
|
||||
return s;
|
||||
} else if (tag == "no_bounds_check") {
|
||||
s = parse_stmt(f);
|
||||
parse_check_directive_for_empty_statement(s, name);
|
||||
s->state_flags |= StateFlag_no_bounds_check;
|
||||
if ((s->state_flags & StateFlag_bounds_check) != 0) {
|
||||
syntax_error(token, "#bounds_check and #no_bounds_check cannot be applied together");
|
||||
}
|
||||
return s;
|
||||
} else if (tag == "complete") {
|
||||
s = parse_stmt(f);
|
||||
switch (s->kind) {
|
||||
case Ast_SwitchStmt:
|
||||
s->SwitchStmt.partial = false;
|
||||
syntax_warning(token, "#complete is now the default and has been replaced with its opposite: #partial");
|
||||
break;
|
||||
case Ast_TypeSwitchStmt:
|
||||
s->TypeSwitchStmt.partial = false;
|
||||
syntax_warning(token, "#complete is now the default and has been replaced with its opposite: #partial");
|
||||
break;
|
||||
default:
|
||||
syntax_error(token, "#complete can only be applied to a switch statement");
|
||||
break;
|
||||
}
|
||||
return s;
|
||||
} else if (tag == "partial") {
|
||||
s = parse_stmt(f);
|
||||
switch (s->kind) {
|
||||
@@ -4475,16 +4455,19 @@ Ast *parse_stmt(AstFile *f) {
|
||||
case Ast_TypeSwitchStmt:
|
||||
s->TypeSwitchStmt.partial = true;
|
||||
break;
|
||||
case Ast_EmptyStmt:
|
||||
parse_check_directive_for_empty_statement(s, name);
|
||||
break;
|
||||
default:
|
||||
syntax_error(token, "#partial can only be applied to a switch statement");
|
||||
break;
|
||||
}
|
||||
return s;
|
||||
} else if (tag == "assert") {
|
||||
Ast *t = ast_basic_directive(f, hash_token, tag);
|
||||
Ast *t = ast_basic_directive(f, hash_token, name);
|
||||
return ast_expr_stmt(f, parse_call_expr(f, t));
|
||||
} else if (tag == "panic") {
|
||||
Ast *t = ast_basic_directive(f, hash_token, tag);
|
||||
Ast *t = ast_basic_directive(f, hash_token, name);
|
||||
return ast_expr_stmt(f, parse_call_expr(f, t));
|
||||
} else if (name.string == "force_inline" ||
|
||||
name.string == "force_no_inline") {
|
||||
@@ -4571,6 +4554,7 @@ ParseFileError init_ast_file(AstFile *f, String fullpath, TokenPos *err_pos) {
|
||||
GB_ASSERT(f != nullptr);
|
||||
f->fullpath = string_trim_whitespace(fullpath); // Just in case
|
||||
set_file_path_string(f->id, fullpath);
|
||||
set_ast_file_from_id(f->id, f);
|
||||
if (!string_ends_with(f->fullpath, str_lit(".odin"))) {
|
||||
return ParseFile_WrongExtension;
|
||||
}
|
||||
|
||||
+26
-21
@@ -7,22 +7,21 @@ struct AstFile;
|
||||
struct AstPackage;
|
||||
|
||||
enum AddressingMode {
|
||||
Addressing_Invalid, // invalid addressing mode
|
||||
Addressing_NoValue, // no value (void in C)
|
||||
Addressing_Value, // computed value (rvalue)
|
||||
Addressing_Context, // context value
|
||||
Addressing_Variable, // addressable variable (lvalue)
|
||||
Addressing_Constant, // constant
|
||||
Addressing_Type, // type
|
||||
Addressing_Builtin, // built-in procedure
|
||||
Addressing_ProcGroup, // procedure group (overloaded procedure)
|
||||
Addressing_MapIndex, // map index expression -
|
||||
// lhs: acts like a Variable
|
||||
// rhs: acts like OptionalOk
|
||||
Addressing_OptionalOk, // rhs: acts like a value with an optional boolean part (for existence check)
|
||||
Addressing_SoaVariable, // Struct-Of-Arrays indexed variable
|
||||
Addressing_Invalid = 0, // invalid addressing mode
|
||||
Addressing_NoValue = 1, // no value (void in C)
|
||||
Addressing_Value = 2, // computed value (rvalue)
|
||||
Addressing_Context = 3, // context value
|
||||
Addressing_Variable = 4, // addressable variable (lvalue)
|
||||
Addressing_Constant = 5, // constant
|
||||
Addressing_Type = 6, // type
|
||||
Addressing_Builtin = 7, // built-in procedure
|
||||
Addressing_ProcGroup = 8, // procedure group (overloaded procedure)
|
||||
Addressing_MapIndex = 9, // map index expression -
|
||||
// lhs: acts like a Variable
|
||||
// rhs: acts like OptionalOk
|
||||
Addressing_OptionalOk = 10, // rhs: acts like a value with an optional boolean part (for existence check)
|
||||
Addressing_SoaVariable = 11, // Struct-Of-Arrays indexed variable
|
||||
|
||||
Addressing_AtomOpAssign, // Specialized for custom atom operations for assignments
|
||||
};
|
||||
|
||||
struct TypeAndValue {
|
||||
@@ -215,8 +214,9 @@ enum ProcCallingConvention {
|
||||
ProcCC_FastCall = 5,
|
||||
|
||||
ProcCC_None = 6,
|
||||
ProcCC_Naked = 7,
|
||||
|
||||
ProcCC_InlineAsm = 7,
|
||||
ProcCC_InlineAsm = 8,
|
||||
|
||||
ProcCC_MAX,
|
||||
|
||||
@@ -286,8 +286,8 @@ char const *inline_asm_dialect_strings[InlineAsmDialect_COUNT] = {
|
||||
Token token; \
|
||||
}) \
|
||||
AST_KIND(BasicDirective, "basic directive", struct { \
|
||||
Token token; \
|
||||
String name; \
|
||||
Token token; \
|
||||
Token name; \
|
||||
}) \
|
||||
AST_KIND(Ellipsis, "ellipsis", struct { \
|
||||
Token token; \
|
||||
@@ -324,7 +324,7 @@ AST_KIND(_ExprBegin, "", bool) \
|
||||
AST_KIND(ImplicitSelectorExpr, "implicit selector expression", struct { Token token; Ast *selector; }) \
|
||||
AST_KIND(SelectorCallExpr, "selector call expression", struct { Token token; Ast *expr, *call; bool modified_call; }) \
|
||||
AST_KIND(IndexExpr, "index expression", struct { Ast *expr, *index; Token open, close; }) \
|
||||
AST_KIND(DerefExpr, "dereference expression", struct { Token op; Ast *expr; }) \
|
||||
AST_KIND(DerefExpr, "dereference expression", struct { Ast *expr; Token op; }) \
|
||||
AST_KIND(SliceExpr, "slice expression", struct { \
|
||||
Ast *expr; \
|
||||
Token open, close; \
|
||||
@@ -342,10 +342,15 @@ AST_KIND(_ExprBegin, "", bool) \
|
||||
i32 builtin_id; \
|
||||
}) \
|
||||
AST_KIND(FieldValue, "field value", struct { Token eq; Ast *field, *value; }) \
|
||||
AST_KIND(TernaryExpr, "ternary expression", struct { Ast *cond, *x, *y; }) \
|
||||
AST_KIND(TernaryIfExpr, "ternary if expression", struct { Ast *x, *cond, *y; }) \
|
||||
AST_KIND(TernaryWhenExpr, "ternary when expression", struct { Ast *x, *cond, *y; }) \
|
||||
AST_KIND(TypeAssertion, "type assertion", struct { Ast *expr; Token dot; Ast *type; Type *type_hint; }) \
|
||||
AST_KIND(TypeAssertion, "type assertion", struct { \
|
||||
Ast *expr; \
|
||||
Token dot; \
|
||||
Ast *type; \
|
||||
Type *type_hint; \
|
||||
bool ignores[2]; \
|
||||
}) \
|
||||
AST_KIND(TypeCast, "type cast", struct { Token token; Ast *type, *expr; }) \
|
||||
AST_KIND(AutoCast, "auto_cast", struct { Token token; Ast *expr; }) \
|
||||
AST_KIND(InlineAsmExpr, "inline asm expression", struct { \
|
||||
|
||||
@@ -0,0 +1,331 @@
|
||||
Token ast_token(Ast *node) {
|
||||
switch (node->kind) {
|
||||
case Ast_Ident: return node->Ident.token;
|
||||
case Ast_Implicit: return node->Implicit;
|
||||
case Ast_Undef: return node->Undef;
|
||||
case Ast_BasicLit: return node->BasicLit.token;
|
||||
case Ast_BasicDirective: return node->BasicDirective.token;
|
||||
case Ast_ProcGroup: return node->ProcGroup.token;
|
||||
case Ast_ProcLit: return ast_token(node->ProcLit.type);
|
||||
case Ast_CompoundLit:
|
||||
if (node->CompoundLit.type != nullptr) {
|
||||
return ast_token(node->CompoundLit.type);
|
||||
}
|
||||
return node->CompoundLit.open;
|
||||
|
||||
case Ast_TagExpr: return node->TagExpr.token;
|
||||
case Ast_BadExpr: return node->BadExpr.begin;
|
||||
case Ast_UnaryExpr: return node->UnaryExpr.op;
|
||||
case Ast_BinaryExpr: return ast_token(node->BinaryExpr.left);
|
||||
case Ast_ParenExpr: return node->ParenExpr.open;
|
||||
case Ast_CallExpr: return ast_token(node->CallExpr.proc);
|
||||
case Ast_SelectorExpr:
|
||||
if (node->SelectorExpr.selector != nullptr) {
|
||||
return ast_token(node->SelectorExpr.selector);
|
||||
}
|
||||
return node->SelectorExpr.token;
|
||||
case Ast_SelectorCallExpr:
|
||||
if (node->SelectorCallExpr.expr != nullptr) {
|
||||
return ast_token(node->SelectorCallExpr.expr);
|
||||
}
|
||||
return node->SelectorCallExpr.token;
|
||||
case Ast_ImplicitSelectorExpr:
|
||||
if (node->ImplicitSelectorExpr.selector != nullptr) {
|
||||
return ast_token(node->ImplicitSelectorExpr.selector);
|
||||
}
|
||||
return node->ImplicitSelectorExpr.token;
|
||||
case Ast_IndexExpr: return node->IndexExpr.open;
|
||||
case Ast_SliceExpr: return node->SliceExpr.open;
|
||||
case Ast_Ellipsis: return node->Ellipsis.token;
|
||||
case Ast_FieldValue: return node->FieldValue.eq;
|
||||
case Ast_DerefExpr: return node->DerefExpr.op;
|
||||
case Ast_TernaryIfExpr: return ast_token(node->TernaryIfExpr.x);
|
||||
case Ast_TernaryWhenExpr: return ast_token(node->TernaryWhenExpr.x);
|
||||
case Ast_TypeAssertion: return ast_token(node->TypeAssertion.expr);
|
||||
case Ast_TypeCast: return node->TypeCast.token;
|
||||
case Ast_AutoCast: return node->AutoCast.token;
|
||||
case Ast_InlineAsmExpr: return node->InlineAsmExpr.token;
|
||||
|
||||
case Ast_BadStmt: return node->BadStmt.begin;
|
||||
case Ast_EmptyStmt: return node->EmptyStmt.token;
|
||||
case Ast_ExprStmt: return ast_token(node->ExprStmt.expr);
|
||||
case Ast_TagStmt: return node->TagStmt.token;
|
||||
case Ast_AssignStmt: return node->AssignStmt.op;
|
||||
case Ast_BlockStmt: return node->BlockStmt.open;
|
||||
case Ast_IfStmt: return node->IfStmt.token;
|
||||
case Ast_WhenStmt: return node->WhenStmt.token;
|
||||
case Ast_ReturnStmt: return node->ReturnStmt.token;
|
||||
case Ast_ForStmt: return node->ForStmt.token;
|
||||
case Ast_RangeStmt: return node->RangeStmt.token;
|
||||
case Ast_UnrollRangeStmt: return node->UnrollRangeStmt.unroll_token;
|
||||
case Ast_CaseClause: return node->CaseClause.token;
|
||||
case Ast_SwitchStmt: return node->SwitchStmt.token;
|
||||
case Ast_TypeSwitchStmt: return node->TypeSwitchStmt.token;
|
||||
case Ast_DeferStmt: return node->DeferStmt.token;
|
||||
case Ast_BranchStmt: return node->BranchStmt.token;
|
||||
case Ast_UsingStmt: return node->UsingStmt.token;
|
||||
|
||||
case Ast_BadDecl: return node->BadDecl.begin;
|
||||
case Ast_Label: return node->Label.token;
|
||||
|
||||
case Ast_ValueDecl: return ast_token(node->ValueDecl.names[0]);
|
||||
case Ast_PackageDecl: return node->PackageDecl.token;
|
||||
case Ast_ImportDecl: return node->ImportDecl.token;
|
||||
case Ast_ForeignImportDecl: return node->ForeignImportDecl.token;
|
||||
|
||||
case Ast_ForeignBlockDecl: return node->ForeignBlockDecl.token;
|
||||
|
||||
case Ast_Attribute:
|
||||
return node->Attribute.token;
|
||||
|
||||
case Ast_Field:
|
||||
if (node->Field.names.count > 0) {
|
||||
return ast_token(node->Field.names[0]);
|
||||
}
|
||||
return ast_token(node->Field.type);
|
||||
case Ast_FieldList:
|
||||
return node->FieldList.token;
|
||||
|
||||
case Ast_TypeidType: return node->TypeidType.token;
|
||||
case Ast_HelperType: return node->HelperType.token;
|
||||
case Ast_DistinctType: return node->DistinctType.token;
|
||||
case Ast_PolyType: return node->PolyType.token;
|
||||
case Ast_ProcType: return node->ProcType.token;
|
||||
case Ast_RelativeType: return ast_token(node->RelativeType.tag);
|
||||
case Ast_PointerType: return node->PointerType.token;
|
||||
case Ast_ArrayType: return node->ArrayType.token;
|
||||
case Ast_DynamicArrayType: return node->DynamicArrayType.token;
|
||||
case Ast_StructType: return node->StructType.token;
|
||||
case Ast_UnionType: return node->UnionType.token;
|
||||
case Ast_EnumType: return node->EnumType.token;
|
||||
case Ast_BitSetType: return node->BitSetType.token;
|
||||
case Ast_MapType: return node->MapType.token;
|
||||
}
|
||||
|
||||
return empty_token;
|
||||
}
|
||||
|
||||
TokenPos token_pos_end(Token const &token) {
|
||||
TokenPos pos = token.pos;
|
||||
pos.offset += cast(i32)token.string.len;
|
||||
for (isize i = 0; i < token.string.len; i++) {
|
||||
// TODO(bill): This assumes ASCII
|
||||
char c = token.string[i];
|
||||
if (c == '\n') {
|
||||
pos.line += 1;
|
||||
pos.column = 1;
|
||||
} else {
|
||||
pos.column += 1;
|
||||
}
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
Token ast_end_token(Ast *node) {
|
||||
GB_ASSERT(node != nullptr);
|
||||
|
||||
switch (node->kind) {
|
||||
case Ast_Ident: return node->Ident.token;
|
||||
case Ast_Implicit: return node->Implicit;
|
||||
case Ast_Undef: return node->Undef;
|
||||
case Ast_BasicLit: return node->BasicLit.token;
|
||||
case Ast_BasicDirective: return node->BasicDirective.token;
|
||||
case Ast_ProcGroup: return node->ProcGroup.close;
|
||||
case Ast_ProcLit:
|
||||
if (node->ProcLit.body) {
|
||||
return ast_end_token(node->ProcLit.body);
|
||||
}
|
||||
return ast_end_token(node->ProcLit.type);
|
||||
case Ast_CompoundLit:
|
||||
return node->CompoundLit.close;
|
||||
|
||||
case Ast_BadExpr: return node->BadExpr.end;
|
||||
case Ast_TagExpr: return ast_end_token(node->TagExpr.expr);
|
||||
case Ast_UnaryExpr: return ast_end_token(node->UnaryExpr.expr);
|
||||
case Ast_BinaryExpr: return ast_end_token(node->BinaryExpr.right);
|
||||
case Ast_ParenExpr: return node->ParenExpr.close;
|
||||
case Ast_CallExpr: return node->CallExpr.close;
|
||||
case Ast_SelectorExpr:
|
||||
return ast_end_token(node->SelectorExpr.selector);
|
||||
case Ast_SelectorCallExpr:
|
||||
return ast_end_token(node->SelectorCallExpr.call);
|
||||
case Ast_ImplicitSelectorExpr:
|
||||
return ast_end_token(node->SelectorExpr.selector);
|
||||
case Ast_IndexExpr: return node->IndexExpr.close;
|
||||
case Ast_SliceExpr: return node->SliceExpr.close;
|
||||
case Ast_Ellipsis:
|
||||
if (node->Ellipsis.expr) {
|
||||
return ast_end_token(node->Ellipsis.expr);
|
||||
}
|
||||
return node->Ellipsis.token;
|
||||
case Ast_FieldValue: return ast_end_token(node->FieldValue.value);
|
||||
case Ast_DerefExpr: return node->DerefExpr.op;
|
||||
case Ast_TernaryIfExpr: return ast_end_token(node->TernaryIfExpr.y);
|
||||
case Ast_TernaryWhenExpr: return ast_end_token(node->TernaryWhenExpr.y);
|
||||
case Ast_TypeAssertion: return ast_end_token(node->TypeAssertion.type);
|
||||
case Ast_TypeCast: return ast_end_token(node->TypeCast.expr);
|
||||
case Ast_AutoCast: return ast_end_token(node->AutoCast.expr);
|
||||
case Ast_InlineAsmExpr: return node->InlineAsmExpr.close;
|
||||
|
||||
case Ast_BadStmt: return node->BadStmt.end;
|
||||
case Ast_EmptyStmt: return node->EmptyStmt.token;
|
||||
case Ast_ExprStmt: return ast_end_token(node->ExprStmt.expr);
|
||||
case Ast_TagStmt: return ast_end_token(node->TagStmt.stmt);
|
||||
case Ast_AssignStmt:
|
||||
if (node->AssignStmt.rhs.count > 0) {
|
||||
return ast_end_token(node->AssignStmt.rhs[node->AssignStmt.rhs.count-1]);
|
||||
}
|
||||
return node->AssignStmt.op;
|
||||
case Ast_BlockStmt: return node->BlockStmt.close;
|
||||
case Ast_IfStmt:
|
||||
if (node->IfStmt.else_stmt) {
|
||||
return ast_end_token(node->IfStmt.else_stmt);
|
||||
}
|
||||
return ast_end_token(node->IfStmt.body);
|
||||
case Ast_WhenStmt:
|
||||
if (node->WhenStmt.else_stmt) {
|
||||
return ast_end_token(node->WhenStmt.else_stmt);
|
||||
}
|
||||
return ast_end_token(node->WhenStmt.body);
|
||||
case Ast_ReturnStmt:
|
||||
if (node->ReturnStmt.results.count > 0) {
|
||||
return ast_end_token(node->ReturnStmt.results[node->ReturnStmt.results.count-1]);
|
||||
}
|
||||
return node->ReturnStmt.token;
|
||||
case Ast_ForStmt: return ast_end_token(node->ForStmt.body);
|
||||
case Ast_RangeStmt: return ast_end_token(node->RangeStmt.body);
|
||||
case Ast_UnrollRangeStmt: return ast_end_token(node->UnrollRangeStmt.body);
|
||||
case Ast_CaseClause:
|
||||
if (node->CaseClause.stmts.count) {
|
||||
return ast_end_token(node->CaseClause.stmts[node->CaseClause.stmts.count-1]);
|
||||
} else if (node->CaseClause.list.count) {
|
||||
return ast_end_token(node->CaseClause.list[node->CaseClause.list.count-1]);
|
||||
}
|
||||
return node->CaseClause.token;
|
||||
case Ast_SwitchStmt: return ast_end_token(node->SwitchStmt.body);
|
||||
case Ast_TypeSwitchStmt: return ast_end_token(node->TypeSwitchStmt.body);
|
||||
case Ast_DeferStmt: return ast_end_token(node->DeferStmt.stmt);
|
||||
case Ast_BranchStmt:
|
||||
if (node->BranchStmt.label) {
|
||||
return ast_end_token(node->BranchStmt.label);
|
||||
}
|
||||
return node->BranchStmt.token;
|
||||
case Ast_UsingStmt:
|
||||
if (node->UsingStmt.list.count > 0) {
|
||||
return ast_end_token(node->UsingStmt.list[node->UsingStmt.list.count-1]);
|
||||
}
|
||||
return node->UsingStmt.token;
|
||||
|
||||
case Ast_BadDecl: return node->BadDecl.end;
|
||||
case Ast_Label:
|
||||
if (node->Label.name) {
|
||||
return ast_end_token(node->Label.name);
|
||||
}
|
||||
return node->Label.token;
|
||||
|
||||
case Ast_ValueDecl:
|
||||
if (node->ValueDecl.values.count > 0) {
|
||||
return ast_end_token(node->ValueDecl.values[node->ValueDecl.values.count-1]);
|
||||
}
|
||||
if (node->ValueDecl.type) {
|
||||
return ast_end_token(node->ValueDecl.type);
|
||||
}
|
||||
if (node->ValueDecl.names.count > 0) {
|
||||
return ast_end_token(node->ValueDecl.names[node->ValueDecl.names.count-1]);
|
||||
}
|
||||
return {};
|
||||
|
||||
case Ast_PackageDecl: return node->PackageDecl.name;
|
||||
case Ast_ImportDecl: return node->ImportDecl.relpath;
|
||||
case Ast_ForeignImportDecl:
|
||||
if (node->ForeignImportDecl.filepaths.count > 0) {
|
||||
return node->ForeignImportDecl.filepaths[node->ForeignImportDecl.filepaths.count-1];
|
||||
}
|
||||
if (node->ForeignImportDecl.library_name.kind != Token_Invalid) {
|
||||
return node->ForeignImportDecl.library_name;
|
||||
}
|
||||
return node->ForeignImportDecl.token;
|
||||
|
||||
case Ast_ForeignBlockDecl:
|
||||
return ast_end_token(node->ForeignBlockDecl.body);
|
||||
|
||||
case Ast_Attribute:
|
||||
if (node->Attribute.close.kind != Token_Invalid) {
|
||||
return node->Attribute.close;
|
||||
}
|
||||
return ast_end_token(node->Attribute.elems[node->Attribute.elems.count-1]);
|
||||
|
||||
case Ast_Field:
|
||||
if (node->Field.tag.kind != Token_Invalid) {
|
||||
return node->Field.tag;
|
||||
}
|
||||
if (node->Field.default_value) {
|
||||
return ast_end_token(node->Field.default_value);
|
||||
}
|
||||
if (node->Field.type) {
|
||||
return ast_end_token(node->Field.type);
|
||||
}
|
||||
return ast_end_token(node->Field.names[node->Field.names.count-1]);
|
||||
case Ast_FieldList:
|
||||
if (node->FieldList.list.count > 0) {
|
||||
return ast_end_token(node->FieldList.list[node->FieldList.list.count-1]);
|
||||
}
|
||||
return node->FieldList.token;
|
||||
|
||||
case Ast_TypeidType:
|
||||
if (node->TypeidType.specialization) {
|
||||
return ast_end_token(node->TypeidType.specialization);
|
||||
}
|
||||
return node->TypeidType.token;
|
||||
case Ast_HelperType: return ast_end_token(node->HelperType.type);
|
||||
case Ast_DistinctType: return ast_end_token(node->DistinctType.type);
|
||||
case Ast_PolyType:
|
||||
if (node->PolyType.specialization) {
|
||||
return ast_end_token(node->PolyType.specialization);
|
||||
}
|
||||
return ast_end_token(node->PolyType.type);
|
||||
case Ast_ProcType:
|
||||
if (node->ProcType.results) {
|
||||
return ast_end_token(node->ProcType.results);
|
||||
}
|
||||
if (node->ProcType.params) {
|
||||
return ast_end_token(node->ProcType.params);
|
||||
}
|
||||
return node->ProcType.token;
|
||||
case Ast_RelativeType:
|
||||
return ast_end_token(node->RelativeType.type);
|
||||
case Ast_PointerType: return ast_end_token(node->PointerType.type);
|
||||
case Ast_ArrayType: return ast_end_token(node->ArrayType.elem);
|
||||
case Ast_DynamicArrayType: return ast_end_token(node->DynamicArrayType.elem);
|
||||
case Ast_StructType:
|
||||
if (node->StructType.fields.count > 0) {
|
||||
return ast_end_token(node->StructType.fields[node->StructType.fields.count-1]);
|
||||
}
|
||||
return node->StructType.token;
|
||||
case Ast_UnionType:
|
||||
if (node->UnionType.variants.count > 0) {
|
||||
return ast_end_token(node->UnionType.variants[node->UnionType.variants.count-1]);
|
||||
}
|
||||
return node->UnionType.token;
|
||||
case Ast_EnumType:
|
||||
if (node->EnumType.fields.count > 0) {
|
||||
return ast_end_token(node->EnumType.fields[node->EnumType.fields.count-1]);
|
||||
}
|
||||
if (node->EnumType.base_type) {
|
||||
return ast_end_token(node->EnumType.base_type);
|
||||
}
|
||||
return node->EnumType.token;
|
||||
case Ast_BitSetType:
|
||||
if (node->BitSetType.underlying) {
|
||||
return ast_end_token(node->BitSetType.underlying);
|
||||
}
|
||||
return ast_end_token(node->BitSetType.elem);
|
||||
case Ast_MapType: return ast_end_token(node->MapType.value);
|
||||
}
|
||||
|
||||
return empty_token;
|
||||
}
|
||||
|
||||
TokenPos ast_end_pos(Ast *node) {
|
||||
return token_pos_end(ast_end_token(node));
|
||||
}
|
||||
@@ -35,8 +35,6 @@ void thread_pool_destroy(ThreadPool *pool);
|
||||
void thread_pool_start(ThreadPool *pool);
|
||||
void thread_pool_join(ThreadPool *pool);
|
||||
void thread_pool_add_task(ThreadPool *pool, WorkerTaskProc *proc, void *data);
|
||||
void thread_pool_kick(ThreadPool *pool);
|
||||
void thread_pool_kick_and_wait(ThreadPool *pool);
|
||||
GB_THREAD_PROC(worker_thread_internal);
|
||||
|
||||
void thread_pool_init(ThreadPool *pool, gbAllocator const &a, isize thread_count, char const *worker_prefix) {
|
||||
@@ -181,4 +179,3 @@ GB_THREAD_PROC(worker_thread_internal) {
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
+170
-51
@@ -51,8 +51,10 @@ TOKEN_KIND(Token__AssignOpBegin, ""), \
|
||||
TOKEN_KIND(Token_CmpAndEq, "&&="), \
|
||||
TOKEN_KIND(Token_CmpOrEq, "||="), \
|
||||
TOKEN_KIND(Token__AssignOpEnd, ""), \
|
||||
TOKEN_KIND(Token_ArrowRight, "->"), \
|
||||
TOKEN_KIND(Token_Undef, "---"), \
|
||||
TOKEN_KIND(Token_Increment, "++"), \
|
||||
TOKEN_KIND(Token_Decrement, "--"), \
|
||||
TOKEN_KIND(Token_ArrowRight,"->"), \
|
||||
TOKEN_KIND(Token_Undef, "---"), \
|
||||
\
|
||||
TOKEN_KIND(Token__ComparisonBegin, ""), \
|
||||
TOKEN_KIND(Token_CmpEq, "=="), \
|
||||
@@ -74,6 +76,7 @@ TOKEN_KIND(Token__ComparisonEnd, ""), \
|
||||
TOKEN_KIND(Token_Period, "."), \
|
||||
TOKEN_KIND(Token_Comma, ","), \
|
||||
TOKEN_KIND(Token_Ellipsis, ".."), \
|
||||
TOKEN_KIND(Token_RangeFull, "..="), \
|
||||
TOKEN_KIND(Token_RangeHalf, "..<"), \
|
||||
TOKEN_KIND(Token_BackSlash, "\\"), \
|
||||
TOKEN_KIND(Token__OperatorEnd, ""), \
|
||||
@@ -185,9 +188,11 @@ void init_keyword_hash_table(void) {
|
||||
GB_ASSERT(max_keyword_size < 16);
|
||||
}
|
||||
|
||||
gb_global Array<String> global_file_path_strings; // index is file id
|
||||
gb_global Array<String> global_file_path_strings; // index is file id
|
||||
gb_global Array<struct AstFile *> global_files; // index is file id
|
||||
|
||||
String get_file_path_string(i32 index);
|
||||
String get_file_path_string(i32 index);
|
||||
struct AstFile *get_ast_file_from_id(i32 index);
|
||||
|
||||
struct TokenPos {
|
||||
i32 file_id;
|
||||
@@ -281,6 +286,7 @@ void init_global_error_collector(void) {
|
||||
array_init(&global_error_collector.errors, heap_allocator());
|
||||
array_init(&global_error_collector.error_buffer, heap_allocator());
|
||||
array_init(&global_file_path_strings, heap_allocator(), 4096);
|
||||
array_init(&global_files, heap_allocator(), 4096);
|
||||
}
|
||||
|
||||
|
||||
@@ -302,6 +308,24 @@ bool set_file_path_string(i32 index, String const &path) {
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool set_ast_file_from_id(i32 index, AstFile *file) {
|
||||
bool ok = false;
|
||||
GB_ASSERT(index >= 0);
|
||||
gb_mutex_lock(&global_error_collector.string_mutex);
|
||||
|
||||
if (index >= global_files.count) {
|
||||
array_resize(&global_files, index);
|
||||
}
|
||||
AstFile *prev = global_files[index];
|
||||
if (prev == nullptr) {
|
||||
global_files[index] = file;
|
||||
ok = true;
|
||||
}
|
||||
|
||||
gb_mutex_unlock(&global_error_collector.string_mutex);
|
||||
return ok;
|
||||
}
|
||||
|
||||
String get_file_path_string(i32 index) {
|
||||
GB_ASSERT(index >= 0);
|
||||
gb_mutex_lock(&global_error_collector.string_mutex);
|
||||
@@ -315,6 +339,20 @@ String get_file_path_string(i32 index) {
|
||||
return path;
|
||||
}
|
||||
|
||||
AstFile *get_ast_file_from_id(i32 index) {
|
||||
GB_ASSERT(index >= 0);
|
||||
gb_mutex_lock(&global_error_collector.string_mutex);
|
||||
|
||||
AstFile *file = nullptr;
|
||||
if (index < global_files.count) {
|
||||
file = global_files[index];
|
||||
}
|
||||
|
||||
gb_mutex_unlock(&global_error_collector.string_mutex);
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
void begin_error_block(void) {
|
||||
gb_mutex_lock(&global_error_collector.mutex);
|
||||
global_error_collector.in_block = true;
|
||||
@@ -374,6 +412,8 @@ ErrorOutProc *error_out_va = default_error_out_va;
|
||||
// NOTE: defined in build_settings.cpp
|
||||
bool global_warnings_as_errors(void);
|
||||
bool global_ignore_warnings(void);
|
||||
bool show_error_line(void);
|
||||
gbString get_file_line_as_string(TokenPos const &pos, i32 *offset);
|
||||
|
||||
void error_out(char const *fmt, ...) {
|
||||
va_list va;
|
||||
@@ -383,17 +423,85 @@ void error_out(char const *fmt, ...) {
|
||||
}
|
||||
|
||||
|
||||
void error_va(Token token, char const *fmt, va_list va) {
|
||||
bool show_error_on_line(TokenPos const &pos, TokenPos end) {
|
||||
if (!show_error_line()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
i32 offset = 0;
|
||||
gbString the_line = get_file_line_as_string(pos, &offset);
|
||||
defer (gb_string_free(the_line));
|
||||
|
||||
if (the_line != nullptr) {
|
||||
String line = make_string(cast(u8 const *)the_line, gb_string_length(the_line));
|
||||
|
||||
// TODO(bill): This assumes ASCII
|
||||
|
||||
enum {
|
||||
MAX_LINE_LENGTH = 76,
|
||||
MAX_TAB_WIDTH = 8,
|
||||
ELLIPSIS_PADDING = 8
|
||||
};
|
||||
|
||||
error_out("\n\t");
|
||||
if (line.len+MAX_TAB_WIDTH+ELLIPSIS_PADDING > MAX_LINE_LENGTH) {
|
||||
i32 const half_width = MAX_LINE_LENGTH/2;
|
||||
i32 left = cast(i32)(offset);
|
||||
i32 right = cast(i32)(line.len - offset);
|
||||
left = gb_min(left, half_width);
|
||||
right = gb_min(right, half_width);
|
||||
|
||||
line.text += offset-left;
|
||||
line.len -= offset+right-left;
|
||||
|
||||
line = string_trim_whitespace(line);
|
||||
|
||||
offset = left + ELLIPSIS_PADDING/2;
|
||||
|
||||
error_out("... %.*s ...", LIT(line));
|
||||
} else {
|
||||
error_out("%.*s", LIT(line));
|
||||
}
|
||||
error_out("\n\t");
|
||||
|
||||
for (i32 i = 0; i < offset; i++) {
|
||||
error_out(" ");
|
||||
}
|
||||
error_out("^");
|
||||
if (end.file_id == pos.file_id) {
|
||||
if (end.line > pos.line) {
|
||||
for (i32 i = offset; i < line.len; i++) {
|
||||
error_out("~");
|
||||
}
|
||||
} else if (end.line == pos.line && end.column > pos.column) {
|
||||
i32 length = gb_min(end.offset - pos.offset, cast(i32)(line.len-offset));
|
||||
for (i32 i = 1; i < length-1; i++) {
|
||||
error_out("~");
|
||||
}
|
||||
if (length > 1) {
|
||||
error_out("^");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
error_out("\n\n");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void error_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) {
|
||||
gb_mutex_lock(&global_error_collector.mutex);
|
||||
global_error_collector.count++;
|
||||
// NOTE(bill): Duplicate error, skip it
|
||||
if (token.pos.line == 0) {
|
||||
if (pos.line == 0) {
|
||||
error_out("Error: %s\n", gb_bprintf_va(fmt, va));
|
||||
} else if (global_error_collector.prev != token.pos) {
|
||||
global_error_collector.prev = token.pos;
|
||||
} else if (global_error_collector.prev != pos) {
|
||||
global_error_collector.prev = pos;
|
||||
error_out("%s %s\n",
|
||||
token_pos_to_string(token.pos),
|
||||
token_pos_to_string(pos),
|
||||
gb_bprintf_va(fmt, va));
|
||||
show_error_on_line(pos, end);
|
||||
}
|
||||
gb_mutex_unlock(&global_error_collector.mutex);
|
||||
if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT) {
|
||||
@@ -401,22 +509,23 @@ void error_va(Token token, char const *fmt, va_list va) {
|
||||
}
|
||||
}
|
||||
|
||||
void warning_va(Token token, char const *fmt, va_list va) {
|
||||
void warning_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) {
|
||||
if (global_warnings_as_errors()) {
|
||||
error_va(token, fmt, va);
|
||||
error_va(pos, end, fmt, va);
|
||||
return;
|
||||
}
|
||||
gb_mutex_lock(&global_error_collector.mutex);
|
||||
global_error_collector.warning_count++;
|
||||
if (!global_ignore_warnings()) {
|
||||
// NOTE(bill): Duplicate error, skip it
|
||||
if (token.pos.line == 0) {
|
||||
if (pos.line == 0) {
|
||||
error_out("Warning: %s\n", gb_bprintf_va(fmt, va));
|
||||
} else if (global_error_collector.prev != token.pos) {
|
||||
global_error_collector.prev = token.pos;
|
||||
} else if (global_error_collector.prev != pos) {
|
||||
global_error_collector.prev = pos;
|
||||
error_out("%s Warning: %s\n",
|
||||
token_pos_to_string(token.pos),
|
||||
token_pos_to_string(pos),
|
||||
gb_bprintf_va(fmt, va));
|
||||
show_error_on_line(pos, end);
|
||||
}
|
||||
}
|
||||
gb_mutex_unlock(&global_error_collector.mutex);
|
||||
@@ -429,16 +538,16 @@ void error_line_va(char const *fmt, va_list va) {
|
||||
gb_mutex_unlock(&global_error_collector.mutex);
|
||||
}
|
||||
|
||||
void error_no_newline_va(Token token, char const *fmt, va_list va) {
|
||||
void error_no_newline_va(TokenPos const &pos, char const *fmt, va_list va) {
|
||||
gb_mutex_lock(&global_error_collector.mutex);
|
||||
global_error_collector.count++;
|
||||
// NOTE(bill): Duplicate error, skip it
|
||||
if (token.pos.line == 0) {
|
||||
if (pos.line == 0) {
|
||||
error_out("Error: %s", gb_bprintf_va(fmt, va));
|
||||
} else if (global_error_collector.prev != token.pos) {
|
||||
global_error_collector.prev = token.pos;
|
||||
} else if (global_error_collector.prev != pos) {
|
||||
global_error_collector.prev = pos;
|
||||
error_out("%s %s",
|
||||
token_pos_to_string(token.pos),
|
||||
token_pos_to_string(pos),
|
||||
gb_bprintf_va(fmt, va));
|
||||
}
|
||||
gb_mutex_unlock(&global_error_collector.mutex);
|
||||
@@ -448,16 +557,17 @@ void error_no_newline_va(Token token, char const *fmt, va_list va) {
|
||||
}
|
||||
|
||||
|
||||
void syntax_error_va(Token token, char const *fmt, va_list va) {
|
||||
void syntax_error_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) {
|
||||
gb_mutex_lock(&global_error_collector.mutex);
|
||||
global_error_collector.count++;
|
||||
// NOTE(bill): Duplicate error, skip it
|
||||
if (global_error_collector.prev != token.pos) {
|
||||
global_error_collector.prev = token.pos;
|
||||
if (global_error_collector.prev != pos) {
|
||||
global_error_collector.prev = pos;
|
||||
error_out("%s Syntax Error: %s\n",
|
||||
token_pos_to_string(token.pos),
|
||||
token_pos_to_string(pos),
|
||||
gb_bprintf_va(fmt, va));
|
||||
} else if (token.pos.line == 0) {
|
||||
show_error_on_line(pos, end);
|
||||
} else if (pos.line == 0) {
|
||||
error_out("Syntax Error: %s\n", gb_bprintf_va(fmt, va));
|
||||
}
|
||||
|
||||
@@ -467,21 +577,22 @@ void syntax_error_va(Token token, char const *fmt, va_list va) {
|
||||
}
|
||||
}
|
||||
|
||||
void syntax_warning_va(Token token, char const *fmt, va_list va) {
|
||||
void syntax_warning_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) {
|
||||
if (global_warnings_as_errors()) {
|
||||
syntax_error_va(token, fmt, va);
|
||||
syntax_error_va(pos, end, fmt, va);
|
||||
return;
|
||||
}
|
||||
gb_mutex_lock(&global_error_collector.mutex);
|
||||
global_error_collector.warning_count++;
|
||||
if (!global_ignore_warnings()) {
|
||||
// NOTE(bill): Duplicate error, skip it
|
||||
if (global_error_collector.prev != token.pos) {
|
||||
global_error_collector.prev = token.pos;
|
||||
if (global_error_collector.prev != pos) {
|
||||
global_error_collector.prev = pos;
|
||||
error_out("%s Syntax Warning: %s\n",
|
||||
token_pos_to_string(token.pos),
|
||||
token_pos_to_string(pos),
|
||||
gb_bprintf_va(fmt, va));
|
||||
} else if (token.pos.line == 0) {
|
||||
show_error_on_line(pos, end);
|
||||
} else if (pos.line == 0) {
|
||||
error_out("Warning: %s\n", gb_bprintf_va(fmt, va));
|
||||
}
|
||||
}
|
||||
@@ -490,17 +601,17 @@ void syntax_warning_va(Token token, char const *fmt, va_list va) {
|
||||
|
||||
|
||||
|
||||
void warning(Token token, char const *fmt, ...) {
|
||||
void warning(Token const &token, char const *fmt, ...) {
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
warning_va(token, fmt, va);
|
||||
warning_va(token.pos, {}, fmt, va);
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
void error(Token token, char const *fmt, ...) {
|
||||
void error(Token const &token, char const *fmt, ...) {
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
error_va(token, fmt, va);
|
||||
error_va(token.pos, {}, fmt, va);
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
@@ -509,7 +620,7 @@ void error(TokenPos pos, char const *fmt, ...) {
|
||||
va_start(va, fmt);
|
||||
Token token = {};
|
||||
token.pos = pos;
|
||||
error_va(token, fmt, va);
|
||||
error_va(pos, {}, fmt, va);
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
@@ -521,26 +632,24 @@ void error_line(char const *fmt, ...) {
|
||||
}
|
||||
|
||||
|
||||
void syntax_error(Token token, char const *fmt, ...) {
|
||||
void syntax_error(Token const &token, char const *fmt, ...) {
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
syntax_error_va(token, fmt, va);
|
||||
syntax_error_va(token.pos, {}, fmt, va);
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
void syntax_error(TokenPos pos, char const *fmt, ...) {
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
Token token = {};
|
||||
token.pos = pos;
|
||||
syntax_error_va(token, fmt, va);
|
||||
syntax_error_va(pos, {}, fmt, va);
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
void syntax_warning(Token token, char const *fmt, ...) {
|
||||
void syntax_warning(Token const &token, char const *fmt, ...) {
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
syntax_warning_va(token, fmt, va);
|
||||
syntax_warning_va(token.pos, {}, fmt, va);
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
@@ -652,13 +761,14 @@ void tokenizer_err(Tokenizer *t, char const *msg, ...) {
|
||||
if (column < 1) {
|
||||
column = 1;
|
||||
}
|
||||
Token token = {};
|
||||
token.pos.file_id = t->curr_file_id;
|
||||
token.pos.line = t->line_count;
|
||||
token.pos.column = cast(i32)column;
|
||||
TokenPos pos = {};
|
||||
pos.file_id = t->curr_file_id;
|
||||
pos.line = t->line_count;
|
||||
pos.column = cast(i32)column;
|
||||
pos.offset = cast(i32)(t->read_curr - t->start);
|
||||
|
||||
va_start(va, msg);
|
||||
syntax_error_va(token, msg, va);
|
||||
syntax_error_va(pos, {}, msg, va);
|
||||
va_end(va);
|
||||
|
||||
t->error_count++;
|
||||
@@ -670,11 +780,9 @@ void tokenizer_err(Tokenizer *t, TokenPos const &pos, char const *msg, ...) {
|
||||
if (column < 1) {
|
||||
column = 1;
|
||||
}
|
||||
Token token = {};
|
||||
token.pos = pos;
|
||||
|
||||
va_start(va, msg);
|
||||
syntax_error_va(token, msg, va);
|
||||
syntax_error_va(pos, {}, msg, va);
|
||||
va_end(va);
|
||||
|
||||
t->error_count++;
|
||||
@@ -1202,6 +1310,9 @@ void tokenizer_get_token(Tokenizer *t, Token *token, int repeat=0) {
|
||||
if (t->curr_rune == '<') {
|
||||
advance_to_next_rune(t);
|
||||
token->kind = Token_RangeHalf;
|
||||
} else if (t->curr_rune == '=') {
|
||||
advance_to_next_rune(t);
|
||||
token->kind = Token_RangeFull;
|
||||
}
|
||||
} else if ('0' <= t->curr_rune && t->curr_rune <= '9') {
|
||||
scan_number_to_token(t, token, true);
|
||||
@@ -1287,6 +1398,10 @@ void tokenizer_get_token(Tokenizer *t, Token *token, int repeat=0) {
|
||||
if (t->curr_rune == '=') {
|
||||
advance_to_next_rune(t);
|
||||
token->kind = Token_AddEq;
|
||||
} else if (t->curr_rune == '+') {
|
||||
advance_to_next_rune(t);
|
||||
token->kind = Token_Increment;
|
||||
insert_semicolon = true;
|
||||
}
|
||||
break;
|
||||
case '-':
|
||||
@@ -1298,6 +1413,10 @@ void tokenizer_get_token(Tokenizer *t, Token *token, int repeat=0) {
|
||||
advance_to_next_rune(t);
|
||||
advance_to_next_rune(t);
|
||||
token->kind = Token_Undef;
|
||||
} else if (t->curr_rune == '-') {
|
||||
advance_to_next_rune(t);
|
||||
token->kind = Token_Decrement;
|
||||
insert_semicolon = true;
|
||||
} else if (t->curr_rune == '>') {
|
||||
advance_to_next_rune(t);
|
||||
token->kind = Token_ArrowRight;
|
||||
|
||||
+46
-20
@@ -128,21 +128,6 @@ enum StructSoaKind {
|
||||
StructSoa_Dynamic = 3,
|
||||
};
|
||||
|
||||
enum TypeAtomOpKind {
|
||||
TypeAtomOp_Invalid,
|
||||
|
||||
TypeAtomOp_index_get,
|
||||
TypeAtomOp_index_set,
|
||||
TypeAtomOp_slice,
|
||||
TypeAtomOp_index_get_ptr,
|
||||
|
||||
TypeAtomOp_COUNT,
|
||||
};
|
||||
|
||||
struct TypeAtomOpTable {
|
||||
Entity *op[TypeAtomOp_COUNT];
|
||||
};
|
||||
|
||||
struct TypeStruct {
|
||||
Array<Entity *> fields;
|
||||
Array<String> tags;
|
||||
@@ -156,8 +141,6 @@ struct TypeStruct {
|
||||
i64 custom_align;
|
||||
Entity * names;
|
||||
|
||||
TypeAtomOpTable *atom_op_table;
|
||||
|
||||
Type * soa_elem;
|
||||
i64 soa_count;
|
||||
StructSoaKind soa_kind;
|
||||
@@ -180,8 +163,6 @@ struct TypeUnion {
|
||||
Type * polymorphic_params; // Type_Tuple
|
||||
Type * polymorphic_parent;
|
||||
|
||||
TypeAtomOpTable *atom_op_table;
|
||||
|
||||
bool no_nil;
|
||||
bool maybe;
|
||||
bool is_polymorphic;
|
||||
@@ -1915,6 +1896,18 @@ bool is_type_comparable(Type *t) {
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
case Type_Union:
|
||||
if (type_size_of(t) == 0) {
|
||||
return false;
|
||||
}
|
||||
for_array(i, t->Union.variants) {
|
||||
Type *v = t->Union.variants[i];
|
||||
if (!is_type_comparable(v)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1959,7 +1952,8 @@ bool is_type_simple_compare(Type *t) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
// make it dumb on purpose
|
||||
return t->Union.variants.count == 1;
|
||||
|
||||
case Type_SimdVector:
|
||||
return is_type_simple_compare(t->SimdVector.elem);
|
||||
@@ -2766,7 +2760,36 @@ void type_path_pop(TypePath *tp) {
|
||||
|
||||
i64 type_size_of_internal (Type *t, TypePath *path);
|
||||
i64 type_align_of_internal(Type *t, TypePath *path);
|
||||
i64 type_size_of(Type *t);
|
||||
i64 type_align_of(Type *t);
|
||||
|
||||
i64 type_size_of_struct_pretend_is_packed(Type *ot) {
|
||||
if (ot == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
Type *t = core_type(ot);
|
||||
if (t->kind != Type_Struct) {
|
||||
return type_size_of(ot);
|
||||
}
|
||||
|
||||
if (t->Struct.is_packed) {
|
||||
return type_size_of(ot);
|
||||
}
|
||||
|
||||
i64 count = 0, size = 0, align = 1;
|
||||
|
||||
auto const &fields = t->Struct.fields;
|
||||
count = fields.count;
|
||||
if (count == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for_array(i, fields) {
|
||||
size += type_size_of(fields[i]->type);
|
||||
}
|
||||
|
||||
return align_formula(size, align);
|
||||
}
|
||||
|
||||
|
||||
i64 type_size_of(Type *t) {
|
||||
@@ -3618,6 +3641,9 @@ gbString write_type_to_string(gbString str, Type *type) {
|
||||
case ProcCC_None:
|
||||
str = gb_string_appendc(str, " \"none\" ");
|
||||
break;
|
||||
case ProcCC_Naked:
|
||||
str = gb_string_appendc(str, " \"naked\" ");
|
||||
break;
|
||||
// case ProcCC_VectorCall:
|
||||
// str = gb_string_appendc(str, " \"vectorcall\" ");
|
||||
// break;
|
||||
|
||||
Reference in New Issue
Block a user