Merge branch 'master' of github.com:odin-lang/Odin into posix-linux

This commit is contained in:
Isaac Andrade
2024-08-27 18:51:58 -06:00
67 changed files with 3045 additions and 2704 deletions
+2
View File
@@ -219,6 +219,8 @@ type_map_cell_info :: proc($T: typeid) -> ^runtime.Map_Cell_Info ---
type_convert_variants_to_pointers :: proc($T: typeid) -> typeid where type_is_union(T) ---
type_merge :: proc($U, $V: typeid) -> typeid where type_is_union(U), type_is_union(V) ---
type_has_shared_fields :: proc($U, $V: typeid) -> bool typeid where type_is_struct(U), type_is_struct(V) ---
constant_utf16_cstring :: proc($literal: string) -> [^]u16 ---
constant_log2 :: proc($v: $T) -> T where type_is_integer(T) ---
+34
View File
@@ -0,0 +1,34 @@
package runtime
Thread_Local_Cleaner :: #type proc "odin" ()
@(private="file")
thread_local_cleaners: [8]Thread_Local_Cleaner
// Add a procedure that will be run at the end of a thread for the purpose of
// deallocating state marked as `thread_local`.
//
// Intended to be called in an `init` procedure of a package with
// dynamically-allocated memory that is stored in `thread_local` variables.
add_thread_local_cleaner :: proc "contextless" (p: Thread_Local_Cleaner) {
for &v in thread_local_cleaners {
if v == nil {
v = p
return
}
}
panic_contextless("There are no more thread-local cleaner slots available.")
}
// Run all of the thread-local cleaner procedures.
//
// Intended to be called by the internals of a threading API at the end of a
// thread's lifetime.
run_thread_local_cleaners :: proc "odin" () {
for p in thread_local_cleaners {
if p == nil {
break
}
p()
}
}
+20 -15
View File
@@ -11,7 +11,7 @@ Write a UUID in the 8-4-4-4-12 format.
This procedure performs error checking with every byte written.
If you can guarantee beforehand that your stream has enough space to hold the
UUID (32 bytes), then it is better to use `unsafe_write` instead as that will
UUID (36 bytes), then it is better to use `unsafe_write` instead as that will
be faster.
Inputs:
@@ -22,7 +22,7 @@ Returns:
- error: An `io` error, if one occurred, otherwise `nil`.
*/
write :: proc(w: io.Writer, id: Identifier) -> (error: io.Error) #no_bounds_check {
write_octet :: proc (w: io.Writer, octet: u8) -> io.Error #no_bounds_check {
write_octet :: proc(w: io.Writer, octet: u8) -> io.Error #no_bounds_check {
high_nibble := octet >> 4
low_nibble := octet & 0xF
@@ -31,15 +31,15 @@ write :: proc(w: io.Writer, id: Identifier) -> (error: io.Error) #no_bounds_chec
return nil
}
for index in 0 ..< 4 { write_octet(w, id[index]) or_return }
for index in 0 ..< 4 {write_octet(w, id[index]) or_return}
io.write_byte(w, '-') or_return
for index in 4 ..< 6 { write_octet(w, id[index]) or_return }
for index in 4 ..< 6 {write_octet(w, id[index]) or_return}
io.write_byte(w, '-') or_return
for index in 6 ..< 8 { write_octet(w, id[index]) or_return }
for index in 6 ..< 8 {write_octet(w, id[index]) or_return}
io.write_byte(w, '-') or_return
for index in 8 ..< 10 { write_octet(w, id[index]) or_return }
for index in 8 ..< 10 {write_octet(w, id[index]) or_return}
io.write_byte(w, '-') or_return
for index in 10 ..< 16 { write_octet(w, id[index]) or_return }
for index in 10 ..< 16 {write_octet(w, id[index]) or_return}
return nil
}
@@ -54,7 +54,7 @@ Inputs:
- id: The identifier to convert.
*/
unsafe_write :: proc(w: io.Writer, id: Identifier) #no_bounds_check {
write_octet :: proc (w: io.Writer, octet: u8) #no_bounds_check {
write_octet :: proc(w: io.Writer, octet: u8) #no_bounds_check {
high_nibble := octet >> 4
low_nibble := octet & 0xF
@@ -62,15 +62,15 @@ unsafe_write :: proc(w: io.Writer, id: Identifier) #no_bounds_check {
io.write_byte(w, strconv.digits[low_nibble])
}
for index in 0 ..< 4 { write_octet(w, id[index]) }
for index in 0 ..< 4 {write_octet(w, id[index])}
io.write_byte(w, '-')
for index in 4 ..< 6 { write_octet(w, id[index]) }
for index in 4 ..< 6 {write_octet(w, id[index])}
io.write_byte(w, '-')
for index in 6 ..< 8 { write_octet(w, id[index]) }
for index in 6 ..< 8 {write_octet(w, id[index])}
io.write_byte(w, '-')
for index in 8 ..< 10 { write_octet(w, id[index]) }
for index in 8 ..< 10 {write_octet(w, id[index])}
io.write_byte(w, '-')
for index in 10 ..< 16 { write_octet(w, id[index]) }
for index in 10 ..< 16 {write_octet(w, id[index])}
}
/*
@@ -106,7 +106,7 @@ Convert a UUID to a string in the 8-4-4-4-12 format.
Inputs:
- id: The identifier to convert.
- buffer: A byte buffer to store the result. Must be at least 32 bytes large.
- buffer: A byte buffer to store the result. Must be at least 36 bytes large.
- loc: The caller location for debugging purposes (default: #caller_location)
Returns:
@@ -119,7 +119,11 @@ to_string_buffer :: proc(
) -> (
str: string,
) {
assert(len(buffer) >= EXPECTED_LENGTH, "The buffer provided is not at least 32 bytes large.", loc)
assert(
len(buffer) >= EXPECTED_LENGTH,
"The buffer provided is not at least 36 bytes large.",
loc,
)
builder := strings.builder_from_bytes(buffer)
unsafe_write(strings.to_writer(&builder), id)
return strings.to_string(builder)
@@ -129,3 +133,4 @@ to_string :: proc {
to_string_allocated,
to_string_buffer,
}
+2
View File
@@ -1393,6 +1393,7 @@ expand_grayscale :: proc(img: ^Image, allocator := context.allocator) -> (ok: bo
for p in inp {
out[0].rgb = p.r // Gray component.
out[0].a = p.g // Alpha component.
out = out[1:]
}
case:
@@ -1417,6 +1418,7 @@ expand_grayscale :: proc(img: ^Image, allocator := context.allocator) -> (ok: bo
for p in inp {
out[0].rgb = p.r // Gray component.
out[0].a = p.g // Alpha component.
out = out[1:]
}
case:
+2 -2
View File
@@ -178,11 +178,11 @@ make_dynamic_array :: proc($T: typeid/[dynamic]$E, allocator := context.allocato
}
@(require_results)
make_dynamic_array_len :: proc($T: typeid/[dynamic]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) {
return runtime.make_dynamic_array(T, len, allocator, loc)
return runtime.make_dynamic_array_len_cap(T, len, len, allocator, loc)
}
@(require_results)
make_dynamic_array_len_cap :: proc($T: typeid/[dynamic]$E, #any_int len: int, #any_int cap: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) {
return runtime.make_dynamic_array(T, len, cap, allocator, loc)
return runtime.make_dynamic_array_len_cap(T, len, cap, allocator, loc)
}
@(require_results)
make_map :: proc($T: typeid/map[$K]$E, #any_int cap: int = 1<<runtime.MAP_MIN_LOG2_CAPACITY, allocator := context.allocator, loc := #caller_location) -> (m: T, err: Allocator_Error) {
+18 -14
View File
@@ -128,33 +128,37 @@ _get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator :
append(&recs, record)
case .SRV:
target := strings.clone(string(r.Data.SRV.pNameTarget)) // The target hostname/address that the service can be found on
priority := int(r.Data.SRV.wPriority)
weight := int(r.Data.SRV.wWeight)
port := int(r.Data.SRV.wPort)
// NOTE(tetra): Srv record name should be of the form '_servicename._protocol.hostname'
// The record name is the name of the record.
// Not to be confused with the _target_ of the record, which is--in combination with the port--what we're looking up
// by making this request in the first place.
// NOTE(Jeroen): Service Name and Protocol Name can probably just be string slices into the record name.
// It's already cloned, after all. I wouldn't put them on the temp allocator like this.
service_name, protocol_name: string
parts := strings.split_n(base_record.record_name, ".", 3, context.temp_allocator)
if len(parts) != 3 {
s := base_record.record_name
i := strings.index_byte(s, '.')
if i > -1 {
service_name = s[:i]
s = s[len(service_name) + 1:]
} else {
continue
}
i = strings.index_byte(s, '.')
if i > -1 {
protocol_name = s[:i]
} else {
continue
}
service_name, protocol_name := parts[0], parts[1]
append(&recs, DNS_Record_SRV {
base = base_record,
target = target,
port = port,
target = strings.clone(string(r.Data.SRV.pNameTarget)), // The target hostname/address that the service can be found on
port = int(r.Data.SRV.wPort),
service_name = service_name,
protocol_name = protocol_name,
priority = priority,
weight = weight,
priority = int(r.Data.SRV.wPriority),
weight = int(r.Data.SRV.wWeight),
})
}
+5
View File
@@ -61,3 +61,8 @@ TEMP_ALLOCATOR_GUARD :: #force_inline proc(loc := #caller_location) -> (runtime.
global_default_temp_allocator_index = (global_default_temp_allocator_index+1)%MAX_TEMP_ARENA_COUNT
return tmp, loc
}
@(init, private)
init_thread_local_cleaner :: proc() {
runtime.add_thread_local_cleaner(temp_allocator_fini)
}
-3
View File
@@ -125,9 +125,6 @@ read_entire_file_from_file :: proc(f: ^File, allocator: runtime.Allocator) -> (d
has_size = true
size = int(size64)
}
} else if serr != .No_Size {
err = serr
return
}
if has_size && size > 0 {
+13 -12
View File
@@ -167,14 +167,14 @@ Process_Info :: struct {
This procedure obtains an information, specified by `selection` parameter of
a process given by `pid`.
Use `free_process_info` to free the memory allocated by this procedure. In
case the function returns an error it may only have been an error for one part
of the information and you would still need to call it to free the other parts.
Use `free_process_info` to free the memory allocated by this procedure. The
`free_process_info` procedure needs to be called, even if this procedure
returned an error, as some of the fields may have been allocated.
**Note**: The resulting information may or may contain the fields specified
by the `selection` parameter. Always check whether the returned
`Process_Info` struct has the required fields before checking the error code
returned by this function.
returned by this procedure.
*/
@(require_results)
process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) {
@@ -188,14 +188,14 @@ process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator:
about a process that has been opened by the application, specified in
the `process` parameter.
Use `free_process_info` to free the memory allocated by this procedure. In
case the function returns an error it may only have been an error for one part
of the information and you would still need to call it to free the other parts.
Use `free_process_info` to free the memory allocated by this procedure. The
`free_process_info` procedure needs to be called, even if this procedure
returned an error, as some of the fields may have been allocated.
**Note**: The resulting information may or may contain the fields specified
by the `selection` parameter. Always check whether the returned
`Process_Info` struct has the required fields before checking the error code
returned by this function.
returned by this procedure.
*/
@(require_results)
process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) {
@@ -208,14 +208,14 @@ process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields,
This procedure obtains the information, specified by `selection` parameter
about the currently running process.
Use `free_process_info` to free the memory allocated by this procedure. In
case the function returns an error it may only have been an error for one part
of the information and you would still need to call it to free the other parts.
Use `free_process_info` to free the memory allocated by this procedure. The
`free_process_info` procedure needs to be called, even if this procedure
returned an error, as some of the fields may have been allocated.
**Note**: The resulting information may or may contain the fields specified
by the `selection` parameter. Always check whether the returned
`Process_Info` struct has the required fields before checking the error code
returned by this function.
returned by this procedure.
*/
@(require_results)
current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) {
@@ -305,6 +305,7 @@ Process_Desc :: struct {
// A slice of strings, each having the format `KEY=VALUE` representing the
// full environment that the child process will receive.
// In case this slice is `nil`, the current process' environment is used.
// NOTE(laytan): maybe should be `Maybe([]string)` so you can do `nil` == current env, empty == empty/no env.
env: []string,
// The `stderr` handle to give to the child process. It can be either a file
// or a writeable end of a pipe. Passing `nil` will shut down the process'
+5 -11
View File
@@ -490,7 +490,6 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
if errno = linux.pipe2(&child_pipe_fds, {.CLOEXEC}); errno != .NONE {
return process, _get_platform_error(errno)
}
defer linux.close(child_pipe_fds[WRITE])
defer linux.close(child_pipe_fds[READ])
@@ -508,6 +507,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
//
pid: linux.Pid
if pid, errno = linux.fork(); errno != .NONE {
linux.close(child_pipe_fds[WRITE])
return process, _get_platform_error(errno)
}
@@ -573,25 +573,19 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno)
}
success_byte: [1]u8
linux.write(child_pipe_fds[WRITE], success_byte[:])
errno = linux.execveat(exe_fd, "", &cargs[0], env, {.AT_EMPTY_PATH})
// NOTE: we can't tell the parent about this failure because we already wrote the success byte.
// So if this happens the user will just see the process failed when they call process_wait.
assert(errno != nil)
intrinsics.trap()
write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno)
}
linux.close(child_pipe_fds[WRITE])
process.pid = int(pid)
n: int
child_byte: [1]u8
errno = .EINTR
for errno == .EINTR {
n, errno = linux.read(child_pipe_fds[READ], child_byte[:])
_, errno = linux.read(child_pipe_fds[READ], child_byte[:])
}
// If the read failed, something weird happened. Do not return the read
+7 -10
View File
@@ -139,20 +139,22 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
err = _get_platform_error()
return
}
defer posix.close(pipe[WRITE])
defer posix.close(pipe[READ])
if posix.fcntl(pipe[READ], .SETFD, i32(posix.FD_CLOEXEC)) == -1 {
posix.close(pipe[WRITE])
err = _get_platform_error()
return
}
if posix.fcntl(pipe[WRITE], .SETFD, i32(posix.FD_CLOEXEC)) == -1 {
posix.close(pipe[WRITE])
err = _get_platform_error()
return
}
switch pid := posix.fork(); pid {
case -1:
posix.close(pipe[WRITE])
err = _get_platform_error()
return
@@ -179,25 +181,20 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
if posix.chdir(cwd) != .OK { abort(pipe[WRITE]) }
}
ok := u8(0)
posix.write(pipe[WRITE], &ok, 1)
res := posix.execve(strings.to_cstring(&exe_builder), raw_data(cmd), env)
// NOTE: we can't tell the parent about this failure because we already wrote the success byte.
// So if this happens the user will just see the process failed when they call process_wait.
assert(res == -1)
runtime.trap()
abort(pipe[WRITE])
case:
posix.close(pipe[WRITE])
errno: posix.Errno
for {
errno_byte: u8
switch posix.read(pipe[READ], &errno_byte, 1) {
case 1:
errno = posix.Errno(errno_byte)
case:
case -1:
errno = posix.errno()
if errno == .EINTR {
continue
+182 -138
View File
@@ -93,34 +93,11 @@ read_memory_as_slice :: proc(h: win32.HANDLE, addr: rawptr, dest: []$T) -> (byte
@(private="package")
_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
info.pid = pid
defer if err != nil {
free_process_info(info, allocator)
}
// Data obtained from process snapshots
if selection >= {.PPid, .Priority} {
entry, entry_err := _process_entry_by_pid(info.pid)
if entry_err != nil {
err = General_Error.Not_Exist
return
}
if .PPid in selection {
info.fields += {.PPid}
info.ppid = int(entry.th32ParentProcessID)
}
if .Priority in selection {
info.fields += {.Priority}
info.priority = int(entry.pcPriClassBase)
}
}
if .Executable_Path in selection { // snap module
info.executable_path = _process_exe_by_pid(pid, allocator) or_return
info.fields += {.Executable_Path}
}
// Note(flysand): Open the process handle right away to prevent some race
// conditions. Once the handle is open, the process will be kept alive by
// the OS.
ph := win32.INVALID_HANDLE_VALUE
if selection >= {.Command_Line, .Environment, .Working_Dir, .Username} { // need process handle
if selection >= {.Command_Line, .Environment, .Working_Dir, .Username} {
ph = win32.OpenProcess(
win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.PROCESS_VM_READ,
false,
@@ -134,84 +111,15 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
defer if ph != win32.INVALID_HANDLE_VALUE {
win32.CloseHandle(ph)
}
if selection >= {.Command_Line, .Environment, .Working_Dir} { // need peb
process_info_size: u32
process_info: win32.PROCESS_BASIC_INFORMATION
status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size)
if status != 0 {
// TODO(flysand): There's probably a mismatch between NTSTATUS and
// windows userland error codes, I haven't checked.
err = Platform_Error(status)
return
}
if process_info.PebBaseAddress == nil {
// Not sure what the error is
err = General_Error.Unsupported
return
}
process_peb: win32.PEB
_ = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) or_return
process_params: win32.RTL_USER_PROCESS_PARAMETERS
_ = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) or_return
if selection >= {.Command_Line, .Command_Args} {
TEMP_ALLOCATOR_GUARD()
cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return
_ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return
if .Command_Line in selection {
info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return
info.fields += {.Command_Line}
}
if .Command_Args in selection {
info.command_args = _parse_command_line(raw_data(cmdline_w), allocator) or_return
info.fields += {.Command_Args}
}
}
if .Environment in selection {
TEMP_ALLOCATOR_GUARD()
env_len := process_params.EnvironmentSize / 2
envs_w := make([]u16, env_len, temp_allocator()) or_return
_ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return
info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return
info.fields += {.Environment}
}
if .Working_Dir in selection {
TEMP_ALLOCATOR_GUARD()
cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return
_ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return
info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return
info.fields += {.Working_Dir}
}
}
if .Username in selection {
info.username = _get_process_user(ph, allocator) or_return
info.fields += {.Username}
}
err = nil
return
}
@(private="package")
_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
pid := process.pid
info.pid = pid
defer if err != nil {
free_process_info(info, allocator)
}
// Data obtained from process snapshots
if selection >= {.PPid, .Priority} { // snap process
snapshot_process: if selection >= {.PPid, .Priority} {
entry, entry_err := _process_entry_by_pid(info.pid)
if entry_err != nil {
err = General_Error.Not_Exist
err = entry_err
if entry_err == General_Error.Not_Exist {
return
} else {
break snapshot_process
}
}
if .PPid in selection {
info.fields += {.PPid}
@@ -222,12 +130,18 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
info.priority = int(entry.pcPriClassBase)
}
}
if .Executable_Path in selection { // snap module
info.executable_path = _process_exe_by_pid(pid, allocator) or_return
snapshot_modules: if .Executable_Path in selection {
exe_path: string
exe_path, err = _process_exe_by_pid(pid, allocator)
if _, ok := err.(runtime.Allocator_Error); ok {
return
} else if err != nil {
break snapshot_modules
}
info.executable_path = exe_path
info.fields += {.Executable_Path}
}
ph := win32.HANDLE(process.handle)
if selection >= {.Command_Line, .Environment, .Working_Dir} { // need peb
read_peb: if selection >= {.Command_Line, .Environment, .Working_Dir} {
process_info_size: u32
process_info: win32.PROCESS_BASIC_INFORMATION
status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size)
@@ -235,25 +149,26 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
// TODO(flysand): There's probably a mismatch between NTSTATUS and
// windows userland error codes, I haven't checked.
err = Platform_Error(status)
return
break read_peb
}
if process_info.PebBaseAddress == nil {
// Not sure what the error is
err = General_Error.Unsupported
return
}
assert(process_info.PebBaseAddress != nil)
process_peb: win32.PEB
_ = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) or_return
_, err = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb)
if err != nil {
break read_peb
}
process_params: win32.RTL_USER_PROCESS_PARAMETERS
_ = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) or_return
_, err = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params)
if err != nil {
break read_peb
}
if selection >= {.Command_Line, .Command_Args} {
TEMP_ALLOCATOR_GUARD()
cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return
_ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return
_, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w)
if err != nil {
break read_peb
}
if .Command_Line in selection {
info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return
info.fields += {.Command_Line}
@@ -263,28 +178,147 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
info.fields += {.Command_Args}
}
}
if .Environment in selection {
TEMP_ALLOCATOR_GUARD()
env_len := process_params.EnvironmentSize / 2
envs_w := make([]u16, env_len, temp_allocator()) or_return
_ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return
_, err = read_memory_as_slice(ph, process_params.Environment, envs_w)
if err != nil {
break read_peb
}
info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return
info.fields += {.Environment}
}
if .Working_Dir in selection {
TEMP_ALLOCATOR_GUARD()
cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return
_ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return
_, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w)
if err != nil {
break read_peb
}
info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return
info.fields += {.Working_Dir}
}
}
if .Username in selection {
info.username = _get_process_user(ph, allocator) or_return
read_username: if .Username in selection {
username: string
username, err = _get_process_user(ph, allocator)
if _, ok := err.(runtime.Allocator_Error); ok {
return
} else if err != nil {
break read_username
}
info.username = username
info.fields += {.Username}
}
err = nil
return
}
@(private="package")
_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
pid := process.pid
info.pid = pid
// Data obtained from process snapshots
snapshot_process: if selection >= {.PPid, .Priority} {
entry, entry_err := _process_entry_by_pid(info.pid)
if entry_err != nil {
err = entry_err
if entry_err == General_Error.Not_Exist {
return
} else {
break snapshot_process
}
}
if .PPid in selection {
info.fields += {.PPid}
info.ppid = int(entry.th32ParentProcessID)
}
if .Priority in selection {
info.fields += {.Priority}
info.priority = int(entry.pcPriClassBase)
}
}
snapshot_module: if .Executable_Path in selection {
exe_path: string
exe_path, err = _process_exe_by_pid(pid, allocator)
if _, ok := err.(runtime.Allocator_Error); ok {
return
} else if err != nil {
break snapshot_module
}
info.executable_path = exe_path
info.fields += {.Executable_Path}
}
ph := win32.HANDLE(process.handle)
read_peb: if selection >= {.Command_Line, .Environment, .Working_Dir} {
process_info_size: u32
process_info: win32.PROCESS_BASIC_INFORMATION
status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size)
if status != 0 {
// TODO(flysand): There's probably a mismatch between NTSTATUS and
// windows userland error codes, I haven't checked.
err = Platform_Error(status)
return
}
assert(process_info.PebBaseAddress != nil)
process_peb: win32.PEB
_, err = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb)
if err != nil {
break read_peb
}
process_params: win32.RTL_USER_PROCESS_PARAMETERS
_, err = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params)
if err != nil {
break read_peb
}
if selection >= {.Command_Line, .Command_Args} {
TEMP_ALLOCATOR_GUARD()
cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return
_, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w)
if err != nil {
break read_peb
}
if .Command_Line in selection {
info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return
info.fields += {.Command_Line}
}
if .Command_Args in selection {
info.command_args = _parse_command_line(raw_data(cmdline_w), allocator) or_return
info.fields += {.Command_Args}
}
}
if .Environment in selection {
TEMP_ALLOCATOR_GUARD()
env_len := process_params.EnvironmentSize / 2
envs_w := make([]u16, env_len, temp_allocator()) or_return
_, err = read_memory_as_slice(ph, process_params.Environment, envs_w)
if err != nil {
break read_peb
}
info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return
info.fields += {.Environment}
}
if .Working_Dir in selection {
TEMP_ALLOCATOR_GUARD()
cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return
_, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w)
if err != nil {
break read_peb
}
info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return
info.fields += {.Working_Dir}
}
}
read_username: if .Username in selection {
username: string
username, err = _get_process_user(ph, allocator)
if _, ok := err.(runtime.Allocator_Error); ok {
return
} else if err != nil {
break read_username
}
info.username = username
info.fields += {.Username}
}
err = nil
@@ -294,15 +328,15 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
@(private="package")
_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
info.pid = get_pid()
defer if err != nil {
free_process_info(info, allocator)
}
if selection >= {.PPid, .Priority} { // snap process
snapshot_process: if selection >= {.PPid, .Priority} {
entry, entry_err := _process_entry_by_pid(info.pid)
if entry_err != nil {
err = General_Error.Not_Exist
err = entry_err
if entry_err == General_Error.Not_Exist {
return
} else {
break snapshot_process
}
}
if .PPid in selection {
info.fields += {.PPid}
@@ -313,14 +347,16 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime
info.priority = int(entry.pcPriClassBase)
}
}
if .Executable_Path in selection {
module_filename: if .Executable_Path in selection {
exe_filename_w: [256]u16
path_len := win32.GetModuleFileNameW(nil, raw_data(exe_filename_w[:]), len(exe_filename_w))
assert(path_len > 0)
info.executable_path = win32_utf16_to_utf8(exe_filename_w[:path_len], allocator) or_return
info.fields += {.Executable_Path}
}
if selection >= {.Command_Line, .Command_Args} {
command_line: if selection >= {.Command_Line, .Command_Args} {
command_line_w := win32.GetCommandLineW()
assert(command_line_w != nil)
if .Command_Line in selection {
info.command_line = win32_wstring_to_utf8(command_line_w, allocator) or_return
info.fields += {.Command_Line}
@@ -330,14 +366,22 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime
info.fields += {.Command_Args}
}
}
if .Environment in selection {
read_environment: if .Environment in selection {
env_block := win32.GetEnvironmentStringsW()
assert(env_block != nil)
info.environment = _parse_environment_block(env_block, allocator) or_return
info.fields += {.Environment}
}
if .Username in selection {
read_username: if .Username in selection {
process_handle := win32.GetCurrentProcess()
info.username = _get_process_user(process_handle, allocator) or_return
username: string
username, err = _get_process_user(process_handle, allocator)
if _, ok := err.(runtime.Allocator_Error); ok {
return
} else if err != nil {
break read_username
}
info.username = username
info.fields += {.Username}
}
if .Working_Dir in selection {
+5
View File
@@ -19,6 +19,7 @@ iterate_array :: proc(val: any, it: ^int) -> (elem: any, index: int, ok: bool) {
elem.data = rawptr(uintptr(val.data) + uintptr(it^ * info.elem_size))
elem.id = info.elem.id
ok = true
index = it^
it^ += 1
}
case Type_Info_Slice:
@@ -27,6 +28,7 @@ iterate_array :: proc(val: any, it: ^int) -> (elem: any, index: int, ok: bool) {
elem.data = rawptr(uintptr(array.data) + uintptr(it^ * info.elem_size))
elem.id = info.elem.id
ok = true
index = it^
it^ += 1
}
case Type_Info_Dynamic_Array:
@@ -35,6 +37,7 @@ iterate_array :: proc(val: any, it: ^int) -> (elem: any, index: int, ok: bool) {
elem.data = rawptr(uintptr(array.data) + uintptr(it^ * info.elem_size))
elem.id = info.elem.id
ok = true
index = it^
it^ += 1
}
}
@@ -69,6 +72,7 @@ iterate_map :: proc(val: any, it: ^int) -> (key, value: any, ok: bool) {
key.id = info.key.id
value.id = info.value.id
ok = true
it^ += 1
break
}
@@ -76,3 +80,4 @@ iterate_map :: proc(val: any, it: ^int) -> (key, value: any, ok: bool) {
}
return
}
+7 -14
View File
@@ -159,7 +159,7 @@ StringEncodings :: enum Index {
ShiftJIS_X0213_00 = 0x0628, // Deprecated. Use `ShiftJIS_X0213` instead.
}
@(link_prefix = "CF", default_calling_convention = "c")
@(link_prefix="CF", default_calling_convention="c")
foreign CoreFoundation {
// Copies the character contents of a string to a local C string buffer after converting the characters to a given encoding.
StringGetCString :: proc(theString: String, buffer: [^]byte, bufferSize: Index, encoding: StringEncoding) -> b8 ---
@@ -181,23 +181,16 @@ foreign CoreFoundation {
STR :: StringMakeConstantString
StringCopyToOdinString :: proc(
theString: String,
allocator := context.allocator,
) -> (
str: string,
ok: bool,
) #optional_ok {
StringCopyToOdinString :: proc(theString: String, allocator := context.allocator) -> (str: string, ok: bool) #optional_ok {
length := StringGetLength(theString)
max := StringGetMaximumSizeForEncoding(length, StringEncoding(StringBuiltInEncodings.UTF8))
buf, err := make([]byte, max, allocator)
if err != nil { return }
raw_str := runtime.Raw_String {
data = raw_data(buf),
if err != nil {
return
}
StringGetBytes(theString, {0, length}, StringEncoding(StringBuiltInEncodings.UTF8), 0, false, raw_data(buf), max, (^Index)(&raw_str.len))
return transmute(string)raw_str, true
n: Index
StringGetBytes(theString, {0, length}, StringEncoding(StringBuiltInEncodings.UTF8), 0, false, raw_data(buf), Index(len(buf)), &n)
return string(buf[:n]), true
}
+1 -1
View File
@@ -4,7 +4,7 @@ package sys_windows
foreign import dwmapi "system:Dwmapi.lib"
DWMWINDOWATTRIBUTE :: enum {
DWMWA_NCRENDERING_ENABLED,
DWMWA_NCRENDERING_ENABLED = 1,
DWMWA_NCRENDERING_POLICY,
DWMWA_TRANSITIONS_FORCEDISABLED,
DWMWA_ALLOW_NCPAINT,
+6 -2
View File
@@ -175,10 +175,12 @@ pool_stop_task :: proc(pool: ^Pool, user_index: int, exit_code: int = 1) -> bool
intrinsics.atomic_sub(&pool.num_outstanding, 1)
intrinsics.atomic_sub(&pool.num_in_processing, 1)
old_thread_user_index := t.user_index
destroy(t)
replacement := create(pool_thread_runner)
replacement.user_index = t.user_index
replacement.user_index = old_thread_user_index
replacement.data = data
data.task = {}
pool.threads[i] = replacement
@@ -207,10 +209,12 @@ pool_stop_all_tasks :: proc(pool: ^Pool, exit_code: int = 1) {
intrinsics.atomic_sub(&pool.num_outstanding, 1)
intrinsics.atomic_sub(&pool.num_in_processing, 1)
old_thread_user_index := t.user_index
destroy(t)
replacement := create(pool_thread_runner)
replacement.user_index = t.user_index
replacement.user_index = old_thread_user_index
replacement.data = data
data.task = {}
pool.threads[i] = replacement
+5 -1
View File
@@ -2,6 +2,7 @@
// +private
package thread
import "base:runtime"
import "core:sync"
import "core:sys/unix"
import "core:time"
@@ -55,7 +56,10 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
// Here on Unix, we start the OS thread in a running state, and so we manually have it wait on a condition
// variable above. We must perform that waiting BEFORE we select the context!
context = _select_context_for_thread(init_context)
defer _maybe_destroy_default_temp_allocator(init_context)
defer {
_maybe_destroy_default_temp_allocator(init_context)
runtime.run_thread_local_cleaners()
}
t.procedure(t)
}
+5 -1
View File
@@ -3,6 +3,7 @@
package thread
import "base:intrinsics"
import "base:runtime"
import "core:sync"
import win32 "core:sys/windows"
@@ -39,7 +40,10 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
// Here on Windows, the thread is created in a suspended state, and so we can select the context anywhere before the call
// to t.procedure().
context = _select_context_for_thread(init_context)
defer _maybe_destroy_default_temp_allocator(init_context)
defer {
_maybe_destroy_default_temp_allocator(init_context)
runtime.run_thread_local_cleaners()
}
t.procedure(t)
}
+61
View File
@@ -2048,6 +2048,14 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
return ok;
}
if (BuiltinProc__atomic_begin < id && id < BuiltinProc__atomic_end) {
if (build_context.metrics.arch == TargetArch_riscv64) {
if (!check_target_feature_is_enabled(str_lit("a"), nullptr)) {
error(call, "missing required target feature \"a\" for atomics, enable it by setting a different -microarch or explicitly adding it through -target-features");
}
}
}
switch (id) {
default:
GB_PANIC("Implement built-in procedure: %.*s", LIT(builtin_name));
@@ -5665,6 +5673,59 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
break;
}
break;
case BuiltinProc_type_has_shared_fields:
{
Type *u = check_type(c, ce->args[0]);
Type *ut = base_type(u);
if (ut == nullptr || ut == t_invalid) {
error(ce->args[0], "Expected a type for '%.*s'", LIT(builtin_name));
return false;
}
if (ut->kind != Type_Struct || ut->Struct.soa_kind != StructSoa_None) {
gbString t = type_to_string(ut);
error(ce->args[0], "Expected a struct type for '%.*s', got %s", LIT(builtin_name), t);
gb_string_free(t);
return false;
}
Type *v = check_type(c, ce->args[1]);
Type *vt = base_type(v);
if (vt == nullptr || vt == t_invalid) {
error(ce->args[1], "Expected a type for '%.*s'", LIT(builtin_name));
return false;
}
if (vt->kind != Type_Struct || vt->Struct.soa_kind != StructSoa_None) {
gbString t = type_to_string(vt);
error(ce->args[1], "Expected a struct type for '%.*s', got %s", LIT(builtin_name), t);
gb_string_free(t);
return false;
}
bool is_shared = true;
for (Entity *v_field : vt->Struct.fields) {
bool found = false;
for (Entity *u_field : ut->Struct.fields) {
if (v_field->token.string == u_field->token.string &&
are_types_identical(v_field->type, u_field->type)) {
found = true;
break;
}
}
if (!found) {
is_shared = false;
break;
}
}
operand->mode = Addressing_Constant;
operand->value = exact_value_bool(is_shared);
operand->type = t_untyped_bool;
break;
}
case BuiltinProc_type_field_type:
{
Operand op = {};
+30 -7
View File
@@ -1180,11 +1180,15 @@ gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *typ
LIT(article),
LIT(context_name));
} else {
ERROR_BLOCK();
error(operand->expr,
"Cannot assign '%s', a type, to %.*s%.*s",
op_type_str,
LIT(article),
LIT(context_name));
if (type && are_types_identical(type, t_any)) {
error_line("\tSuggestion: 'typeid_of(%s)'", expr_str);
}
}
break;
default:
@@ -3782,10 +3786,10 @@ gb_internal void check_binary_expr(CheckerContext *c, Operand *x, Ast *node, Typ
// NOTE(bill): Allow comparisons between types
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, y, be->right, nullptr /* ignore 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, x, be->left, nullptr /* ignore type hint */);
check_expr_or_type(c, y, be->right, x->type);
}
bool xt = x->mode == Addressing_Type;
@@ -4658,7 +4662,8 @@ gb_internal bool check_index_value(CheckerContext *c, Type *main_type, bool open
check_expr_with_type_hint(c, &operand, index_value, type_hint);
if (operand.mode == Addressing_Invalid) {
if (value) *value = 0;
return false;
// NOTE(bill): return true here to propagate the errors better
return true;
}
Type *index_type = t_int;
@@ -4879,7 +4884,7 @@ gb_internal ExactValue get_constant_field_single(CheckerContext *c, ExactValue v
TypeAndValue tav = fv->value->tav;
if (success_) *success_ = true;
if (finish_) *finish_ = false;
return tav.value;;
return tav.value;
}
}
@@ -4954,7 +4959,6 @@ gb_internal ExactValue get_constant_field(CheckerContext *c, Operand const *oper
return value;
}
}
if (success_) *success_ = true;
return value;
} else if (value.kind == ExactValue_Quaternion) {
@@ -7625,7 +7629,7 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O
gbString s = gb_string_make_reserve(heap_allocator(), e->token.string.len+3);
s = gb_string_append_fmt(s, "%.*s(", LIT(e->token.string));
TypeTuple *tuple = get_record_polymorphic_params(e->type);
TypeTuple *tuple = get_record_polymorphic_params(bt);
if (tuple != nullptr) for_array(i, tuple->variables) {
Entity *v = tuple->variables[i];
String name = v->token.string;
@@ -7640,10 +7644,12 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O
s = write_type_to_string(s, v->type, false);
}
} else if (v->kind == Entity_Constant) {
if (v->Constant.value.kind != ExactValue_Invalid) {
s = gb_string_append_fmt(s, "=");
s = write_exact_value_to_string(s, v->Constant.value);
}
}
}
s = gb_string_append_fmt(s, ")");
String new_name = make_string_c(s);
@@ -10055,6 +10061,22 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast *
is_constant = o->mode == Addressing_Constant;
}
if (elem->kind == Ast_BinaryExpr) {
switch (elem->BinaryExpr.op.kind) {
case Token_Or:
{
gbString x = expr_to_string(elem->BinaryExpr.left);
gbString y = expr_to_string(elem->BinaryExpr.right);
gbString e = expr_to_string(elem);
error(elem, "Was the following intended? '%s, %s'; if not, surround the expression with parentheses '(%s)'", x, y, e);
gb_string_free(e);
gb_string_free(y);
gb_string_free(x);
}
break;
}
}
check_assignment(c, o, t->BitSet.elem, str_lit("bit_set literal"));
if (o->mode == Addressing_Constant) {
i64 lower = t->BitSet.lower;
@@ -10544,7 +10566,8 @@ gb_internal ExprKind check_index_expr(CheckerContext *c, Operand *o, Ast *node,
o->expr = node;
return kind;
} else if (ok && !is_type_matrix(t)) {
ExactValue value = type_and_value_of_expr(ie->expr).value;
TypeAndValue tav = type_and_value_of_expr(ie->expr);
ExactValue value = tav.value;
o->mode = Addressing_Constant;
bool success = false;
bool finish = false;
+10 -1
View File
@@ -199,6 +199,9 @@ gb_internal bool check_has_break(Ast *stmt, String const &label, bool implicit)
}
break;
case Ast_DeferStmt:
return check_has_break(stmt->DeferStmt.stmt, label, implicit);
case Ast_BlockStmt:
return check_has_break_list(stmt->BlockStmt.stmts, label, implicit);
@@ -1695,7 +1698,7 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags)
}
}
}
bool is_ptr = type_deref(operand.type);
bool is_ptr = is_type_pointer(type_deref(operand.type));
Type *t = base_type(type_deref(operand.type));
switch (t->kind) {
@@ -1735,6 +1738,7 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags)
break;
case Type_EnumeratedArray:
is_possibly_addressable = operand.mode == Addressing_Variable || is_ptr;
array_add(&vals, t->EnumeratedArray.elem);
array_add(&vals, t->EnumeratedArray.index);
break;
@@ -2706,6 +2710,7 @@ gb_internal void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags)
error(bs->label, "A branch statement's label name must be an identifier");
return;
}
Ast *ident = bs->label;
String name = ident->Ident.token.string;
Operand o = {};
@@ -2737,6 +2742,10 @@ gb_internal void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags)
break;
}
if (ctx->in_defer) {
error(bs->label, "A labelled '%.*s' cannot be used within a 'defer'", LIT(token.string));
}
}
case_end;
+29 -9
View File
@@ -3174,8 +3174,8 @@ gb_internal DECL_ATTRIBUTE_PROC(foreign_block_decl_attribute) {
return true;
} else if (name == "link_prefix") {
if (ev.kind == ExactValue_String) {
String link_prefix = ev.value_string;
if (!is_foreign_name_valid(link_prefix)) {
String link_prefix = string_trim_whitespace(ev.value_string);
if (link_prefix.len != 0 && !is_foreign_name_valid(link_prefix)) {
error(elem, "Invalid link prefix: '%.*s'", LIT(link_prefix));
} else {
c->foreign_context.link_prefix = link_prefix;
@@ -3186,8 +3186,8 @@ gb_internal DECL_ATTRIBUTE_PROC(foreign_block_decl_attribute) {
return true;
} else if (name == "link_suffix") {
if (ev.kind == ExactValue_String) {
String link_suffix = ev.value_string;
if (!is_foreign_name_valid(link_suffix)) {
String link_suffix = string_trim_whitespace(ev.value_string);
if (link_suffix.len != 0 && !is_foreign_name_valid(link_suffix)) {
error(elem, "Invalid link suffix: '%.*s'", LIT(link_suffix));
} else {
c->foreign_context.link_suffix = link_suffix;
@@ -3489,7 +3489,7 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) {
if (ev.kind == ExactValue_String) {
ac->link_prefix = ev.value_string;
if (!is_foreign_name_valid(ac->link_prefix)) {
if (ac->link_prefix.len != 0 && !is_foreign_name_valid(ac->link_prefix)) {
error(elem, "Invalid link prefix: %.*s", LIT(ac->link_prefix));
}
} else {
@@ -3501,7 +3501,7 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) {
if (ev.kind == ExactValue_String) {
ac->link_suffix = ev.value_string;
if (!is_foreign_name_valid(ac->link_suffix)) {
if (ac->link_suffix.len != 0 && !is_foreign_name_valid(ac->link_suffix)) {
error(elem, "Invalid link suffix: %.*s", LIT(ac->link_suffix));
}
} else {
@@ -3774,7 +3774,7 @@ gb_internal DECL_ATTRIBUTE_PROC(var_decl_attribute) {
ExactValue ev = check_decl_attribute_value(c, value);
if (ev.kind == ExactValue_String) {
ac->link_prefix = ev.value_string;
if (!is_foreign_name_valid(ac->link_prefix)) {
if (ac->link_prefix.len != 0 && !is_foreign_name_valid(ac->link_prefix)) {
error(elem, "Invalid link prefix: %.*s", LIT(ac->link_prefix));
}
} else {
@@ -3785,7 +3785,7 @@ gb_internal DECL_ATTRIBUTE_PROC(var_decl_attribute) {
ExactValue ev = check_decl_attribute_value(c, value);
if (ev.kind == ExactValue_String) {
ac->link_suffix = ev.value_string;
if (!is_foreign_name_valid(ac->link_suffix)) {
if (ac->link_suffix.len != 0 && !is_foreign_name_valid(ac->link_suffix)) {
error(elem, "Invalid link suffix: %.*s", LIT(ac->link_suffix));
}
} else {
@@ -4532,7 +4532,9 @@ gb_internal void check_collect_entities(CheckerContext *c, Slice<Ast *> const &n
case_end;
case_ast_node(fb, ForeignBlockDecl, decl);
check_add_foreign_block_decl(c, decl);
if (curr_file != nullptr) {
array_add(&curr_file->delayed_decls_queues[AstDelayQueue_ForeignBlock], decl);
}
case_end;
default:
@@ -4548,6 +4550,14 @@ gb_internal void check_collect_entities(CheckerContext *c, Slice<Ast *> const &n
// NOTE(bill): 'when' stmts need to be handled after the other as the condition may refer to something
// declared after this stmt in source
if (curr_file == nullptr) {
// For 'foreign' block statements that are not in file scope.
for_array(decl_index, nodes) {
Ast *decl = nodes[decl_index];
if (decl->kind == Ast_ForeignBlockDecl) {
check_add_foreign_block_decl(c, decl);
}
}
for_array(decl_index, nodes) {
Ast *decl = nodes[decl_index];
if (decl->kind == Ast_WhenStmt) {
@@ -5508,6 +5518,16 @@ gb_internal void check_import_entities(Checker *c) {
correct_type_aliases_in_scope(&ctx, pkg->scope);
}
for_array(i, pkg->files) {
AstFile *f = pkg->files[i];
reset_checker_context(&ctx, f, &untyped);
for (Ast *decl : f->delayed_decls_queues[AstDelayQueue_ForeignBlock]) {
check_add_foreign_block_decl(&ctx, decl);
}
array_clear(&f->delayed_decls_queues[AstDelayQueue_ForeignBlock]);
}
for_array(i, pkg->files) {
AstFile *f = pkg->files[i];
reset_checker_context(&ctx, f, &untyped);
+7
View File
@@ -99,6 +99,7 @@ enum BuiltinProcId {
BuiltinProc_prefetch_write_instruction,
BuiltinProc_prefetch_write_data,
BuiltinProc__atomic_begin,
BuiltinProc_atomic_type_is_lock_free,
BuiltinProc_atomic_thread_fence,
BuiltinProc_atomic_signal_fence,
@@ -124,6 +125,7 @@ enum BuiltinProcId {
BuiltinProc_atomic_compare_exchange_strong_explicit,
BuiltinProc_atomic_compare_exchange_weak,
BuiltinProc_atomic_compare_exchange_weak_explicit,
BuiltinProc__atomic_end,
BuiltinProc_fixed_point_mul,
BuiltinProc_fixed_point_div,
@@ -313,6 +315,8 @@ BuiltinProc__type_simple_boolean_end,
BuiltinProc_type_map_info,
BuiltinProc_type_map_cell_info,
BuiltinProc_type_has_shared_fields,
BuiltinProc__type_end,
BuiltinProc_procedure_of,
@@ -436,6 +440,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
{STR_LIT("prefetch_write_instruction"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
{STR_LIT("prefetch_write_data"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
{STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
{STR_LIT("atomic_type_is_lock_free"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("atomic_thread_fence"), 1, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
{STR_LIT("atomic_signal_fence"), 1, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
@@ -461,6 +466,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
{STR_LIT("atomic_compare_exchange_strong_explicit"), 5, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true},
{STR_LIT("atomic_compare_exchange_weak"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true},
{STR_LIT("atomic_compare_exchange_weak_explicit"), 5, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true},
{STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
{STR_LIT("fixed_point_mul"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("fixed_point_div"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics},
@@ -647,6 +653,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
{STR_LIT("type_map_info"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("type_map_cell_info"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("type_has_shared_fields"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
+13 -3
View File
@@ -55,6 +55,16 @@ gb_internal void lb_debug_file_line(lbModule *m, Ast *node, LLVMMetadataRef *fil
}
}
gb_internal LLVMMetadataRef lb_debug_procedure_parameters(lbModule *m, Type *type) {
if (is_type_proc(type)) {
return lb_debug_type(m, t_rawptr);
}
if (type->kind == Type_Tuple && type->Tuple.variables.count == 1) {
return lb_debug_procedure_parameters(m, type->Tuple.variables[0]->type);
}
return lb_debug_type(m, type);
}
gb_internal LLVMMetadataRef lb_debug_type_internal_proc(lbModule *m, Type *type) {
i64 size = type_size_of(type); // Check size
gb_unused(size);
@@ -78,7 +88,7 @@ gb_internal LLVMMetadataRef lb_debug_type_internal_proc(lbModule *m, Type *type)
if (type->Proc.result_count == 0) {
parameters[param_index++] = nullptr;
} else {
parameters[param_index++] = lb_debug_type(m, type->Proc.results);
parameters[param_index++] = lb_debug_procedure_parameters(m, type->Proc.results);
}
LLVMMetadataRef file = nullptr;
@@ -88,7 +98,7 @@ gb_internal LLVMMetadataRef lb_debug_type_internal_proc(lbModule *m, Type *type)
if (e->kind != Entity_Variable) {
continue;
}
parameters[param_index] = lb_debug_type(m, e->type);
parameters[param_index] = lb_debug_procedure_parameters(m, e->type);
param_index += 1;
}
@@ -969,7 +979,7 @@ gb_internal LLVMMetadataRef lb_debug_type(lbModule *m, Type *type) {
return lb_debug_struct(m, type, bt, name, scope, file, line);
}
case Type_Struct: return lb_debug_struct(m, type, base_type(type), name, scope, file, line);
case Type_Struct: return lb_debug_struct(m, type, bt, name, scope, file, line);
case Type_Slice: return lb_debug_slice(m, type, name, scope, file, line);
case Type_DynamicArray: return lb_debug_dynamic_array(m, type, name, scope, file, line);
case Type_Union: return lb_debug_union(m, type, name, scope, file, line);
+1 -1
View File
@@ -263,7 +263,7 @@ gb_internal lbValue lb_emit_transmute(lbProcedure *p, lbValue value, Type *t) {
if (is_type_simd_vector(src) && is_type_simd_vector(dst)) {
res.value = LLVMBuildBitCast(p->builder, value.value, lb_type(p->module, t), "");
return res;
} else if (is_type_array_like(src) && is_type_simd_vector(dst)) {
} else if (is_type_array_like(src) && (is_type_simd_vector(dst) || is_type_integer_128bit(dst))) {
unsigned align = cast(unsigned)gb_max(type_align_of(src), type_align_of(dst));
lbValue ptr = lb_address_from_load_or_generate_local(p, value);
if (lb_try_update_alignment(ptr, align)) {
+38 -8
View File
@@ -1921,6 +1921,9 @@ gb_internal Array<Ast *> parse_enum_field_list(AstFile *f) {
f->curr_token.kind != Token_EOF) {
CommentGroup *docs = f->lead_comment;
CommentGroup *comment = nullptr;
parse_enforce_tabs(f);
Ast *name = parse_value(f);
Ast *value = nullptr;
if (f->curr_token.kind == Token_Eq) {
@@ -2259,6 +2262,7 @@ gb_internal Array<Ast *> parse_union_variant_list(AstFile *f) {
auto variants = array_make<Ast *>(ast_allocator(f));
while (f->curr_token.kind != Token_CloseBrace &&
f->curr_token.kind != Token_EOF) {
parse_enforce_tabs(f);
Ast *type = parse_type(f);
if (type->kind != Ast_BadExpr) {
array_add(&variants, type);
@@ -4274,6 +4278,7 @@ gb_internal Ast *parse_field_list(AstFile *f, isize *name_count_, u32 allowed_fl
while (f->curr_token.kind != follow &&
f->curr_token.kind != Token_Colon &&
f->curr_token.kind != Token_EOF) {
if (!is_signature) parse_enforce_tabs(f);
u32 flags = parse_field_prefixes(f);
Ast *param = parse_var_type(f, allow_ellipsis, allow_typeid_token);
if (param->kind == Ast_Ellipsis) {
@@ -4363,6 +4368,8 @@ gb_internal Ast *parse_field_list(AstFile *f, isize *name_count_, u32 allowed_fl
f->curr_token.kind != Token_EOF &&
f->curr_token.kind != Token_Semicolon) {
CommentGroup *docs = f->lead_comment;
if (!is_signature) parse_enforce_tabs(f);
u32 set_flags = parse_field_prefixes(f);
Token tag = {};
Array<Ast *> names = parse_ident_list(f, allow_poly_names);
@@ -4532,6 +4539,10 @@ gb_internal Ast *parse_if_stmt(AstFile *f) {
return ast_bad_stmt(f, f->curr_token, f->curr_token);
}
Ast *top_if_stmt = nullptr;
Ast *prev_if_stmt = nullptr;
if_else_chain:;
Token token = expect_token(f, Token_if);
Ast *init = nullptr;
Ast *cond = nullptr;
@@ -4573,12 +4584,24 @@ gb_internal Ast *parse_if_stmt(AstFile *f) {
ignore_strict_style = true;
}
skip_possible_newline_for_literal(f, ignore_strict_style);
Ast *curr_if_stmt = ast_if_stmt(f, token, init, cond, body, nullptr);
if (top_if_stmt == nullptr) {
top_if_stmt = curr_if_stmt;
}
if (prev_if_stmt != nullptr) {
prev_if_stmt->IfStmt.else_stmt = curr_if_stmt;
}
if (f->curr_token.kind == Token_else) {
Token else_token = expect_token(f, Token_else);
switch (f->curr_token.kind) {
case Token_if:
else_stmt = parse_if_stmt(f);
break;
// NOTE(bill): Instead of relying on recursive descent for an if-else chain
// we can just inline the tail-recursion manually with a simple loop like
// construct using a `goto`
prev_if_stmt = curr_if_stmt;
goto if_else_chain;
case Token_OpenBrace:
else_stmt = parse_block_stmt(f, false);
break;
@@ -4593,7 +4616,9 @@ gb_internal Ast *parse_if_stmt(AstFile *f) {
}
}
return ast_if_stmt(f, token, init, cond, body, else_stmt);
curr_if_stmt->IfStmt.else_stmt = else_stmt;
return top_if_stmt;
}
gb_internal Ast *parse_when_stmt(AstFile *f) {
@@ -5357,6 +5382,11 @@ gb_internal u64 check_vet_flags(AstFile *file) {
gb_internal void parse_enforce_tabs(AstFile *f) {
// Checks to see if tabs have been used for indentation
if ((check_vet_flags(f) & VetFlag_Tabs) == 0) {
return;
}
Token prev = f->prev_token;
Token curr = f->curr_token;
if (prev.pos.line < curr.pos.line) {
@@ -5373,6 +5403,10 @@ gb_internal void parse_enforce_tabs(AstFile *f) {
isize len = end-it;
for (isize i = 0; i < len; i++) {
if (it[i] == '/') {
// ignore comments
break;
}
if (it[i] == ' ') {
syntax_error(curr, "With '-vet-tabs', tabs must be used for indentation");
break;
@@ -5387,11 +5421,7 @@ gb_internal Array<Ast *> parse_stmt_list(AstFile *f) {
while (f->curr_token.kind != Token_case &&
f->curr_token.kind != Token_CloseBrace &&
f->curr_token.kind != Token_EOF) {
// Checks to see if tabs have been used for indentation
if (check_vet_flags(f) & VetFlag_Tabs) {
parse_enforce_tabs(f);
}
Ast *stmt = parse_stmt(f);
if (stmt && stmt->kind != Ast_EmptyStmt) {
@@ -6416,7 +6446,7 @@ gb_internal bool parse_file(Parser *p, AstFile *f) {
}
f->total_file_decl_count += calc_decl_count(stmt);
if (stmt->kind == Ast_WhenStmt || stmt->kind == Ast_ExprStmt || stmt->kind == Ast_ImportDecl) {
if (stmt->kind == Ast_WhenStmt || stmt->kind == Ast_ExprStmt || stmt->kind == Ast_ImportDecl || stmt->kind == Ast_ForeignBlockDecl) {
f->delayed_decl_count += 1;
}
}
+4
View File
@@ -82,6 +82,7 @@ enum AstFileFlag : u32 {
enum AstDelayQueueKind {
AstDelayQueue_Import,
AstDelayQueue_Expr,
AstDelayQueue_ForeignBlock,
AstDelayQueue_COUNT,
};
@@ -886,3 +887,6 @@ gb_internal Ast *alloc_ast_node(AstFile *f, AstKind kind);
gb_internal gbString expr_to_string(Ast *expression);
gb_internal bool allow_field_separator(AstFile *f);
gb_internal void parse_enforce_tabs(AstFile *f);
+1 -1
View File
@@ -429,7 +429,7 @@ map_with_integer_keys :: proc(t: ^testing.T) {
defer delete_map(my_map2)
unmarshal_err := json.unmarshal(marshaled_data, &my_map2)
defer for key, item in my_map2 {
defer for _, item in my_map2 {
runtime.delete_string(item)
}
testing.expectf(t, unmarshal_err == nil, "Expected `json.unmarshal` to return nil, got %v", unmarshal_err)
@@ -11,6 +11,7 @@ import "core:testing"
test_temp_allocator_alignment_boundary :: proc(t: ^testing.T) {
arena: runtime.Arena
context.allocator = runtime.arena_allocator(&arena)
defer runtime.arena_destroy(&arena)
_, _ = mem.alloc(int(runtime.DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE)-120)
_, err := mem.alloc(112, 32)
@@ -22,6 +23,7 @@ test_temp_allocator_alignment_boundary :: proc(t: ^testing.T) {
test_temp_allocator_big_alloc_and_alignment :: proc(t: ^testing.T) {
arena: runtime.Arena
context.allocator = runtime.arena_allocator(&arena)
defer runtime.arena_destroy(&arena)
mappy: map[[8]int]int
err := reserve(&mappy, 50000)
@@ -32,6 +34,7 @@ test_temp_allocator_big_alloc_and_alignment :: proc(t: ^testing.T) {
test_temp_allocator_returns_correct_size :: proc(t: ^testing.T) {
arena: runtime.Arena
context.allocator = runtime.arena_allocator(&arena)
defer runtime.arena_destroy(&arena)
bytes, err := mem.alloc_bytes(10, 16)
testing.expect(t, err == nil)
+1 -2
View File
@@ -16,8 +16,7 @@ run_test_cases :: proc(t: ^testing.T, test_cases: []Test_Case, loc := #caller_lo
result, _, _ := utf8.grapheme_count(c.str)
if !testing.expectf(t, result == c.expected_clusters,
"(#% 4i) graphemes: %i != %i, %q %s", i, result, c.expected_clusters, c.str, c.str,
loc = loc)
{
loc = loc) {
failed += 1
}
}
+1 -1
View File
@@ -338,7 +338,7 @@ foreign lib {
node_set_list_tight :: proc(node: ^Node, tight: b32) -> (success: b32) ---
// Returns the info string from a fenced code block.
get_fence_info :: proc(node: ^Node) -> (fence_info: cstring) ---
node_get_fence_info :: proc(node: ^Node) -> (fence_info: cstring) ---
// Sets the info string in a fenced code block, returning `true` on success and `false` on failure.
node_set_fence_info :: proc(node: ^Node, fence_info: cstring) -> (success: b32) ---
+20 -5
View File
@@ -84,7 +84,6 @@ package raylib
import "core:c"
import "core:fmt"
import "core:mem"
import "core:strings"
import "core:math/linalg"
_ :: linalg
@@ -1029,7 +1028,6 @@ foreign lib {
SetTraceLogLevel :: proc(logLevel: TraceLogLevel) --- // Set the current threshold (minimum) log level
MemAlloc :: proc(size: c.uint) -> rawptr --- // Internal memory allocator
MemRealloc :: proc(ptr: rawptr, size: c.uint) -> rawptr --- // Internal memory reallocator
MemFree :: proc(ptr: rawptr) --- // Internal memory free
// Set custom callbacks
// WARNING: Callbacks setup is intended for advance users
@@ -1256,7 +1254,7 @@ foreign lib {
LoadImage :: proc(fileName: cstring) -> Image --- // Load image from file into CPU memory (RAM)
LoadImageRaw :: proc(fileName: cstring, width, height: c.int, format: PixelFormat, headerSize: c.int) -> Image --- // Load image from RAW file data
LoadImageSvg :: proc(fileNameOrString: cstring, width, height: c.int) -> Image --- // Load image from SVG file data or string with specified size
LoadImageAnim :: proc(fileName: cstring, frames: [^]c.int) -> Image --- // Load image sequence from file (frames appended to image.data)
LoadImageAnim :: proc(fileName: cstring, frames: ^c.int) -> Image --- // Load image sequence from file (frames appended to image.data)
LoadImageFromMemory :: proc(fileType: cstring, fileData: rawptr, dataSize: c.int) -> Image --- // Load image from memory buffer, fileType refers to extension: i.e. '.png'
LoadImageFromTexture :: proc(texture: Texture2D) -> Image --- // Load image from GPU texture data
LoadImageFromScreen :: proc() -> Image --- // Load image from screen buffer and (screenshot)
@@ -1684,8 +1682,25 @@ TextFormat :: proc(text: cstring, args: ..any) -> cstring {
// Text formatting with variables (sprintf style) and allocates (must be freed with 'MemFree')
TextFormatAlloc :: proc(text: cstring, args: ..any) -> cstring {
str := fmt.tprintf(string(text), ..args)
return strings.clone_to_cstring(str, MemAllocator())
return fmt.caprintf(string(text), ..args, allocator=MemAllocator())
}
// Internal memory free
MemFree :: proc{
MemFreePtr,
MemFreeCstring,
}
@(default_calling_convention="c")
foreign lib {
@(link_name="MemFree")
MemFreePtr :: proc(ptr: rawptr) ---
}
MemFreeCstring :: proc "c" (s: cstring) {
MemFreePtr(rawptr(s))
}
+6
View File
@@ -41,6 +41,12 @@ MAJOR_VERSION :: 2
MINOR_VERSION :: 0
PATCHLEVEL :: 16
VERSION :: proc "contextless" (ver: ^version) {
ver.major = MAJOR_VERSION
ver.minor = MINOR_VERSION
ver.patch = PATCHLEVEL
}
@(default_calling_convention="c", link_prefix="SDL_")
foreign lib {
GetVersion :: proc(ver: ^version) ---
+2 -2
View File
@@ -11,8 +11,8 @@ Have a look at the `example/` directory for the rendering of a basic triangle.
## Getting the wgpu-native libraries
For native support (not the browser), some libraries are required. Fortunately this is
extremely easy, just download them from the [releases on GitHub](https://github.com/gfx-rs/wgpu-native/releases/tag/v0.19.4.1),
the bindings are for v0.19.4.1 at the moment.
extremely easy, just download them from the [releases on GitHub](https://github.com/gfx-rs/wgpu-native/releases/tag/v22.1.0.1),
the bindings are for v22.1.0.1 at the moment.
These are expected in the `lib` folder under the same name as they are released (just unzipped).
By default it will look for a static release version (`wgpu-OS-ARCH-release.a|lib`),
+3 -1
View File
@@ -158,15 +158,17 @@ frame :: proc "c" (dt: f32) {
view = frame,
loadOp = .Clear,
storeOp = .Store,
depthSlice = wgpu.DEPTH_SLICE_UNDEFINED,
clearValue = { 0, 1, 0, 1 },
},
},
)
defer wgpu.RenderPassEncoderRelease(render_pass_encoder)
wgpu.RenderPassEncoderSetPipeline(render_pass_encoder, state.pipeline)
wgpu.RenderPassEncoderDraw(render_pass_encoder, vertexCount=3, instanceCount=1, firstVertex=0, firstInstance=0)
wgpu.RenderPassEncoderEnd(render_pass_encoder)
wgpu.RenderPassEncoderRelease(render_pass_encoder)
command_buffer := wgpu.CommandEncoderFinish(command_encoder, nil)
defer wgpu.CommandBufferRelease(command_buffer)
+3 -1
View File
@@ -158,15 +158,17 @@ frame :: proc "c" (dt: f32) {
view = frame,
loadOp = .Clear,
storeOp = .Store,
depthSlice = wgpu.DEPTH_SLICE_UNDEFINED,
clearValue = { 0, 1, 0, 1 },
},
},
)
defer wgpu.RenderPassEncoderRelease(render_pass_encoder)
wgpu.RenderPassEncoderSetPipeline(render_pass_encoder, state.pipeline)
wgpu.RenderPassEncoderDraw(render_pass_encoder, vertexCount=3, instanceCount=1, firstVertex=0, firstInstance=0)
wgpu.RenderPassEncoderEnd(render_pass_encoder)
wgpu.RenderPassEncoderRelease(render_pass_encoder)
command_buffer := wgpu.CommandEncoderFinish(command_encoder, nil)
defer wgpu.CommandBufferRelease(command_buffer)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2
View File
@@ -5,7 +5,9 @@ import "vendor:wgpu"
GetSurface :: proc(instance: wgpu.Instance, window: ^sdl2.Window) -> wgpu.Surface {
window_info: sdl2.SysWMinfo
sdl2.VERSION(&window_info.version)
sdl2.GetWindowWMInfo(window, &window_info)
if window_info.subsystem == .WAYLAND {
display := window_info.info.wl.display
surface := window_info.info.wl.surface
+91 -96
View File
@@ -44,6 +44,7 @@ class WebGPUInterface {
BlendFactor: ["zero", "one", "src", "one-minus-src", "src-alpha", "one-minus-src-alpha", "dst", "one-minus-dst", "dst-alpha", "one-minus-dst-alpha", "src-alpha-saturated", "constant", "one-minus-constant", ],
PresentMode: ["fifo", "fifo-relaxed", "immediate", "mailbox", ],
TextureAspect: ["all", "stencil-only", "depth-only"],
DeviceLostReason: [undefined, "unknown", "destroyed"],
};
/** @type {WebGPUObjectManager<{}>} */
@@ -382,13 +383,19 @@ class WebGPUInterface {
*/
RenderPassColorAttachment(start) {
const viewIdx = this.mem.loadPtr(start + 4);
const resolveTargetIdx = this.mem.loadPtr(start + 8);
const resolveTargetIdx = this.mem.loadPtr(start + 12);
let depthSlice = this.mem.loadU32(start + 8);
if (depthSlice == 0xFFFFFFFF) { // DEPTH_SLICE_UNDEFINED.
depthSlice = undefined;
}
return {
view: viewIdx > 0 ? this.textureViews.get(viewIdx) : undefined,
resolveTarget: resolveTargetIdx > 0 ? this.textureViews.get(resolveTargetIdx) : undefined,
loadOp: this.enumeration("LoadOp", start + 12),
storeOp: this.enumeration("StoreOp", start + 16),
depthSlice: depthSlice,
loadOp: this.enumeration("LoadOp", start + 16),
storeOp: this.enumeration("StoreOp", start + 20),
clearValue: this.Color(start + 24),
};
}
@@ -950,14 +957,25 @@ class WebGPUInterface {
/**
* @param {number} adapterIdx
* @param {number} propertiesPtr
* @param {number} infoPtr
*/
wgpuAdapterGetProperties: (adapterIdx, propertiesPtr) => {
this.assert(propertiesPtr != 0);
// Unknown adapter.
this.mem.storeI32(propertiesPtr + 28, 3);
wgpuAdapterGetInfo: (adapterIdx, infoPtr) => {
this.assert(infoPtr != 0);
// WebGPU backend.
this.mem.storeI32(propertiesPtr + 32, 2);
this.mem.storeI32(infoPtr + 20, 2);
// Unknown adapter.
this.mem.storeI32(infoPtr + 24, 3);
// NOTE: I don't think getting the other fields in this struct is possible.
// `adapter.requestAdapterInfo` is deprecated.
},
/**
* @param {number} infoPtr
*/
wgpuAdapterInfoFreeMembers: (infoPtr) => {
// NOTE: nothing to free.
},
/**
@@ -970,50 +988,6 @@ class WebGPUInterface {
return adapter.features.has(this.enums.FeatureName[featureInt]);
},
/**
* @param {number} adapterIdx
* @param {number} callbackPtr
* @param {0|number} userdata
*/
wgpuAdapterRequestAdapterInfo: async (adapterIdx, callbackPtr, userdata) => {
const adapter = this.adapters.get(adapterIdx);
const callback = this.mem.exports.__indirect_function_table.get(callbackPtr);
const info = await adapter.requestAdapterInfo();
const addr = this.mem.exports.wgpu_alloc(16);
const vendorLength = new TextEncoder().encode(info.vendor).length;
const vendorAddr = this.mem.exports.wgpu_alloc(vendorLength);
this.mem.storeString(vendorAddr, info.vendor);
this.mem.storeI32(addr + 0, vendorAddr);
const architectureLength = new TextEncoder().encode(info.architecture).length;
const architectureAddr = this.mem.exports.wgpu_alloc(architectureLength);
this.mem.storeString(architectureAddr, info.architecture);
this.mem.storeI32(addr + 4, architectureAddr);
const deviceLength = new TextEncoder().encode(info.device).length;
const deviceAddr = this.mem.exports.wgpu_alloc(deviceLength);
this.mem.storeString(deviceAddr, info.device);
this.mem.storeI32(addr + 8, deviceAddr);
const descriptionLength = new TextEncoder().encode(info.description).length;
const descriptionAddr = this.mem.exports.wgpu_alloc(descriptionLength);
this.mem.storeString(descriptionAddr, info.description);
this.mem.storeI32(addr + 12, descriptionAddr);
callback(addr, userdata);
this.mem.exports.wgpu_free(descriptionAddr);
this.mem.exports.wgpu_free(deviceAddr);
this.mem.exports.wgpu_free(architectureAddr);
this.mem.exports.wgpu_free(vendorAddr);
this.mem.exports.wgpu_free(addr);
},
/**
* @param {number} adapterIdx
* @param {0|number} descriptorPtr
@@ -1040,14 +1014,69 @@ class WebGPUInterface {
};
}
let device;
let deviceIdx;
try {
const device = await adapter.requestDevice(descriptor);
device = await adapter.requestDevice(descriptor);
deviceIdx = this.devices.create(device);
// NOTE: don't callback here, any errors that happen later will then be caught by the catch here.
} catch (e) {
console.warn(e);
callback(1, null, null, userdata);
const messageLength = new TextEncoder().encode(e.message).length;
const messageAddr = this.mem.exports.wgpu_alloc(messageLength + 1);
this.mem.storeString(messageAddr, e.message);
callback(1, null, messageAddr, userdata);
this.mem.exports.wgpu_free(messageAddr);
}
let callbacksPtr = descriptorPtr + 24 + this.mem.intSize;
const deviceLostCallbackPtr = this.mem.loadPtr(callbacksPtr);
if (deviceLostCallbackPtr != 0) {
const deviceLostUserData = this.mem.loadPtr(callbacksPtr) + 4;
const deviceLostCallback = this.mem.exports.__indirect_function_table.get(deviceLostCallbackPtr);
device.lost.then((info) => {
const reason = this.enums.DeviceLostReason.indexOf(info.reason);
const messageLength = new TextEncoder().encode(info.message).length;
const messageAddr = this.mem.exports.wgpu_alloc(messageLength + 1);
this.mem.storeString(messageAddr, info.message);
deviceLostCallback(reason, messageAddr, deviceLostUserData);
this.mem.exports.wgpu_free(messageAddr);
});
}
callbacksPtr += 8;
// Skip over `nextInChain`.
callbacksPtr += 4;
const uncapturedErrorCallbackPtr = this.mem.loadPtr(callbacksPtr);
if (uncapturedErrorCallbackPtr != 0) {
const uncapturedErrorUserData = this.mem.loadPtr(callbacksPtr + 4);
const uncapturedErrorCallback = this.mem.exports.__indirect_function_table.get(uncapturedErrorCallbackPtr);
device.onuncapturederror = (ev) => {
let status = 4; // Unknown
if (ev.error instanceof GPUValidationError) {
status = 1; // Validation
} else if (ev.error instanceof GPUOutOfMemoryError) {
status = 2; // OutOfMemory
} else if (ev.error instanceof GPUInternalError) {
status = 3; // Internal
}
const messageLength = new TextEncoder().encode(ev.error.message).length;
const messageAddr = this.mem.exports.wgpu_alloc(messageLength + 1);
this.mem.storeString(messageAddr, ev.error.message);
uncapturedErrorCallback(status, messageAddr, uncapturedErrorUserData);
this.mem.exports.wgpu_free(messageAddr);
};
}
callback(0, deviceIdx, null, userdata);
@@ -1918,29 +1947,6 @@ class WebGPUInterface {
device.pushErrorScope(this.enums.ErrorFilter[filterInt]);
},
/**
* @param {number} deviceIdx
* @param {number} callbackPtr
* @param {number} userdata
*/
wgpuDeviceSetUncapturedErrorCallback: (deviceIdx, callbackPtr, userdata) => {
const device = this.devices.get(deviceIdx);
const callback = this.mem.exports.__indirect_function_table.get(callbackPtr);
device.onuncapturederror = (ev) => {
console.warn(ev.error);
let status = 4;
if (error instanceof GPUValidationError) {
status = 1;
} else if (error instanceof GPUOutOfMemoryError) {
status = 2;
} else if (error instanceof GPUInternalError) {
status = 3;
}
callback(status, null, userdata);
};
},
...this.devices.interface(true),
/* ---------------------- Instance ---------------------- */
@@ -2646,23 +2652,23 @@ class WebGPUInterface {
const formatStr = navigator.gpu.getPreferredCanvasFormat();
const format = this.enums.TextureFormat.indexOf(formatStr);
this.mem.storeUint(capabilitiesPtr + this.mem.intSize, 1);
this.mem.storeUint(capabilitiesPtr + 8, 1);
const formatAddr = this.mem.exports.wgpu_alloc(4);
this.mem.storeI32(formatAddr, format);
this.mem.storeI32(capabilitiesPtr + this.mem.intSize*2, formatAddr);
this.mem.storeI32(capabilitiesPtr + 8 + this.mem.intSize, formatAddr);
// NOTE: present modes don't seem to actually do anything in JS, we can just give back a default FIFO though.
this.mem.storeUint(capabilitiesPtr + this.mem.intSize*3, 1);
this.mem.storeUint(capabilitiesPtr + 8 + this.mem.intSize*2, 1);
const presentModesAddr = this.mem.exports.wgpu_alloc(4);
this.mem.storeI32(presentModesAddr, 0);
this.mem.storeI32(capabilitiesPtr + this.mem.intSize*4, presentModesAddr);
this.mem.storeI32(capabilitiesPtr + 8 + this.mem.intSize*3, presentModesAddr);
// Browser seems to support opaque (1) and premultiplied (2).
this.mem.storeUint(capabilitiesPtr + this.mem.intSize*5, 2);
this.mem.storeUint(capabilitiesPtr + 8 + this.mem.intSize*4, 2);
const alphaModesAddr = this.mem.exports.wgpu_alloc(8);
this.mem.storeI32(alphaModesAddr + 0, 1); // Opaque.
this.mem.storeI32(alphaModesAddr + 4, 2); // premultiplied.
this.mem.storeI32(capabilitiesPtr + this.mem.intSize*6, alphaModesAddr);
this.mem.storeI32(capabilitiesPtr + 8 + this.mem.intSize*5, alphaModesAddr);
},
/**
@@ -2680,17 +2686,6 @@ class WebGPUInterface {
// TODO: determine suboptimal and/or status.
},
/**
* @param {number} surfaceIdx
* @param {number} texturePtr
* @returns {number}
*/
wgpuSurfaceGetPreferredFormat: (surfaceIdx, adapterIdx) => {
const formatStr = navigator.gpu.getPreferredCanvasFormat();
const format = this.enums.TextureFormat.indexOf(formatStr);
return format;
},
/**
* @param {number} surfaceIdx
*/
+111 -53
View File
@@ -13,7 +13,7 @@ when ODIN_OS == .Windows {
@(private) LIB :: "lib/wgpu-windows-" + ARCH + "-" + TYPE + "/wgpu_native" + EXT
when !#exists(LIB) {
#panic("Could not find the compiled WGPU Native library at '" + #directory + LIB + "', these can be downloaded from https://github.com/gfx-rs/wgpu-native/releases/tag/v0.19.4.1, make sure to read the README at '" + #directory + "vendor/wgpu/README.md'")
#panic("Could not find the compiled WGPU Native library at '" + #directory + LIB + "', these can be downloaded from https://github.com/gfx-rs/wgpu-native/releases/tag/v22.1.0.1, make sure to read the README at '" + #directory + "vendor/wgpu/README.md'")
}
foreign import libwgpu {
@@ -34,7 +34,7 @@ when ODIN_OS == .Windows {
@(private) LIB :: "lib/wgpu-macos-" + ARCH + "-" + TYPE + "/libwgpu_native" + EXT
when !#exists(LIB) {
#panic("Could not find the compiled WGPU Native library at '" + #directory + LIB + "', these can be downloaded from https://github.com/gfx-rs/wgpu-native/releases/tag/v0.19.4.1, make sure to read the README at '" + #directory + "vendor/wgpu/README.md'")
#panic("Could not find the compiled WGPU Native library at '" + #directory + LIB + "', these can be downloaded from https://github.com/gfx-rs/wgpu-native/releases/tag/v22.1.0.1, make sure to read the README at '" + #directory + "vendor/wgpu/README.md'")
}
foreign import libwgpu {
@@ -49,7 +49,7 @@ when ODIN_OS == .Windows {
@(private) LIB :: "lib/wgpu-linux-" + ARCH + "-" + TYPE + "/libwgpu_native" + EXT
when !#exists(LIB) {
#panic("Could not find the compiled WGPU Native library at '" + #directory + LIB + "', these can be downloaded from https://github.com/gfx-rs/wgpu-native/releases/tag/v0.19.4.1, make sure to read the README at '" + #directory + "vendor/wgpu/README.md'")
#panic("Could not find the compiled WGPU Native library at '" + #directory + LIB + "', these can be downloaded from https://github.com/gfx-rs/wgpu-native/releases/tag/v22.1.0.1, make sure to read the README at '" + #directory + "vendor/wgpu/README.md'")
}
foreign import libwgpu {
@@ -220,7 +220,8 @@ CullMode :: enum i32 {
DeviceLostReason :: enum i32 {
Undefined = 0x00000000,
Destroyed = 0x00000001,
Unknown = 0x00000001,
Destroyed = 0x00000002,
}
ErrorFilter :: enum i32 {
@@ -264,6 +265,30 @@ FeatureName :: enum i32 {
PipelineStatisticsQuery,
StorageResourceBindingArray,
PartiallyBoundBindingArray,
TextureFormat16bitNorm,
TextureCompressionAstcHdr,
// TODO: requires wgpu.h api change
// TimestampQueryInsidePasses,
MappablePrimaryBuffers = 0x0003000E,
BufferBindingArray,
UniformBufferAndStorageTextureArrayNonUniformIndexing,
// TODO: requires wgpu.h api change
// AddressModeClampToZero,
// AddressModeClampToBorder,
// PolygonModeLine,
// PolygonModePoint,
// ConservativeRasterization,
// ClearTexture,
// SprivShaderPassThrough,
// MultiView,
VertexAttribute64bit = 0x00030019,
TextureFormatNv12,
RayTracingAccelarationStructure,
RayQuery,
ShaderF64,
ShaderI16,
ShaderPrimitiveIndex,
ShaderEarlyDepthTest,
}
FilterMode :: enum i32 {
@@ -520,6 +545,18 @@ TextureFormat :: enum i32 {
ASTC12x10UnormSrgb = 0x0000005D,
ASTC12x12Unorm = 0x0000005E,
ASTC12x12UnormSrgb = 0x0000005F,
// Native.
// From FeatureName.TextureFormat16bitNorm
R16Unorm = 0x00030001,
R16Snorm,
Rg16Unorm,
Rg16Snorm,
Rgba16Unorm,
Rgba16Snorm,
// From FeatureName.TextureFormatNv12
NV12,
}
TextureSampleType :: enum i32 {
@@ -581,13 +618,13 @@ VertexStepMode :: enum i32 {
VertexBufferNotUsed = 0x00000002,
}
// WGSLFeatureName :: enum i32 {
// Undefined = 0x00000000,
// ReadonlyAndReadwriteStorageTextures = 0x00000001,
// Packed4x8IntegerDotProduct = 0x00000002,
// UnrestrictedPointerParameters = 0x00000003,
// PointerCompositeAccess = 0x00000004,
// }
WGSLFeatureName :: enum i32 {
Undefined = 0x00000000,
ReadonlyAndReadwriteStorageTextures = 0x00000001,
Packed4x8IntegerDotProduct = 0x00000002,
UnrestrictedPointerParameters = 0x00000003,
PointerCompositeAccess = 0x00000004,
}
BufferUsage :: enum i32 {
MapRead = 0x00000000,
@@ -634,22 +671,18 @@ TextureUsage :: enum i32 {
}
TextureUsageFlags :: bit_set[TextureUsage; Flags]
BufferMapAsyncCallback :: #type proc "c" (status: BufferMapAsyncStatus, /* NULLABLE */ userdata: rawptr)
ShaderModuleGetCompilationInfoCallback :: #type proc "c" (status: CompilationInfoRequestStatus, compilationInfo: ^CompilationInfo, /* NULLABLE */ userdata: rawptr)
DeviceCreateComputePipelineAsyncCallback :: #type proc "c" (status: CreatePipelineAsyncStatus, pipeline: ComputePipeline, message: cstring, /* NULLABLE */ userdata: rawptr)
DeviceCreateRenderPipelineAsyncCallback :: #type proc "c" (status: CreatePipelineAsyncStatus, pipeline: RenderPipeline, message: cstring, /* NULLABLE */ userdata: rawptr)
Proc :: distinct rawptr
DeviceLostCallback :: #type proc "c" (reason: DeviceLostReason, message: cstring, userdata: rawptr)
ErrorCallback :: #type proc "c" (type: ErrorType, message: cstring, userdata: rawptr)
Proc :: distinct rawptr
QueueOnSubmittedWorkDoneCallback :: #type proc "c" (status: QueueWorkDoneStatus, /* NULLABLE */ userdata: rawptr)
InstanceRequestAdapterCallback :: #type proc "c" (status: RequestAdapterStatus, adapter: Adapter, message: cstring, /* NULLABLE */ userdata: rawptr)
AdapterRequestDeviceCallback :: #type proc "c" (status: RequestDeviceStatus, device: Device, message: cstring, /* NULLABLE */ userdata: rawptr)
// AdapterRequestAdapterInfoCallback :: #type proc "c" (adapterInfo: AdapterInfo, /* NULLABLE */ userdata: rawptr)
BufferMapAsyncCallback :: #type proc "c" (status: BufferMapAsyncStatus, /* NULLABLE */ userdata: rawptr)
DeviceCreateComputePipelineAsyncCallback :: #type proc "c" (status: CreatePipelineAsyncStatus, pipeline: ComputePipeline, message: cstring, /* NULLABLE */ userdata: rawptr)
DeviceCreateRenderPipelineAsyncCallback :: #type proc "c" (status: CreatePipelineAsyncStatus, pipeline: RenderPipeline, message: cstring, /* NULLABLE */ userdata: rawptr)
InstanceRequestAdapterCallback :: #type proc "c" (status: RequestAdapterStatus, adapter: Adapter, message: cstring, /* NULLABLE */ userdata: rawptr)
QueueOnSubmittedWorkDoneCallback :: #type proc "c" (status: QueueWorkDoneStatus, /* NULLABLE */ userdata: rawptr)
ShaderModuleGetCompilationInfoCallback :: #type proc "c" (status: CompilationInfoRequestStatus, compilationInfo: ^CompilationInfo, /* NULLABLE */ userdata: rawptr)
ChainedStruct :: struct {
next: ^ChainedStruct,
@@ -661,28 +694,23 @@ ChainedStructOut :: struct {
sType: SType,
}
// AdapterInfo :: struct {
// next: ^ChainedStructOut,
// vendor: cstring,
// architecture: cstring,
// device: cstring,
// description: cstring,
// backendType: BackendType,
// adapterType: AdapterType,
// vendorID: u32,
// deviceID: u32,
// }
AdapterProperties :: struct {
AdapterInfo :: struct {
nextInChain: ^ChainedStructOut,
vendorID: u32,
vendorName: cstring,
vendor: cstring,
architecture: cstring,
deviceID: u32,
name: cstring,
driverDescription: cstring,
adapterType: AdapterType,
device: cstring,
description: cstring,
backendType: BackendType,
adapterType: AdapterType,
vendorID: u32,
deviceID: u32,
}
when ODIN_OS == .JS {
#assert(int(BackendType.WebGPU) == 2)
#assert(offset_of(AdapterInfo, backendType) == 20)
#assert(int(AdapterType.Unknown) == 3)
#assert(offset_of(AdapterInfo, adapterType) == 24)
}
BindGroupEntry :: struct {
@@ -943,6 +971,7 @@ StorageTextureBindingLayout :: struct {
SurfaceCapabilities :: struct {
nextInChain: ^ChainedStructOut,
usages: TextureUsageFlags,
formatCount: uint,
formats: /* const */ [^]TextureFormat `fmt:"v,formatCount"`,
presentModeCount: uint,
@@ -950,6 +979,16 @@ SurfaceCapabilities :: struct {
alphaModeCount: uint,
alphaModes: /* const */ [^]CompositeAlphaMode `fmt:"v,alphaModeCount"`,
}
when ODIN_OS == .JS {
#assert(offset_of(SurfaceCapabilities, formatCount) == 8)
#assert(offset_of(SurfaceCapabilities, formats) == 8 + 1*size_of(int))
#assert(offset_of(SurfaceCapabilities, presentModeCount) == 8 + 2*size_of(int))
#assert(offset_of(SurfaceCapabilities, presentModes) == 8 + 3*size_of(int))
#assert(offset_of(SurfaceCapabilities, alphaModeCount) == 8 + 4*size_of(int))
#assert(offset_of(SurfaceCapabilities, alphaModes) == 8 + 5*size_of(int))
}
SurfaceConfiguration :: struct {
nextInChain: ^ChainedStruct,
@@ -1040,6 +1079,12 @@ TextureViewDescriptor :: struct {
aspect: TextureAspect,
}
UncapturedErrorCallbackInfo :: struct {
nextInChain: ^ChainedStruct,
callback: ErrorCallback,
userdata: rawptr,
}
VertexAttribute :: struct {
format: VertexFormat,
offset: u64,
@@ -1120,12 +1165,21 @@ ProgrammableStageDescriptor :: struct {
RenderPassColorAttachment :: struct {
nextInChain: ^ChainedStruct,
/* NULLABLE */ view: TextureView,
// depthSlice: u32,
depthSlice: u32,
/* NULLABLE */ resolveTarget: TextureView,
loadOp: LoadOp,
storeOp: StoreOp,
clearValue: Color,
}
when ODIN_OS == .JS {
#assert(size_of(RenderPassColorAttachment) == 56)
#assert(offset_of(RenderPassColorAttachment, view) == 4)
#assert(offset_of(RenderPassColorAttachment, depthSlice) == 8)
#assert(offset_of(RenderPassColorAttachment, resolveTarget) == 12)
#assert(offset_of(RenderPassColorAttachment, loadOp) == 16)
#assert(offset_of(RenderPassColorAttachment, storeOp) == 20)
#assert(offset_of(RenderPassColorAttachment, clearValue) == 24)
}
RequiredLimits :: struct {
nextInChain: ^ChainedStruct,
@@ -1194,6 +1248,10 @@ DeviceDescriptor :: struct {
defaultQueue: QueueDescriptor,
deviceLostCallback: DeviceLostCallback,
deviceLostUserdata: rawptr,
uncapturedErrorCallbackInfo: UncapturedErrorCallbackInfo,
}
when ODIN_OS == .JS {
#assert(offset_of(DeviceDescriptor, deviceLostCallback) == 24 + size_of(int))
}
RenderPassDescriptor :: struct {
@@ -1245,16 +1303,18 @@ foreign libwgpu {
// Methods of Adapter
@(link_name="wgpuAdapterEnumerateFeatures")
RawAdapterEnumerateFeatures :: proc(adapter: Adapter, features: [^]FeatureName) -> uint ---
@(link_name="wgpuAdapterGetInfo")
RawAdapterGetInfo :: proc(adapter: Adapter, info: ^AdapterInfo) ---
@(link_name="wgpuAdapterGetLimits")
RawAdapterGetLimits :: proc(adapter: Adapter, limits: ^SupportedLimits) -> b32 ---
@(link_name="wgpuAdapterGetProperties")
RawAdapterGetProperties :: proc(adapter: Adapter, properties: ^AdapterProperties) ---
AdapterHasFeature :: proc(adapter: Adapter, feature: FeatureName) -> b32 ---
// AdapterRequestAdapterInfo :: proc(adapter: Adapter, callback: AdapterRequestAdapterInfoCallback, /* NULLABLE */ userdata: rawptr) ---
AdapterRequestDevice :: proc(adapter: Adapter, /* NULLABLE */ descriptor: /* const */ ^DeviceDescriptor, callback: AdapterRequestDeviceCallback, /* NULLABLE */ userdata: rawptr = nil) ---
AdapterReference :: proc(adapter: Adapter) ---
AdapterRelease :: proc(adapter: Adapter) ---
// Procs of AdapterInfo
AdapterInfoFreeMembers :: proc(adapterInfo: AdapterInfo) ---
// Methods of BindGroup
BindGroupSetLabel :: proc(bindGroup: BindGroup, label: cstring) ---
BindGroupReference :: proc(bindGroup: BindGroup) ---
@@ -1348,13 +1408,12 @@ foreign libwgpu {
DevicePopErrorScope :: proc(device: Device, callback: ErrorCallback, userdata: rawptr) ---
DevicePushErrorScope :: proc(device: Device, filter: ErrorFilter) ---
DeviceSetLabel :: proc(device: Device, label: cstring) ---
DeviceSetUncapturedErrorCallback :: proc(device: Device, callback: ErrorCallback, userdata: rawptr) ---
DeviceReference :: proc(device: Device) ---
DeviceRelease :: proc(device: Device) ---
// Methods of Instance
InstanceCreateSurface :: proc(instance: Instance, descriptor: /* const */ ^SurfaceDescriptor) -> Surface ---
// InstanceHasWGSLLanguageFeature :: proc(instance: Instance, feature: WGSLFeatureName) -> b32 ---
InstanceHasWGSLLanguageFeature :: proc(instance: Instance, feature: WGSLFeatureName) -> b32 ---
InstanceProcessEvents :: proc(instance: Instance) ---
InstanceRequestAdapter :: proc(instance: Instance, /* NULLABLE */ options: /* const */ ^RequestAdapterOptions, callback: InstanceRequestAdapterCallback, /* NULLABLE */ userdata: rawptr = nil) ---
InstanceReference :: proc(instance: Instance) ---
@@ -1455,9 +1514,8 @@ foreign libwgpu {
RawSurfaceGetCapabilities :: proc(surface: Surface, adapter: Adapter, capabilities: ^SurfaceCapabilities) ---
@(link_name="wgpuSurfaceGetCurrentTexture")
RawSurfaceGetCurrentTexture :: proc(surface: Surface, surfaceTexture: ^SurfaceTexture) ---
SurfaceGetPreferredFormat :: proc(surface: Surface, adapter: Adapter) -> TextureFormat ---
SurfacePresent :: proc(surface: Surface) ---
// SurfaceSetLabel :: proc(surface: Surface, label: cstring) ---
SurfaceSetLabel :: proc(surface: Surface, label: cstring) ---
SurfaceUnconfigure :: proc(surface: Surface) ---
SurfaceReference :: proc(surface: Surface) ---
SurfaceRelease :: proc(surface: Surface) ---
@@ -1500,8 +1558,8 @@ AdapterGetLimits :: proc(adapter: Adapter) -> (limits: SupportedLimits, ok: bool
return
}
AdapterGetProperties :: proc(adapter: Adapter) -> (properties: AdapterProperties) {
RawAdapterGetProperties(adapter, &properties)
AdapterGetInfo :: proc(adapter: Adapter) -> (info: AdapterInfo) {
RawAdapterGetInfo(adapter, &info)
return
}
@@ -1634,8 +1692,8 @@ SurfaceGetCurrentTexture :: proc(surface: Surface) -> (surface_texture: SurfaceT
// WGPU Native bindings
BINDINGS_VERSION :: [4]u8{0, 19, 4, 1}
BINDINGS_VERSION_STRING :: "0.19.4.1"
BINDINGS_VERSION :: [4]u8{22, 1, 0, 1}
BINDINGS_VERSION_STRING :: "22.1.0.1"
when ODIN_OS != .JS {
@(private="file", init)