Skip to content
Closed
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
31 changes: 31 additions & 0 deletions crates/perry-api-manifest/src/entries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1925,6 +1925,37 @@ pub static API_MANIFEST: &[ApiEntry] = &[
method("os", "networkInterfaces", false, None),
method("os", "userInfo", false, None),
method("os", "version", false, None),
method_sig(
"os",
"getPriority",
false,
None,
&[ParamSpec::Named {
name: "pid",
ty: TypeSpec::Number,
optional: true,
}],
TypeSpec::Number,
),
method_sig(
"os",
"setPriority",
false,
None,
&[
ParamSpec::Named {
name: "pidOrPriority",
ty: TypeSpec::Number,
optional: false,
},
ParamSpec::Named {
name: "priority",
ty: TypeSpec::Number,
optional: true,
},
],
TypeSpec::Void,
),
property("os", "EOL"),
property("os", "devNull"),
// Issue #649: os/crypto.constants tables — see
Expand Down
19 changes: 19 additions & 0 deletions crates/perry-codegen/src/lower_call/native_table/node_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,25 @@ pub(super) const NODE_CORE_ROWS: &[NativeModSig] = &[
args: &[NA_F64],
ret: NR_F64,
},
// ========== Node OS ==========
NativeModSig {
module: "os",
has_receiver: false,
method: "getPriority",
class_filter: None,
runtime: "js_os_get_priority",
args: &[NA_F64],
ret: NR_F64,
},
NativeModSig {
module: "os",
has_receiver: false,
method: "setPriority",
class_filter: None,
runtime: "js_os_set_priority",
args: &[NA_F64, NA_F64],
ret: NR_F64,
},
// ========== Node process EventEmitter ==========
NativeModSig {
module: "process",
Expand Down
9 changes: 9 additions & 0 deletions crates/perry-hir/src/lower/expr_call/globals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,15 @@ pub(super) fn try_global_builtins(
"cpus" => return Ok(Ok(Expr::OsCpus)),
"networkInterfaces" => return Ok(Ok(Expr::OsNetworkInterfaces)),
"userInfo" => return Ok(Ok(Expr::OsUserInfo)),
"getPriority" | "setPriority" => {
return Ok(Ok(Expr::NativeMethodCall {
module: "os".to_string(),
class_name: None,
object: None,
method: method_name.to_string(),
args,
}));
}
_ => {}
}
}
Expand Down
9 changes: 9 additions & 0 deletions crates/perry-hir/src/lower/expr_call/module_static.rs
Original file line number Diff line number Diff line change
Expand Up @@ -968,6 +968,15 @@ pub(super) fn try_module_static_methods(
"userInfo" => {
return Ok(Ok(Expr::OsUserInfo));
}
"getPriority" | "setPriority" => {
return Ok(Ok(Expr::NativeMethodCall {
module: "os".to_string(),
class_name: None,
object: None,
method: method_name.to_string(),
args,
}));
}
_ => {} // Fall through to generic handling
}
}
Expand Down
9 changes: 9 additions & 0 deletions crates/perry-hir/src/lower/expr_call/native_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,15 @@ pub(super) fn try_native_module_methods(
"cpus" => return Ok(Ok(Expr::OsCpus)),
"networkInterfaces" => return Ok(Ok(Expr::OsNetworkInterfaces)),
"userInfo" => return Ok(Ok(Expr::OsUserInfo)),
"getPriority" | "setPriority" => {
return Ok(Ok(Expr::NativeMethodCall {
module: "os".to_string(),
class_name: None,
object: None,
method: method_name.to_string(),
args,
}));
}
_ => {} // Fall through to generic handling
}
}
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-runtime/src/object/native_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,8 @@ pub(crate) fn is_native_module_callable_export(module: &str, prop: &str) -> bool
| ("os", "loadavg")
| ("os", "machine")
| ("os", "version")
| ("os", "getPriority")
| ("os", "setPriority")
| ("fs", "accessSync")
| ("fs", "access")
| ("fs", "appendFile")
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-runtime/src/object/native_module_dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,8 @@ pub(crate) unsafe fn dispatch_native_module_method(
("os", "userInfo") => {
f64::from_bits(JSValue::pointer(crate::os::js_os_user_info() as *const u8).bits())
}
("os", "getPriority") => crate::os::js_os_get_priority(arg(0)),
("os", "setPriority") => crate::os::js_os_set_priority(arg(0), arg(1)),

// ── path module (args are NaN-boxed strings → extract raw StringHeader ptr) ──
("path", "dirname") => str_to_f64(crate::path::js_path_dirname(arg_str_ptr(0))),
Expand Down
180 changes: 180 additions & 0 deletions crates/perry-runtime/src/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,186 @@ fn get_hrtime_start() -> &'static Instant {
HRTIME_START.get_or_init(Instant::now)
}

