Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
320d49e
posix: take an open file descriptor in the supports_fd entry points t…
youknowone Aug 6, 2026
118706a
posix: implement os.truncate
youknowone Aug 6, 2026
974f88e
posix: decode getcwd with the filesystem handler, not lossily
youknowone Aug 6, 2026
836d557
extra_tests: check every os.supports_fd member against a descriptor
youknowone Aug 6, 2026
286bf97
posix: resolve chown's dir_fd, and advertise HAVE_FCHOWNAT / HAVE_UTI…
youknowone Aug 6, 2026
e19b117
extra_tests: exercise dir_fd and follow_symlinks on every name that c…
youknowone Aug 6, 2026
b6f1193
majit: fold a virtual's never-stored field read to the zero constant
youknowone Aug 6, 2026
4b700f5
posix: derive HAVE_LSTAT from HAVE_FSTATAT and serve the three claims…
youknowone Aug 6, 2026
e46cc06
posix: listdir and scandir accept a directory descriptor
youknowone Aug 6, 2026
0be78fc
posix: a buffer is not a path
youknowone Aug 6, 2026
fea2597
posix: chmod takes dir_fd and follow_symlinks, and lchmod is a real call
youknowone Aug 6, 2026
59879cc
posix: a stat that fails for a reason other than ENOENT is the caller…
youknowone Aug 6, 2026
3a99081
posix: open, mkdir, mkfifo, rmdir and unlink take dir_fd
youknowone Aug 6, 2026
e33b26f
posix: chflags, lchflags and mknod are real calls, or absent
youknowone Aug 6, 2026
db1d225
posix: major, minor and makedev compute a device number
youknowone Aug 6, 2026
1f524f8
posix: stop binding spawnv, so os.py can define the spawn family
youknowone Aug 6, 2026
f9b69d0
posix: EX_*, ST_*, SCHED_* and RTLD_* carry the header's values
youknowone Aug 6, 2026
4194fdf
posix: stop binding the C entry points and the names os.py writes itself
youknowone Aug 6, 2026
2ca0b48
posix: getpgrp, getpgid and ctermid call the host
youknowone Aug 6, 2026
85d10be
check.py, extra_tests: keyword-only performance arguments and temp-di…
youknowone Aug 6, 2026
ee24580
posix: truncate's open and close, and utime's signed timestamps
youknowone Aug 6, 2026
1412e4e
posix: a descriptor of -1 does not become a BorrowedFd
youknowone Aug 6, 2026
3db70cc
posix: confstr reads the host's string table, and pathconf answers -1
youknowone Aug 6, 2026
5950473
posix: lockf, waitid and the sparse-file whence values
youknowone Aug 6, 2026
36a1a57
posix: EX_OK and the spawn entry points nt carries, and one constant …
youknowone Aug 7, 2026
ea93329
extra_tests: the four parity claims that are POSIX's, not every host's
youknowone Aug 7, 2026
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 majit/majit-metainterp/src/optimizeopt/virtualize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -974,6 +974,37 @@ impl OptVirtualize {
}
}
}
// virtualize.py:188-189: a field the trace never stored reads the
// zeroed allocation, so `fieldop is None` folds to
// `optimizer.new_const(fielddescr)` and the read is dropped.
// Without the fold the load survives to the arg-forcing pass,
// which materializes the very virtual it reads: an exception
// whose traceback slot is read before it is written
// (`pytraceback.rs:462`) escapes with its args list and the
// traceback node behind it.
//
// `w_class` and `typeptr` are excluded: both are header fields
// resolved from class identity above, and neither is ever zero on
// a live object, so folding them to null/0 would answer a read
// the allocation does not satisfy. Raw field reads are excluded
// because upstream defines this handler for GETFIELD_GC_{I,R,F}
// only.
let folds_to_zero = !is_raw_op
&& !is_typeptr
&& !field_descr.is_w_class()
&& matches!(info, PtrInfo::Virtual(_) | PtrInfo::VirtualStruct(_));
if folds_to_zero {
// optimizer.py:528-534 new_const: CONST_NULL for a pointer
// field, CONST_ZERO_FLOAT for a float field, else CONST_0.
let zero = match op.opcode {
majit_ir::OpCode::GetfieldGcR => Value::Ref(majit_ir::GcRef::NULL),
majit_ir::OpCode::GetfieldGcF => Value::Float(0.0),
_ => Value::Int(0),
};
let b = ctx.materialize_operand_at(op.pos.get());
ctx.make_constant_box(&b, zero);
return OptimizationResult::Remove;
}
}
// virtualize.py:192: self.make_nonnull(op.getarg(0))
// optimizer.py:437-448: only set NonNull if no existing PtrInfo.
Expand Down
2 changes: 1 addition & 1 deletion pyre/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -2106,7 +2106,7 @@ def run_bench(
self, name, script, timeout,
dynasm_vs_cpython=None, dynasm_vs_pypy=None,
cranelift_vs_cpython=None, cranelift_vs_pypy=None,
skip_backends=(), wasm_float_tol=False,
skip_backends=(), *, wasm_float_tol=False,
):
"""Run one benchmark on each enabled backend."""
need_cpython = False
Expand Down
2 changes: 2 additions & 0 deletions pyre/extra_tests/parity_tests/os_call_effects.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
the ones both platforms report.
"""

import atexit
import os
import shutil
import stat
Expand All @@ -20,6 +21,7 @@
WIN32 = sys.platform == "win32"

base = tempfile.mkdtemp(prefix="pyre_effects_")
atexit.register(shutil.rmtree, base, ignore_errors=True)
os.chdir(base)
# The name a temporary directory is made under can be reached by a shorter
# path than the one it is spelled with, so the directory that `getcwd`
Expand Down
192 changes: 192 additions & 0 deletions pyre/extra_tests/parity_tests/os_chflags_mknod.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
"""os.chflags, os.lchflags and os.mknod either work or are not there.

