mirror of
https://github.com/odin-lang/Odin.git
synced 2026-08-26 23:11:32 +00:00
Merge branch 'master' into bill/asm-cfg
This commit is contained in:
@@ -561,7 +561,7 @@ uint_max :: proc(n: uint, gen := context.random_generator) -> (val: uint) {
|
||||
Generates a random unsigned 32 bit value in the range `[lo, hi)` using the provided random number generator. If no generator is provided the global random number generator will be used.
|
||||
|
||||
Inputs:
|
||||
- lo: The lower bound of the generated number, this value is inclusice
|
||||
- lo: The lower bound of the generated number, this value is inclusive
|
||||
- hi: The upper bound of the generated number, this value is exclusive
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -19,6 +19,38 @@ import win "core:sys/windows"
|
||||
@(private="package")
|
||||
_FULLY_SUPPORTED :: true
|
||||
|
||||
// Poll is driven by AFD, the socket driver underneath winsock.
|
||||
// `WSAEventSelect` is edge triggered (`FD_WRITE` is only recorded again after a
|
||||
// send fails with WOULDBLOCK) and neither `select` nor `WSAPoll` reports send
|
||||
// buffer space, so neither can give the level triggered readiness `poll` promises.
|
||||
// AFD also completes on the IOCP, which makes a poll an ordinary overlapped operation.
|
||||
IOCTL_AFD_POLL :: 0x00012024
|
||||
SIO_BASE_HANDLE :: win.DWORD(0x48000022)
|
||||
|
||||
AFD_POLL_RECEIVE :: 0x0001
|
||||
AFD_POLL_RECEIVE_EXPEDITED :: 0x0002
|
||||
AFD_POLL_SEND :: 0x0004
|
||||
AFD_POLL_DISCONNECT :: 0x0008
|
||||
AFD_POLL_ABORT :: 0x0010
|
||||
AFD_POLL_LOCAL_CLOSE :: 0x0020
|
||||
AFD_POLL_ACCEPT :: 0x0080
|
||||
AFD_POLL_CONNECT_FAIL :: 0x0100
|
||||
|
||||
AFD_Poll_Handle_Info :: struct {
|
||||
handle: win.HANDLE,
|
||||
events: win.ULONG,
|
||||
status: win.NTSTATUS,
|
||||
}
|
||||
|
||||
AFD_Poll_Info :: struct {
|
||||
timeout: i64,
|
||||
number_of_handles: win.ULONG,
|
||||
exclusive: win.ULONG,
|
||||
handles: [1]AFD_Poll_Handle_Info,
|
||||
}
|
||||
|
||||
afd_device_name := [?]u16{'\\','D','e','v','i','c','e','\\','A','f','d','\\','E','n','d','p','o','i','n','t'}
|
||||
|
||||
@(private="package")
|
||||
_Event_Loop :: struct {
|
||||
timeouts: avl.Tree(^Operation),
|
||||
@@ -88,7 +120,7 @@ _Timeout :: struct {
|
||||
|
||||
@(private="package")
|
||||
_Poll :: struct {
|
||||
wait_handle: win.HANDLE,
|
||||
info: AFD_Poll_Info,
|
||||
}
|
||||
|
||||
@(private="package")
|
||||
@@ -165,15 +197,7 @@ __tick :: proc(l: ^Event_Loop, timeout: time.Duration) -> (err: General_Error) {
|
||||
|
||||
if pool.num_outstanding(&l.operation_pool) == 0 { return nil }
|
||||
|
||||
actual_timeout := win.INFINITE
|
||||
if queue.len(l.completed) > 0 || mpsc_count(&l.completed_oob) > 0 {
|
||||
actual_timeout = 0
|
||||
} else if timeout >= 0 {
|
||||
actual_timeout = win.DWORD(timeout / time.Millisecond)
|
||||
}
|
||||
if nt, ok := next_timeout.?; ok {
|
||||
actual_timeout = min(actual_timeout, win.DWORD(nt / time.Millisecond))
|
||||
}
|
||||
actual_timeout := compute_timeout(l, timeout, next_timeout)
|
||||
|
||||
if actual_timeout > 0 {
|
||||
sync.atomic_store_explicit(&l.state, .Sleeping, .Release)
|
||||
@@ -181,7 +205,6 @@ __tick :: proc(l: ^Event_Loop, timeout: time.Duration) -> (err: General_Error) {
|
||||
// There could be a race condition where we go sleeping at the same time as things get queued
|
||||
// and a wakeup isn't done because the state is not .Sleeping yet.
|
||||
// So after sleeping we first check our queues.
|
||||
|
||||
for {
|
||||
op := (^Operation)(mpsc_dequeue(&l.queue))
|
||||
if op == nil { break }
|
||||
@@ -193,6 +216,10 @@ __tick :: proc(l: ^Event_Loop, timeout: time.Duration) -> (err: General_Error) {
|
||||
if op == nil { break }
|
||||
handle_completed(op)
|
||||
}
|
||||
|
||||
// The drains can add timeouts, and `timeout_exec` only puts those in
|
||||
// `l.timeouts` without posting anything
|
||||
actual_timeout = compute_timeout(l, timeout, check_timeouts(l))
|
||||
}
|
||||
|
||||
for {
|
||||
@@ -228,7 +255,7 @@ __tick :: proc(l: ^Event_Loop, timeout: time.Duration) -> (err: General_Error) {
|
||||
handle_completed(op)
|
||||
} else {
|
||||
op_l := op.l
|
||||
for !mpsc_enqueue(&op.l.completed_oob, op) {
|
||||
for !mpsc_enqueue(&op_l.completed_oob, op) {
|
||||
warn("oob queue filled up, QUEUE_SIZE may need increasing")
|
||||
_wake_up(op_l)
|
||||
win.SwitchToThread()
|
||||
@@ -246,8 +273,35 @@ __tick :: proc(l: ^Event_Loop, timeout: time.Duration) -> (err: General_Error) {
|
||||
actual_timeout = 0
|
||||
}
|
||||
|
||||
// A wake, or another loop routing a completion to us, can leave work queued.
|
||||
// Handle it here instead of waiting for the caller to tick again.
|
||||
for {
|
||||
op := (^Operation)(mpsc_dequeue(&l.queue))
|
||||
if op == nil { break }
|
||||
_exec(op)
|
||||
}
|
||||
|
||||
for {
|
||||
op := (^Operation)(mpsc_dequeue(&l.completed_oob))
|
||||
if op == nil { break }
|
||||
handle_completed(op)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
compute_timeout :: proc(l: ^Event_Loop, timeout: time.Duration, next_timeout: Maybe(time.Duration)) -> win.DWORD {
|
||||
actual: win.DWORD = win.INFINITE
|
||||
if queue.len(l.completed) > 0 || mpsc_count(&l.completed_oob) > 0 {
|
||||
actual = 0
|
||||
} else if timeout >= 0 {
|
||||
actual = win.DWORD(timeout / time.Millisecond)
|
||||
}
|
||||
if nt, ok := next_timeout.?; ok {
|
||||
actual = min(actual, win.DWORD(nt / time.Millisecond))
|
||||
}
|
||||
return actual
|
||||
}
|
||||
|
||||
check_timeouts :: proc(l: ^Event_Loop) -> (expires: Maybe(time.Duration)) {
|
||||
curr := l.now
|
||||
|
||||
@@ -677,19 +731,6 @@ _remove :: proc(target: ^Operation) {
|
||||
target._impl.timeout = (^Operation)(REMOVED)
|
||||
|
||||
switch target.type {
|
||||
case .Poll:
|
||||
win.UnregisterWaitEx(target.poll._impl.wait_handle, nil)
|
||||
target.poll._impl.wait_handle = nil
|
||||
|
||||
ok := win.PostQueuedCompletionStatus(
|
||||
g.iocp,
|
||||
0,
|
||||
0,
|
||||
&target._impl.over,
|
||||
)
|
||||
ensure(ok == true, "unexpected PostQueuedCompletionStatus error")
|
||||
return
|
||||
|
||||
case .Timeout:
|
||||
if avl.remove_value(&target.l.timeouts, target) {
|
||||
debug("removed timeout directly")
|
||||
@@ -705,6 +746,17 @@ _remove :: proc(target: ^Operation) {
|
||||
// Synchronous ops, picked up in handler.
|
||||
return
|
||||
|
||||
case .Poll:
|
||||
// The poll may have completed already, with its completion queued but not yet
|
||||
// handled, `NOT_FOUND` is expected rather than exceptional.
|
||||
if !win.CancelIoEx(g.afd, &target._impl.over) {
|
||||
#partial switch win.System_Error(win.GetLastError()) {
|
||||
case .NOT_FOUND:
|
||||
// nop
|
||||
case: assert(false, "unexpected CancelIoEx error")
|
||||
}
|
||||
}
|
||||
|
||||
case .Accept, .Dial, .Read, .Recv, .Send, .Write, .Send_File:
|
||||
if is_pending(target._impl.over) {
|
||||
handle := operation_handle(target)
|
||||
@@ -802,6 +854,7 @@ g: struct{
|
||||
mu: sync.Mutex,
|
||||
refs: int,
|
||||
iocp: win.HANDLE,
|
||||
afd: win.HANDLE,
|
||||
err: General_Error,
|
||||
}
|
||||
|
||||
@@ -817,6 +870,36 @@ g_ref :: proc() -> General_Error {
|
||||
if g.iocp == nil {
|
||||
g.err = General_Error(win.GetLastError())
|
||||
}
|
||||
|
||||
if g.err != nil { return g.err }
|
||||
|
||||
// A handle on the socket driver, used to poll sockets for readiness.
|
||||
iosb: win.IO_STATUS_BLOCK
|
||||
status := win.NtCreateFile(
|
||||
&g.afd,
|
||||
win.SYNCHRONIZE,
|
||||
&{
|
||||
Length = size_of(win.OBJECT_ATTRIBUTES),
|
||||
ObjectName = &{
|
||||
Length = u16(len(afd_device_name)*2),
|
||||
MaximumLength = u16(len(afd_device_name)*2),
|
||||
Buffer = raw_data(afd_device_name[:]),
|
||||
},
|
||||
},
|
||||
&iosb,
|
||||
nil,
|
||||
0,
|
||||
win.FILE_SHARE_READ|win.FILE_SHARE_WRITE,
|
||||
win.FILE_OPEN,
|
||||
0,
|
||||
nil,
|
||||
0,
|
||||
)
|
||||
if syserr := win.System_Error(win.RtlNtStatusToDosError(status)); syserr != .SUCCESS {
|
||||
g.err = General_Error(syserr)
|
||||
} else if win.CreateIoCompletionPort(g.afd, g.iocp, 0, 0) != g.iocp {
|
||||
g.err = General_Error(win.GetLastError())
|
||||
}
|
||||
}
|
||||
|
||||
sync.atomic_add(&g.refs, 1)
|
||||
@@ -828,6 +911,7 @@ g_unref :: proc() {
|
||||
sync.guard(&g.mu)
|
||||
|
||||
if sync.atomic_sub(&g.refs, 1) == 1 {
|
||||
if g.afd != nil { win.CloseHandle(g.afd) }
|
||||
win.CloseHandle(g.iocp)
|
||||
g.err = nil
|
||||
}
|
||||
@@ -850,7 +934,7 @@ operation_handle :: proc(op: ^Operation) -> win.HANDLE {
|
||||
case .Recv: return win.HANDLE(uintptr(net.any_socket_to_socket(op.recv.socket)))
|
||||
case .Send: return win.HANDLE(uintptr(net.any_socket_to_socket(op.send.socket)))
|
||||
case .Send_File: return win.HANDLE(uintptr(net.any_socket_to_socket(op.sendfile.socket)))
|
||||
case .Poll: return win.HANDLE(uintptr(net.any_socket_to_socket(op.poll.socket)))
|
||||
case .Poll: return g.afd
|
||||
case .Stat: return win.HANDLE(uintptr(op.stat.handle))
|
||||
|
||||
case .Timeout, .Open, ._Splice, ._Link_Timeout, ._Remove, .None:
|
||||
@@ -925,6 +1009,9 @@ accept_exec :: proc(op: ^Operation) -> Op_Result {
|
||||
return .Pending
|
||||
} else if op._impl.over.Internal == nil {
|
||||
op.accept.err = net._accept_error()
|
||||
} else {
|
||||
link_timeout(op, op.accept.expires)
|
||||
return .Pending
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1026,6 +1113,9 @@ dial_exec :: proc(op: ^Operation) -> (result: Op_Result) {
|
||||
return .Pending
|
||||
} else if op._impl.over.Internal == nil {
|
||||
op.dial.err = net._dial_error()
|
||||
} else {
|
||||
link_timeout(op, op.dial.expires)
|
||||
return .Pending
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1085,6 +1175,13 @@ read_exec :: proc(op: ^Operation) -> Op_Result {
|
||||
return .Pending
|
||||
}
|
||||
op.read.err = FS_Error(err)
|
||||
} else {
|
||||
// The read completed synchronously with a failure status. `FILE_SKIP_COMPLETION_PORT_ON_SUCCESS`
|
||||
// only suppresses the completion packet on success, so one is still queued for
|
||||
// this. Returning `.Done` here would complete the operation a second time, on an
|
||||
// Operation that has already been recycled into the pool.
|
||||
link_timeout(op, op.read.expires)
|
||||
return .Pending
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1159,6 +1256,9 @@ write_exec :: proc(op: ^Operation) -> Op_Result {
|
||||
return .Pending
|
||||
}
|
||||
op.write.err = FS_Error(err)
|
||||
} else {
|
||||
link_timeout(op, op.write.expires)
|
||||
return .Pending
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1252,6 +1352,9 @@ recv_exec :: proc(op: ^Operation) -> Op_Result {
|
||||
case TCP_Socket: op.recv.err = net._tcp_recv_error()
|
||||
case UDP_Socket: op.recv.err = net._udp_recv_error()
|
||||
}
|
||||
} else {
|
||||
link_timeout(op, op.recv.expires)
|
||||
return .Pending
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1370,6 +1473,9 @@ send_exec :: proc(op: ^Operation) -> Op_Result {
|
||||
case TCP_Socket: op.send.err = net._tcp_send_error()
|
||||
case UDP_Socket: op.send.err = net._udp_send_error()
|
||||
}
|
||||
} else {
|
||||
link_timeout(op, op.send.expires)
|
||||
return .Pending
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1459,6 +1565,9 @@ sendfile_exec :: proc(op: ^Operation) -> Op_Result {
|
||||
return .Pending
|
||||
} else if op._impl.over.Internal == nil {
|
||||
op.sendfile.err = net._tcp_send_error()
|
||||
} else {
|
||||
link_timeout(op, op.sendfile.expires)
|
||||
return .Pending
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1505,84 +1614,91 @@ sendfile_callback :: proc(op: ^Operation) -> Op_Result {
|
||||
@(require_results)
|
||||
poll_exec :: proc(op: ^Operation) -> Op_Result {
|
||||
assert(op.type == .Poll)
|
||||
op._impl.over = {} // Operations are recycled, clear stale state from a previous use.
|
||||
|
||||
events: i32 = win.FD_CLOSE
|
||||
events: win.ULONG = AFD_POLL_ABORT|AFD_POLL_DISCONNECT|AFD_POLL_LOCAL_CLOSE|AFD_POLL_CONNECT_FAIL
|
||||
switch op.poll.event {
|
||||
case .Send: events |= win.FD_WRITE|win.FD_CONNECT
|
||||
case .Receive: events |= win.FD_READ|win.FD_ACCEPT
|
||||
case .Receive: events |= AFD_POLL_RECEIVE|AFD_POLL_RECEIVE_EXPEDITED|AFD_POLL_ACCEPT
|
||||
case .Send: events |= AFD_POLL_SEND
|
||||
case:
|
||||
op.poll.result = .Invalid_Argument
|
||||
return .Done
|
||||
}
|
||||
|
||||
op._impl.over.hEvent = win.WSACreateEvent()
|
||||
if win.WSAEventSelect(
|
||||
// AFD needs the socket underneath any layered service providers.
|
||||
base: win.SOCKET
|
||||
bytes: win.DWORD
|
||||
if win.WSAIoctl(
|
||||
win.SOCKET(net.any_socket_to_socket(op.poll.socket)),
|
||||
op._impl.over.hEvent,
|
||||
events,
|
||||
SIO_BASE_HANDLE,
|
||||
nil, 0,
|
||||
&base, size_of(base),
|
||||
&bytes, nil, nil,
|
||||
) != 0 {
|
||||
#partial switch win.System_Error(win.GetLastError()) {
|
||||
#partial switch win.System_Error(win.WSAGetLastError()) {
|
||||
case .WSAEINVAL, .WSAENOTSOCK: op.poll.result = .Invalid_Argument
|
||||
case: op.poll.result = .Error
|
||||
}
|
||||
return .Done
|
||||
}
|
||||
|
||||
timeout := win.INFINITE
|
||||
// A negative timeout is relative, in 100ns units.
|
||||
timeout := max(i64)
|
||||
if op.poll.expires != {} {
|
||||
diff := max(0, time.diff(op.l.now, op.poll.expires))
|
||||
timeout = win.DWORD(diff / time.Millisecond)
|
||||
timeout = -i64(diff / 100)
|
||||
}
|
||||
|
||||
ok := win.RegisterWaitForSingleObject(
|
||||
&op.poll._impl.wait_handle,
|
||||
op._impl.over.hEvent,
|
||||
wait_callback,
|
||||
op,
|
||||
timeout,
|
||||
win.WT_EXECUTEINWAITTHREAD|win.WT_EXECUTEONLYONCE,
|
||||
op.poll._impl.info = {
|
||||
timeout = timeout,
|
||||
number_of_handles = 1,
|
||||
handles = {{handle = win.HANDLE(uintptr(base)), events = events}},
|
||||
}
|
||||
|
||||
// The OVERLAPPED doubles as the IO_STATUS_BLOCK, their first two fields line up.
|
||||
status := win.NtDeviceIoControlFile(
|
||||
g.afd,
|
||||
nil,
|
||||
nil,
|
||||
&op._impl.over,
|
||||
win.PIO_STATUS_BLOCK(rawptr(&op._impl.over)),
|
||||
IOCTL_AFD_POLL,
|
||||
&op.poll._impl.info,
|
||||
size_of(AFD_Poll_Info),
|
||||
&op.poll._impl.info,
|
||||
size_of(AFD_Poll_Info),
|
||||
)
|
||||
ensure(ok == true, "unexpected RegisterWaitForSingleObject error")
|
||||
|
||||
return .Pending
|
||||
|
||||
wait_callback :: proc "system" (lpParameter: win.PVOID, TimerOrWaitFired: win.BOOLEAN) {
|
||||
op := (^Operation)(lpParameter)
|
||||
assert_contextless(op.type == .Poll)
|
||||
|
||||
if TimerOrWaitFired {
|
||||
op.poll.result = .Timeout
|
||||
}
|
||||
|
||||
ok := win.PostQueuedCompletionStatus(
|
||||
g.iocp,
|
||||
0,
|
||||
0,
|
||||
&op._impl.over,
|
||||
)
|
||||
ensure_contextless(ok == true, "unexpected PostQueuedCompletionStatus error")
|
||||
// The AFD handle is not set to skip completion on success, so a completion is
|
||||
// queued even when this finishes synchronously.
|
||||
#partial switch win.System_Error(win.RtlNtStatusToDosError(status)) {
|
||||
case .SUCCESS, .IO_PENDING:
|
||||
return .Pending
|
||||
case:
|
||||
op.poll.result = .Error
|
||||
return .Done
|
||||
}
|
||||
}
|
||||
|
||||
poll_callback :: proc(op: ^Operation) {
|
||||
assert(op.type == .Poll)
|
||||
|
||||
if op._impl.over.hEvent != nil {
|
||||
win.WSACloseEvent(op._impl.over.hEvent)
|
||||
}
|
||||
|
||||
if op.poll._impl.wait_handle != nil {
|
||||
win.UnregisterWaitEx(op.poll._impl.wait_handle, nil)
|
||||
}
|
||||
|
||||
if op.poll.result != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err := get_result(op._impl.over)
|
||||
#partial switch err {
|
||||
case .SUCCESS:
|
||||
case:
|
||||
// AFD reports a timeout by coming back with no handles.
|
||||
if op.poll._impl.info.number_of_handles == 0 {
|
||||
op.poll.result = .Timeout
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := get_result(op._impl.over); err != .SUCCESS {
|
||||
op.poll.result = .Error
|
||||
return
|
||||
}
|
||||
|
||||
if op.poll._impl.info.handles[0].events & (AFD_POLL_ABORT|AFD_POLL_CONNECT_FAIL) != 0 {
|
||||
op.poll.result = .Error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1626,6 +1626,9 @@ Poll a socket for readiness.
|
||||
NOTE: this is provided to help with "legacy" APIs that require polling behavior.
|
||||
If you can avoid it and use the other procs in this package, do so.
|
||||
|
||||
NOTE: on Windows only one poll per socket is delivered, a second poll on the same
|
||||
socket does not complete.
|
||||
|
||||
Any user data can be set on the returned operation's `user_data` field.
|
||||
Polymorphic variants for type safe user data are available under `poll_poly`, `poll_poly2`, and `poll_poly3`.
|
||||
|
||||
@@ -1656,6 +1659,9 @@ Poll a socket for readiness.
|
||||
NOTE: this is provided to help with "legacy" APIs that require polling behavior.
|
||||
If you can avoid it and use the other procs in this package, do so.
|
||||
|
||||
NOTE: on Windows only one poll per socket is delivered, a second poll on the same
|
||||
socket does not complete.
|
||||
|
||||
This procedure uses polymorphism for type safe user data up to a certain size.
|
||||
|
||||
Inputs:
|
||||
@@ -1690,6 +1696,9 @@ Poll a socket for readiness.
|
||||
NOTE: this is provided to help with "legacy" APIs that require polling behavior.
|
||||
If you can avoid it and use the other procs in this package, do so.
|
||||
|
||||
NOTE: on Windows only one poll per socket is delivered, a second poll on the same
|
||||
socket does not complete.
|
||||
|
||||
This procedure uses polymorphism for type safe user data up to a certain size.
|
||||
|
||||
Inputs:
|
||||
@@ -1725,6 +1734,9 @@ Poll a socket for readiness.
|
||||
NOTE: this is provided to help with "legacy" APIs that require polling behavior.
|
||||
If you can avoid it and use the other procs in this package, do so.
|
||||
|
||||
NOTE: on Windows only one poll per socket is delivered, a second poll on the same
|
||||
socket does not complete.
|
||||
|
||||
This procedure uses polymorphism for type safe user data up to a certain size.
|
||||
|
||||
Inputs:
|
||||
|
||||
@@ -7,6 +7,19 @@ foreign import ntdll_lib "system:ntdll.lib"
|
||||
foreign ntdll_lib {
|
||||
RtlGetVersion :: proc(lpVersionInformation: ^OSVERSIONINFOEXW) -> NTSTATUS ---
|
||||
|
||||
NtDeviceIoControlFile :: proc(
|
||||
FileHandle: HANDLE,
|
||||
Event: HANDLE,
|
||||
ApcRoutine: PIO_APC_ROUTINE,
|
||||
ApcContext: rawptr,
|
||||
IoStatusBlock: PIO_STATUS_BLOCK,
|
||||
IoControlCode: ULONG,
|
||||
InputBuffer: rawptr,
|
||||
InputBufferLength: ULONG,
|
||||
OutputBuffer: rawptr,
|
||||
OutputBufferLength: ULONG,
|
||||
) -> NTSTATUS ---
|
||||
|
||||
|
||||
NtQueryInformationProcess :: proc(
|
||||
ProcessHandle: HANDLE,
|
||||
|
||||
@@ -2146,9 +2146,8 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta
|
||||
|
||||
gb_internal bool check_single_target_feature_is_valid(String const &feature_list, String const &feature) {
|
||||
String_Iterator it = {feature_list, 0};
|
||||
for (;;) {
|
||||
String str = string_split_iterator(&it, ',');
|
||||
if (str == "") break;
|
||||
String str = {};
|
||||
while (string_split_iterator_next(&it, ',', &str)) {
|
||||
if (str == feature) {
|
||||
return true;
|
||||
}
|
||||
@@ -2160,8 +2159,8 @@ gb_internal bool check_single_target_feature_is_valid(String const &feature_list
|
||||
gb_internal bool check_target_feature_is_valid(String const &feature, TargetArchKind arch, String *invalid) {
|
||||
String feature_list = target_features_list[arch];
|
||||
String_Iterator it = {feature, 0};
|
||||
for (;;) {
|
||||
String str = string_split_iterator(&it, ',');
|
||||
String str = {};
|
||||
while (string_split_iterator_next(&it, ',', &str)) {
|
||||
String feature_str = str;
|
||||
if (string_starts_with(feature_str, '+') || string_starts_with(feature_str, '-')) {
|
||||
feature_str = substring(feature_str, 1, feature_str.len);
|
||||
@@ -2169,7 +2168,6 @@ gb_internal bool check_target_feature_is_valid(String const &feature, TargetArch
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (feature_str == "") break;
|
||||
if (!check_single_target_feature_is_valid(feature_list, feature_str)) {
|
||||
if (invalid) *invalid = str;
|
||||
return false;
|
||||
@@ -2181,10 +2179,8 @@ gb_internal bool check_target_feature_is_valid(String const &feature, TargetArch
|
||||
|
||||
gb_internal bool check_target_feature_is_valid_globally(String const &feature, String *invalid) {
|
||||
String_Iterator it = {feature, 0};
|
||||
for (;;) {
|
||||
String str = string_split_iterator(&it, ',');
|
||||
if (str == "") break;
|
||||
|
||||
String str = {};
|
||||
while (string_split_iterator_next(&it, ',', &str)) {
|
||||
bool valid = false;
|
||||
for (int arch = TargetArch_Invalid; arch < TargetArch_COUNT; arch += 1) {
|
||||
if (check_target_feature_is_valid(str, cast(TargetArchKind)arch, invalid)) {
|
||||
@@ -2208,15 +2204,19 @@ gb_internal bool check_target_feature_is_valid_for_target_arch(String const &fea
|
||||
|
||||
gb_internal bool check_target_feature_is_enabled(String const &feature, String *not_enabled) {
|
||||
String_Iterator it = {feature, 0};
|
||||
for (;;) {
|
||||
String str = string_split_iterator(&it, ',');
|
||||
String str = {};
|
||||
while (string_split_iterator_next(&it, ',', &str)) {
|
||||
String feature_str = str;
|
||||
bool want_enabled = true;
|
||||
if (string_starts_with(feature_str, '+') || string_starts_with(feature_str, '-')) {
|
||||
want_enabled = feature_str[0] == '+';
|
||||
feature_str = substring(feature_str, 1, feature_str.len);
|
||||
}
|
||||
if (feature_str == "") break;
|
||||
if (feature_str == "") {
|
||||
// a bare sign names no feature, which cannot be enabled
|
||||
if (not_enabled) *not_enabled = str;
|
||||
return false;
|
||||
}
|
||||
|
||||
String plus_str = concatenate_strings(temporary_allocator(), make_string_c("+"), feature_str);
|
||||
String minus_str = concatenate_strings(temporary_allocator(), make_string_c("-"), feature_str);
|
||||
@@ -2240,9 +2240,8 @@ gb_internal bool check_target_feature_is_enabled(String const &feature, String *
|
||||
|
||||
gb_internal bool check_target_feature_is_superset_of(String const &superset, String const &of, String *missing) {
|
||||
String_Iterator it = {of, 0};
|
||||
for (;;) {
|
||||
String str = string_split_iterator(&it, ',');
|
||||
if (str == "") break;
|
||||
String str = {};
|
||||
while (string_split_iterator_next(&it, ',', &str)) {
|
||||
if (!check_single_target_feature_is_valid(superset, str)) {
|
||||
if (missing) *missing = str;
|
||||
return false;
|
||||
|
||||
@@ -3164,10 +3164,9 @@ gb_internal bool lb_generate_code(lbGenerator *gen) {
|
||||
|
||||
gbString llvm_features = gb_string_make(temporary_allocator(), "");
|
||||
String_Iterator it = {build_context.target_features_string, 0};
|
||||
String str = {};
|
||||
bool first = true;
|
||||
for (;;) {
|
||||
String str = string_split_iterator(&it, ',');
|
||||
if (str == "") break;
|
||||
while (string_split_iterator_next(&it, ',', &str)) {
|
||||
if (!first) {
|
||||
llvm_features = gb_string_appendc(llvm_features, ",");
|
||||
}
|
||||
|
||||
@@ -322,6 +322,21 @@ gb_internal IntegerDivisionByZeroKind lb_check_for_integer_division_by_zero_beha
|
||||
}
|
||||
|
||||
|
||||
// LLVM has srem(min(Integer_Type), -1) as UB and it raises an FP exception on a hardware
|
||||
// divide, yet `x % -1` is 0 for every x; `x srem 1` is 0 too and cannot trap, so a runtime
|
||||
// -1 divisor can be swapped for 1. Vectorizable.
|
||||
gb_internal LLVMValueRef lb_srem_safe_divisor(lbProcedure *p, LLVMValueRef rhs) {
|
||||
LLVMValueRef minus_one = LLVMConstAllOnes(LLVMTypeOf(rhs));
|
||||
// build 1 as neg(-1), this folds for both scalars and vectors
|
||||
LLVMValueRef one = LLVMBuildNeg(p->builder, minus_one, "");
|
||||
if (LLVMIsAConstantInt(rhs)) {
|
||||
return rhs == minus_one ? one : rhs;
|
||||
}
|
||||
LLVMValueRef is_minus_one = LLVMBuildICmp(p->builder, LLVMIntEQ, rhs, minus_one, "");
|
||||
return LLVMBuildSelect(p->builder, is_minus_one, one, rhs, "");
|
||||
}
|
||||
|
||||
|
||||
// implements %% (the remainder/floored mod operator) on signed integers;
|
||||
// this is branchless and vectorizable, so it also covers vectors
|
||||
gb_internal LLVMValueRef lb_emit_signed_floor_mod(lbProcedure *p, LLVMValueRef lhs, LLVMValueRef rhs) {
|
||||
@@ -329,24 +344,11 @@ gb_internal LLVMValueRef lb_emit_signed_floor_mod(lbProcedure *p, LLVMValueRef l
|
||||
// and works for arbitrary precision integers, but the add can wrap at finite precision
|
||||
|
||||
// the Odin spec mandates min(Integer_Type) %% -1 must be 0,
|
||||
// but LLVM has srem(min(Integer_Type), -1) as UB and results in FP exception;
|
||||
// since x %% -1 == 0 for every x, a constant rhs = -1 can fold,
|
||||
// and a runtime -1 can be swapped with 1 (x srem 1 is 0 for every x, no exceptions)
|
||||
LLVMValueRef minus_one = LLVMConstAllOnes(LLVMTypeOf(rhs));
|
||||
LLVMValueRef safe_rhs = rhs;
|
||||
if (LLVMIsAConstantInt(rhs)) {
|
||||
if (rhs == minus_one) {
|
||||
return LLVMConstNull(LLVMTypeOf(rhs)); // the entire %% op folds to 0
|
||||
}
|
||||
} else {
|
||||
// safe_rhs = (rhs == -1) ? 1 : rhs
|
||||
// vectorizable construction,
|
||||
// build 1 as neg(-1), this folds for both scalars and vectors
|
||||
LLVMValueRef one = LLVMBuildNeg(p->builder, minus_one, "");
|
||||
LLVMValueRef is_minus_one = LLVMBuildICmp(p->builder, LLVMIntEQ, rhs, minus_one, "");
|
||||
safe_rhs = LLVMBuildSelect(p->builder, is_minus_one, one, rhs, "");
|
||||
// a constant rhs = -1 can fold the whole operation, a runtime one is handled by the swap
|
||||
if (LLVMIsAConstantInt(rhs) && rhs == LLVMConstAllOnes(LLVMTypeOf(rhs))) {
|
||||
return LLVMConstNull(LLVMTypeOf(rhs)); // the entire %% op folds to 0
|
||||
}
|
||||
LLVMValueRef r = LLVMBuildSRem(p->builder, lhs, safe_rhs, "");
|
||||
LLVMValueRef r = LLVMBuildSRem(p->builder, lhs, lb_srem_safe_divisor(p, rhs), "");
|
||||
// srem truncs to 0, so r needs a +rhs correction when the operands signs differ (and r != 0)
|
||||
// so we implement
|
||||
// r = lhs % rhs
|
||||
@@ -498,9 +500,10 @@ gb_internal bool lb_try_direct_vector_arith(lbProcedure *p, TokenKind op, lbValu
|
||||
}
|
||||
break;
|
||||
case Token_Mod:
|
||||
{
|
||||
auto *call = is_type_unsigned(integral_type) ? LLVMBuildURem : LLVMBuildSRem;
|
||||
z = call(p->builder, x, y, "");
|
||||
if (is_type_unsigned(integral_type)) {
|
||||
z = LLVMBuildURem(p->builder, x, y, "");
|
||||
} else {
|
||||
z = LLVMBuildSRem(p->builder, x, lb_srem_safe_divisor(p, y), "");
|
||||
}
|
||||
break;
|
||||
case Token_ModMod:
|
||||
@@ -610,9 +613,10 @@ gb_internal bool lb_try_direct_vector_arith(lbProcedure *p, TokenKind op, lbValu
|
||||
}
|
||||
break;
|
||||
case Token_Mod:
|
||||
{
|
||||
auto *call = is_type_unsigned(integral_type) ? LLVMBuildURem : LLVMBuildSRem;
|
||||
z = call(p->builder, x, y, "");
|
||||
if (is_type_unsigned(integral_type)) {
|
||||
z = LLVMBuildURem(p->builder, x, y, "");
|
||||
} else {
|
||||
z = LLVMBuildSRem(p->builder, x, lb_srem_safe_divisor(p, y), "");
|
||||
}
|
||||
break;
|
||||
case Token_ModMod:
|
||||
@@ -1684,7 +1688,8 @@ gb_internal LLVMValueRef lb_integer_modulo(lbProcedure *p, LLVMValueRef lhs, LLV
|
||||
if (is_unsigned) {
|
||||
return LLVMBuildURem(p->builder, lhs, rhs, "");
|
||||
} else {
|
||||
return LLVMBuildSRem(p->builder, lhs, rhs, "");
|
||||
// min(Integer_Type) % -1 is 0, matching the constant folder, and must not trap
|
||||
return LLVMBuildSRem(p->builder, lhs, lb_srem_safe_divisor(p, rhs), "");
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -2407,6 +2412,13 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) {
|
||||
// boolean -> boolean/integer
|
||||
if (is_type_boolean(src) && (is_type_boolean(dst) || is_type_integer(dst))) {
|
||||
LLVMValueRef b = LLVMBuildICmp(p->builder, LLVMIntNE, value.value, LLVMConstNull(lb_type(m, value.type)), "");
|
||||
if (type_size_of(default_type(dst)) > 1 && is_type_different_to_arch_endianness(dst)) {
|
||||
Type *platform_dst_type = integer_endian_type_to_platform_type(dst);
|
||||
lbValue res = {};
|
||||
res.value = LLVMBuildIntCast2(p->builder, b, lb_type(m, platform_dst_type), false, "");
|
||||
res.type = t;
|
||||
return lb_emit_byte_swap(p, res, t);
|
||||
}
|
||||
lbValue res = {};
|
||||
res.value = LLVMBuildIntCast2(p->builder, b, lb_type(m, t), false, "");
|
||||
res.type = t;
|
||||
|
||||
@@ -226,10 +226,9 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i
|
||||
gbString feature_str = gb_string_make(temporary_allocator(), "");
|
||||
|
||||
String_Iterator it = {pt->Proc.enable_target_feature, 0};
|
||||
String str = {};
|
||||
bool first = true;
|
||||
for (;;) {
|
||||
String str = string_split_iterator(&it, ',');
|
||||
if (str == "") break;
|
||||
while (string_split_iterator_next(&it, ',', &str)) {
|
||||
bool add_prefix = !(string_starts_with(str, '+') || string_starts_with(str, '-'));
|
||||
if (!first) {
|
||||
feature_str = gb_string_appendc(feature_str, ",");
|
||||
|
||||
42
src/main.cpp
42
src/main.cpp
@@ -1455,12 +1455,8 @@ gb_internal bool parse_build_flags(Array<String> args) {
|
||||
GB_ASSERT(value.kind == ExactValue_String);
|
||||
String val = value.value_string;
|
||||
String_Iterator it = {val, 0};
|
||||
for (;;) {
|
||||
String pkg = string_split_iterator(&it, ',');
|
||||
if (pkg.len == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
String pkg = {};
|
||||
while (string_split_iterator_next(&it, ',', &pkg)) {
|
||||
pkg = string_trim_whitespace(pkg);
|
||||
if (!string_is_valid_identifier(pkg)) {
|
||||
gb_printf_err("-%.*s '%.*s' must be a valid identifier\n", LIT(name), LIT(pkg));
|
||||
@@ -1478,12 +1474,8 @@ gb_internal bool parse_build_flags(Array<String> args) {
|
||||
GB_ASSERT(value.kind == ExactValue_String);
|
||||
String val = value.value_string;
|
||||
String_Iterator it = {val, 0};
|
||||
for (;;) {
|
||||
String attr = string_split_iterator(&it, ',');
|
||||
if (attr.len == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
String attr = {};
|
||||
while (string_split_iterator_next(&it, ',', &attr)) {
|
||||
attr = string_trim_whitespace(attr);
|
||||
if (!string_is_valid_identifier(attr)) {
|
||||
gb_printf_err("-%.*s '%.*s' must be a valid identifier\n", LIT(name), LIT(attr));
|
||||
@@ -4166,9 +4158,8 @@ int main(int arg_count, char const **arg_ptr) {
|
||||
} else {
|
||||
String march_list = target_microarch_list[build_context.metrics.arch];
|
||||
String_Iterator it = {march_list, 0};
|
||||
for (;;) {
|
||||
String str = string_split_iterator(&it, ',');
|
||||
if (str == "") break;
|
||||
String str = {};
|
||||
while (string_split_iterator_next(&it, ',', &str)) {
|
||||
if (str == build_context.microarch) {
|
||||
// Found matching microarch
|
||||
print_microarch_list = false;
|
||||
@@ -4193,9 +4184,8 @@ int main(int arg_count, char const **arg_ptr) {
|
||||
String march_list = target_microarch_list[build_context.metrics.arch];
|
||||
String_Iterator it = {march_list, 0};
|
||||
|
||||
for (;;) {
|
||||
String str = string_split_iterator(&it, ',');
|
||||
if (str == "") break;
|
||||
String str = {};
|
||||
while (string_split_iterator_next(&it, ',', &str)) {
|
||||
if (str == default_march) {
|
||||
gb_printf("\t%.*s (default)\n", LIT(str));
|
||||
} else {
|
||||
@@ -4209,9 +4199,8 @@ int main(int arg_count, char const **arg_ptr) {
|
||||
String default_features = get_default_features();
|
||||
{
|
||||
String_Iterator it = {default_features, 0};
|
||||
for (;;) {
|
||||
String str = string_split_iterator(&it, ',');
|
||||
if (str == "") break;
|
||||
String str = {};
|
||||
while (string_split_iterator_next(&it, ',', &str)) {
|
||||
string_set_add(&build_context.target_features_set, str);
|
||||
}
|
||||
}
|
||||
@@ -4231,10 +4220,8 @@ int main(int arg_count, char const **arg_ptr) {
|
||||
|
||||
if (build_context.target_features_string.len != 0) {
|
||||
String_Iterator target_it = {build_context.target_features_string, 0};
|
||||
for (;;) {
|
||||
String item = string_split_iterator(&target_it, ',');
|
||||
if (item == "") break;
|
||||
|
||||
String item = {};
|
||||
while (string_split_iterator_next(&target_it, ',', &item)) {
|
||||
String stripped_item = item;
|
||||
if (*stripped_item.text == '+' || *stripped_item.text == '-') {
|
||||
stripped_item.text++;
|
||||
@@ -4251,9 +4238,8 @@ int main(int arg_count, char const **arg_ptr) {
|
||||
|
||||
String feature_list = target_features_list[build_context.metrics.arch];
|
||||
String_Iterator it = {feature_list, 0};
|
||||
for (;;) {
|
||||
String str = string_split_iterator(&it, ',');
|
||||
if (str == "") break;
|
||||
String str = {};
|
||||
while (string_split_iterator_next(&it, ',', &str)) {
|
||||
if (check_single_target_feature_is_valid(default_features, str)) {
|
||||
if (has_ansi_terminal_colours()) {
|
||||
gb_printf("\t%.*s\x1b[38;5;244m (implied by target microarch %.*s)\x1b[0m\n", LIT(str), LIT(march));
|
||||
|
||||
@@ -293,6 +293,20 @@ gb_internal String string_split_iterator(String_Iterator *it, const char sep) {
|
||||
return substring(it->str, start, end);
|
||||
}
|
||||
|
||||
// NOTE: `string_split_iterator` returns a zero-length `String` both for an empty element and at
|
||||
// exhaustion, so a loop that stops on an empty result stops at the first empty element instead.
|
||||
// This skips empty elements and stops only once the iterator is exhausted.
|
||||
gb_internal bool string_split_iterator_next(String_Iterator *it, char const sep, String *str_) {
|
||||
while (it->pos < it->str.len) {
|
||||
String str = string_split_iterator(it, sep);
|
||||
if (str.len != 0) {
|
||||
*str_ = str;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
gb_internal gb_inline bool is_separator(u8 const &ch) {
|
||||
return (ch == '/' || ch == '\\');
|
||||
}
|
||||
|
||||
@@ -3640,9 +3640,8 @@ gb_internal int matched_target_features(TypeProc *t) {
|
||||
|
||||
int matches = 0;
|
||||
String_Iterator it = {t->require_target_feature, 0};
|
||||
for (;;) {
|
||||
String str = string_split_iterator(&it, ',');
|
||||
if (str == "") break;
|
||||
String str = {};
|
||||
while (string_split_iterator_next(&it, ',', &str)) {
|
||||
if (check_target_feature_is_valid_for_target_arch(str, nullptr)) {
|
||||
matches += 1;
|
||||
}
|
||||
|
||||
@@ -244,8 +244,11 @@ wake_up :: proc(t: ^testing.T) {
|
||||
}, context)
|
||||
defer thread.destroy(thr)
|
||||
|
||||
// Should block forever until the thread calling wake_up will make it return.
|
||||
ev(t, nbio.tick(), nil)
|
||||
// A tick can return without progress; loop until the wake is observed.
|
||||
// A lost wake would block here forever and trip the fail timeout.
|
||||
for !hit {
|
||||
ev(t, nbio.tick(), nil)
|
||||
}
|
||||
e(t, hit)
|
||||
|
||||
nbio.remove(accept)
|
||||
|
||||
@@ -245,7 +245,7 @@ And it tests big send/recv buffers being handled properly.
|
||||
@(test)
|
||||
poll :: proc(t: ^testing.T) {
|
||||
if event_loop_guard(t) {
|
||||
// testing.set_fail_timeout(t, time.Minute)
|
||||
testing.set_fail_timeout(t, time.Minute)
|
||||
|
||||
can_recv: bool
|
||||
|
||||
@@ -302,13 +302,25 @@ poll :: proc(t: ^testing.T) {
|
||||
on_poll1 :: proc(op: ^nbio.Operation, t: ^testing.T, can_recv: ^bool) {
|
||||
ev(t, op.poll.result, nil)
|
||||
|
||||
// Send 4 GB of data, which in my experience causes a Would_Block error because we filled up the internal buffer.
|
||||
// Fill the socket until sending actually blocks. How much that takes depends
|
||||
// on the machine's socket buffers, so keep sending rather than assuming a
|
||||
// fixed amount does it. Nothing is reading yet, so this terminates.
|
||||
buf, mem_err := make([]byte, mem.Gigabyte*4, context.temp_allocator)
|
||||
ev(t, mem_err, nil)
|
||||
|
||||
// Use `core:net` as example external code that doesn't care about the event loop.
|
||||
net.set_blocking(op.poll.socket, false)
|
||||
n, send_err := net.send(op.poll.socket, buf)
|
||||
|
||||
n: int
|
||||
send_err: net.Network_Error
|
||||
for _ in 0..<16 {
|
||||
sent: int
|
||||
sent, send_err = net.send(op.poll.socket, buf)
|
||||
n += sent
|
||||
if send_err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
ev(t, send_err, net.TCP_Send_Error.Would_Block)
|
||||
|
||||
log.debugf("blocking after %M", n)
|
||||
|
||||
@@ -58,7 +58,12 @@ immediate_remove_of_sendfile :: proc(t: ^testing.T) {
|
||||
}
|
||||
|
||||
on_recv :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.recv.err, nil)
|
||||
// The server cancelled a sendfile that had already put bytes on the wire and
|
||||
// then closed, which ends the connection with a reset rather than gracefully
|
||||
// often enough that both have to be accepted here.
|
||||
if op.recv.err != nil {
|
||||
ev(t, op.recv.err, net.TCP_Recv_Error.Connection_Closed)
|
||||
}
|
||||
|
||||
nbio.close(op.recv.socket.(net.TCP_Socket))
|
||||
}
|
||||
@@ -126,7 +131,12 @@ immediate_remove_of_sendfile_without_stat :: proc(t: ^testing.T) {
|
||||
}
|
||||
|
||||
on_recv :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.recv.err, nil)
|
||||
// The server cancelled a sendfile that had already put bytes on the wire and
|
||||
// then closed, which ends the connection with a reset rather than gracefully
|
||||
// often enough that both have to be accepted here.
|
||||
if op.recv.err != nil {
|
||||
ev(t, op.recv.err, net.TCP_Recv_Error.Connection_Closed)
|
||||
}
|
||||
|
||||
nbio.close(op.recv.socket.(net.TCP_Socket))
|
||||
}
|
||||
@@ -212,13 +222,18 @@ remove_multiple_poll :: proc(t: ^testing.T) {
|
||||
if event_loop_guard(t) {
|
||||
testing.set_fail_timeout(t, time.Minute)
|
||||
|
||||
sock, ep := open_next_available_local_port(t)
|
||||
defer nbio.close(sock)
|
||||
// Two sockets rather than two polls on one socket: only one poll per socket is
|
||||
// delivered on Windows, and what this tests is removal, not that.
|
||||
removed_sock, removed_ep := open_next_available_local_port(t)
|
||||
defer nbio.close(removed_sock)
|
||||
|
||||
kept_sock, kept_ep := open_next_available_local_port(t)
|
||||
defer nbio.close(kept_sock)
|
||||
|
||||
hit: bool
|
||||
|
||||
first := nbio.poll(sock, .Receive, on_poll)
|
||||
nbio.poll_poly2(sock, .Receive, t, &hit, on_poll2)
|
||||
first := nbio.poll(removed_sock, .Receive, on_poll)
|
||||
nbio.poll_poly2(kept_sock, .Receive, t, &hit, on_poll2)
|
||||
|
||||
on_poll :: proc(op: ^nbio.Operation) {
|
||||
log.error("shouldn't be called")
|
||||
@@ -235,7 +250,9 @@ remove_multiple_poll :: proc(t: ^testing.T) {
|
||||
|
||||
ev(t, nbio.tick(0), nil)
|
||||
|
||||
nbio.dial_poly(ep, t, on_dial)
|
||||
// Make both readable, the removed poll must still not fire.
|
||||
nbio.dial_poly(removed_ep, t, on_dial)
|
||||
nbio.dial_poly(kept_ep, t, on_dial)
|
||||
|
||||
on_dial :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.dial.err, nil)
|
||||
|
||||
105
tests/internal/test_mod.odin
Normal file
105
tests/internal/test_mod.odin
Normal file
@@ -0,0 +1,105 @@
|
||||
package test_internal
|
||||
|
||||
import "core:testing"
|
||||
|
||||
// % operator (truncated remainder)
|
||||
// remainder = x - y * trunc(x / y)
|
||||
|
||||
@(private="file")
|
||||
trunc_mod :: proc(x, y: $T) -> T {
|
||||
return x - y*(x/y)
|
||||
}
|
||||
|
||||
// this seems to prevent folding at least at -o:minimal
|
||||
@(private="file")
|
||||
not_const :: #force_no_inline proc(v: $T) -> T { return v }
|
||||
|
||||
@(test)
|
||||
mod_i8_exhaustive :: proc(t: ^testing.T) {
|
||||
for i in -128..=127 {
|
||||
for j in -128..=127 {
|
||||
if j == 0 { continue }
|
||||
// min(T) % -1 == 0 is tested in mod_exception,
|
||||
// the trunc_mod reference itself would trap here
|
||||
if i == -128 && j == -1 { continue }
|
||||
x, y := i8(i), i8(j)
|
||||
got := x % y
|
||||
want := trunc_mod(x, y)
|
||||
testing.expectf(t, got == want, "%v %% %v == %v, want %v", x, y, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
mod_exception :: proc(t: ^testing.T) {
|
||||
// min(T) % -1 is 0, which is what the constant folder answers
|
||||
#assert(min(i8) % i8(-1) == 0)
|
||||
#assert(min(i16) % i16(-1) == 0)
|
||||
#assert(min(i32) % i32(-1) == 0)
|
||||
#assert(min(i64) % i64(-1) == 0)
|
||||
#assert(min(i128) % i128(-1) == 0)
|
||||
|
||||
check :: proc(t: ^testing.T, $T: typeid, loc := #caller_location) {
|
||||
x, y := not_const(min(T)), not_const(T(-1))
|
||||
testing.expectf(t, x % y == 0, "min(%v) %% -1 (rt divisor) == %v, want 0", typeid_of(T), x % y, loc = loc)
|
||||
testing.expectf(t, x % -1 == 0, "min(%v) %% -1 (const divisor) == %v, want 0", typeid_of(T), x % -1, loc = loc)
|
||||
}
|
||||
check(t, i8)
|
||||
check(t, i16)
|
||||
check(t, i32)
|
||||
check(t, i64)
|
||||
check(t, i128)
|
||||
}
|
||||
|
||||
@(test)
|
||||
mod_exception_vec :: proc(t: ^testing.T) {
|
||||
{
|
||||
// [4]i32 emits `srem <4 x i32>`
|
||||
x := not_const([4]i32{min(i32), 0, -7, 5})
|
||||
y := not_const([4]i32{-1, -1, -1, -1})
|
||||
testing.expect_value(t, x % y, [4]i32{0, 0, 0, 0})
|
||||
}
|
||||
{
|
||||
// [16]i32 emits scalar `srem i32`, which is the other call site
|
||||
x, y: [16]i32
|
||||
for i in 0..<16 {
|
||||
x[i] = i == 0 ? min(i32) : i32(i) - 8
|
||||
y[i] = -1
|
||||
}
|
||||
testing.expect_value(t, not_const(x) % not_const(y), [16]i32{})
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
mod_assign :: proc(t: ^testing.T) {
|
||||
// %= must agree with %
|
||||
{
|
||||
x := not_const(min(i32))
|
||||
y := not_const(i32(-1))
|
||||
x %= y
|
||||
testing.expect_value(t, x, 0)
|
||||
}
|
||||
{
|
||||
x := not_const(i64(-17))
|
||||
y := not_const(i64(5))
|
||||
x %= y
|
||||
testing.expect_value(t, x, -17 % 5)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
mod_unsigned_unchanged :: proc(t: ^testing.T) {
|
||||
// the guard is signed-only; unsigned max is all-ones and must stay a real divisor
|
||||
{
|
||||
x, y := not_const(max(u32)), not_const(max(u32))
|
||||
testing.expect_value(t, x % y, 0)
|
||||
}
|
||||
{
|
||||
x, y := not_const(u32(7)), not_const(max(u32))
|
||||
testing.expect_value(t, x % y, 7)
|
||||
}
|
||||
{
|
||||
x, y := not_const(u8(200)), not_const(u8(255))
|
||||
testing.expect_value(t, x % y, 200)
|
||||
}
|
||||
}
|
||||
@@ -43,12 +43,9 @@ set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style -ignore-unused
|
||||
..\..\..\odin test ..\test_issue_7008.odin %COMMON% || exit /b
|
||||
..\..\..\odin check ..\test_issue_7012.odin -no-entry-point %COMMON% || exit /b
|
||||
..\..\..\odin check ..\test_issue_7260.odin -no-entry-point %COMMON% || exit /b
|
||||
..\..\..\odin check ..\test_issue_asm_named_register_slot.odin -no-entry-point %COMMON% 2>&1 | find /c "Error:" | findstr /x "8" || exit /b
|
||||
..\..\..\odin check ..\test_issue_ellipsis_type_call.odin -no-entry-point %COMMON% 2>&1 | find /c "Error:" | findstr /x "10" || exit /b
|
||||
..\..\..\odin check ..\test_issue_foreign_redeclaration.odin -no-entry-point %COMMON% || exit /b
|
||||
..\..\..\odin check ..\test_issue_foreign_redeclaration_mismatch.odin -no-entry-point %COMMON% 2>&1 | find /c "Error:" | findstr /x "1" || exit /b
|
||||
..\..\..\odin check ..\test_issue_asm_rip_register.odin -no-entry-point %COMMON% 2>&1 | find /c "Error:" | findstr /x "6" || exit /b
|
||||
..\..\..\odin check ..\test_issue_asm_template_as_value.odin -no-entry-point %COMMON% 2>&1 | find /c "Error:" | findstr /x "10" || exit /b
|
||||
..\..\..\odin build ..\test_issue_7037.odin %COMMON% -o:none || exit /b
|
||||
..\..\..\odin build ..\test_issue_7188.odin %COMMON% || exit /b
|
||||
clang -c ..\test_issue_sysv_abi.c -o test_issue_sysv_abi_c.o || exit /b
|
||||
|
||||
@@ -95,16 +95,8 @@ $ODIN test ../test_issue_7356.odin $COMMON
|
||||
$ODIN build ../test_issue_7167.odin $COMMON
|
||||
$ODIN build ../test_issue_7188.odin $COMMON
|
||||
$ODIN check ../test_issue_7260.odin -no-entry-point $COMMON_CHECK
|
||||
$ODIN test ../test_issue_bool_to_be_conversion.odin $COMMON
|
||||
|
||||
# `asm` templates are amd64-only, so this file is empty on every other architecture
|
||||
if [[ "$(uname -m)" == "x86_64" || "$(uname -m)" == "amd64" ]]; then
|
||||
if [[ $($ODIN check ../test_issue_asm_named_register_slot.odin -no-entry-point $COMMON_CHECK 2>&1 >/dev/null | grep -c "Error:") -eq 8 ]]; then
|
||||
echo "SUCCESSFUL 1/1"
|
||||
else
|
||||
echo "SUCCESSFUL 0/1"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
$ODIN check ../test_issue_foreign_redeclaration.odin -no-entry-point $COMMON_CHECK
|
||||
if [[ $($ODIN check ../test_issue_foreign_redeclaration_mismatch.odin -no-entry-point $COMMON_CHECK 2>&1 >/dev/null | grep -c "Error:") -eq 1 ]]; then
|
||||
echo "SUCCESSFUL 1/1"
|
||||
@@ -120,22 +112,6 @@ else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# `asm` templates are amd64-only, so this file is empty on every other architecture
|
||||
if [[ "$(uname -m)" == "x86_64" || "$(uname -m)" == "amd64" ]]; then
|
||||
if [[ $($ODIN check ../test_issue_asm_rip_register.odin -no-entry-point $COMMON_CHECK 2>&1 >/dev/null | grep -c "Error:") -eq 6 ]]; then
|
||||
echo "SUCCESSFUL 1/1"
|
||||
else
|
||||
echo "SUCCESSFUL 0/1"
|
||||
exit 1
|
||||
fi
|
||||
if [[ $($ODIN check ../test_issue_asm_template_as_value.odin -no-entry-point $COMMON_CHECK 2>&1 >/dev/null | grep -c "Error:") -eq 10 ]]; then
|
||||
echo "SUCCESSFUL 1/1"
|
||||
else
|
||||
echo "SUCCESSFUL 0/1"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ $($ODIN build ../test_issue_7108.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 2 ]]; then
|
||||
echo "SUCCESSFUL 1/1"
|
||||
else
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
#+build amd64
|
||||
// A slot that only a named hardware register can fill (segment/control/debug/x87/MMX)
|
||||
// carries no width and no register class, so it used to absorb any operand at all and
|
||||
// hand the backend an instruction that does not encode.
|
||||
package test_issues
|
||||
|
||||
// Rejected: none of these widths pair up, and only `mov`'s segment-register forms ever
|
||||
// admitted them.
|
||||
bad_64_32 :: asm(a: i64) -> (r: i32) { mov r, a; }
|
||||
bad_8_16 :: asm(a: u8) -> (r: u16) { mov r, a; }
|
||||
bad_16_8 :: asm(a: u16) -> (r: u8) { mov r, a; }
|
||||
bad_64_8 :: asm(a: u64) -> (r: u8) { mov r, a; }
|
||||
|
||||
// Accepted: equal widths, regardless of signedness or pointer spelling.
|
||||
ok_32 :: asm(a: i32) -> (r: u32) { mov r, a; }
|
||||
ok_64 :: asm(a: u64) -> (r: i64) { mov r, a; }
|
||||
ok_ptr :: asm(a: rawptr) -> (r: ^i32) { mov r, a; }
|
||||
|
||||
// Accepted: the named registers those forms are actually for.
|
||||
ok_seg :: asm(a: u64) -> (r: u64) { mov %ds, a; mov r, a; }
|
||||
ok_ctrl :: asm(a: u64) -> (r: u64) { mov %cr0, a; mov r, a; }
|
||||
ok_dbg :: asm(a: u64) -> (r: u64) { mov %dr0, a; mov r, a; }
|
||||
|
||||
use :: proc() {
|
||||
a8: u8
|
||||
a16: u16
|
||||
a32: i32
|
||||
a64: i64
|
||||
au64: u64
|
||||
ap: rawptr
|
||||
_ = bad_64_32(a64)
|
||||
_ = bad_8_16(a8)
|
||||
_ = bad_16_8(a16)
|
||||
_ = bad_64_8(au64)
|
||||
_ = ok_32(a32)
|
||||
_ = ok_64(au64)
|
||||
_ = ok_ptr(ap)
|
||||
_ = ok_seg(au64)
|
||||
_ = ok_ctrl(au64)
|
||||
_ = ok_dbg(au64)
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
#+build amd64
|
||||
// `%rip` is in the amd64 register table, but its class carries no width, so the checker's width
|
||||
// switch reached its `GB_PANIC` default arm and aborted with SIGILL instead of diagnosing. Every
|
||||
// position that can name a register reached it, including `[%rip + disp]`.
|
||||
package test_issues
|
||||
|
||||
rip_src :: asm() -> (v: u64) { mov v, %rip; }
|
||||
rip_dst :: asm(x: u64) { mov %rip, x; }
|
||||
rip_mem :: asm() { mov %rax, [%rip + 8]; }
|
||||
rip_clob :: asm() [#clobber %rip] { nop; }
|
||||
rip_in :: asm(x: u64) [x = %rip] { nop; }
|
||||
rip_out :: asm() -> (r: u64) [r = %rip] { nop; }
|
||||
|
||||
// the nearest special-purpose register that does carry a class has to keep checking cleanly
|
||||
rsp_ok :: asm() -> (v: u64) { mov v, %rsp; }
|
||||
@@ -1,48 +0,0 @@
|
||||
#+build amd64
|
||||
// A named asm template got a plain `Addressing_Value`, so every value gate let it through:
|
||||
// a cast, a transmute, an `auto_cast`, a blank assignment, a polymorphic parameter and a
|
||||
// comparison against `nil` all passed the checker and then aborted the compiler in the
|
||||
// backend, which has no value to lower for a template. Only a direct call and a listing in
|
||||
// an `asm` group are legal.
|
||||
package test_issues
|
||||
|
||||
t :: asm(a: i32) -> (v: i32) { mov v, a; }
|
||||
|
||||
a32 :: asm(a: i32) -> (v: i32) { mov v, a; }
|
||||
a64 :: asm(a: i64) -> (v: i64) { mov v, a; }
|
||||
g :: asm { a32, a64 }
|
||||
|
||||
G := cast(rawptr)(t)
|
||||
|
||||
take_rawptr :: proc(p: rawptr) {
|
||||
_ = p
|
||||
}
|
||||
|
||||
poly :: proc(x: $T) {
|
||||
_ = size_of(T)
|
||||
}
|
||||
|
||||
bad :: proc() {
|
||||
_ = cast(proc "c" (i32) -> i32)(t)
|
||||
_ = transmute(proc "c" (i32) -> i32)(t)
|
||||
_ = cast(rawptr)(t)
|
||||
_ = transmute(uintptr)(t)
|
||||
take_rawptr(auto_cast t)
|
||||
take_rawptr(cast(rawptr)(t))
|
||||
_ = t
|
||||
poly(t)
|
||||
if t == nil {
|
||||
take_rawptr(nil)
|
||||
}
|
||||
}
|
||||
|
||||
// these forms must remain valid
|
||||
good :: proc() -> i32 {
|
||||
x := t(1)
|
||||
y := (t)(2)
|
||||
z := g(i32(3))
|
||||
w := g(i64(4))
|
||||
v := asm(a: i32) -> (v: i32) { mov v, a; }(5)
|
||||
take_rawptr(G)
|
||||
return x + y + z + i32(w) + v
|
||||
}
|
||||
37
tests/issues/test_issue_bool_to_be_conversion.odin
Normal file
37
tests/issues/test_issue_bool_to_be_conversion.odin
Normal file
@@ -0,0 +1,37 @@
|
||||
package test_issues
|
||||
|
||||
import "core:testing"
|
||||
|
||||
// Converting a boolean to a big-endian integer skipped the endian fixup that the integer source
|
||||
// path performs, so the result carried a native bit pattern labelled big-endian. The checker
|
||||
// folded the same conversion to the right value, so only the runtime disagreed.
|
||||
|
||||
@(test)
|
||||
bool_to_big_endian :: proc(t: ^testing.T) {
|
||||
b: bool = true
|
||||
f: bool = false
|
||||
b8v: b8 = true
|
||||
b16v: b16 = true
|
||||
b32v: b32 = true
|
||||
b64v: b64 = true
|
||||
i: int = 1
|
||||
|
||||
testing.expect_value(t, int(i16be(b)), 1)
|
||||
testing.expect_value(t, int(u32be(b)), 1)
|
||||
testing.expect_value(t, int(u64be(b)), 1)
|
||||
testing.expect_value(t, int(u128be(b)), 1)
|
||||
testing.expect_value(t, int(i16be(f)), 0)
|
||||
|
||||
testing.expect_value(t, int(i16be(b8v)), 1)
|
||||
testing.expect_value(t, int(i16be(b16v)), 1)
|
||||
testing.expect_value(t, int(i16be(b32v)), 1)
|
||||
testing.expect_value(t, int(i16be(b64v)), 1)
|
||||
|
||||
// the little-endian target and the integer source were already correct
|
||||
testing.expect_value(t, int(i16le(b)), 1)
|
||||
testing.expect_value(t, int(i16be(i)), 1)
|
||||
|
||||
// the bytes have to actually be big-endian, not a native pattern relabelled
|
||||
testing.expect_value(t, transmute([4]u8)u32be(b), transmute([4]u8)u32be(i))
|
||||
testing.expect_value(t, transmute([4]u8)u32be(b), [4]u8{0, 0, 0, 1})
|
||||
}
|
||||
2
vendor/raylib/raylib.odin
vendored
2
vendor/raylib/raylib.odin
vendored
@@ -663,7 +663,7 @@ MouseButton :: enum c.int {
|
||||
MIDDLE = 2, // Mouse button middle (pressed wheel)
|
||||
SIDE = 3, // Mouse button side (advanced mouse device)
|
||||
EXTRA = 4, // Mouse button extra (advanced mouse device)
|
||||
FORWARD = 5, // Mouse button fordward (advanced mouse device)
|
||||
FORWARD = 5, // Mouse button forward (advanced mouse device)
|
||||
BACK = 6, // Mouse button back (advanced mouse device)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user