Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -969,7 +969,7 @@ jobs:
env:
WAVE_RUNTIME_ARTIFACT_DIR: ${{ runner.temp }}
run: >-
cargo test --locked --test runtime_regressions --target aarch64-pc-windows-msvc
cargo test --locked --test runtime_regressions --test std_io_regressions --target aarch64-pc-windows-msvc
--no-default-features --features llvm-target-aarch64 --jobs 2

- name: Save failed native runtime compiler and fixture
Expand Down
47 changes: 33 additions & 14 deletions std/fs/file.wave
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ import("std::sys::fs")::{
Stat,
stat,
fstat,
ftruncate,
same_file,
};

// Portable metadata returned by metadata(). Native mode bits and Stat layouts
Expand Down Expand Up @@ -205,7 +207,7 @@ pub fun fs_rename(old_path: str, new_path: str) -> i64 {
// file is larger than dst_cap; this function does not allocate or return str.
// Example: var n: i64 = fs_read_all("notes.txt", &buf[0], 256);
pub fun fs_read_all(path: str, dst: ptr<u8>, dst_cap: i64) -> i64 {
if (dst_cap < 0) {
if (dst_cap < 0 || (dst == null && dst_cap > 0)) {
return IO_ERR_INVALID;
}

Expand All @@ -214,18 +216,17 @@ pub fun fs_read_all(path: str, dst: ptr<u8>, dst_cap: i64) -> i64 {
return fd;
}

var sz: i64 = fs_file_size_fd(fd);
if (sz < 0) {
io_close(fd);
return sz;
}

if (sz > dst_cap) {
io_close(fd);
return IO_ERR_NO_SPACE;
var n: i64 = io_read_at_most(fd, dst, dst_cap);
if (n == dst_cap) {
// A full buffer is not evidence of EOF, including zero-capacity calls.
var extra: u8 = 0;
var probe: i64 = io_read(fd, &extra, 1);
if (probe < 0) {
n = probe;
} else if (probe > 0) {
n = IO_ERR_NO_SPACE;
}
}

var n: i64 = io_read_exact(fd, dst, sz);
var cr: i64 = io_close(fd);

if (n < 0) {
Expand Down Expand Up @@ -299,7 +300,7 @@ pub fun fs_copy(
scratch_cap: i64,
mode: i32
) -> i64 {
if (scratch_cap <= 0) {
if (scratch_cap <= 0 || scratch == null) {
return IO_ERR_INVALID;
}

Expand All @@ -308,12 +309,30 @@ pub fun fs_copy(
return src_fd;
}

var dst_fd: i64 = fs_open_write(dst_path, mode);
// Hold both opened objects before any destructive operation. Path aliases
// and concurrent renames cannot redirect the subsequent truncation.
var dst_fd: i64 = fs_open(dst_path, FS_O_WRONLY | FS_O_CREAT, mode);
if (dst_fd < 0) {
io_close(src_fd);
return dst_fd;
}

var identity: i64 = same_file(src_fd, dst_fd);
if (identity != 0) {
io_close(src_fd);
io_close(dst_fd);
if (identity < 0) {
return identity;
}
return IO_ERR_INVALID;
}
var resized: i64 = ftruncate(dst_fd, 0);
if (resized < 0) {
io_close(src_fd);
io_close(dst_fd);
return resized;
}

var copied: i64 = io_copy(src_fd, dst_fd, scratch, scratch_cap);

var src_cr: i64 = io_close(src_fd);
Expand Down
110 changes: 96 additions & 14 deletions std/math/float.wave
Original file line number Diff line number Diff line change
Expand Up @@ -262,13 +262,23 @@ pub fun ceil_f64(value: f64) -> f64 {
}

pub fun round_f32(value: f32) -> f32 {
if (value < 0.0) { return ceil_f32(value - 0.5); }
return floor_f32(value + 0.5);
// Subtraction from truncation preserves the halfway decision. Adding 0.5
// to the original value can round an integer or its fractional part first.
var truncated: f32 = trunc_f32(value);
if (abs_f32(value - truncated) >= 0.5) {
return truncated + copy_sign_f32(1.0, value);
}
return truncated;
}

pub fun round_f64(value: f64) -> f64 {
if (value < 0.0) { return ceil_f64(value - 0.5); }
return floor_f64(value + 0.5);
// Subtraction from truncation preserves the halfway decision. Adding 0.5
// to the original value can round an integer or its fractional part first.
var truncated: f64 = trunc_f64(value);
if (abs_f64(value - truncated) >= 0.5) {
return truncated + copy_sign_f64(1.0, value);
}
return truncated;
}

pub fun fract_f32(value: f32) -> f32 {
Expand All @@ -280,19 +290,51 @@ pub fun fract_f64(value: f64) -> f64 {
}

pub fun approx_eq_f32(a: f32, b: f32, absolute_tolerance: f32, relative_tolerance: f32) -> bool {
if (a == b) { return true; }
if (is_nan_f32(a) || is_nan_f32(b)) { return false; }
// Exact equality includes signed zeros and equal infinities regardless of
// tolerance. Otherwise both tolerances must be finite and nonnegative.
if (a == b) {
return true;
}
if (!is_finite_f32(a) || !is_finite_f32(b)
|| !is_finite_f32(absolute_tolerance) || absolute_tolerance < 0.0
|| !is_finite_f32(relative_tolerance) || relative_tolerance < 0.0) {
return false;
}
var difference: f32 = abs_f32(a - b);
if (difference <= absolute_tolerance) { return true; }
return difference <= max_f32(abs_f32(a), abs_f32(b)) * relative_tolerance;
if (difference <= absolute_tolerance) {
return true;
}
var scale: f32 = max_f32(abs_f32(a), abs_f32(b));
if (is_finite_f32(difference)) {
return difference / scale <= relative_tolerance;
}
// Only opposite-sign finite operands can overflow the difference. Scale
// them before subtracting; the normalized difference is bounded by two.
return abs_f32(a / scale - b / scale) <= relative_tolerance;
}

pub fun approx_eq_f64(a: f64, b: f64, absolute_tolerance: f64, relative_tolerance: f64) -> bool {
if (a == b) { return true; }
if (is_nan_f64(a) || is_nan_f64(b)) { return false; }
// Exact equality includes signed zeros and equal infinities regardless of
// tolerance. Otherwise both tolerances must be finite and nonnegative.
if (a == b) {
return true;
}
if (!is_finite_f64(a) || !is_finite_f64(b)
|| !is_finite_f64(absolute_tolerance) || absolute_tolerance < 0.0
|| !is_finite_f64(relative_tolerance) || relative_tolerance < 0.0) {
return false;
}
var difference: f64 = abs_f64(a - b);
if (difference <= absolute_tolerance) { return true; }
return difference <= max_f64(abs_f64(a), abs_f64(b)) * relative_tolerance;
if (difference <= absolute_tolerance) {
return true;
}
var scale: f64 = max_f64(abs_f64(a), abs_f64(b));
if (is_finite_f64(difference)) {
return difference / scale <= relative_tolerance;
}
// Only opposite-sign finite operands can overflow the difference. Scale
// them before subtracting; the normalized difference is bounded by two.
return abs_f64(a / scale - b / scale) <= relative_tolerance;
}

pub fun sqrt_f32(value: f32) -> f32 {
Expand Down Expand Up @@ -352,9 +394,49 @@ pub fun hypot_f64(a: f64, b: f64) -> f64 {
}

pub fun lerp_f32(a: f32, b: f32, amount: f32) -> f32 {
return a + (b - a) * amount;
// Endpoints are exact, including signed zero. Away from the endpoints,
// non-finite inputs yield NaN; finite extrapolation may overflow.
if (amount == 0.0) {
return a;
}
if (amount == 1.0) {
return b;
}
if (!is_finite_f32(a) || !is_finite_f32(b) || !is_finite_f32(amount)) {
return nan_f32();
}
if ((a <= 0.0 && b >= 0.0) || (a >= 0.0 && b <= 0.0)) {
return (1.0 - amount) * a + amount * b;
}
// Same-sign endpoints have a finite difference, preserving precision for
// nearby values. Clamp rounding at b to keep the result monotonic.
var result: f32 = a + (b - a) * amount;
if ((amount > 1.0 && b > a) || (amount <= 1.0 && b <= a)) {
return max_f32(b, result);
}
return min_f32(b, result);
}

pub fun lerp_f64(a: f64, b: f64, amount: f64) -> f64 {
return a + (b - a) * amount;
// Endpoints are exact, including signed zero. Away from the endpoints,
// non-finite inputs yield NaN; finite extrapolation may overflow.
if (amount == 0.0) {
return a;
}
if (amount == 1.0) {
return b;
}
if (!is_finite_f64(a) || !is_finite_f64(b) || !is_finite_f64(amount)) {
return nan_f64();
}
if ((a <= 0.0 && b >= 0.0) || (a >= 0.0 && b <= 0.0)) {
return (1.0 - amount) * a + amount * b;
}
// Same-sign endpoints have a finite difference, preserving precision for
// nearby values. Clamp rounding at b to keep the result monotonic.
var result: f64 = a + (b - a) * amount;
if ((amount > 1.0 && b > a) || (amount <= 1.0 && b <= a)) {
return max_f64(b, result);
}
return min_f64(b, result);
}
20 changes: 8 additions & 12 deletions std/math/num.wave
Original file line number Diff line number Diff line change
Expand Up @@ -179,13 +179,14 @@ pub fun pow_i32_checked(base_value: i32, exponent_value: u32) -> MathResult<i32>

pub fun pow_f32_i32(base_value: f32, exponent_value: i32) -> f32 {
var exponent: u32 = exponent_value as u32;
var reciprocal: bool = false;
var base: f32 = base_value;
if (exponent_value < 0) {
reciprocal = true;
// Invert before exponentiation so a representable subnormal result
// does not depend on an overflowing positive power.
base = 1.0 / base;
exponent = (-(exponent_value + 1)) as u32 + 1;
}

var base: f32 = base_value;
var result: f32 = 1.0;
while (exponent != 0) {
if ((exponent & 1) == 1) {
Expand All @@ -196,21 +197,19 @@ pub fun pow_f32_i32(base_value: f32, exponent_value: i32) -> f32 {
base = base * base;
}
}
if (reciprocal) {
return 1.0 / result;
}
return result;
}

pub fun pow_f64_i32(base_value: f64, exponent_value: i32) -> f64 {
var exponent: u32 = exponent_value as u32;
var reciprocal: bool = false;
var base: f64 = base_value;
if (exponent_value < 0) {
reciprocal = true;
// Invert before exponentiation so a representable subnormal result
// does not depend on an overflowing positive power.
base = 1.0 / base;
exponent = (-(exponent_value + 1)) as u32 + 1;
}

var base: f64 = base_value;
var result: f64 = 1.0;
while (exponent != 0) {
if ((exponent & 1) == 1) {
Expand All @@ -221,9 +220,6 @@ pub fun pow_f64_i32(base_value: f64, exponent_value: i32) -> f64 {
base = base * base;
}
}
if (reciprocal) {
return 1.0 / result;
}
return result;
}

Expand Down
72 changes: 72 additions & 0 deletions std/net/posix_timeout.wave
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: Apache-2.0
// A readiness event can be consumed by an alias before our syscall. Each I/O
// attempt must itself be nonblocking; never change the shared descriptor mode.
import("std::sys::socket")::{send, recv, MSG_NOSIGNAL, MSG_DONTWAIT};
import("std::net::poll")::{net_wait_readable, net_wait_writable};
import("std::net::error")::{
NetIoResult, NetError, net_io_ok, net_io_eof, net_io_error,
net_error_from_native, NET_ERROR_INTERRUPTED, NET_ERROR_WOULD_BLOCK,
};
import("std::time::clock")::{time_now_monotonic_ns};

pub fun net_io_timeout(fd: i64, buffer: ptr<u8>, size: i64, timeout_ms: i32, writing: bool) -> NetIoResult {
var start: i64 = 0;
if (timeout_ms >= 0) {
start = time_now_monotonic_ns();
if (start < 0) {
return net_io_error(0, start);
}
}
var budget: i64 = timeout_ms as i64 * 1000000;
while (true) {
var count: i64 = 0;
if (writing) {
count = send(fd, buffer, size, MSG_DONTWAIT | MSG_NOSIGNAL);
} else {
count = recv(fd, buffer, size, MSG_DONTWAIT);
}
if (count >= 0) {
if (!writing && count == 0) {
return net_io_eof(0);
}
return net_io_ok(count);
}
var error: NetError = net_error_from_native(count);
if (error.kind != NET_ERROR_INTERRUPTED && error.kind != NET_ERROR_WOULD_BLOCK) {
return net_io_error(0, count);
}

var remaining: i32 = -1;
if (timeout_ms >= 0) {
var now: i64 = time_now_monotonic_ns();
if (now < 0) {
return net_io_error(0, now);
}
if (now < start) {
return net_io_error(0, -5);
}
var elapsed: i64 = now - start;
if (elapsed >= budget) {
return net_io_error(0, -110);
}
remaining = ((budget - elapsed + 999999) / 1000000) as i32;
}
var ready: i64 = 0;
if (writing) {
ready = net_wait_writable(fd, remaining);
} else {
ready = net_wait_readable(fd, remaining);
}
if (ready == 0) {
return net_io_error(0, -110);
}
if (ready < 0) {
error = net_error_from_native(ready);
if (error.kind != NET_ERROR_INTERRUPTED) {
return net_io_error(0, ready);
}
}
// The next EAGAIN or EINTR recomputes the same monotonic budget.
}
return net_io_error(0, -5);
}
Loading
Loading