A stub that takes any argument and reports success is worse than an absent
name, because the callers probe for presence and believe the answer:
`shutil.copystat` (shutil.py:467) and `tempfile._resetperms`
(tempfile.py:276-282) both reach chflags that way, and `tarfile` reaches mknod
through `hasattr(os, "mknod")`. So each name that exists is exercised here, and
each one that does not is checked for being absent on both sides of its pair.

os.py:126 puts mknod in supports_dir_fd from HAVE_MKNODAT; os.py:182 puts
chflags in supports_follow_symlinks from HAVE_LCHFLAGS.
"""

import atexit
import os
import shutil
import stat
import sys
import tempfile


def check(cond, what):
if not cond:
raise AssertionError(what)


def raises(call, exc, message=None):
try:
call()
except exc as e:
if message is not None:
check(str(e) == message, f"expected {message!r}, got {e!r}")
return
raise AssertionError(f"{message or exc.__name__} was not raised")


start = os.getcwd()
d = tempfile.mkdtemp()
atexit.register(shutil.rmtree, d, ignore_errors=True)
p = os.path.join(d, "f")
with open(p, "wb") as f:
f.write(b"x")

if sys.platform == "win32":
for name in ("chflags", "lchflags", "mknod"):
check(not hasattr(os, name), f"windows grew an os.{name}")
print("OK")
raise SystemExit

os.chdir(d)
link = os.path.join(d, "l")
os.symlink("f", link)
sub = os.path.join(d, "sub")
os.mkdir(sub)

# A name that is present converts its path, so a float is turned away rather
# than accepted and ignored.
for name in ("chflags", "lchflags", "mknod"):
fn = getattr(os, name, None)
if fn is None:
continue
raises(
lambda fn=fn, name=name: fn(1.5, 0),
TypeError,
f"{name}: path should be string, bytes or os.PathLike, not float",
)

# ── mknod makes a node ────────────────────────────────────────────────────
if hasattr(os, "mknod"):
# Only a FIFO is unprivileged; a plain mode asks for a regular file and
# the kernel refuses that to anyone but root, which is why the default
# form is not exercised.
os.mknod("n", 0o600 | stat.S_IFIFO)
check(stat.S_ISFIFO(os.lstat(os.path.join(d, "n")).st_mode), "mknod made no fifo")
os.unlink("n")

os.mknod(path="n", mode=0o600 | stat.S_IFIFO, device=0)
check(stat.S_ISFIFO(os.lstat(os.path.join(d, "n")).st_mode), "mknod by keyword made no fifo")
os.unlink("n")

if os.mknod in os.supports_dir_fd:
dfd = os.open("sub", os.O_RDONLY)
try:
os.mknod("n", 0o600 | stat.S_IFIFO, 0, dir_fd=dfd)
check(os.path.exists(os.path.join(sub, "n")), "mknod(dir_fd) missed the descriptor")
check(not os.path.exists(os.path.join(d, "n")), "mknod(dir_fd) resolved against the cwd")
os.unlink("n", dir_fd=dfd)
finally:
os.close(dfd)

raises(
lambda: os.mknod("x", 0, 0, 0),
TypeError,
"mknod() takes at most 3 positional arguments (4 given)",
)
raises(
lambda: os.mknod(),
TypeError,
"mknod() missing required argument 'path' (pos 1)",
)
raises(
lambda: os.mknod("x", nope=1),
TypeError,
"mknod() got an unexpected keyword argument 'nope'",
)
raises(lambda: os.mknod("x", dir_fd="x"), TypeError, "argument should be integer or None, not str")

# ── the device number mknod is handed comes apart and back together ───────
# tarfile reads the pair out of st_rdev to write a header (tarfile.py:2275-2276)
# and puts one back together to recreate the node (:2735), so these have to be
# the host's own encoding rather than a plausible one.
if hasattr(os, "makedev"):
check(hasattr(os, "major") and hasattr(os, "minor"), "makedev without major/minor")
for pair in ((5, 1), (0, 0), (1, 0x1ffff), (0xff, 0)):
device = os.makedev(*pair)
check(isinstance(device, int), f"makedev{pair} is not a number: {device!r}")
check(
(os.major(device), os.minor(device)) == pair,
f"makedev{pair} did not survive major/minor: {device!r}",
)
# A node the process can make is one whose pair reads back.
if hasattr(os, "mknod"):
os.mknod("n", 0o600 | stat.S_IFIFO)
rdev = os.lstat(os.path.join(d, "n")).st_rdev
check(os.makedev(os.major(rdev), os.minor(rdev)) == rdev, "st_rdev did not round-trip")
os.unlink("n")
raises(lambda: os.major(1.5), TypeError)
raises(lambda: os.makedev(1.5, 0), TypeError)
raises(lambda: os.makedev(5), TypeError, "makedev expected 2 arguments, got 1")
else:
check(not hasattr(os, "major"), "major without makedev")

# ── chflags sets the flag it is given ─────────────────────────────────────
if hasattr(os, "chflags"):
check(hasattr(os, "lchflags"), "chflags without lchflags")
check(hasattr(os.stat(p), "st_flags"), "chflags without st_flags to read it back")

os.chflags(p, stat.UF_NODUMP)
check(os.stat(p).st_flags & stat.UF_NODUMP, "chflags set nothing")
os.chflags(p, 0)
check(not os.stat(p).st_flags & stat.UF_NODUMP, "chflags cleared nothing")

# follow_symlinks is a positional-or-keyword parameter here, not a
# keyword-only one, and it reaches the link rather than its target.
if os.chflags in os.supports_follow_symlinks:
os.chflags(link, stat.UF_NODUMP, follow_symlinks=False)
check(os.lstat(link).st_flags & stat.UF_NODUMP, "chflags(follow=False) missed the link")
check(not os.stat(p).st_flags & stat.UF_NODUMP, "chflags(follow=False) changed the target")
os.lchflags(link, 0)
check(not os.lstat(link).st_flags & stat.UF_NODUMP, "lchflags cleared nothing")
os.chflags(link, 0, False)
else:
check(not hasattr(os, "lchflags"), "lchflags without supports_follow_symlinks")

os.chflags(path=p, flags=0)
os.chflags(p, 0, True)

# Neither takes a keyword-only argument, so every argument counts against
# the one limit — an extra keyword is over it rather than unknown.
raises(
lambda: os.lchflags(link, 0, follow_symlinks=False),
TypeError,
"lchflags() takes at most 2 arguments (3 given)",
)
raises(
lambda: os.chflags(p, 0, 0, 0),
TypeError,
"chflags() takes at most 3 arguments (4 given)",
)
raises(
lambda: os.chflags(p),
TypeError,
"chflags() missing required argument 'flags' (pos 2)",
)
raises(
lambda: os.chflags(p, 0, nope=1),
TypeError,
"chflags() got an unexpected keyword argument 'nope'",
)

# A missing name reports itself.
try:
os.chflags(os.path.join(d, "nope"), 0)
except FileNotFoundError as e:
check(e.filename == os.path.join(d, "nope"), f"chflags filename: {e.filename!r}")
else:
raise AssertionError("chflags on a missing name did not raise")
else:
check(not hasattr(os, "lchflags"), "lchflags without chflags")

os.chdir(start)
print("OK")
Loading
Loading