mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
64330b80b913eb36f8f04bc266a1be1dd9cc07e8
969
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9a8b2f4102 |
build(deps): bump mac_address (#9157)
Bumps the unix group with 1 update in the / directory: [mac_address](https://github.com/repnop/mac_address). Updates `mac_address` from 1.2.0 to 1.2.1 - [Commits](https://github.com/repnop/mac_address/commits) --- updated-dependencies: - dependency-name: mac_address dependency-version: 1.2.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: unix ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
88f5189a93 |
Update pyo3 to 0.30 (#9152)
* Update `pyo3` to `0.30` * Fix cfunction with no module as self |
||
|
|
2e5febce5e |
Update jiff-tzdb to 0.1.9 (#9129)
jiff-tzdb 0.1.9 bundles tzdb 2026e. It is used through jiff-tzdb-platform on Windows and wasm targets. Assisted-by: Claude Code:claude-opus-5-5 |
||
|
|
0f127b07e9 |
Add Python 3.15 math APIs using pymath (#9122)
* Add Python 3.15 math APIs using pymath Pin pymath to merged dd1e9a4cf26d68caf3b549c09476f52e356e16a3 and expose isnormal, issubnormal, signbit, fmax and fmin through thin wrappers. Share the existing integer functions through math.integer and _math_integer, matching CPython module identity and metadata. Migrate test_math.py and test_math_integer.py via scripts/update_lib from CPython d220409a13b77de7a6c30abe16ea90db01528edc, preserving the existing Windows remainder marker. Add bounded Python snippet coverage. AI assistance: OpenAI Codex implemented and source-reviewed this change, ran update_lib, and performed focused automated validation. No human manual testing is claimed. Assisted-by: OpenAI Codex:model version unavailable * Track integer math tests in library updates Assisted-by: OpenAI Codex:model version unavailable |
||
|
|
796f522306 |
build(deps): bump jiff (#9120)
Bumps the jiff group with 1 update in the / directory: [jiff](https://github.com/BurntSushi/jiff). Updates `jiff` from 0.2.37 to 0.2.38 - [Release notes](https://github.com/BurntSushi/jiff/releases) - [Changelog](https://github.com/BurntSushi/jiff/blob/master/CHANGELOG.md) - [Commits](https://github.com/BurntSushi/jiff/compare/jiff-static-0.2.37...jiff-static-0.2.38) --- updated-dependencies: - dependency-name: jiff dependency-version: 0.2.38 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: jiff ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
1269277af5 |
build(deps): bump indexmap from 2.14.0 to 2.14.2 (#8993)
Bumps [indexmap](https://github.com/indexmap-rs/indexmap) from 2.14.0 to 2.14.2. - [Changelog](https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md) - [Commits](https://github.com/indexmap-rs/indexmap/compare/2.14.0...2.14.2) --- updated-dependencies: - dependency-name: indexmap dependency-version: 2.14.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
0816af6543 |
Update malachite crates to 0.14.0 (#9117)
pymath requires malachite-bigint ^0, so the example projects, which have no lock file, resolve it to 0.14.0 while the workspace pinned 0.13.0, and fail to build. Assisted-by: Claude Code:claude-opus-5-5 |
||
|
|
8b66b7c48f |
Unify PyObjectRef ownership with extensible PyRef views (#9080)
Represent PyObjectRef through PyRef<PyObject> while preserving typed ownership, payload projection, and drop behavior. Use the sealed target selector for borrowed set/frozenset union views and typed tuple elements. Keep tuple storage erased behind invariant typed slice views, with checked conversions and reference-count ownership tests. Preserve exact tuple identity for normalized nonempty full slices. Integrate the ownership contract into the public object module documentation and retain focused Rust ownership/layout tests without modifying bundled Lib sources. Spell the set/frozenset union owner directly as PyRef<PyAnySet> without a second owned alias; retain the sealed internal view registration. Assisted-by: OpenAI Codex:model identifier unavailable |
||
|
|
5f3daa5386 |
Implement Python 3.15 base64 APIs (#9085)
* Implement Python 3.15 base64 encoding and decoding Follow CPython 3.15.0 final d220409a13b77de7a6c30abe16ea90db01528edc for base64 options, alphabets, padding and canonical decoding. Keep reverse-table caches in traced module-native state and preserve module-bound callable semantics. Add bounded Python regressions without editing Lib. Assisted-by: OpenAI Codex:model identifier unavailable * Complete binascii codecs for canonical Python 3.15 tests Migrate Lib/test/test_binascii.py unchanged from CPython 3.15.0 final using scripts/update_lib and replace the superseded custom base64 snippet. Implement the base32, Base85, Ascii85 and hex ignorechars APIs required by the canonical suite. Share the existing module-bound original-key alphabet cache across base32/base64/base85 and preserve argument and buffer cleanup semantics. Keep the existing base64 kernels and VM state integration unchanged. Assisted-by: OpenAI Codex:model identifier unavailable |
||
|
|
b6c60a5939 |
build(deps): bump mac_address (#9007)
Bumps the unix group with 1 update in the / directory: [mac_address](https://github.com/repnop/mac_address). Updates `mac_address` from 1.1.8 to 1.2.0 - [Commits](https://github.com/repnop/mac_address/commits) --- updated-dependencies: - dependency-name: mac_address dependency-version: 1.2.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: unix ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
1e72b22470 |
Update malachite crates to 0.13.0 (#9003)
Assisted-by: Claude Code:claude-opus-5-5 |
||
|
|
945469f294 |
build(deps): bump dns-lookup from 4.0.1 to 4.0.2 (#9002)
Bumps [dns-lookup](https://github.com/keeperofdakeys/dns-lookup) from 4.0.1 to 4.0.2. - [Commits](https://github.com/keeperofdakeys/dns-lookup/compare/4.0.1...4.0.2) --- updated-dependencies: - dependency-name: dns-lookup dependency-version: 4.0.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
3d11c8b053 |
build(deps): bump rustls-pki-types from 1.15.0 to 1.15.1 (#9001)
Bumps [rustls-pki-types](https://github.com/rustls/pki-types) from 1.15.0 to 1.15.1. - [Release notes](https://github.com/rustls/pki-types/releases) - [Commits](https://github.com/rustls/pki-types/compare/v/1.15.0...v/1.15.1) --- updated-dependencies: - dependency-name: rustls-pki-types dependency-version: 1.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
0e67b82f52 |
build(deps): bump cc from 1.2.66 to 1.5.1 (#8994)
Bumps [cc](https://github.com/rust-lang/cc-rs) from 1.2.66 to 1.5.1. - [Release notes](https://github.com/rust-lang/cc-rs/releases) - [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md) - [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.66...cc-v1.5.1) --- updated-dependencies: - dependency-name: cc dependency-version: 1.5.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
a403341791 |
Take SyntaxError messages and ranges from the parser (#8983)
* Match CPython's SyntaxError range for unparenthesized `except` types
CPython's `invalid_except_stmt` rule raises the error only once the whole
clause has matched, and reports a range that starts at the first exception
type and ends at the `:` closing the clause, so it covers the `as NAME`
part as well:
invalid_except_stmt:
| 'except' a=expression ',' expressions 'as' NAME ':' {
RAISE_SYNTAX_ERROR_STARTING_FROM(a, "multiple exception types
must be parenthesized when using 'as'") }
The parser reports the exception types alone, so look up that `:` in the
source and widen the range to match. Only `as NAME` may follow the
exception types, which is why the first `:` after them is the one closing
the clause.
try:
pass
except A, B, C as e:
pass
CPython 3.14: ('x.py', 3, 8, 'except A, B, C as e:\n', 3, 20)
before: ('x.py', 3, 8, 'except A, B, C as e:\n', 3, 15)
Fixes #8496
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Clarify that the `except` error range stops before the closing colon
`invalid_except_stmt_end` returns the exclusive end of the range, so the
column it yields is the one the `:` sits on even though the `:` is not part
of the range. The previous "ending at the `:`" wording read as if the colon
were included, which invites an off-by-one "fix".
Comments only; no behavior change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Fix the `except ... as` error column for non-ASCII names and line joins
`invalid_except_stmt_end` recovers the end of CPython's range by scanning the
source, which has to redo by hand what CPython gets from its tokenizer:
- The column was the byte distance from the line start, but `end_offset` is a
character column. `except Ä, B as e:` reported 18 instead of 17. CPython
converts explicitly, in `_PyPegen_byte_offset_to_character_offset`.
- The colon scan already spans explicit line joins, but the `as` check ran on
the raw slice, so a `\` before `as` left the range unextended.
`except A, B \` + `as exc:` reported (3, 12) instead of (4, 7).
Add regression cases to `syntax_invalid.py` covering both, plus the whitespace,
`except*` and line-join placements that already worked. All values are verified
against CPython 3.14.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Update the Ruff fork to 0.16.10
Point the rustpython-ruff_* dependencies at the 0.16.10-rustpython tag
of the RustPython Ruff fork and port to its AST changes:
`ExprCompare` stores all operands in `operands`, call arguments use
`ThinVec`, dict comprehension generators and f-string elements are boxed
slices, `FStringValue` iterates `FStringPartRef`, and `TokenKind::Name`
is now `TokenKind::Identifier`.
Assisted-by: Grok:grok-4.6
Assisted-by: Claude Code:claude-opus-5-5
* Drop the source scan for the `except ... as` error range
The parser now reports the unparenthesized exception types error with a
range ending at the `:` that closes the clause, so
`invalid_except_stmt_end` and the message match that widened the range
are removed.
Assisted-by: Claude Code:claude-opus-5-5
* Take parse error messages from the parser as they are
The Ruff fork now emits the final message texts, so `vm_new.rs` no
longer rewrites parse error messages by matching their text and no
longer lowercases the first character of every message.
`analyze_compile_error` keeps only the caret narrowing for starred
expressions and the line number in the unterminated string message.
The compiler matches the new lowercase texts for indented block and
mixed bytes literal errors.
The fork is referenced by revision until it is published.
Assisted-by: Claude Code:claude-opus-5-5
* Take starred and bytes literal error ranges from the parser
The Ruff fork now reports misplaced starred expressions at the `*` and
non-ASCII bytes literal errors over the whole literal. `vm_new.rs` drops
`narrow_caret` together with `SyntaxErrorInfo`, which only carried the
message after that, and the compiler drops `bytes_literal_span`.
Assisted-by: Claude Code:claude-opus-5-5
* Take indentation error kinds and positions from the parser
The Ruff fork now reports `TabError` and `TooDeepIndentation` as their
own lexer errors and places indentation and line continuation errors
itself. `vm_new.rs` picks TabError or IndentationError from the error
kind instead of scanning the source for mixed indentation, drops the
TabError message override, and sets the end offset of tokenizer errors
to 0 or -1 by kind. The compiler drops the line-end relocation of
unindent errors and the line continuation rewrite. `_tokenize` raises
TabError and depth errors for the token they replace, also for sources
containing tabs.
Remove expectedFailure from test_tokenize.test_max_indent and four
test_tabnanny tests.
Assisted-by: Claude Code:claude-opus-5-5
* Take unterminated string messages from the parser
The Ruff fork's unterminated string errors now include the detected
line and are reported at the start of the string. `vm_new.rs` drops its
message override for `UnclosedStringError`, and the shell continues a
line on an unterminated triple-quoted string by the error's
`triple_quoted` flag instead of reading the quotes from the source.
Assisted-by: Claude Code:claude-opus-5-5
* Take bracket errors and tokenizer error priority from the parser
Bump the ruff fork, which now reports unmatched, mismatched, unclosed
and too deeply nested brackets and decides whether a tokenizer error
replaces the parser's first error.
Remove the bracket, unterminated string and nesting depth source
scanners and `pre_parse_source_error`. A tokenizer error from the
parser now wins over source scanner diagnostics found after it. An
unclosed bracket is incomplete input only when the parser reports it
as such, and other tokenizer errors end where they start.
Remove the expected failure markers from test_unicode_identifiers
test_invalid and the `import ä £` doctest in test_syntax.
Assisted-by: Claude Code:claude-opus-5-5
* Take number literal, string prefix and non-printable errors from the parser
Bump the ruff fork, which now reports malformed number literals,
incompatible string prefixes and non-printable characters itself.
Remove the number literal, string prefix and non-printable character
source scanners and the override classes that ranked them. Leading zero
and string prefix errors keep their range instead of ending where they
start.
Assisted-by: Claude Code:claude-opus-5-5
* Take the missing indented block error from the parser
Bump the ruff fork, which now reports `ExpectedIndentedBlock` with the
clause and the line of its header keyword.
Remove the message rewrite that derived the clause and line from the
source. The VM chooses IndentationError or incomplete input by the
error variant instead of its message.
Assisted-by: Claude Code:claude-opus-5-5
* Take binding target errors from the parser
Bump the ruff fork, which now names the invalid expression in
assignment, augmented assignment, delete, `for`, `with`, import,
pattern and `except` target errors.
Remove the source scanners for those targets and the message rewrites
of `InvalidAssignmentTarget` and `InvalidNamedAssignmentTarget`.
Remove the expected failure markers from test_syntax doctests that now
pass.
Assisted-by: Claude Code:claude-opus-5-5
* Take parameter, argument, star, comma and dictionary errors from the parser
Bump the ruff fork to 36919b62fa. Remove the source scanners for type
parameters, parameter lists, star annotations, call arguments, def type
parameters, parenthesized groups, missing commas and dictionary items.
Set end_offset 0 for ExpectedColonAfterDictionaryKey. Remove 14
EXPECTED_FAILURE markers in test_syntax and test_unpack_ex.
Assisted-by: Claude Code:claude-opus-5-5
* Take expression errors from the parser
Bump the ruff fork to 4f922d2124. Remove the source scanners for
comprehensions, collection and condition assignments, expressions
between strings, named expression targets, missing `in` after for-loop
variables and statements in `if` expressions. Remove the now passing
expected failure markers in test_syntax and test_named_expressions.
Assisted-by: Claude Code:claude-opus-5-5
* Take statement errors from the parser
Bump the ruff fork to c7f6594ed6. Remove the source scanners for
standalone except, `import ... from`, `**_` mapping rests, `elif` after
`else` and mixed except handlers, and the post-parse scanners for call
arguments, match targets, yield after a comma and parenthesized star
imports.
Assisted-by: Claude Code:claude-opus-5-5
* Take string and f-string errors from the parser
Bump the ruff fork to 6b46d0dfc8. Remove the source scanners for
malformed `\N` escapes, f-string and t-string replacement fields, mixed
t-string literals and `print`/`exec` statements, and the override
plumbing they used. Drop the `f'{x'; '` case from the compiler test,
whose expected message differs from CPython 3.14.
Assisted-by: Claude Code:claude-opus-5-5
* Restore the bracket depth check and fix incomplete unclosed strings
Restore `pre_parse_source_error`, which rejects bracket nesting past 200
levels before parsing. The parser reports the same error but still
builds the full tree, which overflows the native stack when dropped.
Remove the VM's unclosed string scanner, which ignored the mode and
treated `'abc` in exec mode as incomplete input. The compiler's check
now skips escaped newlines.
Assisted-by: Claude Code:claude-opus-5-5
* Report multiple statements before later errors in single mode
In single mode, when the first statement is a simple statement, report
code after its line as multiple statements before any error found
later, at the newline token or the comment before it. This replaces the
multiple statements check on the parsed body and also applies to
`ast.parse(mode='single')`.
An unclosed string that follows an earlier syntax error is no longer
incomplete input. The bracket depth error range is now empty.
Assisted-by: Claude Code:claude-opus-5-5
* Update the Ruff fork for parser speed and starred annotations
Bump the ruff fork to 8b59b85344, which skips the tokenizer error pass
when parsing finds no errors, stops allocating for every identifier's
string prefix check, and reports `def f(*args: *b = ...)` as invalid
syntax.
Remove expected failure markers from test_code_module
test_indentation_error and test_sysexcepthook_indentation_error, and
from test_dictcomps test_illegal_assignment.
Assisted-by: Claude Code:claude-opus-5-5
* Use the published Ruff fork 0.16.10 crates
Switch the rustpython-ruff_* dependencies from the git revision to the
0.16.10 crates.io release, published from the same commit (8b59b85344).
Assisted-by: Claude Code:claude-opus-5-5
---------
Co-authored-by: zzarbttoo <qodbwls70@naver.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
4b6ad58469 |
build(deps): bump rustix from 1.1.4 to 1.1.5 (#8990)
Bumps [rustix](https://github.com/bytecodealliance/rustix) from 1.1.4 to 1.1.5. - [Release notes](https://github.com/bytecodealliance/rustix/releases) - [Changelog](https://github.com/bytecodealliance/rustix/blob/main/CHANGES.md) - [Commits](https://github.com/bytecodealliance/rustix/compare/v1.1.4...v1.1.5) --- updated-dependencies: - dependency-name: rustix dependency-version: 1.1.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
1fbb58bf01 |
build(deps): bump rustls from 0.23.43 to 0.23.45 (#8985)
Bumps [rustls](https://github.com/rustls/rustls) from 0.23.43 to 0.23.45. - [Release notes](https://github.com/rustls/rustls/releases) - [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md) - [Commits](https://github.com/rustls/rustls/compare/v/0.23.43...v/0.23.45) --- updated-dependencies: - dependency-name: rustls dependency-version: 0.23.45 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
f2f8f95a80 |
build(deps): bump bitflags from 2.13.1 to 2.13.2 (#8979)
Bumps [bitflags](https://github.com/bitflags/bitflags) from 2.13.1 to 2.13.2. - [Release notes](https://github.com/bitflags/bitflags/releases) - [Changelog](https://github.com/bitflags/bitflags/blob/main/CHANGELOG.md) - [Commits](https://github.com/bitflags/bitflags/compare/2.13.1...2.13.2) --- updated-dependencies: - dependency-name: bitflags dependency-version: 2.13.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
3b881b6a08 |
build(deps): bump which from 8.0.5 to 8.0.6 (#8978)
Bumps [which](https://github.com/harryfei/which-rs) from 8.0.5 to 8.0.6. - [Release notes](https://github.com/harryfei/which-rs/releases) - [Changelog](https://github.com/harryfei/which-rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/harryfei/which-rs/compare/8.0.5...8.0.6) --- updated-dependencies: - dependency-name: which dependency-version: 8.0.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
64dafe0d56 |
build(deps): bump thin-vec from 0.2.19 to 0.2.20 (#8980)
Bumps [thin-vec](https://github.com/mozilla/thin-vec) from 0.2.19 to 0.2.20. - [Changelog](https://github.com/mozilla/thin-vec/blob/main/RELEASES.md) - [Commits](https://github.com/mozilla/thin-vec/compare/v0.2.19...v0.2.20) --- updated-dependencies: - dependency-name: thin-vec dependency-version: 0.2.20 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
052549a550 |
build(deps): bump bstr from 1.13.0 to 1.13.1 (#8973)
Bumps [bstr](https://github.com/BurntSushi/bstr) from 1.13.0 to 1.13.1. - [Commits](https://github.com/BurntSushi/bstr/compare/1.13.0...1.13.1) --- updated-dependencies: - dependency-name: bstr dependency-version: 1.13.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
54e119d3c1 |
build(deps): bump glob from 0.3.3 to 0.3.4 (#8972)
Bumps [glob](https://github.com/rust-lang/glob) from 0.3.3 to 0.3.4. - [Release notes](https://github.com/rust-lang/glob/releases) - [Changelog](https://github.com/rust-lang/glob/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-lang/glob/compare/v0.3.3...v0.3.4) --- updated-dependencies: - dependency-name: glob dependency-version: 0.3.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
55f7abb2f4 |
build(deps): bump siphasher from 1.0.3 to 1.0.4 (#8974)
Bumps [siphasher](https://github.com/jedisct1/rust-siphash) from 1.0.3 to 1.0.4. - [Commits](https://github.com/jedisct1/rust-siphash/compare/1.0.3...1.0.4) --- updated-dependencies: - dependency-name: siphasher dependency-version: 1.0.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
d88c344847 |
build(deps): bump rand (#8886)
Bumps the random group with 1 update in the / directory: [rand](https://github.com/rust-random/rand). Updates `rand` from 0.10.2 to 0.10.3 - [Release notes](https://github.com/rust-random/rand/releases) - [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-random/rand/compare/0.10.2...0.10.3) --- updated-dependencies: - dependency-name: rand dependency-version: 0.10.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: random ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
1f83b1593f |
Fix CI failures and bump version to 0.6.1 (#8940)
* Use AtomicPyTypeFlags in PyType_FromSlots PyType_FromSlots still imported the old PyAtomicTypeFlags name. Assisted-by: Grok:4.7 * Pass CPython test args as a bash array Assisted-by: Grok:4.7 * Bump workspace version to 0.6.1 Assisted-by: Grok:4.7 * Restore the CPython test step in ci.yaml Assisted-by: Grok:4.7 |
||
|
|
e1dba29fb2 |
Revert "Drop parking_lot_core git patch and tkinter deps"
Tag 0.6.0 keeps that removal. This restores the parking_lot_core git patch and the tkinter dependencies on main. Assisted-by: Grok:4.7 |
||
|
|
b3c84c0fc7 |
Add first draft of PyType_FromSlots (#8918)
|
||
|
|
b2626c56de |
Drop parking_lot_core git patch and tkinter deps
Comment out the parking_lot_core git patch and use the crates.io crate. Remove tcl-sys, tk-sys, and the tkinter feature. Assisted-by: Grok:4.7 |
||
|
|
1d7d704414 |
Use bitflagset for type flags (#8873)
* Use bitflagset for type flags PyTypeFlags is defined with bitflagset! and the atomic tp_flags word with atomic_bitflagset!, replacing the hand-written PyAtomicTypeFlags. Assisted-by: Grok:4.7 Assisted-by: Claude:claude-opus-5-5 * Use bitflagset 0.0.4 from its git main branch bitflagset 0.0.4 is not on crates.io yet. Patch it to the git main branch in the workspace and in the example projects that depend on rustpython-vm, since each of them is a separate workspace. Assisted-by: Claude:claude-opus-5-5 * Depend on crates.io bitflagset 0.0.4 bitflagset 0.0.4 is published. Drop the git patches from the workspace and the separate example-project workspaces. Assisted-by: Grok:4.7 * Keep empty patch tables in example workspaces The example workspaces already have an empty [patch.crates-io] table. Dropping the bitflagset git line leaves that table in place. Assisted-by: Grok:4.7 * Call bitflagset methods directly for type flags Drop the plain-set has_feature wrapper and the intersects, is_created_with_flags, DEFAULT, and heap_type_flags aliases. Single-bit updates use insert, and flag combinations use from_slice. Assisted-by: Grok:4.7 * OR paired type flags in one update HAS_WEAKREF with MANAGED_WEAKREF, and HAS_DICT with MANAGED_DICT (and INLINE_VALUES when present), are applied with one |=. Assisted-by: Grok:4.7 * Store heap type flag sets as constants HEAPTYPE | BASETYPE, and the dict and immutable variants built from it, are const values instead of from_slice calls at each use. Assisted-by: Grok:4.7 * Test SEQUENCE and MAPPING in one load PyTypeFlags::COLLECTION is the two-bit mask. Callers check it with one is_disjoint instead of two contains loads. Assisted-by: Grok:4.7 * Fix clippy 1.99 lints in the vm Pass closure arguments by value where clippy reports a needless borrow, drop the unfulfilled redundant_else expectation on sum, and return the HRESULT after todo!(). Assisted-by: Grok:4.7 |
||
|
|
b190a3ba29 |
build(deps): bump libz-rs-sys from 0.6.7 to 0.6.8 (#8892)
Bumps [libz-rs-sys](https://github.com/trifectatechfoundation/zlib-rs) from 0.6.7 to 0.6.8. - [Release notes](https://github.com/trifectatechfoundation/zlib-rs/releases) - [Changelog](https://github.com/trifectatechfoundation/zlib-rs/blob/main/docs/release.md) - [Commits](https://github.com/trifectatechfoundation/zlib-rs/compare/v0.6.7...v0.6.8) --- updated-dependencies: - dependency-name: libz-rs-sys dependency-version: 0.6.8 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
744dd9c487 |
build(deps): bump the wasmtime group across 1 directory with 2 updates (#8891)
Bumps the wasmtime group with 2 updates in the / directory: [cranelift-jit](https://github.com/bytecodealliance/wasmtime) and [cranelift-module](https://github.com/bytecodealliance/wasmtime). Updates `cranelift-jit` from 0.135.2 to 0.135.3 - [Release notes](https://github.com/bytecodealliance/wasmtime/releases) - [Changelog](https://github.com/bytecodealliance/wasmtime/blob/main/RELEASES.md) - [Commits](https://github.com/bytecodealliance/wasmtime/commits) Updates `cranelift-module` from 0.135.2 to 0.135.3 - [Release notes](https://github.com/bytecodealliance/wasmtime/releases) - [Changelog](https://github.com/bytecodealliance/wasmtime/blob/main/RELEASES.md) - [Commits](https://github.com/bytecodealliance/wasmtime/commits) --- updated-dependencies: - dependency-name: cranelift-jit dependency-version: 0.135.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: wasmtime - dependency-name: cranelift-module dependency-version: 0.135.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: wasmtime ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
b499e70e12 |
build(deps): bump thiserror in the thiserror group across 1 directory (#8888)
Bumps the thiserror group with 1 update in the / directory: [thiserror](https://github.com/dtolnay/thiserror). Updates `thiserror` from 2.0.20 to 2.0.21 - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/2.0.20...2.0.21) --- updated-dependencies: - dependency-name: thiserror dependency-version: 2.0.21 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: thiserror ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
65647e99ee |
fix(csv): quote non-numeric objects in writer QUOTE_NONNUMERIC mode (#8843)
* fix(csv): quote non-numeric objects in writer QUOTE_NONNUMERIC mode * style: format extra_tests/snippets/stdlib_csv.py Assisted-by: Antigravity:Gemini-3.8-Flash |
||
|
|
be23b2c562 |
Store database docstrings xz-compressed behind an optional doc feature (#8836)
* Skip Python-defined classes when collecting builtin type docs generate.py recorded every object.__subclasses__() entry as builtins.<name> after importing the whole stdlib, so classes written in Python (tkinter, unittest, tarfile, ...) ended up in the doc database. Classes that are mutable heap types without C method descriptors and whose module is not a C extension are now skipped, and their entries are removed from data.inc.rs. Assisted-by: Claude:claude-opus-5-5 Assisted-by: Grok:grok-4.7 * Store database docstrings xz-compressed behind an optional doc feature - rustpython-doc's build script compresses all database docstrings into one xz blob with an offset index; identical texts share one offset. - Macros emit blob references for database docs; vm decompresses the blob on first access. - New `doc` cargo feature (default on) in rustpython, rustpython-vm and rustpython-stdlib. Without it the blob and xz are not linked and database-backed __doc__ is None; __text_signature__ is unaffected. - Native types without a docstring get __doc__ = None in their dict. - Workflows that build with --no-default-features enable `doc` where tests or releases need docstrings. Assisted-by: Claude:claude-opus-5-5 Assisted-by: Grok:grok-4.7 |
||
|
|
00dfed8ff9 |
Resolve class attribute docs at macro time and drop rustpython-doc from vm (#8830)
* Resolve class attribute docs at macro time and drop rustpython-doc from vm - #[pyclass] and #[pystruct_sequence] emit a sorted PyClassDef::ATTR_DOCS table from the doc database; attribute and slot docs look up that table. - rustpython-vm no longer depends on rustpython-doc. - _operator.attrgetter, itemgetter, methodcaller and object get constructor Args with FromArgs::PARAMS, replacing the hardcoded class signature docs in class.rs. - methodcaller() with no arguments raises "methodcaller needs at least one argument, the method name". Assisted-by: Claude:claude-opus-5-5 Assisted-by: Grok:grok-4.7 * Drop default values that equal the field type's default Bare `default` on integer and bool fields already renders the zero value in the text signature, so `default = 0` and `default = false` are replaced with bare `default`. Assisted-by: Claude:claude-opus-5-5 Assisted-by: Grok:grok-4.7 * Return the empty str singleton from Context::new_str Assisted-by: Claude:claude-opus-5-5 |
||
|
|
3c654204f5 |
Match native text signatures to CPython names, kinds and defaults (#8815)
* Render CPython defaults in generated text signatures Arguments whose default rendered as `<unrepresentable>`, while the same callable has a representable default in CPython, now render that default: - Where the Rust value can be a literal, the argument becomes a plain field with `default = <literal>`. - The remaining cases keep their Rust default and add `py_default` with a comment. These are wrappers such as ArgIntoFloat or ArgIntoBool, values that need the VM, enums, and Option where None means another value. Byte string literals render as Python bytes. A function registered under several names gets an internal doc per name, so its __doc__ no longer carries the signature header. Removed expectedFailure from test_pydoc tests that now pass. Assisted-by: Grok:grok-4.7 Assisted-by: Claude:claude-opus-5-5 * Match native callable signatures to CPython names and kinds Rename parameters and adjust positional-only / keyword-only kinds and defaults of native functions and methods so their generated text signatures match CPython. Remove expectedFailure markers from tests that now pass. Assisted-by: Claude:claude-opus-5-5 Assisted-by: Grok:grok-4.7 * Fix Windows console read size and keep type.__prepare__ permissive Assisted-by: Claude:claude-opus-5-5 * Derive signature defaults from typed Rust values Param.default is now a DefaultRepr rendered by the const writer. Literals, integer and bool constants become typed defaults, and other default expressions use the type's const fn py_default. A bare `optional` renders as None, so `default = None` rewrites are reverted. Address review comments: zlib max_length defaults, get_ca_certs binary_form keyword, getpeercert der, WASM ssl certificate keywords, fstatvfs fd-only, and _sre endpos shown as sys.maxsize. Assisted-by: Claude:claude-opus-5-5 Assisted-by: Grok:grok-4.7 * Generate class constructor text signatures at compile time A #[pyclass] impl composes the type's internal doc from the Args of its Initializer or Constructor, so type.__text_signature__ and inspect.signature work for native classes. __doc__ stays the plain doc. Struct sequences get (iterable=(), /) and __reduce__ (self, /). Hand-parsed constructors are converted to FromArgs structs, and _weakref.proxy is split from ProxyType. Assisted-by: Claude:claude-opus-5-5 Assisted-by: Grok:grok-4.7 * Remove expectedFailure from test_hmac tests that now pass Assisted-by: Claude:claude-opus-5-5 * Match remaining native signatures and count missing CPython signatures as matches Give lock.__enter__ and RLock.__enter__ their own no-argument methods, add FromArgs for start_new_thread and start_joinable_thread keeping their errors, take RTLD_GLOBAL/RTLD_LOCAL from libc, and align names, kinds and <unrepresentable> defaults where CPython's signature is invalid (bytes.hex, reduce, warn, link, setlocale, add_done_callback, ...). getaddrinfo accepts keyword arguments. whats_left treats a callable CPython has no signature for as matching. Assisted-by: Claude:claude-opus-5-5 Assisted-by: Grok:grok-4.7 * Drop unfulfilled dead_code expectation on os.link dir_fd args Assisted-by: Claude:claude-opus-5-5 * Distinguish OptionalArg and Option in signature defaults An `optional` field's signature default now comes from its type: Option<T> renders None, while OptionalArg<T> (a missing argument that is not any Python value) renders <unrepresentable>. Fields where a missing argument behaves exactly like None become Option<T>; fields whose missing state differs keep OptionalArg. Redundant py_default = "<unrepresentable>" entries are removed. Assisted-by: Claude:claude-opus-5-5 Assisted-by: Grok:grok-4.7 * Remove expectedFailure from test_rlcompleter.test_attr_matches Assisted-by: Claude:claude-opus-5-5 * Use Option for optional arguments whose missing and None states match OptionalOption and OptionalArg<Option<T>> fields that treat a missing argument like None become Option<T> with #[pyarg(optional)], which renders =None in the text signature. Plain optional parameters of that kind move into small FromArgs structs. Uses where missing and None differ (reduce initial, StringIO newline, TextIOWrapper.reconfigure, set_asyncgen_hooks) keep OptionalOption. Assisted-by: Claude:claude-opus-5-5 Assisted-by: Grok:grok-4.7 * Render ::NAME defaults by name and restore named constant defaults - default = ::NAME writes NAME into the text signature and stores Into::into(NAME) as the Rust value. - A one-field tuple-struct argument pattern such as Fildes(fd) names the parameter after its binding, so fcntl and termios keep destructuring. - zlib, lzma, syslog, mmap and select defaults use their named constants again instead of literals or <unrepresentable>. Assisted-by: Claude:claude-opus-5-5 Assisted-by: Grok:grok-4.7 * Name FromArgs fields after their Python parameters Drop #[pyarg(name = ...)] where the Python name is a valid Rust field name and rename the field instead. The option stays only for keywords (fn, loop, final, type, self) and the non-snake_case memLevel. Assisted-by: Claude:claude-opus-5-5 Assisted-by: Grok:grok-4.7 * Build literal defaults from Python values and type <unrepresentable> - default = LIT on a non-primitive field converts the literal through ToPyObject and TryFromObject when the argument is missing, so object fields no longer need a vm expression plus a matching py_default. - A bare default on a primitive integer, bool or float field renders its zero value. - DefaultRepr::Unrepresentable replaces the <unrepresentable> text, and py_default = "<unrepresentable>" is a compile error. - pyexpat.ParserCreate distinguishes a missing intern from intern=None, mmap AccessMode renders its value, and sqlite blobopen names rowid. Assisted-by: Claude:claude-opus-5-5 Assisted-by: Grok:grok-4.7 * Resolve native docstrings from the doc database at compile time - rustpython-doc stores its table as a sorted const slice with const fn lookups (get, get_attr, class_attr_doc) instead of a phf map, and the update-doc-db workflow emits that form. - Class, method, getset, module function and struct sequence docs, and per-class slot wrapper docs, are chosen by const lookups in the derive output. The database text wins over Rust doc comments. - Macros reach the table through rustpython_vm::__exports, so only the vm depends on rustpython-doc. - Remove expectedFailure markers from doctest and pydoc tests that pass. Assisted-by: Claude:claude-opus-5-5 Assisted-by: Grok:grok-4.7 * Keep time.gmtime, localtime and ctime without a text signature Their [seconds] parameter has no text signature, and unittest.mock relies on inspect.signature failing for them. Take OptionalOption again so the default stays unrepresentable, and treat None like a missing argument. Assisted-by: Claude:claude-opus-5-5 |
||
|
|
c8f1ab7fbc |
ucd: Parse Unihan data for 3.2.0; unmask numeric test (#8783)
* ucd: Parse Unihan data for 3.2.0; unmask test Python's 3.2.0 path uses the Unihan data for supplementary numeric values. The Unihan data isn't bundled with the "full" data which means we have to parse it ourselves. The full data is large, so I compressed it with zlib. AI disclosure: I asked an LLM to elaborate on why the full Unicode 3.2.0 data precludes \u5793. It explained that the code point is part of Unihan. I wrote the parser myself. I linted the zlib snippet with AI. Assisted-by: GPT-5.6: Luna * Use spare_capacity_mut + set_len over transmute |
||
|
|
afad92bf1b |
Add OrderedDict, marshal, inspect, and chflags bitflagset (#8780)
* Implement _collections.OrderedDict Extracted from #6799: native OrderedDict as a dict subclass, iterator mutation tracking, and mapping-view set operations. * Add OrderedDict, warnings source lines, and socket sendmsg Implement _collections.OrderedDict as a dict subclass with a linked list of insertion-order nodes. Pack struct F/D as native float/double complex pairs, and report unexpected __replace__ field names as a list repr. Register encodings.latin_1 at startup. Reject NUL in input() prompts. Copy non-exact type dicts in mapping order. Fetch warning source lines through _bless_my_loader and unicode splitlines. Keep the last warnings.filters list after deletion. Snapshot sendmsg sequences before converting buffers, parse ancillary ints with __index__, and dispatch AF_CAN by protocol. Store sys.trace and sys.profile on each ThreadSlot so _settraceallthreads/_setprofileallthreads are visible to sys.gettrace/getprofile on other threads. Use <frozen importlib._bootstrap> as frozen importlib co_filename. Keep HEAPTYPE socket objects GC-tracked. Assisted-by: Grok:grok-4.6 * Load marshal via readinto and dump sets in dumps order marshal.load fills a scratch buffer with readinto on a contiguous writable memoryview, and rejects a returned length other than the requested size. set and frozenset dumps write elements in sorted(v, key=marshal.dumps) order. Assisted-by: Grok:grok-4.6 * Add classmethod_descriptor and ABC IS_ABSTRACT flags Give METH_CLASS methods a distinct classmethod_descriptor type. Store Py_TPFLAGS_IS_ABSTRACT from __abstractmethods__ and look up that attribute only in the type's own dict. Expose defaultdict.default_factory as a member descriptor. Inherit bool's unused number slots from int. Mark set and frozenset __contains__ as METH_COEXIST. Parse classmethod, staticmethod, object.__new__, and heap type text signatures. Register interactive source in linecache. Assisted-by: Grok:grok-4.6 * address review: odict reuse, descr call, new wrapper Reuse odict node slots and walk the live list in next_after. Require both | operands to be dicts. Drop the odict iterator on GC clear. Bind classmethod descriptors through descr_get and update IS_ABSTRACT under the type lock. Give odict keys and items sq_contains. Walk tp_new_wrapper past new_wrapper so HEAPTYPE socket subclasses can call socket.__new__. Split FreeBSD Bluetooth protocol constants from Linux values. Assisted-by: Grok:grok-4.6 * Represent chflags bits with bitflagset Assisted-by: Grok:grok-4.6 * Represent access and mode bits with bitflagset Assisted-by: Grok:grok-4.6 * Continue unfinished REPL suites as incomplete input Assisted-by: Grok:grok-4.6 * Add pickle and os.stat inspect signatures Give _pickle.Pickler the clinic constructor text signature and accept fix_imports and buffer_callback as positional-or-keyword. Allow #[pyfunction(text_signature)] and use it so os.stat reports (path, *, dir_fd=None, follow_symlinks=True). Uncomment import _pickle and drop the pickle inspect expectedFailures. Assisted-by: Grok:grok-4.6 * Add Unpickler, dump/load, and lstat signatures Give _pickle Unpickler, dump, dumps, load, and loads clinic text signatures, and os.lstat (path, *, dir_fd=None). Assisted-by: Grok:grok-4.6 * Complete OrderedDict with insertion-order links Move the native OrderedDict into ordered_dict.rs and track order with a linked list of nodes, keeping mapping-view set operations. Assisted-by: Grok:grok-4.6 * address CI: stat casts, HCI_DATA_DIR, passing xfails Compare stat permission bits as Mode so Linux clippy does not flag a u32-to-u32 cast. Export HCI_DATA_DIR with the Bluetooth socket option values. Drop expectedFailure from tests that now pass. Assisted-by: Grok:grok-4.6 * Fix wasm teardown deadlock and bluetooth constants Assisted-by: Grok:grok-4.6 * Clear process-wide eval breaker bits in STW test Assisted-by: Grok:grok-4.6 * Drop exclusive-held panic that tripped wasm clippy Assisted-by: Grok:grok-4.6 * Fix OrderedDict contains and export Windows BDADDR Assisted-by: Grok:grok-4.6 * Add cmp_to_key and hash-once LRU cache _functools.cmp_to_key now returns KeyWrapper. LRU lookups store the key hash on _lru_list_elem so __hash__ is not called again on hits or eviction. Assisted-by: Grok:grok-4.6 * Allow subclass keywords on list, tuple, and float list.__init__ rejects keywords only when tp_new is still list's. tuple/float.__new__ reject them only when tp_init is still the base's. Assisted-by: Grok:grok-4.6 * Fix CI lint, test re-export, and structseq members Drop the duplicate clear_eval_breaker_for_test re-export. Format structseq imports. Expose struct sequence fields as readonly members so assignment raises "readonly attribute". Assisted-by: Grok:grok-4.6 * Allow subclass keywords on itertools and filter Positional-only constructors ignore extra keywords when a subclass has replaced tp_init, matching list/tuple/float. Assisted-by: Grok:grok-4.6 * Remove text_signature derive parameter Drop the string override on #[pyfunction] and #[pyclass]. Signatures are generated from the Rust parameter list only. Restore inspect expectedFailures that depended on the override. Assisted-by: Grok:grok-4.6 * Rename set contains_method to __contains__ Keep a borrowed contains helper for slots and C-API. The coexist pymethod is named __contains__. Assisted-by: Grok:grok-4.6 * Use MonitoringEvent instead of EVENT_* aliases Drop the unused MonitoringEvents bitflags and the EVENT_PY_START style mask constants. Call sites use the enum and .mask(). Assisted-by: Grok:grok-4.6 * Store warnings filters as PyListRef The list already serializes its elements. Drop the extra mutex around the cached list pointer. Assisted-by: Grok:grok-4.6 * Re-export clear_eval_breaker_for_test after rebase Keep the STW test helper visible without bringing back stop_bit_set. Assisted-by: Grok:grok-4.6 * Fix groupby reentrancy and islice arguments Give _grouper its own tgtkey and take currvalue after the key comparison. Accept __index__ on islice bounds, reject step 0, and drain the source up to stop. Assisted-by: Grok:grok-4.6 * SETREF warnings filters, onceregistry, and defaultaction Keep the last list, dict, or str after the module attributes are deleted by swapping the interpreter cache pointer. Do not store a None fallback for _warnings_context. Assisted-by: Grok:grok-4.6 * Iterate OrderedDict inner dict via next_entry &PyDict is not an iterator after the borrowed-ref signature change. Assisted-by: Grok:grok-4.6 * Remove expectedFailure from testBluetoothConstants Linux now exports BTPROTO_HCI and the rest of the set. Assisted-by: Grok:grok-4.6 * Borrow get_filter arguments as PyObject Match the warn_explicit call site that passes borrowed handles after the internal-helper borrow change. Assisted-by: Grok:grok-4.6 * Fix bool slots and SemHandle safety docs Leave unused bool number slots unset so inspect reports __add__ on int. Document SemHandle::from_raw safety. Assisted-by: Grok:grok-4.6 * Keep sendmsg errors and drop duplicate linecache Replace sendmsg iterable TypeError only. Register REPL source in linecache once per compiled input. Assisted-by: Grok:grok-4.6 * Nest REPL parse or-patterns for clippy Assisted-by: Grok:grok-4.6 * Mark pydoc os.stat signature test expectedFailure Assisted-by: Grok:grok-4.6 --------- Co-authored-by: Lee Dogeon <dev.moreal@gmail.com> |
||
|
|
854b5af434 |
build(deps): bump jiff in the jiff group across 1 directory (#8768)
Bumps the jiff group with 1 update in the / directory: [jiff](https://github.com/BurntSushi/jiff). Updates `jiff` from 0.2.35 to 0.2.37 - [Release notes](https://github.com/BurntSushi/jiff/releases) - [Changelog](https://github.com/BurntSushi/jiff/blob/jiff-static-0.2.37/CHANGELOG.md) - [Commits](https://github.com/BurntSushi/jiff/compare/jiff-static-0.2.35...jiff-static-0.2.37) --- updated-dependencies: - dependency-name: jiff dependency-version: 0.2.37 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: jiff ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
28ba022056 |
build(deps): bump crossbeam-utils from 0.8.22 to 0.8.23 (#8770)
Bumps [crossbeam-utils](https://github.com/crossbeam-rs/crossbeam) from 0.8.22 to 0.8.23. - [Release notes](https://github.com/crossbeam-rs/crossbeam/releases) - [Changelog](https://github.com/crossbeam-rs/crossbeam/blob/main/CHANGELOG.md) - [Commits](https://github.com/crossbeam-rs/crossbeam/compare/crossbeam-utils-0.8.22...crossbeam-utils-0.8.23) --- updated-dependencies: - dependency-name: crossbeam-utils dependency-version: 0.8.23 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
94298caaf2 |
build(deps): bump zlib-rs from 0.6.7 to 0.6.8 (#8772)
Bumps [zlib-rs](https://github.com/trifectatechfoundation/zlib-rs) from 0.6.7 to 0.6.8. - [Release notes](https://github.com/trifectatechfoundation/zlib-rs/releases) - [Changelog](https://github.com/trifectatechfoundation/zlib-rs/blob/main/docs/release.md) - [Commits](https://github.com/trifectatechfoundation/zlib-rs/compare/v0.6.7...v0.6.8) --- updated-dependencies: - dependency-name: zlib-rs dependency-version: 0.6.8 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
2116a6d38a |
build(deps): bump cranelift in the wasmtime group across 1 directory (#8769)
Bumps the wasmtime group with 1 update in the / directory: [cranelift](https://github.com/bytecodealliance/wasmtime). Updates `cranelift` from 0.135.1 to 0.135.2 - [Release notes](https://github.com/bytecodealliance/wasmtime/releases) - [Changelog](https://github.com/bytecodealliance/wasmtime/blob/main/RELEASES.md) - [Commits](https://github.com/bytecodealliance/wasmtime/commits) --- updated-dependencies: - dependency-name: cranelift dependency-version: 0.135.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: wasmtime ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
7b018871a3 |
host_env: export remaining stdlib constants (#8760)
* host_env: export winsound, spawn, and remaining SSL_OP flags Assisted-by: Claude * stdlib: take winsound, nt spawn, and SSL_OP names from host_env Assisted-by: Claude * host_env: export ssl alerts, sysexits, socket extras Assisted-by: Claude * stdlib: take remaining host_env and common constants Assisted-by: Claude * Remove unused workspace exitcode dependency Assisted-by: Claude * host_env: export PROTO, access, reparse, wait flags Assisted-by: Claude * stdlib: take PROTO, access, reparse, wait from host_env Assisted-by: Claude |
||
|
|
e0df2a10f0 |
Update malachite to 0.12 (#8755)
malachite-bigint 0.12.0 was published while the workspace pinned 0.11.0. pymath requires malachite-bigint = "0", so a fresh resolve picks 0.12 for it and leaves the workspace on 0.11, putting two incompatible BigInt types in one graph. example_projects carry no lockfile and resolve fresh. malachite-q and malachite-base move with it. No source change is needed. Assisted-by: Grok:grok-4.6 |
||
|
|
d02d81e740 |
perf: VM and runtime hot paths from pyperformance (#8737)
* time: use CLOCK_UPTIME_RAW for monotonic/perf_counter on Apple
CLOCK_MONOTONIC keeps advancing during system sleep on macOS, unlike
CPython's mach_absolute_time()-backed perf_counter/monotonic. Benchmarks
that spanned a laptop sleep recorded thousands of seconds for a single
iteration (pyperformance float/fannkuch showed ~1000x slowdowns).
Use CLOCK_UPTIME_RAW (equivalent to mach_absolute_time) on Apple targets
and report it as such in time.get_clock_info().
Assisted-by: Claude Code:claude-fable-5-1
* vm: derive f_lineno lazily instead of tracking prev_line per instruction
The dispatch loop wrote prev_line (a locations lookup plus a Cell store)
on every instruction only so f_lineno could read it. f_lineno now derives
the line from lasti and code.locations, prev_line is only maintained on
the cold tracing path for 'line' event de-duplication, the two use_tracing
reads per instruction are merged into one, and the pre-dispatch lasti
reload is replaced by the value just stored.
When a trace function is installed on an already-running frame (bdb
stepping back into an untraced caller, frame.f_trace assignment, a local
trace returned from the 'call' event on a resumed generator) prev_line is
synced from lasti so no spurious 'line' event fires.
Assisted-by: Claude Code:claude-fable-5-1
* vm: coalesce per-call thread-locals into one FRAME_SLOT_CACHE
set_current_frame ran on every frame enter/exit and touched three separate
thread_local! statics (each a _tlv_get_addr call on macOS). Merge them into
a single thread-local struct so the hot path does one TLS access.
Assisted-by: Claude Code:claude-fable-5-1
* vm: fold stop/finalizing checks into EVAL_BREAKER
eval_breaker_tripped ran three checks per bytecode: the finalizing flag,
a thread-local stop_requested lookup, and the EVAL_BREAKER load. Add
STOP_BIT and FINALIZING_BIT so the per-instruction check is a single
relaxed atomic load; the slow path (check_signals) still re-derives the
exact per-thread answer. The bits are set alongside the per-thread flags
and cleared under the stop-the-world exclusion.
Assisted-by: Claude Code:claude-fable-5-1
* gc: set the TRACKED bit with a plain store for freshly allocated objects
gc_bits is zero for a fresh allocation (both the freelist reuse path and
PyInner::new zero it), so track_new_object can initialise the bit with a
relaxed store instead of a fetch_or. Re-tracking resurrected objects keeps
the read-modify-write variant.
Assisted-by: Claude Code:claude-fable-5-1
* vm: skip thread exception publish when pushing/popping a None exc slot
Generator/coroutine resume pushes and pops an exc_info slot on every
send. When that slot is None the value returned by topmost_exception
cannot change (it skips None entries), so the thread-local exception
publish in push_exception/pop_exception is unnecessary.
Assisted-by: Claude Code:claude-fable-5-1
* vm: allocation-free keyword-call fast path and super() MRO walk
Keyword calls to plain functions where every parameter is filled exactly
once are reordered in place into the fast-locals layout (no IndexMap, no
keyword name clones); any mismatch falls back to the existing slow path
so error messages are unchanged. super().attr now walks the type's MRO
by reference instead of cloning every class into two Vecs per lookup.
Assisted-by: Claude Code:claude-fable-5-1
* builtins: cheaper enumerate and zip iteration
enumerate kept its counter as a BigInt behind a write lock and did bignum
arithmetic on every item; use a machine integer that promotes to BigInt
only for huge/negative starts or on overflow. zip pre-sizes its per-item
Vec so the common two-iterable case allocates once.
Assisted-by: Claude Code:claude-fable-5-1
* _asyncio: cache module objects and use native set/dict operations
Task registration re-resolved _asyncio module attributes and re-imported
contextvars on every task; the eager path additionally went through generic
call_method for set add/discard. Cache the module objects per thread
(validated by identity), split the contextvars cache from the task
containers, and use PySet/PyDict methods directly.
Assisted-by: Claude Code:claude-fable-5-1
* perf: use mimalloc as global allocator for rustpython CLI
Adds mimalloc (0.1.52, default-features = false) as the #[global_allocator]
for the rustpython binary, gated behind a new `mimalloc` feature enabled
by default on non-wasm targets (dep declared under
target.'cfg(not(target_arch = "wasm32"))'.dependencies, so wasm builds
never pull it in regardless of default-features).
Profiling a `t = (a, b, c)` loop showed malloc/free at ~40% of samples on
macOS with the system allocator. Switching to mimalloc gives consistent
wins across small-object-heavy workloads, medians over 5-9 interleaved
A/B rounds (release build, this machine):
tuple (5M `(a,b,c)`) 1.020s -> 0.863s (1.18x)
4-thread tuple (5M total) 1.643s -> 1.382s (1.19x)
class instances (2M) 0.874s -> 0.797s (1.10x)
dict-heavy (200k x 20) 2.196s -> 1.992s (1.10x)
string-heavy (join x 20) 0.662s -> 0.592s (1.12x)
nqueens (pyperformance) 0.327s -> 0.286s (1.14x)
chaos (pyperformance) 0.204s -> 0.191s (1.06x)
richards (pyperformance) 0.154s -> 0.151s (1.02x)
go (pyperformance) 0.411s -> 0.405s (1.01x)
listcomp / fib ~neutral (<=1%)
Peak RSS (/usr/bin/time -l): instances workload roughly unchanged
(29.0MB -> 29.1MB); dict-heavy workload actually drops (155MB -> 106MB),
likely because mimalloc reclaims freed pages more aggressively than the
system allocator across repeated large-dict churn.
`cargo build` and `cargo build --release` are warning-free with the
default feature set, and `cargo build --release --no-default-features
--features stdlib,threading,importlib,stdio,host_env` (mimalloc
excluded) also builds cleanly. CI's wasm32-wasip1 and other
--no-default-features build matrices in .github/workflows already omit
`mimalloc` from their feature lists and target wasm via the
cfg(not(wasm32)) dependency gate, so no workflow changes needed.
`rustpython -m test -j4 test_gc test_weakref test_threading test_dict
test_list test_tuple test_str test_bytes test_memoryview test_ctypes
test_mmap test_array` passes identically (2541 tests, 241 skipped) on
both the baseline and mimalloc binaries.
Assisted-by: Claude Code:claude-fable-5-1
* vm: cap dict/set table shrink and compact holes on resize
asyncio's eager task path (and its per-task bookkeeping sets in general)
repeatedly adds and discards distinct task objects from a handful of
long-lived sets (_eager_tasks, _scheduled_tasks). Two dict_inner issues
turned that steady-state churn into the dominant cost of async_tree_eager:
1. resize() picked a target size of `used * 2` with no floor, so once a
churny set settled around 1-2 live entries it shrank to a table that
the very next insert would already overflow, forcing a resize on
almost every single add. Clamping to the original minimum table size
(8) restores the headroom dict/set start with.
2. `entries` only ever grows (holes from deletions are never reclaimed),
so resize()'s rebuild pass scanned every entry ever inserted into the
dict's lifetime, not just the live ones - a set with a stable working
set size but heavy churn grew this scan, and the dict's memory, without
bound. resize() now compacts holes out of `entries` first, matching
CPython's compacting hash table and keeping the shape-stamp fast path
(which already requires a hole-free layout) reachable more often.
Measured with a standalone async_tree micro-benchmark (6 levels x 6
branches, pyperformance's bm_async_tree "none"/"eager" shape) run inside
one process for 10 asyncio.run() iterations, comparing medians of
interleaved binary runs on an otherwise idle machine:
- eager: ~2.91s -> ~0.86s median (about 3.4x)
- none: ~1.98s -> ~1.23s median (about 1.6x)
A single fresh-process run (no in-process accumulation) still improves
by roughly 15-20% for eager, since the resize-storm from point 1 hits
even a single run's worth of task churn.
test_asyncio (34 files, all passing), test_dict, test_set, test_weakref,
test_weakset, test_ordered_dict, test_gc, test_descr, test_class, and
`cargo test -p rustpython-vm -p rustpython-stdlib` all pass.
Assisted-by: Claude Code:claude-fable-5-1
* _functools: add native _lru_cache_wrapper
RustPython had no native _functools._lru_cache_wrapper, so
functools.lru_cache always fell back to the pure-Python implementation
in Lib/functools.py (RLock, linked-list bookkeeping, closures). That
fallback dominates typing.Protocol's __instancecheck__ path: it calls
inspect.getattr_static, whose _shadowed_dict is itself lru_cache'd, so
every isinstance() check against a runtime-checkable protocol paid for
several pure-Python cache-wrapper calls plus lock acquisitions.
Add a native pyclass mirroring CPython's C _lru_cache_wrapper: a
reentrant lock scoped only around the cache lookup/insert steps (not
held across the wrapped call, matching CPython's threading behavior),
LRU ordering piggybacked on the cache dict's own insertion order
(move-to-end via del+reinsert, oldest evicted from the front) so no
separate key-comparison structure is needed, and __copy__/__deepcopy__
returning self to match CPython since __reduce__ (used only for
pickling) requires __qualname__, which wrapped non-function callables
such as functools.partial don't have.
Measured with a standalone script reproducing pyperformance's
bm_typing_runtime_protocols isinstance() loop (300 loops, medians of
5 interleaved runs): 0.260s -> 0.182s, about 1.43x faster. In
isolation, a cache-hit through lru_cache went from ~0.31s to ~0.07s
for 200k calls, now faster than calling the underlying function
uncached (matching CPython's C accelerator).
Verified with test_typing, test_abc, test_inspect, test_functools,
test_weakref, test_isinstance, and test_descr (all pass, matching the
pre-existing baseline, including test_functools's already-present
TestLRUC suite which now actually exercises the native implementation).
Assisted-by: Claude Code:claude-fable-5-1
* sre_engine: ASCII fast path for \d \s \w unicode category checks
is_uni_digit/is_uni_space/is_uni_alnum went through icu_properties'
GeneralCategory table lookup for every character, even the common
ASCII case. Add a `ch < 0x80` fast path that resolves directly via
std's ascii predicates, falling back to the full table only for
non-ASCII code points (Latin-1 supplement chars like 'A-umlaut' still
route through the table since they are alphanumeric but not ASCII).
Measured on a tight `re.findall(r"\w+", ...)` / `re.sub` loop over
mostly-ASCII text (interleaved A/B, 4 rounds, medians):
re.sub: 1.178s -> 1.134s (~3.7% faster)
re.findall: 1.110s -> 1.062s (~4.3% faster)
Assisted-by: Claude Code:claude-fable-5-1
* vm: skip VM attach for no-op generator/coroutine __del__
Generator, coroutine, and async-generator dealloc always went through
drop_slow_inner's call_slot_del, which unconditionally attaches to a VM
(with_vm -> begin_interpreter_section -> per-interpreter thread-slot
hashmap lookup) before running del(), even though del() itself is a
documented no-op once the object is already closed or running - the
common case for a generator/coroutine consumed to completion.
Add PyTypeSlots::del_needed, an optional VM-free fast predicate checked
before call_slot_del/with_vm; generator, coroutine, and async_generator
install a check mirroring their own del() no-op branches, letting
drop_slow_inner and try_call_finalizer skip the VM attach entirely for
finished objects.
Standalone microbenchmarks (median of 5 interleaved runs):
- create+drain a 3-yield generator, 2M times: 1.099s -> 0.997s (-9%)
- pyperformance bm_generators shape (yield-from tree traversal): -4%
- pyperformance bm_coroutines shape (recursive fibonacci coroutines): -13%
Assisted-by: Claude Code:claude-fable-5-1
* _asyncio: skip full __import__ dispatch on module-cache hot path
with_asyncio_cache/with_contextvars_cache called vm.import("_asyncio"/
"contextvars", 0) on every Task/Future registration just to validate
their module-identity cache, even on the (overwhelming) hit path. That
still pays the full __import__ builtin dispatch (attribute lookup on
builtins, FuncArgs construction, native slot_call, ImportArgs parsing,
import_module_level) instead of a cheap sys.modules lookup. Added
fast_import_cached(), which checks sys.modules directly and only falls
back to vm.import on a miss (module missing/reloaded).
Measured with a standalone asyncio_tree-style microbenchmark
(6 levels x 6 branches, several asyncio.run() iterations per process,
median of 5 interleaved A/B rounds):
none: 1.045s -> 0.895s (~14% faster)
eager: 0.716s -> 0.591s (~17% faster)
eager_tg: 1.023s -> 0.849s (~17% faster)
test_asyncio (all files), test_contextvars (module absent from this
checkout, pre-existing), test_weakref, test_weakset all pass/unchanged.
Assisted-by: Claude Code:claude-fable-5-1
* vm: skip VM-attach section in with_vm when already on the current interpreter
Object teardown (drop_slow_inner -> call_slot_del, try_call_finalizer,
weakref callbacks) calls with_vm(), which unconditionally went through
set_current_vm -> begin_interpreter_section: an INTERP_THREAD_SLOTS
HashMap lookup plus an Arc clone, even in the overwhelmingly common
case where the dropping thread is already attached to the exact
interpreter that owns the object (a __del__ object or weakref target
going out of scope mid-bytecode).
with_vm now checks whether the object's owning interpreter is already
the one on top of VM_STACK. Only the top of VM_STACK is ever ATTACHED
on a thread (every push is preceded by an attach, every pop restores
the enclosing attach), so when it matches we call f(vm) directly and
skip begin_interpreter_section/set_current_vm entirely. The rare
not-attached/different-interpreter cases (nested subinterpreters,
thread outside any VM, shutdown) fall through to the original
attach/detach path unchanged.
Interleaved release-build measurements (5 rounds, medians), plain-Rust
micro-scripts, no pyperf:
- 2M create+drop of a __del__ object: ~0.55s -> ~0.46s (~16% faster)
- 2M create+drop with a weakref callback: ~1.14s -> ~1.06s (~7% faster)
- 2M io.StringIO() create+drop: ~0.734s -> ~0.697s (~5% faster)
- fib(30) and a 5M-iteration tuple-unpack loop: unchanged (noise-level)
`sample` profiles of the __del__ benchmark confirm
begin_interpreter_section/ensure_thread_slot frames disappear after
the change.
Verified: `cargo test -p rustpython-vm --release`, and
`-m test -j4 test_gc test_weakref test_finalization test_threading
test_thread test_concurrent_futures test_os test_signal test_generators
test_io test_atexit test_sys` (test_interpreters module absent in this
tree, pre-existing). Also a 4-thread stress script hammering __del__
object creation against a concurrent gc.collect() loop for 10s, and a
threading+queue handoff dropping a __del__ object after cross-thread
transfer, both pass.
Assisted-by: Claude Code:claude-fable-5-1
* vm: general sys.modules fast path for vm.import
VirtualMachine::import always resolved and called builtins.__import__
(FuncArgs construction, native slot_call, ImportArgs::from_args, then
import_module_level) even when the module was already cached in
sys.modules. ~63 Rust stdlib call sites hit this on hot paths
(copyreg, pickle's reduce_newobj, warnings, asyncio, etc).
Add VirtualMachine::try_import_cached: for a plain absolute import
(level 0, empty from-list), if builtins.__import__ is still the
original import function (compared by identity against vm.import_func)
and the module -- or, for a dotted name, its top-level package -- is
already present and fully initialized in sys.modules, return it
directly, skipping the __import__ dispatch and import_module_level
entirely. Falls through to the existing slow path whenever the module
is uncached, still initializing, or __import__ has been overridden by
user code, so test_import/test_importlib/test_builtin overrides still
work.
Exposed import::is_module_initializing (refactored out of
import_ensure_initialized) so the fast path can check
__spec__._initializing the same way the slow path does.
Removes _asyncio's local fast_import_cached workaround (added in
cb2064f0e) now that vm.import itself has the same fast path generally.
Measured (interleaved A/B, 5 rounds, pickle.dumps of a plain object,
which hits copyreg import via reduce_newobj): ~2% wall-clock
improvement per call (baseline ~4.75s / 100k dumps vs ~4.66s
candidate), consistent across all rounds. Verified: overridden
__import__ still invoked, a replaced sys.modules is read through the
same accessor, and a self-importing/initializing module is not handed
out early -- all match baseline behavior.
test suite (test_import test_importlib test_builtin test_pkg
test_pkgutil test_runpy test_zipimport test_site test_warnings
test_copy test_pickle test_asyncio.test_tasks test_threaded_import
test_sys): 13/14 files pass; test_threaded_import fails identically on
baseline (ModuleNotFoundError, pre-existing, unrelated to this change).
Assisted-by: Claude Code:claude-fable-5-1
* _asyncio: bypass generic context.run dispatch in task_eager_start
task_eager_start ran the first step via context.run(coro.send, None),
which builds a bound `send` method object and then re-enters the
generic Callable/FuncArgs dispatch just to invoke context.run's own
generic call. Since eager tasks in async_tree_eager complete almost
entirely synchronously (no event-loop scheduling needed), this
per-task dispatch overhead dominated. Enter/exit the native PyContext
directly and call coro.send via a single call_method instead,
falling back to the generic path for non-native Context subclasses.
async_tree_eager median: ~705ms -> ~697ms across 5 interleaved A/B
rounds (~1% eager-only win); async_tree (none) unaffected (noise-level
delta). test_asyncio (all files), test_weakref, test_weakset pass;
pre-existing test_contextvars module-not-found failure unchanged from
baseline.
Assisted-by: Claude Code:claude-fable-5-1
* vm: drop scopeguard::defer! from generator/coroutine resume hot path
resume_gen_frame restored owner/exc-slot/frame-chain state via a
scopeguard::defer! closure that ran on every exit (Ok, Err, or panic).
Since the closure's body is the function's only tail expression, a
plain captured result plus explicit cleanup (the same shape with_frame
already uses for the analogous call path) covers both normal exit
paths identically; only the recursion-depth decrement keeps a
scopeguard, matching with_frame's precedent that this codebase does
not otherwise guarantee generator/frame state consistency across a
Rust panic (panic=unwind is not used to implement any Python-level
control flow here).
Delegation-chain benchmark (chain.py, forwarding N values through a
yield-from chain) measures a small but consistent win: ~2-5% less
wall time per forwarded value at depths 1/4/16 across interleaved A/B
rounds. Single-generator create+finish cost is a wash.
Assisted-by: Claude Code:claude-fable-5-1
* vm: run generator bodies through the call trampoline
`Py<FrameObject>::resume` built its `ExecutingFrame` with
`tailcall_enabled: false`, because a `TailCall` result had nowhere to go:
the trampoline was reachable only from `run_frame_fast`, whose entry frame
lives on the data stack and can never yield. So every ordinary Python call
made from inside a generator or coroutine body re-entered `run_frame_fast`
recursively instead of being flattened.
Generalize the trampoline to drive a heap-resident entry frame whose
enter/exit bookkeeping belongs to its caller, and whose `Yield` is a valid
terminal result rather than a panic. `FrameKind` now says, per frame, what
the trampoline owes it: `Entry` (exit it, caller frees the storage),
`GenEntry` (`resume_gen_frame` linked it and will unlink it; a `Yield` is
the trampoline's result) or `Callee` (exit it and free its storage). The
three near-identical dispatch arms collapse into one `Step::Finished` /
`Step::Deliver` loop.
The suspended-frame stack moves onto the VM and is reused across
invocations: a generator body enters the trampoline once per resume, so a
`Vec::with_capacity(8)` there cost a malloc/free per yield (+27 ns/resume
when first measured). Invocations nest strictly LIFO, so each owns the
region above the length it finds on entry.
Neutral on its own — a generator body's first call was the only one not
already flattened, since the callee's own frame runs under the trampoline
either way. It is what lets SEND/FOR_ITER hand a generator frame to the
same loop. Measured (chain of `yield from`, 1-yield generator create+finish,
bm_generators, bm_coroutines, async_tree none/eager): within noise, except
generator create+finish 625 ns -> 603 ns.
Assisted-by: Claude Code:claude-fable-5-1
* vm: run a `yield from` chain in one eval loop
`SEND` on a suspended generator recursed: `Coro::send` -> `resume_gen_frame`
-> a fresh `ExecutingFrame::run` per level, and the value came back the same
way. Measured at ~55 ns per level of delegation, against CPython's ~5.8 ns.
Only about 5 ns of that is the resume bookkeeping itself (measured by
stubbing out the claim, the exception slot, the owner swap and the recursion
checks). The rest is what a delegating frame does per value: leave and
re-enter the eval loop, and dispatch `RESUME`, `JUMP_BACKWARD_NO_INTERRUPT`,
`SEND` and `YIELD_VALUE` — four instructions that, in the canonical
`yield from` / `await` shape, only hand a value along.
So the trampoline now runs the chain itself. `SendGen` parks its frame and
returns `ExecutionResult::GenResume`; the trampoline claims and links the
sub-generator (`coroutine::flat_resume_enter`, the first half of what
`Coro::send` does around a resume) and runs its frame in the same loop. On
the way down, every frame it finds already suspended at a `yield from` whose
delegate can be resumed is parked without being run at all
(`yield_from_delegate`); on the way back up, a yielded value is re-yielded
for each such frame by parking its `lasti` past the `YIELD_VALUE`
(`park_after_yield_from`) instead of running it. A whole chain therefore
costs one claim/link/unlink per level and no instruction dispatch at all,
with `Py<FrameObject>::resume` extending the same treatment to the outermost
level, which is where `next()` and asyncio's task step enter a chain.
Each level is still claimed, linked and unlinked exactly as a recursive
`Coro::send` would, so `gi_running`, `f_back`, `gi_frame`, `gi_yieldfrom`,
`sys._getframe`, tracebacks, `throw`, `close`, PEP 479 and the
`RecursionError` depth are unchanged; `resume_gen_frame` and the flat path
share `gen_frame_link`/`gen_frame_unlink` and the invariants are documented
on `FlatResume`. Everything else keeps the recursive path: tracing or
monitoring on (both would see the skipped instructions), `throw`/`close`,
async generators, subclasses, non-generator iterators, `FOR_ITER`, a `SEND`
that is not a `yield from`, and a delegate that has not started or is not
itself delegating — the last two because one lone level costs more to hand
to the trampoline than to send into from the eval loop the caller is in.
Measured (median of 5, ns/value or seconds):
chain of `yield from`, depth 16 911 -> 479 -47%
chain of `yield from`, depth 4 212 -> 158 -26%
bm_generators 0.272 -> 0.187 -31%
richards 0.772 -> 0.771 ~0
bm_coroutines 0.0129 -> 0.0130 +0.8%
async_tree none ~neutral
chain depth 1 (no delegation) 81.1 -> 83.6 +3.1%
fib(24), calls through trampoline 0.0180 -> 0.0183 +1.7%
Per level of delegation: 55.3 ns -> 26.4 ns (CPython 3.14: 5.8 ns).
Assisted-by: Claude Code:claude-fable-5-1
* vm: reset the oparg prefix when a signal unwinds a frame
An `EXTENDED_ARG` leaves its prefix in `arg_state` for the instruction that
follows it. The signal path in the eval loop unwinds to a handler and
`continue`s straight back to the top, skipping the loop's own
`arg_state.reset()`, so the handler's first instruction was decoded with the
interrupted instruction's prefix still applied. Every other path that jumps
somewhere new — the `set_f_lineno` restart just above — resets first.
Reachable whenever a signal arrives on the instruction right after an
`EXTENDED_ARG` in a frame that has a handler: a `KeyboardInterrupt` into a
`yield from` chain panicked with `InvalidBytecode` in roughly half of the
runs of a 16-deep chain, and now does not in ten out of ten.
Assisted-by: Claude Code:claude-fable-5-1
* bytecode: fetch a code unit with one 16-bit acquire load
The dispatch loop read the opcode with an Acquire `AtomicU8` load and the
arg byte with a separate Relaxed one. The acquire load sits between the
two, so LLVM had to re-load `self.code` and `code.instructions` for the
second access: four extra instructions and two extra dependent loads on
the instruction-fetch path of every bytecode instruction.
`CodeUnits::read_unit` reads both bytes as a single aligned Acquire
`AtomicU16`, which pairs with `replace_op`'s Release store exactly as
`read_op` did. `CodeUnit` gains `align(2)` so the 16-bit access is
guaranteed aligned rather than relying on the allocator.
5.91 -> 5.76 ns/bytecode-instruction on a straight-line
LOAD_FAST/STORE_FAST loop (-2.5%); range loop -2%.
Assisted-by: Claude Code:claude-fable-5-1
* vm: compute the cache-entry count after dispatch, not before
`op.cache_entries()` is a byte-table lookup used only to skip inline
cache units after an instruction falls through. Computing it before the
handler kept it live across the whole (very large) inlined match, so
every bytecode instruction paid an address materialization, the table
load, a stack spill and a reload of the count.
Moving it next to its only use lets it be computed on the fall-through
path with the value in a register.
Measured together with the following commit (the eval-breaker outline):
5.76 -> 5.55 ns/bytecode-instruction on a straight-line
LOAD_FAST/STORE_FAST loop, range loop -1.5%, fib(27) -2.5%.
Assisted-by: Claude Code:claude-fable-5-1
* vm: move the eval-breaker body out of the dispatch loop
The eval-breaker check itself stays inline on the per-instruction path
(moving it off regresses badly), but everything it guards -- the
finalizing/main-thread check, stop-the-world suspend, the QSBR
checkpoint, signal delivery and the scheduled GC -- was inlined into the
loop body too. That code is never executed on the hot path yet its
address materializations and live values competed for registers with
every opcode handler, so the loop head spilled more than it needed to.
`eval_breaker_work` is `#[cold] #[inline(never)]`, leaving a single
`ldrb`/`cbz` pair inline for the common "nothing pending" case.
Measured with the preceding commit: 5.76 -> 5.55 ns/bytecode-instruction
on a straight-line LOAD_FAST/STORE_FAST loop.
Assisted-by: Claude Code:claude-fable-5-1
* vm: always inline the eval stack push/pop primitives
`pop_value`, `pop_value_opt`, `top_value` and friends are a handful of
instructions each, but `ExecutingFrame::run` is ~50 KB of inlined opcode
handlers, so LLVM's inline budget refused them and emitted real calls:
STORE_FAST and POP_TOP each began with `bl ...pop_value`, which also
forced spills around the call. `#[inline(always)]` puts the four or five
instructions they actually are back into the handlers.
5.55 -> 5.29 ns/bytecode-instruction on a straight-line
LOAD_FAST/STORE_FAST loop (-4.8%); nbody -3.5%, fib(27) -3.5%,
`s += i` loop -3.5%, `for _ in range(n)` -4%.
Assisted-by: Claude Code:claude-fable-5-1
* vm: carry the instruction fetch state in loop locals
Instruction fetch went through memory three times per bytecode
instruction: `self.lasti` (a pointer field) was re-loaded, the program
counter was stored to it and read back at the top of the next iteration,
and `self.code` -> `code.instructions` was re-loaded to address the code
unit. LLVM cannot keep any of them in registers because the handlers
write through `&mut self`, so the fetch chain started with a
store-to-load round trip followed by two dependent pointer loads.
`lasti_cell` and `instructions` are now copied into locals once (both are
plain shared references with the frame's lifetime), and the program
counter is carried in `idx`: after a handler that did not jump the next
index is already known from `lasti_before` plus the cache count. The
frame's `lasti` is still stored before every instruction, so tracebacks,
`f_lineno`, signals and `sys._getframe` see exactly what they did; only
the read-back is gone. The paths that unwind (signal, exception handler,
`set_f_lineno`) re-read it, since they jump to a new position.
Also collapses the eval-breaker `if`, which clippy flagged after the
previous commit moved its body out of line.
range loop -3.3%, richards -3.1%, nbody -2.9%, `s += i` loop -2.2%,
go -1.7%, raytrace -1.2%; mean -1.1% over the 13-benchmark set. The
straight-line LOAD_FAST/STORE_FAST micro is unchanged at 5.28
ns/bytecode-instruction (it has no cache entries and never jumps, so it
was already the best case for the old code).
Assisted-by: Claude Code:claude-fable-5-1
* bytecode: hoist the instruction array base out of the dispatch loop
Addressing a code unit still went `self.code` -> `code.instructions` ->
the `Box<[CodeUnit]>` data pointer on every instruction, two dependent
loads before the acquire load that fetches the unit itself, because the
handlers write through `&mut self` and LLVM has to assume the fields
could change.
The instruction array is allocated once when the code object is built
and never reallocated -- specialization rewrites units in place -- so
`CodeUnits::units_ptr` may be read once before the loop and reused.
`read_unit_from` is the same 16-bit acquire load taken from that base;
`read_unit` now delegates to it, and the index is unchecked exactly as
it was before.
nqueens -1.9%, raytrace -1.6%, range loop -1.3%, deltablue -1.1%,
nbody -0.8%, dispatch micro -0.7%; mean -0.7% over the 13-benchmark set.
Assisted-by: Claude Code:claude-fable-5-1
* common: an immortal bit in the refcount word
Atomic reference counting is about a third of what a bytecode costs now
that the dispatch loop is trimmed: built `--no-default-features`, where
`PyAtomic` is a `Cell`, the straight-line dispatch micro runs at
3.5 ns/instruction against 5.4. Every `TO_BOOL`/`POP_JUMP_IF_*` pair and
every `None` or small-int push pays a relaxed `ldadd` and a `Release`
`ldaddl` on an object that can never die.
PEP 683's answer is to say so in the word. A fourth flag bit, `IMMORTAL`,
takes a bit from the strong count (leaving 60 on a 64-bit target, 28 on a
32-bit one, both far past what a program reaches); `make_immortal` sets it
and parks the count at CPython's `_Py_IMMORTAL_REFCNT`, and `inc`,
`inc_by`, `dec` and `safe_inc` read the bit and return without touching
the counter. The parked count is what keeps the rest of the tree honest
without a special case: it is far too large for a `strong_count() == 1`
in-place-mutation fast path to fire on, and `start_gc_refs` already reads
a count that large as `GC_REACHABLE`, so an immortal object is a permanent
root that keeps its referents alive.
Two details pay for the branch the mortal path now runs, which cost 4% on
object-heavy benchmarks before they went in:
* `leak` sets `IMMORTAL` alongside `LEAKED`. An interned object could
already never reach zero, so `dec` no longer needs the test it made on
the result of the decrement: the immortal test at the top decides both.
* `inc`'s three exceptional cases — a destructed word, a count at the
ceiling, a count at zero — fold into one comparison against the masked
word, with the handling moved to a cold helper.
What is left is one instruction on `dec` and roughly nothing on `inc`, and
the mortal path measures at parity.
Assisted-by: Claude Code:claude-fable-5-1
* vm: make the singletons, small ints, interned strings and static types immortal
Everything the `Context` builds once and keeps — `None`, `True`, `False`,
`Ellipsis`, `NotImplemented`, the empty tuple, frozenset, bytes and str,
the -5..256 int cache and the interned one-character strings — is now
marked immortal, as is every static type object, which its `static_cell`
holds for the life of the process. So is every entry of the string pool:
`mark_intern` already stopped those refcounts from reaching zero, so
immortality frees no memory that would otherwise have been freed; it only
takes the atomic read-modify-write off every incref and decref of an
attribute name, a dict key or a docstring.
Straight-line dispatch micro (200 LOAD_FAST/STORE_FAST pairs over cached
ints, median of 5 interleaved runs): 5.39 -> 4.01 ns/instruction, -26%.
A four-thread `if flag:` loop, where the contention on the shared bool was
real: 2.36 s -> 2.14 s, -10%.
pyperformance bench functions run standalone, medians of 5 interleaved
rounds against a matched build: fannkuch -7.8%, nqueens -3.8%, chaos
-3.0%, raytrace +1.0%, go +1.2%, richards +4.2%, nine of them together
-2.9%. Per-benchmark deltas under about 4% sit inside this tree's
build-to-build ThinLTO layout noise, which reaches that size between two
builds of identical source.
Observable: `sys.getrefcount` of an immortal object reports 4294967295,
CPython's `_Py_IMMORTAL_REFCNT`, and stops moving — what
`test_sys.test_refcount` expects of `None`. `is_interned`, and the dict
pointer-equality key fast path behind it, keep answering from the separate
`LEAKED` bit.
Assisted-by: Claude Code:claude-fable-5-1
* codecs: fast-path utf-8 decode, skipping the codec registry round trip
bytes.decode('utf-8') / str(bytes, 'utf-8', ...) always went through the
generic codec registry: a per-call attribute lookup for
_is_text_encoding, then a full call into the pure-Python
encodings.utf_8.decode wrapper, which called back into the native
utf_8_decode and unpacked a 2-tuple result.
CPython's PyUnicode_Decode special-cases a handful of built-in encoding
names (utf-8 among them) and decodes them directly, bypassing the codec
registry entirely -- a codecs.register() override does not affect
str(b"...", "utf-8") there either. This adds the same fast path here:
recognized utf-8 aliases go straight to the native decoder.
Measured with a pure-Python `str(bytes, 'utf-8', 'surrogatepass')` loop:
~723ns -> ~401ns per call (1.8x). On the pyperformance unpickle
benchmark (which decodes short strings this way for every
SHORT_BINUNICODE opcode): ~14.4ms -> ~13.2ms per loop, an ~8.3% win.
Assisted-by: Claude Code:claude-fable-5-1
* codecs: fast-path utf-8/ascii/latin-1 encode and ascii/latin-1 decode
Extend the utf-8 decode fast path from 3fe684629 to the symmetric encode
direction (str.encode()/bytes(s, enc)), and add the same registry-skipping
fast path for ascii and latin-1 encode/decode, all of which are simple
native codecs.rs functions already. For utf-8 encode with no surrogates in
the string (the common case), encoding is just a copy of the string's
existing UTF-8 bytes, so that's the fastest path; strings containing
surrogates and non-strict error handlers still go through the native
encoder + ErrorsHandler, matching custom codecs.register_error handlers.
Measured (median of interleaved runs vs baseline, 2M iterations):
str.encode("utf-8"): 351ns -> 195ns (-44%)
str.encode(): 449ns -> 291ns (-35%)
bytes(s, "utf-8"): 542ns -> 382ns (-30%)
str.encode("ascii"): 436ns -> 302ns (-31%)
str.encode("latin-1"): 487ns -> 333ns (-32%)
bytes.decode("ascii"): 519ns -> 359ns (-31%)
bytes.decode("latin-1"):590ns -> 417ns (-29%)
bytes.decode("utf-8")/str(b,"utf-8"): unchanged (already fast-pathed)
Assisted-by: Claude Code:claude-fable-5-1
* vm: make the runtime safe for borrowed stack entries
`PyStackRef` has carried a borrow tag since the stack was unified with the
fastlocals, but only `LOAD_SMALL_INT` -- which borrows immortal `Context`
objects -- ever set it, so nothing in the runtime had to be right about
borrowed entries. Turning `LOAD_FAST_BORROW` into a real borrow makes three
places load-bearing, and this fixes them ahead of the switch.
`Traverse for PyStackRef` handed every entry to the tracer, borrowed ones
included. The cycle collector subtracts one from an object's `gc_refs` per
edge it is shown, so a borrowed entry -- which owns no strong count -- would
have pushed a live object's count to zero and freed it out from under the
frame. Borrowed entries are now skipped; nothing becomes unreachable, because
the fastlocals slot the entry borrows from is on the same traversal.
`park_after_yield_from` stands in for a `YIELD_VALUE` that never runs, so the
`promote_stack()` in that handler never ran either; it now promotes there too,
and the debug assertion after it stops being vacuous.
`STORE_FAST`, `DELETE_FAST` and the fused stores now call
`LocalsPlus::debug_audit_local_release`, which in debug builds panics if the
slot is about to drop the last count of an object a borrowed entry still
points at -- the one shape codegen's `optimize_load_fast` exists to prevent,
and the one that would be a use-after-free if the analysis or a modelled stack
effect were wrong.
`BORROW_LOCAL_LOADS` (off) is the switch for the borrow itself, and
`LOAD_FAST_BORROW`/`LOAD_FAST_BORROW_LOAD_FAST_BORROW` now go through
`push_local`, which is a plain clone while it is off. The invariant the switch
depends on is written out where `PyStackRef` is defined.
No measurable change: with the switch off the only behavioral difference is
the traversal fix, which no current bytecode can reach.
Assisted-by: Claude Code:claude-fable-5-1
* vm: read consumed stack entries in place instead of owning them
`pop_value()` turns whatever is on the stack into an owned `PyObjectRef`, so
an instruction that only reads its operand still pays a reference count round
trip -- and, once `LOAD_FAST_BORROW` starts pushing borrows, would pay an
atomic increment to promote a borrow it is about to throw away. This moves
every consumer that never needs ownership onto `pop_stackref()`, which hands
back the tagged entry and costs nothing for a borrow.
Discarding pops (62 of them, across the `LOAD_ATTR`, `TO_BOOL`, `BINARY_OP`,
`COMPARE_OP`, `CONTAINS_OP` and `CALL` specializations, which read their
operands through `top_value()`/`nth_value()` and then drop them) become
`pop_stackref()`. So do the read-only operands of `POP_TOP`, `POP_JUMP_IF_*`,
`TO_BOOL`, `BINARY_OP`, `COMPARE_OP`, `CONTAINS_OP`, `IS_OP`, `STORE_ATTR`,
`DELETE_ATTR`, `STORE_SUBSCR`, `DELETE_SUBSCR` and `UNPACK_SEQUENCE`.
Two protocol entry points had to move from an owned receiver to `&PyObject` to
make that reachable: `try_to_bool`, which never used the ownership it took,
and `rich_compare`, which forwards to the `&self` `_cmp` anyway. Both keep
working unchanged for `PyObjectRef` callers through deref.
`LOAD_ATTR`'s method specializations popped the receiver and pushed it
straight back below the bound function; they now move the entry across with
its tag intact, which is how CPython keeps a method call's receiver borrowed
from `LOAD_ATTR` all the way through `CALL`. The plain attribute read reads
through the receiver rather than owning it.
Neutral while `BORROW_LOCAL_LOADS` is off -- for an owned entry
`pop_stackref()` and `pop_value()` compile to the same thing.
Assisted-by: Claude Code:claude-fable-5-1
* vm: turn LOAD_FAST_BORROW into an actual borrow
`LOAD_FAST_BORROW` accounts for 21.5% of executed instructions on
nbody+richards+nqueens, and until now it did exactly what `LOAD_FAST` does:
an atomic increment on the way onto the stack and, from the instruction that
consumed it, an atomic release decrement on the way off. Neither is needed.
The fastlocals slot holds a count for as long as the entry is on the stack --
codegen's `optimize_load_fast` only rewrites a `LOAD_FAST` when it can prove
the entry is consumed before anything can release that slot -- so the entry
can be a borrow that owns nothing, which is what CPython 3.14 does with a
tagged `_PyStackRef`.
Flipping `BORROW_LOCAL_LOADS` on is the whole change; the two preceding
commits are what make it safe (cycle-collector traversal, yield-point
promotion, a debug audit of the invariant) and what make it pay (consumers
that read through the entry rather than promoting it back to owned).
Medians of 7 interleaved rounds, this machine, against the pre-series binary:
straight-line while loop 6.32 -> 6.01 ns/instruction (-5.0%)
x = a + b loop -6.3% if flag: loop -3.8%
attribute-read loop -5.2% method-call loop -3.3%
subscript loop -6.7% fib(27) -0.8%
nbody -2.6% fannkuch -1.7% nqueens +1.3% richards -3.0%
deltablue -2.3% go -3.0% raytrace -1.7% float -3.2%
chaos -2.6% spectral_norm -2.5% mean -2.1%
About half of that is the consumer rewrite alone (mean -1.0% with the switch
still off) and the rest is the borrow. nqueens is the one benchmark that goes
the other way, by less than the roughly 4% of build-to-build layout noise this
tree has, and it moves with the consumer rewrite rather than the switch.
Full suite green (418 files, 42,110 tests). A build with debug assertions on
runs the suite with no audit failure, and trips the audit exactly when
`LOAD_FAST` is forced to borrow in a shape codegen refuses to convert
(`f(x, (x := None))`). Adversarial scripts -- swap loops and reassignment
under `__del__` tracking, argument evaluation that deletes its sibling,
`a[a.pop()]`, closures over a rebound cell, generators dropped while
suspended, tracebacks whose `f_locals` are read after unwinding, `settrace`
writing `f_locals` mid-expression, a method receiver whose local is cleared
while the call is being built, `__add__`/`__lt__`/`__bool__` that clear the
local their operand came from, threads sharing objects through locals, and
`gc.collect()` from a finalizer -- all agree with CPython 3.13 under
`gc.set_threshold(1, 1, 1)`, in both builds.
Assisted-by: Claude Code:claude-fable-5-1
* vm: exact-dict fast path for DICT_MERGE and CALL_FUNCTION_EX kwargs
`g(**d)` on an exact dict compiled to BUILD_MAP+DICT_MERGE+CALL_FUNCTION_EX
and paid for two Python-level `keys()`/`__getitem__` round trips per key at
both the DICT_MERGE handler and collect_ex_args' iterate_mapping_keys, even
though the source is a plain dict. Add an exact-dict (downcast + type
check, so subclasses still go through the mapping protocol) fast path to
both sites that iterates entries natively via PyDict::items_vec, matching
CPython's PyDict_Check fast paths in PyDict_Merge / CALL_FUNCTION_EX.
Measured (1M calls, release build): g(**d) 1320ns -> 665ns, f(**d)
1470ns -> 900ns, c.m(**d) 1690ns -> 1135ns, f(x=1,**d) 1600ns -> 1035ns,
f(**d1,**d2) 1700ns -> 1013ns. bm_django_template ~7% faster,
bm_argparse (subparsers) ~7% faster.
Assisted-by: Claude Code:claude-fable-5-1
* vm: skip the recursion guard hashing exact ints inside a tuple
tuple.__hash__ hashed every element through PyObject::hash, which wraps
each call in with_recursion's native-stack-depth check. That guard exists
for elements whose hash can recurse into arbitrary Python code, but
int.__hash__ is a leaf computation - it can neither recurse nor call back
into Python - so the check is pure overhead for the overwhelmingly common
case of tuples of plain ints (e.g. namedtuple-shaped dict keys).
pyperformance's mdp benchmark builds huge tables keyed by tuples of deeply
nested namedtuples whose leaves are ints; profiling with `sample` showed
PyObject::hash/with_recursion/tuple's slot_hash dominating the CPU time.
Hashing exact-int elements directly via hash::hash_bigint cuts mdp's
wall time from ~6.79s to ~4.81s (median of 5 interleaved runs), about 29%
faster, with no change to hash values or recursion-guard behavior for any
other element type.
Assisted-by: Claude Code:claude-fable-5-1
* vm: wire slot descriptors from __slots__ into C-level type slots
Classes that put a dunder name (e.g. "__setitem__") into __slots__ get
a per-instance member descriptor for it, which is a legitimate way to
override a special method per-instance (chameleon's tal.RepeatDict does
exactly this). PyType::new_verbose ran init_slots() to compute
mp_ass_subscript/mp_subscript/etc. before these member descriptors were
added to the type's attribute namespace, so the slot lookup never saw
them and __setitem__/__getitem__ silently fell back to "does not
support item assignment".
Fix by calling update_slot for each dunder-shaped __slots__ member
right after it is installed on the type, mirroring what
type.__setattr__ already does for regular attribute updates.
Assisted-by: Claude Code:claude-fable-5-1
* frame: stop allocating cold data to close a generator
`clear_locals_and_stack`, which runs whenever a generator or coroutine
comes to an end, reached for the frame's lazily-allocated `FrameColdData`
through `cold()`. That allocates it on first access, so every finishing
generator paid a `Box<FrameColdData>` malloc and free just to find
`f_extra_locals`, `f_locals_cache` and `f_overwritten_fast_locals` all
empty -- cold data is only ever populated by tracing and frame
introspection. Ask for it with `cold_opt()` instead and skip the three
reads when it was never allocated.
Medians of interleaved A/B runs, ns per generator create+finish over a
plain call: `for _ in g(): pass` 265 -> 231 (-13%), a coroutine that
completes synchronously 642 -> 617 (-4%), `sum(x for x in range(3))`
1033 -> 997 (-3.5%).
Assisted-by: Claude Code:claude-fable-5-1
* function: bind exact args straight into a generator's frame
Calling a generator, coroutine or async generator function took the
general argument-binding path even when the call site was the simplest
possible one. `invoke_exact_args_slots` and the vectorcall fast path both
recognised the shape -- every parameter positional, filled exactly once
-- and then handed generator-like code back to `invoke()`, which built a
`FuncArgs` (a `Vec` plus an `IndexMap`) only for
`fill_locals_from_args_inner` to walk varargs packing, keyword matching
and defaults that this shape cannot reach.
Add `make_generator_exact_args`, the counterpart of
`prepare_exact_args_frame` for the one call shape it refuses: it builds
the heap frame the generator will own, moves the arguments straight into
its fastlocals and returns the generator. Both fast paths now use it. The
vectorcall one matters on its own for generator expressions, whose fresh
`MAKE_FUNCTION` on every evaluation keeps them out of the call-site
specialization that would otherwise catch them.
Medians of interleaved A/B runs, ns per create+finish over a plain call:
`for _ in g(): pass` 231 -> 218 (-6%), a synchronously-completing
coroutine 616 -> 598 (-3%), `sum(x for x in range(3))` 1007 -> 967 (-4%).
Assisted-by: Claude Code:claude-fable-5-1
* frame: drain a dead frame in place instead of moving it out
Three allocations and a large move on the path a finishing generator
takes to release its frame.
`Traverse::clear` opened with `self.iframe.get_mut().take()`, moving the
whole `InterpreterFrame` -- a couple of hundred bytes -- out of its slot
and onto the stack, only to drop it there. Drain it through `as_mut()`
and empty the slot afterwards.
`LocalsPlus::clear_into` collected the evaluation stack into a `Vec` that
existed only to be extended into the caller's; push into that one
directly. And `clear_stack_and_cells` built (and dropped) a `Vec` for a
stack it had not looked at -- a frame that reached its `return`, which is
every generator that simply finished, left nothing there. Look first.
Medians of 5 interleaved rounds, ns per create+finish: `for _ in g():
pass` 307 -> 297 (-3.4%), a synchronously-completing coroutine 600 -> 585
(-2.5%), `list(g())` 565 -> 558; a plain call is unchanged.
Assisted-by: Claude Code:claude-fable-5-1
* vm: don't raise a TypeError to ask an iterator for its length
`length_hint_opt` opened with `iter.length()`, whose whole job on a type
with no length slot is to build the `TypeError` that says so -- which
this caller then catches, recognises and discards. Every iterator and
every generator takes that path, and `list()` calls it on whatever it is
handed, so a `list(gen)` paid for an exception object per call.
`length_opt` answers the same question with `None`. CPython's
`PyObject_LengthHint` gates the call on the slots for the same reason.
Median of 5 interleaved rounds: `list(g())` x200k for a 1-yield
generator 556 -> 407 ns (-27%). pyperformance generators, coroutines,
comprehensions and async_tree are unchanged, as are nqueens, richards
and chaos.
Assisted-by: Claude Code:claude-fable-5-1
* gc: track a generator and its frame in one pass
Making a generator tracks two objects that are born together and die
together -- the generator and the frame it owns -- and each one took the
gen0 list lock and a thread-local read of the running interpreter for
itself. Give the pair one entry point: `PyGenerator`, `PyCoroutine` and
`PyAsyncGen` opt out of tracking at allocation, the way frames already
do, and `make_generator_or_coro` links both into gen0 under a single
lock, with one owner read and one collection check.
Medians of 5 interleaved rounds: pyperformance coroutines -2.5%,
generators -0.9%; `for _ in g(): pass` over a plain call 205 -> 200 ns.
Assisted-by: Claude Code:claude-fable-5-1
* dict_inner: fast-path exact-int equality in key_eq
Mirrors CPython's lookdict: two exact `int` keys now compare by BigInt
value directly instead of going through the generic rich-compare
dispatch (identical_or_equal -> bool_eq -> rich_compare_bool -> __eq__
slot lookup). Falls back to bool_eq whenever either side isn't exactly
`int` (subclasses may override __eq__), so behavior is unchanged there.
Measured (min-of-6 rounds, 2M-iteration micros, this tree's build has
~4% layout noise so min is used as the noise floor):
- dict_int_lookup (`d[i]`, int keys): 70.9ns -> 62.0ns (-12.5%)
- dict_str_lookup / dict_tuple_lookup / set_str_contains: unchanged
(within noise), confirming no regression on non-int keys.
pyperformance sanity checks (single round each, base vs candidate):
bm_mdp median ~5027ms/loop vs ~5003ms/loop (no regression, hashing
isn't the dominant cost there); bm_nqueens, bm_go, bm_deltablue,
bm_hexiom, bm_chaos all within ~1% (noise).
Hash/equality values cross-checked bit-identical against CPython 3.14
for 0, 1, -1, True, False, 2**61-1, 2**61, -(2**63), 2**100, -(2**100),
and int/float/bool/Decimal/Fraction(1) hash-equality invariants.
Also tried an exact-type fast dispatch in PyObject::hash (skipping the
slots.hash vtable call + with_recursion for exact int/str) - it only
moved hash_small_int/hash_large_int by ~2%, under the 3% bar, so it was
not committed.
Tests: `rustpython -m test -j4 test_hash test_int test_long test_tuple
test_dict test_set test_dictviews test_ordered_dict test_collections
test_fractions test_numeric_tower test_float test_bool test_str
test_userdict test_weakref` all pass (1796 run, 72 skipped). `cargo
test -p rustpython-vm --release -p rustpython-common` passes. clippy
clean on the touched lines.
Assisted-by: Claude Code:claude-fable-5-1
* vm: let the modulus type handle a three-argument pow
CPython's ternary dispatch offers the operation to the third operand's type
whenever its slot differs from the two already tried, including the case where
neither operand had one. The port required both of those slots to exist, so
`pow(10, 2, Decimal(7))` never reached `Decimal` and raised TypeError.
Assisted-by: Claude Code:claude-fable-5-1
* vm: add a feature-gated opcode-pair histogram
Counts `(prev_op, op)` in the dispatch loop and dumps the nonzero pairs at
exit, so superinstruction and specialization candidates can be ranked from
what pyperformance-shaped code actually dispatches rather than guessed at.
Entirely behind the non-default `opcode-histogram` feature: the module is
`#[cfg]`-ed out and the loop's two-line hook with it, so a default build is
byte-identical to one without the patch.
Assisted-by: Claude Code:claude-fable-5-1
* vm: let boolean producers run the branch that follows them
`TO_BOOL*`, `COMPARE_OP*`, `CONTAINS_OP*` and `IS_OP` materialised a bool
only for the `POP_JUMP_IF_*` one dispatch later to pop and test it, and the
compiler's `NOT_TAKEN` marker cost a third dispatch on the fall-through edge.
Generalise the `COMPARE_OP_INT`-only jump fusion so every boolean producer
resolves the jump itself, and step over `NOT_TAKEN` from there and from the
plain `POP_JUMP_IF_*` handlers. Both back off to ordinary dispatch when the
successor is an instrumented opcode or while tracing, so `sys.monitoring`
branch events and `sys.settrace` opcode events are unaffected.
Dispatches over 13 standalone pyperformance benchmarks: -5.0% total
(richards -11.7%, unpickle_pure_python -8.9%, sympy -8.4%, hexiom -8.1%,
django_template -7.1%, go -6.1%; nbody unchanged). Times, best of 7
interleaved rounds: deltablue -4.0%, hexiom -3.5%, go -3.4%,
unpickle_pure_python -3.3%, richards -3.2%, raytrace -3.0%, nqueens -1.9%,
geomean -1.0%; a branch-heavy microbenchmark -4.7% against a straight-line
control at 0%.
Assisted-by: Claude Code:claude-fable-5-1
* Adapt yield-from resume to optional generator frames
Coro.frame is now PyAtomicRef<Option<FrameObject>>, so the flattened
resume path uses frame_opt/clear_except_code instead of a direct frame.
Assisted-by: Grok:4.6
* Fix clippy and cr_origin after try_to_bool/f_lineno changes
try_to_bool now takes &self, so drop the leftover clones. Derive
cr_origin lines from lasti instead of prev_line, and satisfy
-Dwarnings on the immortal-refcount and del_needed types.
Assisted-by: Grok:4.6
* Convert fd filenames without the os ToPyObject impl
host_env-free builds (sandbox check, wasm) do not compile os.rs, so
OsPathOrFd::filename cannot use Borrowed's ToPyObject. Use as_raw()
instead, which is the same conversion.
Assisted-by: Grok:4.6
* Gate fd path helpers on the host_env feature
WASM clippy builds rustpython-vm without host_env, so OsPathOrFd and
the fspath/fd helpers are unused and fail -Dwarnings.
Assisted-by: Grok:4.6
* Drop redundant pickle try_to_bool clones after rebase
Assisted-by: Grok:4.6
* Document why enumerate keeps a Counter beside BigInt
Assisted-by: Grok:4.6
* Move fd path helpers into ospath_fd under host_env
Assisted-by: Grok:4.6
* Group threading eval-breaker bits in one module
Assisted-by: Grok:4.6
* Make InterpreterFrame construction unsafe
run_iframe stays safe: a live frame already has valid identity pointers.
Assisted-by: Grok:4.6
* Move OsPathOrFd re-export to the top of ospath.rs
Assisted-by: Grok:4.6
---------
Co-authored-by: Lee Dogeon <dev.moreal@gmail.com>
|
||
|
|
c382a607bb |
host_env: keep rustls off the rustls-free ssl surface (#8727)
* host_env: keep rustls off the rustls-free ssl surface ssl is MemoryBIO, constants, OID, and ALPN. ssl-rustls is the rustls engine. Drop the wasm32 rustls-pki-types web dep; the browser crate already enables that feature. Assisted-by: Claude * host_env: drop rustls-pki-types from the lockfile Assisted-by: Claude * host_env: name the rustls engine feature rustls ssl is the rustls-free surface. rustls is the engine. stdlib's ssl-rustls product feature still selects this. Assisted-by: Claude |
||
|
|
3191ecaf38 |
host_env: share thread, locale, loader and BSTR (#8702)
* host_env: share thread, locale, loader and BSTR
Add Windows thread name, locale-info/wcscoll, CompareStringOrdinal,
LoadLibraryEx/GetProcAddress/FreeLibrary, BSTR and COM helpers, and
AF_HYPERV UUID conversion. Wire _thread, locale, ctypes LoadLibrary
and WMI BSTRs through those wrappers. Ensure =X: before spawnve/execve.
Assisted-by: Claude
* host_env: expose rustls-free ssl core on wasm
Compile MemoryBIO, protocol constants, OID tables, hostname
checks, and ALPN parsing without the rustls feature so wasm
and other hosts can implement _ssl on the shared types.
Assisted-by: Claude
* stdlib: add wasm _ssl on rustls-free host_env
Install _ssl on wasm32 against rustpython_host_env::ssl.
MemoryBIO, constants, OID, hostname, and ALPN work. Context
settings are stored; wrap/handshake and rustls-only loaders
raise SSLError until a wasm TLS engine is wired.
Assisted-by: Claude
* host_env: address #8702 review comments
Cache-miss Windows ctypes lookup uses GetProcAddress. FreeLibrary
propagates OSError. start_new_thread returns GetThreadId. Export
_thread._get_name and drop pyfunction doc comments. Reject
embedded NULs in Windows locale.strcoll/strxfrm. Terminate
wcscoll/wcsxfrm and SetThreadDescription inputs.
wasm _ssl accepts wrap_bio owner and get_ca_certs(binary_form),
and MemoryBIO.read(-1) returns all pending bytes.
Assisted-by: Claude
* host_env: gate the whole ssl module on feature ssl
Compile rustpython_host_env::ssl only with the ssl feature.
Rustls crates stay native-only, so enabling ssl on wasm does
not pull rustls. Inside the module, rustls files follow
not(wasm32) rather than a second feature flag.
Assisted-by: Claude
* host_env: fix Windows types and thread names
Cast SysAllocStringLen to *mut u16 and RPC status to u32 so
windows-gnu and MSVC agree. Export _NAME_MAXLEN, truncate names
at NUL and the OS byte cap, and decode get_name with fsdecode.
Assisted-by: Claude
* host_env: compile rustls engine on WASI
Keep rustls-free MemoryBIO/constants/OID/ALPN on all wasm
targets. Compile the rustls engine on WASI, where UnixTime::now
exists. Allow the rustpython ssl feature on wasm without
ssl-rustls or ssl-openssl.
Assisted-by: Claude
* compiler: pick the earliest CPython parse override
A later incompatible-prefix scan must not hide an earlier
number or bracket diagnostic. Parser-driven tokenization
stops at the first failure.
Assisted-by: Claude
* host_env: address remaining #8702 review comments
Map a missing Windows export to a symbol-load error, keep
LoadLibrary's Win32 code on the OSError, drop incomplete
UTF-16 pairs when truncating thread names, hide set_name
and _get_name off linux/macOS/Windows, and let ssl-rustls
enable host_env.
Assisted-by: Claude
* stdlib: add wasm _socket and read-all MemoryBIO
Register a rustls-free-style _socket shim on wasm32 so
Lib/ssl.py can import. Treat MemoryBIO.read(-1) as read-all,
matching _ssl_MemoryBIO_read_impl.
Assisted-by: Claude
* host_env: fix clippy, WASI ssl cfg, and wasm rustls
Defer unclosed-bracket overrides until no token diagnostic
exists, keep WASI rustls and wasm ssl modules exclusive,
preserve lone Windows surrogates, compile the rustls engine
on browser wasm, and clear clippy denials.
Assisted-by: Claude
* host_env: fix Windows clippy and wasm UnixTime
Gate thread-name byte truncation to linux/macOS, allow
GetProcAddress/FreeLibrary pointer deref, and enable
rustls-pki-types web time on browser wasm.
Assisted-by: Claude
* host_env: lock rustls-pki-types web for wasm
Assisted-by: Claude
* host_env: silence Windows unused thread id and shear
Assisted-by: Claude
* stdlib: drop Windows locale clippy needless wraps
Windows clippy failed on locale.strcoll/strxfrm needless return
and _getdefaultlocale always wrapping Ok.
Assisted-by: Claude
* compiler: keep lexer errors over print hints
print x; 0x must report an invalid hex literal. Ranking by source
offset let the earlier print hint win. Treat a finished radix
literal as valid so print x; 0x1 still gets the print message.
Assisted-by: Claude
* compiler: keep decode errors over unclosed openers
Ranking by offset made ( '\\N' report an unclosed paren and
wiped the unclosed-bracket flag on f'{, so compile snippets
raised SyntaxError instead of incomplete input. Export wasm
socket._accept for Lib/socket.py.
Assisted-by: Claude
* wasm: drop duplicate _socket; split locale cfgs
_socket lives in rustpython-stdlib's socket_wasm. The wasm crate
only re-exported that file when freeze-stdlib was off. Split
strcoll, strxfrm, and getencoding into cfg'd function defs.
Assisted-by: Claude
* compiler: keep decode blockers over unclosed openers
(print x; '\\N' must keep the print hint. A later malformed
escape still suppresses the EOF opener even when print wins.
Assisted-by: Claude
* compiler: keep earlier f-string over later lexer errors
f'{x'; ' and f'{x'; 0x must not report the trailing quote or
hex literal. Decode and f-string diagnostics now compete with
lexer failures by offset.
Assisted-by: Claude
* compiler: rank decode vs lexer vs print by class
Unterminated quotes no longer hide format-spec newlines or
empty f-string fields. Print is considered only after the
final winner is not a lexer failure, so print x; f'{x'; 0x
keeps the print hint. Line-continuation uses CPython wording.
Assisted-by: Claude
* compiler: keep unclosed paren over line-cont at EOF
test_exceptions wants '(' was never closed at the opener when
a final backslash follows. Skip escaped quotes in the bracket
scan so {\'a\'} still reports the continuation error. Un-xfail
test_join_daemon_thread_in_finalization.
Assisted-by: Claude
* test_threading: xfail daemon join finalization off Linux
The test passes on Linux. macOS and Windows leave an ignored
deallocator exception on stderr.
Assisted-by: Claude
* vm: hang daemon threads at interpreter finalize
Forced-off daemons must stay not-done so join() during
finalization GC raises PythonFinalizationError and
Thread.is_alive() stays true. SystemExit made the handle
look finished on macOS/Windows. Drop the Lib/test xfail.
Assisted-by: Claude
(cherry picked from commit
|
||
|
|
f569444f7d |
build(deps): bump the wasmtime group across 1 directory with 2 updates (#8710)
Bumps the wasmtime group with 1 update in the / directory: [cranelift-jit](https://github.com/bytecodealliance/wasmtime).
Updates `cranelift-jit` from 0.135.1 to 0.135.2
- [Release notes](https://github.com/bytecodealliance/wasmtime/releases)
- [Changelog](https://github.com/bytecodealliance/wasmtime/blob/main/RELEASES.md)
- [Commits](https://github.com/bytecodealliance/wasmtime/commits)
Updates `cranelift-module` from 0.135.1 to 0.135.2
- [Release notes](https://github.com/bytecodealliance/wasmtime/releases)
- [Changelog](https://github.com/bytecodealliance/wasmtime/blob/main/RELEASES.md)
- [Commits](https://github.com/bytecodealliance/wasmtime/commits)
---
updated-dependencies:
- dependency-name: cranelift-jit
dependency-version: 0.135.2
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: wasmtime
- dependency-name: cranelift-module
dependency-version: 0.135.2
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: wasmtime
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
(cherry picked from commit
|
||
|
|
0bc7fe15d0 |
build(deps): bump uuid from 1.24.0 to 1.26.0 (#8715)
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.24.0 to 1.26.0. - [Release notes](https://github.com/uuid-rs/uuid/releases) - [Commits](https://github.com/uuid-rs/uuid/compare/v1.24.0...v1.26.0) --- updated-dependencies: - dependency-name: uuid dependency-version: 1.26.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
1b15903164 |
build(deps): bump der from 0.8.1 to 0.8.2 (#8714)
Bumps [der](https://github.com/RustCrypto/formats) from 0.8.1 to 0.8.2. - [Commits](https://github.com/RustCrypto/formats/compare/der/v0.8.1...der/v0.8.2) --- updated-dependencies: - dependency-name: der dependency-version: 0.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |