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 @@ -861,7 +861,7 @@ jobs:
env:
WAVE_RUNTIME_ARTIFACT_DIR: ${{ runner.temp }}
run: >-
cargo test --locked --no-fail-fast --test runtime_regressions --test std_io_regressions --test native_providers --test stabilization_17 --target aarch64-pc-windows-msvc
cargo test --locked --no-fail-fast --test runtime_regressions --test std_io_regressions --test native_providers --test stabilization_17 --test native_boundaries --target aarch64-pc-windows-msvc
--no-default-features --features llvm-target-aarch64 --jobs 2

- name: Save failed native runtime compiler and fixture
Expand Down
12 changes: 11 additions & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,17 @@ struct BuildPlan {
}

pub fn run() -> Result<(), CliError> {
let args: Vec<String> = env::args().skip(1).collect();
// The CLI grammar currently uses UTF-8 strings. Reject unrepresentable
// arguments before planning any outputs; args() panics and lossy decoding
// can alias distinct filesystem names.
let args: Vec<String> = env::args_os()
.skip(1)
.map(|arg| {
arg.into_string().map_err(|arg| {
CliError::usage(format!("command-line argument is not valid UTF-8: {arg:?}"))
})
})
.collect::<Result<_, _>>()?;
if args.is_empty() {
return Err(CliError::usage("not enough arguments"));
}
Expand Down
6 changes: 5 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@
use std::process;

fn main() {
let json_errors = wavec::cli::args_request_json_errors(std::env::args().skip(1));
let json_errors = wavec::cli::args_request_json_errors(
std::env::args_os()
.skip(1)
.map(|arg| arg.to_string_lossy().into_owned()),
);

if let Err(e) = wavec::cli::run() {
if json_errors {
Expand Down
71 changes: 60 additions & 11 deletions src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,48 @@ fn expand_imports_for_codegen(
})
}

#[allow(dead_code)]
fn default_output_path(file_path: &Path, directory: &Path, extension: Option<&str>) -> PathBuf {
let Some(stem) = file_path.file_stem().filter(|stem| !stem.is_empty()) else {
WaveError::new(
WaveErrorKind::FileWriteError(file_path.display().to_string()),
"cannot derive an output name from the source path",
file_path.display().to_string(),
0,
0,
)
.with_code("E1005")
.with_help("pass an explicit output path with -o <file>")
.display_auto();
process::exit(1);
};
let mut name = stem.to_os_string();
if let Some(extension) = extension {
name.push(".");
name.push(extension);
}
directory.join(name)
}

fn utf8_output_path(path: &Path, file_path: &Path) -> String {
match path.to_str() {
Some(value) => value.to_owned(),
None => {
WaveError::new(
WaveErrorKind::FileWriteError(path.display().to_string()),
"the backend requires a UTF-8 output path",
file_path.display().to_string(),
0,
0,
)
.with_code("E1005")
.with_help("pass a UTF-8 output path with -o <file>")
.display_auto();
process::exit(1);
}
}
}

#[allow(dead_code)]
fn resolve_output_target(
default_output: &str,
Expand Down Expand Up @@ -462,7 +504,7 @@ fn resolve_output_target(
}
}

output.display().to_string()
utf8_output_path(output, file_path)
}

fn build_backend_options(llvm: &LlvmFlags) -> BackendOptions {
Expand Down Expand Up @@ -796,8 +838,10 @@ pub(crate) unsafe fn run_wave_file(

let hir = lower_wave_hir_or_exit(file_path, &code, ast);

let file_stem = file_path.file_stem().unwrap().to_str().unwrap();
let object_patch = format!("{}.o", file_stem);
let object_patch = utf8_output_path(
&default_output_path(file_path, Path::new(""), Some("o")),
file_path,
);
emit_wave_codegen_file_from_hir(
file_path,
&code,
Expand Down Expand Up @@ -826,7 +870,10 @@ pub(crate) unsafe fn run_wave_file(
println!();
}

let exe_patch = format!("target/{}", file_stem);
let exe_patch = utf8_output_path(
&default_output_path(file_path, Path::new("target"), None),
file_path,
);
let backend_opts = build_backend_options(llvm);

match run_panic_guarded(|| {
Expand Down Expand Up @@ -927,20 +974,20 @@ pub(crate) unsafe fn object_build_wave_file(

let hir = lower_wave_hir_or_exit(file_path, &code, ast);

let file_stem = file_path.file_stem().unwrap().to_str().unwrap();
let default_object_path = PathBuf::from(format!("{}.o", file_stem));
let output_path = output.unwrap_or(default_object_path.as_path());
let output_path = output
.map(Path::to_path_buf)
.unwrap_or_else(|| default_output_path(file_path, Path::new(""), Some("o")));
let object_path = utf8_output_path(&output_path, file_path);
emit_wave_codegen_file_from_hir(
file_path,
&code,
&hir,
opt_flag,
debug,
llvm,
output_path,
&output_path,
CodegenFileKind::Object,
);
let object_path = output_path.to_string_lossy().to_string();

if debug.mc {
println!("\n===== MACHINE CODE PATH =====");
Expand Down Expand Up @@ -974,8 +1021,10 @@ pub(crate) unsafe fn build_wave_file(
) {
let object_path = object_build_wave_file(file_path, opt_flag, debug, dep, llvm, None);

let file_stem = file_path.file_stem().unwrap().to_str().unwrap();
let default_exe_path = format!("target/{}", file_stem);
let exe_path = output
.map(Path::to_path_buf)
.unwrap_or_else(|| default_output_path(file_path, Path::new("target"), None));
let default_exe_path = utf8_output_path(&exe_path, file_path);
let source = fs::read_to_string(file_path).unwrap_or_default();
let exe_path =
resolve_output_target(&default_exe_path, output, file_path, &source, "native-link");
Expand Down
3 changes: 3 additions & 0 deletions std/env/consts.wave
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
pub const ENV_ERR_NOT_FOUND: i64 = -2;
pub const ENV_ERR_NO_SPACE: i64 = -3;
pub const ENV_ERR_INVALID_KEY: i64 = -4;
pub const ENV_ERR_READ: i64 = -5;
pub const ENV_ERR_SOURCE_INCOMPLETE: i64 = -6;
pub const ENV_ERR_NO_MEMORY: i64 = -7;

pub const ENV_O_RDONLY: i32 = 0;
pub const ENV_SCAN_CAP: i64 = 32768;
60 changes: 46 additions & 14 deletions std/env/environ.wave
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,24 @@ import("std::env::parse")::{
_env_parse_i64,
};

import("std::env::consts")::{ENV_ERR_NO_SPACE};
import("std::env::consts")::{
ENV_ERR_NO_SPACE, ENV_ERR_NOT_FOUND, ENV_ERR_INVALID_KEY,
ENV_ERR_READ, ENV_ERR_SOURCE_INCOMPLETE, ENV_ERR_NO_MEMORY
};
import("std::sys::memory")::{sys_alloc, sys_free};

// Raw OS error domains differ. Only Linux's complete-read contract reserves
// -ENOSPC for a source buffer that needs to grow.
#[target(os="linux")]
fun _env_source_needs_space(status: i64) -> bool { return status == -28; }
#[target(os="windows")]
fun _env_source_needs_space(status: i64) -> bool { return false; }
#[target(os="macos")]
fun _env_source_needs_space(status: i64) -> bool { return false; }
#[target(os="freebsd")]
fun _env_source_needs_space(status: i64) -> bool { return false; }
#[target(os="wasi")]
fun _env_source_needs_space(status: i64) -> bool { return false; }

pub struct EnvResult<T> {
ok: bool;
Expand Down Expand Up @@ -57,21 +74,36 @@ pub fun env_unwrap_or<T>(result: EnvResult<T>, default_value: T) -> T {

pub fun env_get(name: str, dst: ptr<u8>, dst_cap: i64) -> i64 {
var key_len: i64 = _env_key_len(name);

if (key_len <= 0) {
return -4;
if (key_len <= 0) { return ENV_ERR_INVALID_KEY; }
if (dst == null || dst_cap <= 0) { return ENV_ERR_NO_SPACE; }

var initial: array<u8, 32768>;
var raw: ptr<u8> = &initial[0];
var capacity: i64 = 32768;
var allocated: bool = false;
var n: i64 = env_read(raw, capacity);
while (_env_source_needs_space(n)) {
if (allocated) { sys_free(raw, capacity); }
if (capacity > 4611686018427387903) { return ENV_ERR_NO_MEMORY; }
capacity *= 2;
raw = sys_alloc(capacity);
if (raw == null) { return ENV_ERR_NO_MEMORY; }
allocated = true;
n = env_read(raw, capacity);
}

if (dst_cap <= 0) {
return -3;
var result: i64 = ENV_ERR_READ;
if (n >= 0 && n <= capacity) {
result = _env_lookup(raw, n, name, key_len, dst, dst_cap);
}
if (allocated) { sys_free(raw, capacity); }
return result;
}

var raw: array<u8, 32768>;
var n: i64 = env_read(&raw[0], 32768);

if (n <= 0) {
return -2;
}
fun _env_lookup(raw: ptr<u8>, n: i64, name: str, key_len: i64, dst: ptr<u8>, dst_cap: i64) -> i64 {
if (n == 0) { return ENV_ERR_NOT_FOUND; }
// An unterminated source entry is neither a complete value nor proof that
// an absent key does not exist beyond the buffer boundary.
if (raw[n - 1] != 0) { return ENV_ERR_SOURCE_INCOMPLETE; }

var i: i64 = 0;
while (i < n) {
Expand Down Expand Up @@ -106,7 +138,7 @@ pub fun env_get(name: str, dst: ptr<u8>, dst_cap: i64) -> i64 {
return _env_copy_value(&raw[0], eq_pos + 1, entry_end, dst, dst_cap);
}

return -2;
return ENV_ERR_NOT_FOUND;
}

pub fun env_exists(name: str) -> bool {
Expand Down
28 changes: 19 additions & 9 deletions std/sys/linux/amd64/env.wave
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,27 @@ import("std::sys::linux::amd64::fs")::{
fstat,
};

// A positive result always covers the whole source through EOF. -28 means
// the caller must retry with more space; partial entries are never success.
pub fun env_read(buf: ptr<u8>, cap: i64) -> i64 {
if (cap <= 0) {
return -22;
}

if (buf == null || cap <= 0) { return -22; }
var fd: i64 = open("/proc/self/environ", 0, 0);
if (fd < 0) {
return fd;
if (fd < 0) { return fd; }
var total: i64 = 0;
while (total < cap) {
var count: i64 = read(fd, &buf[total], cap - total);
if (count == -4) { continue; }
if (count < 0) { close(fd); return count; }
if (count == 0) { close(fd); return total; }
total += count;
}

var n: i64 = read(fd, buf, cap);
// Distinguish an exact fit from a truncated source, including when the
// last available byte happens to be a complete entry's NUL terminator.
var extra: u8 = 0;
var count: i64 = read(fd, &extra, 1);
while (count == -4) { count = read(fd, &extra, 1); }
close(fd);
return n;
if (count < 0) { return count; }
if (count == 0) { return total; }
return -28;
}
28 changes: 19 additions & 9 deletions std/sys/linux/arm64/env.wave
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,27 @@ import("std::sys::linux::arm64::fs")::{
fstat,
};

// A positive result always covers the whole source through EOF. -28 means
// the caller must retry with more space; partial entries are never success.
pub fun env_read(buf: ptr<u8>, cap: i64) -> i64 {
if (cap <= 0) {
return -22;
}

if (buf == null || cap <= 0) { return -22; }
var fd: i64 = open("/proc/self/environ", 0, 0);
if (fd < 0) {
return fd;
if (fd < 0) { return fd; }
var total: i64 = 0;
while (total < cap) {
var count: i64 = read(fd, &buf[total], cap - total);
if (count == -4) { continue; }
if (count < 0) { close(fd); return count; }
if (count == 0) { close(fd); return total; }
total += count;
}

var n: i64 = read(fd, buf, cap);
// Distinguish an exact fit from a truncated source, including when the
// last available byte happens to be a complete entry's NUL terminator.
var extra: u8 = 0;
var count: i64 = read(fd, &extra, 1);
while (count == -4) { count = read(fd, &extra, 1); }
close(fd);
return n;
if (count < 0) { return count; }
if (count == 0) { return total; }
return -28;
}
28 changes: 19 additions & 9 deletions std/sys/linux/riscv64/env.wave
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,27 @@ import("std::sys::linux::fs")::{
fstat,
};

// A positive result always covers the whole source through EOF. -28 means
// the caller must retry with more space; partial entries are never success.
pub fun env_read(buf: ptr<u8>, cap: i64) -> i64 {
if (cap <= 0) {
return -22;
}

if (buf == null || cap <= 0) { return -22; }
var fd: i64 = open("/proc/self/environ", 0, 0);
if (fd < 0) {
return fd;
if (fd < 0) { return fd; }
var total: i64 = 0;
while (total < cap) {
var count: i64 = read(fd, &buf[total], cap - total);
if (count == -4) { continue; }
if (count < 0) { close(fd); return count; }
if (count == 0) { close(fd); return total; }
total += count;
}

var n: i64 = read(fd, buf, cap);
// Distinguish an exact fit from a truncated source, including when the
// last available byte happens to be a complete entry's NUL terminator.
var extra: u8 = 0;
var count: i64 = read(fd, &extra, 1);
while (count == -4) { count = read(fd, &extra, 1); }
close(fd);
return n;
if (count < 0) { return count; }
if (count == 0) { return total; }
return -28;
}
Loading
Loading