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
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,29 @@ work.
> `test_plistlib`/`test_xml_etree*`), `test_htmlparser`,
> `test_unittest`/`test_doctest`/`test_warnings`, and
> `test_compileall`.
>
> `RFC 0057` is the **long-tail wave**: the measured whole-suite
> baseline moves from 418 to **496 of 543 `Lib/test` files passing**
> (+78 net flips, zero timeout rows, `unexpected 0`), with the
> ecosystem lane still 27/27 offline. The wave lands the
> comprehension-scope root-cause fix (the
> `test_listcomps`/`test_dictcomps`/`test_setcomps`/
> `test_named_expressions` quartet flips), exception `args` as a real
> slot, the slot-descriptor error taxonomy, `compile()` from AST with
> the `PyCF_*` flags (`test_ast` residual: 169F/80E → **1F**), frozen
> module specs + `AppleFrameworkLoader` (`test_import`/`test_types`
> now run end-to-end), a `_decimal` that passes the decTest corpus
> (`test_decimal` is a measured pass row), pickle protocol 5 with
> out-of-band `PickleBuffer` round-trips (`test_pickle`/
> `test_picklebuffer`/`test_pickletools` all pass), CPython-faithful
> pattern-match codegen + jump threading for trace-event exactness,
> and the retirement of every `timeout` row (`test_deque`/`test_mmap`/
> `test_weakref` pass under measured budgets). The re-baseline itself
> caught two engine bugs: a greedy TLS shutdown drain that could eat
> post-`close_notify` plaintext under load (intermittent `test_ssl`
> STARTTLS deadlock), and a `datetime_CAPI` stand-in shadowing the
> real capsule (segfaulting any extension doing `PyDateTime_IMPORT`,
> e.g. orjson) — both fixed and re-measured.

## Repository layout

Expand Down
16 changes: 16 additions & 0 deletions crates/weavepy-capi/src/capsule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,22 @@ pub unsafe extern "C" fn PyCapsule_Import(
unsafe { crate::object::Py_DecRef(object_ptr) };
return ptr::null_mut();
}
if !is_capsule(next) {
// The attribute resolved but to a non-capsule. RFC 0057:
// `_datetime.py` publishes a Python-level `datetime_CAPI`
// stand-in (so `types.CapsuleType` and `test_types`' module-
// scope import work without the C bridge). A downstream C
// extension doing `PyDateTime_IMPORT` must win over the
// stand-in — mint the real capsule and overwrite the module
// attribute, otherwise `PyCapsule_GetPointer` below returns
// NULL and the extension (orjson, numpy, ...) dereferences it.
if let Some(c) = try_install_well_known_capsule(&dotted, object_ptr) {
unsafe { crate::object::Py_DecRef(next) };
unsafe { crate::object::Py_DecRef(object_ptr) };
object_ptr = c;
continue;
}
}
unsafe { crate::object::Py_DecRef(object_ptr) };
object_ptr = next;
}
Expand Down
4 changes: 4 additions & 0 deletions crates/weavepy-capi/src/memoryview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,10 @@ fn clone_memoryview(other: &PyMemoryView) -> PyMemoryView {
strides: RefCell::new(other.strides.borrow().clone()),
exporter: RefCell::new(other.exporter.borrow().clone()),
zero_dim: Cell::new(other.zero_dim.get()),
hash: Cell::new(-1),
exports: Cell::new(0),
release_inner: RefCell::new(None),
restricted: Cell::new(false),
}
}

Expand Down
63 changes: 56 additions & 7 deletions crates/weavepy-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,10 +300,10 @@ PYTHONSAFEPATH : same as -P option.