fn throw_os_type_error(message: String) -> ! {
let msg_ptr = js_string_from_bytes(message.as_ptr(), message.len() as u32);
crate::node_submodules::register_error_code_pub(msg_ptr, "ERR_INVALID_ARG_TYPE");
let err = crate::error::js_typeerror_new(msg_ptr);
crate::exception::js_throw(crate::value::js_nanbox_pointer(err as i64))
}

fn throw_os_range_error(message: String) -> ! {
let msg_ptr = js_string_from_bytes(message.as_ptr(), message.len() as u32);
crate::node_submodules::register_error_code_pub(msg_ptr, "ERR_OUT_OF_RANGE");
let err = crate::error::js_rangeerror_new(msg_ptr);
crate::exception::js_throw(crate::value::js_nanbox_pointer(err as i64))
}

fn throw_os_system_error(syscall: &'static str, errno: i32) -> ! {
let message = if errno != 0 {
format!("A system error occurred: {syscall} failed with errno {errno}")
} else {
format!("A system error occurred: {syscall} failed")
};
let msg_ptr = js_string_from_bytes(message.as_ptr(), message.len() as u32);
crate::node_submodules::register_error_code_pub(msg_ptr, "ERR_SYSTEM_ERROR");
crate::node_submodules::register_error_syscall(msg_ptr, syscall);
let err = crate::error::js_error_new_with_name_message(b"SystemError", msg_ptr);
crate::exception::js_throw(crate::value::js_nanbox_pointer(err as i64))
}

fn js_value_to_i32(value: f64, name: &str, default: Option<i32>) -> i32 {
let js_value = crate::JSValue::from_bits(value.to_bits());
if js_value.is_undefined() {
if let Some(default) = default {
return default;
}
throw_os_type_error(format!("The \"{name}\" argument must be of type number"));
}

let number = if js_value.is_int32() {
js_value.as_int32() as f64
} else if js_value.is_number() {
js_value.as_number()
} else {
throw_os_type_error(format!("The \"{name}\" argument must be of type number"));
};

if !number.is_finite()
|| number.fract() != 0.0
|| number < i32::MIN as f64
|| number > i32::MAX as f64
{
throw_os_range_error(format!("The value of \"{name}\" is out of range"));
}
number as i32
}

#[cfg(target_os = "linux")]
unsafe fn os_errno_location() -> *mut libc::c_int {
libc::__errno_location()
}

#[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))]
unsafe fn os_errno_location() -> *mut libc::c_int {
libc::__error()
}

#[cfg(all(
unix,
not(any(
target_os = "linux",
target_os = "macos",
target_os = "ios",
target_os = "freebsd"
))
))]
unsafe fn os_errno_location() -> *mut libc::c_int {
std::ptr::null_mut()
}

#[cfg(unix)]
fn clear_os_errno() {
unsafe {
let errno = os_errno_location();
if !errno.is_null() {
*errno = 0;
}
}
}

#[cfg(unix)]
fn current_os_errno() -> i32 {
unsafe {
let errno = os_errno_location();
if errno.is_null() {
0
} else {
*errno
}
}
}

#[cfg(unix)]
fn os_get_priority(pid: i32) -> Result<i32, i32> {
clear_os_errno();
let priority = unsafe { libc::getpriority(libc::PRIO_PROCESS, pid as libc::id_t) };
let errno = current_os_errno();
if priority == -1 && errno != 0 {
Err(errno)
} else {
Ok(priority)
}
}