const HELP_XOPTIONS: &str = "\
The following implementation-specific options are available:
-X faulthandler : enable faulthandler (no-op today).
-X faulthandler : dump the Python traceback on fatal signals.
-X dev : enable runtime checks helpful for development.
-X utf8 : enable UTF-8 mode for the interpreter.
-X tracemalloc : start tracing Python memory allocations (no-op today).
-X tracemalloc[=N] : start tracing Python memory allocations, keeping N frames.
-X importtime : show how long each import takes (no-op today).
-X showrefcount : output the total reference count (no-op today).
-X frozen_modules=on|off : whether frozen modules should be used.
Expand Down Expand Up @@ -924,6 +924,39 @@ fn build_flags(cli: &Cli, env: &EnvOverrides) -> InterpreterFlags {
"PYTHON_GIL / -X gil must be \"0\" or \"1\"",
),
}
// `-X tracemalloc[=NFRAME]` beats `PYTHONTRACEMALLOC` (CPython
// `config_init_tracemalloc`): a parse failure or negative value is a
// startup fatal error; `0` means disabled; a value beyond
// `_tracemalloc`'s MAX_NFRAME fails at init with the module's own
// ValueError text (`test_tracemalloc.TestCommandLine`).
let tracemalloc_nframe = match xoption_value(&cli.xoptions, "tracemalloc") {
Some(Some(v)) => match v.parse::<i64>() {
Ok(n) if n >= 0 => Some(n),
_ => config_fatal_error(
"config_init_tracemalloc",
"-X tracemalloc=NFRAME: invalid number of frames",
),
},
// Bare `-X tracemalloc` behaves as `-X tracemalloc=1`.
Some(None) => Some(1),
None => env.tracemalloc.as_deref().map(|v| match v.parse::<i64>() {
Ok(n) if n >= 0 => n,
_ => config_fatal_error(
"config_init_tracemalloc",
"PYTHONTRACEMALLOC: invalid number of frames",
),
}),
};
let tracemalloc = match tracemalloc_nframe {
None | Some(0) => 0u32,
Some(n) if n > 65535 => {
// CPython surfaces `_PyTraceMalloc_Start`'s ValueError during
// interpreter init.
eprintln!("ValueError: the number of frames must be in range [1; 65535]");
std::process::exit(1);
}
Some(n) => n as u32,
};
let mut xoptions = cli.xoptions.clone();
// `PYTHONDEVMODE` behaves like `-X dev` for `sys.flags.dev_mode`
// (though CPython does *not* mirror it into `sys._xoptions`; the
Expand Down Expand Up @@ -965,6 +998,11 @@ fn build_flags(cli: &Cli, env: &EnvOverrides) -> InterpreterFlags {
pycache_prefix,
int_max_str_digits,
cpu_count,
tracemalloc,
// `-X faulthandler` beats `PYTHONFAULTHANDLER` only in the sense
// that either one turns it on (CPython `config_init_faulthandler`;
// there is no "off" spelling).
faulthandler: env.faulthandler || xoption_value(&cli.xoptions, "faulthandler").is_some(),
}
}

Expand Down Expand Up @@ -993,6 +1031,12 @@ struct EnvOverrides {
cpu_count: Option<String>,
/// `PYTHON_GIL`, raw (`"0"` / `"1"`).
gil: Option<String>,
/// `PYTHONTRACEMALLOC`, raw (validated during flag composition so
/// `-X tracemalloc` precedence applies first).
tracemalloc: Option<String>,
/// `PYTHONFAULTHANDLER` — any non-empty value enables the
/// fatal-signal traceback dumper at startup.
faulthandler: bool,
warning_filters: Vec<String>,
hash_seed: Option<u32>,
/// `PYTHONIOENCODING=encoding[:errors]`, split into its halves. Either
Expand Down Expand Up @@ -1043,6 +1087,8 @@ impl EnvOverrides {
o.int_max_str_digits = nonempty("PYTHONINTMAXSTRDIGITS");
o.cpu_count = nonempty("PYTHON_CPU_COUNT");
o.gil = nonempty("PYTHON_GIL");
o.tracemalloc = nonempty("PYTHONTRACEMALLOC");
o.faulthandler = nonempty("PYTHONFAULTHANDLER").is_some();
if let Ok(w) = env::var("PYTHONWARNINGS") {
o.warning_filters = w.split(',').map(str::to_owned).collect();
}
Expand Down Expand Up @@ -1509,11 +1555,14 @@ fn exit_with_system_exit(code: weavepy::vm::object::Object) -> ! {
// `BaseException.__str__` from the args tuple directly
// (`test_cmd_line_script.test_issue20500_exit_with_exception_value`).
Object::Instance(inst) => {
let args = inst
.dict
.borrow()
.get(&weavepy::vm::object::DictKey(Object::from_static("args")))
.cloned();
// `args` is a real slot on exceptions (RFC 0057); older
// plain instances may still carry it in the dict.
let args = inst.slot_get("args").or_else(|| {
inst.dict
.borrow()
.get(&weavepy::vm::object::DictKey(Object::from_static("args")))
.cloned()
});
let text = match args {
Some(Object::Tuple(args)) => match args.len() {
0 => String::new(),
Expand Down
10 changes: 10 additions & 0 deletions crates/weavepy-compiler/src/bytecode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,14 @@ pub enum OpCode {
BuildMap,
BuildString,
ListAppend,
/// Pop an iterable and extend the list `arg` entries below TOS with
/// it (CPython `LIST_EXTEND`). A non-iterable operand raises
/// CPython's splat wording: "Value after * must be an iterable,
/// not X".
ListExtend,
/// Pop a list, push a tuple of its elements (CPython 3.13's
/// `CALL_INTRINSIC_1` / `INTRINSIC_LIST_TO_TUPLE`).
ListToTuple,
SetAdd,
MapAdd,
/// Unpack iterable at TOS into `arg` values, push them in
Expand Down Expand Up @@ -549,6 +557,8 @@ impl OpCode {
OpCode::BuildMap => "BUILD_MAP",
OpCode::BuildString => "BUILD_STRING",
OpCode::ListAppend => "LIST_APPEND",
OpCode::ListExtend => "LIST_EXTEND",
OpCode::ListToTuple => "LIST_TO_TUPLE",
OpCode::SetAdd => "SET_ADD",
OpCode::MapAdd => "MAP_ADD",
OpCode::UnpackSequence => "UNPACK_SEQUENCE",
Expand Down
Loading
Loading