#[cfg(unix)]
fn os_set_priority(pid: i32, priority: i32) -> Result<(), i32> {
let rc = unsafe {
libc::setpriority(
libc::PRIO_PROCESS,
pid as libc::id_t,
priority as libc::c_int,
)
};
if rc == 0 {
Ok(())
} else {
Err(std::io::Error::last_os_error().raw_os_error().unwrap_or(0))
}
}

#[cfg(not(unix))]
fn os_get_priority(pid: i32) -> Result<i32, i32> {
if pid == 0 {
Ok(0)
} else {
Err(0)
}
}

#[cfg(not(unix))]
fn os_set_priority(pid: i32, _priority: i32) -> Result<(), i32> {
if pid == 0 {
Ok(())
} else {
Err(0)
}
}

/// Get the scheduling priority for a process. `undefined` defaults to the
/// current process, matching Node/Deno's `pid = 0` behavior.
#[no_mangle]
pub extern "C" fn js_os_get_priority(pid_value: f64) -> f64 {
let pid = js_value_to_i32(pid_value, "pid", Some(0));
match os_get_priority(pid) {
Ok(priority) => priority as f64,
Err(errno) => throw_os_system_error("uv_os_getpriority", errno),
}
}

/// Set process priority. One argument is treated as `priority`; two arguments
/// are `pid, priority`.
#[no_mangle]
pub extern "C" fn js_os_set_priority(pid_or_priority: f64, priority_value: f64) -> f64 {
let priority_arg = crate::JSValue::from_bits(priority_value.to_bits());
let (pid, priority) = if priority_arg.is_undefined() {
(0, js_value_to_i32(pid_or_priority, "priority", None))
} else {
(
js_value_to_i32(pid_or_priority, "pid", None),
js_value_to_i32(priority_value, "priority", None),
)
};

if !(-20..=19).contains(&priority) {
throw_os_range_error("The value of \"priority\" is out of range".to_string());
}

match os_set_priority(pid, priority) {
Ok(()) => f64::from_bits(crate::value::TAG_UNDEFINED),
Err(errno) => throw_os_system_error("uv_os_setpriority", errno),
}
}

/// Get the operating system platform
/// Returns: "darwin", "linux", "win32", "freebsd", etc.
#[no_mangle]
Expand Down
6 changes: 5 additions & 1 deletion docs/api/perry.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Auto-generated from Perry's API manifest (#465). Do not edit by hand.
// Source: perry-api-manifest::API_MANIFEST
// Coverage: 1279 entries across 81 modules
// Coverage: 1281 entries across 81 modules

declare module "@perryts/pdf" {
/** stdlib */
Expand Down Expand Up @@ -945,6 +945,8 @@ declare module "os" {
/** stdlib */
export function freemem(...args: any[]): any;
/** stdlib */
export function getPriority(pid?: number): number;
/** stdlib */
export function homedir(...args: any[]): any;
/** stdlib */
export function hostname(...args: any[]): any;
Expand All @@ -959,6 +961,8 @@ declare module "os" {
/** stdlib */
export function release(...args: any[]): any;
/** stdlib */
export function setPriority(pidOrPriority: number, priority?: number): void;
/** stdlib */
export function tmpdir(...args: any[]): any;
/** stdlib */
export function totalmem(...args: any[]): any;
Expand Down
4 changes: 3 additions & 1 deletion docs/src/api/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This page is auto-generated from Perry's compile-time API manifest (`perry-api-manifest::API_MANIFEST`). It is the source of truth for what `perry compile` accepts; references to symbols not listed here produce `R005 UnimplementedApi` (issue #463). Stubs (#464) are flagged ⚠ — they link cleanly but no-op at runtime on the chosen target.

Total: 1279 entries across 81 modules.
Total: 1281 entries across 81 modules.

## Modules

Expand Down Expand Up @@ -1093,13 +1093,15 @@ Total: 1279 entries across 81 modules.
- `cpus` — module
- `endianness` — module
- `freemem` — module
- `getPriority` — module
- `homedir` — module
- `hostname` — module
- `loadavg` — module
- `machine` — module
- `networkInterfaces` — module
- `platform` — module
- `release` — module
- `setPriority` — module
- `tmpdir` — module
- `totalmem` — module
- `type` — module
Expand Down