Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Doc/library/plistlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ and XML plist files.

The property list (``.plist``) file format is a simple serialization supporting
basic object types, like dictionaries, lists, numbers and strings. Usually the
top level object is a dictionary.
top level object is a dictionary or a frozen dictionary.

To write out and to parse a plist file, use the :func:`dump` and
:func:`load` functions.
Expand Down
3 changes: 2 additions & 1 deletion Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,8 @@ For example::

The following standard library modules have been updated to accept
:class:`!frozendict`: :mod:`copy`, :mod:`decimal`, :mod:`json`, :mod:`marshal`,
:mod:`pickle`, :mod:`pprint` and :mod:`xml.etree.ElementTree`.
:mod:`plistlib` (only for serialization), :mod:`pickle`, :mod:`pprint` and
:mod:`xml.etree.ElementTree`.

:func:`eval` and :func:`exec` accept :class:`!frozendict` for *globals*, and
:func:`type` and :meth:`str.maketrans` accept :class:`!frozendict` for *dict*.
Expand Down
18 changes: 17 additions & 1 deletion Include/internal/pycore_long.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,20 @@ _PyLong_IsPositive(const PyLongObject *op)
return (op->long_value.lv_tag & SIGN_MASK) == 0;
}

/* Return true if the argument is a small int */
static inline bool
_PyLong_IsSmallInt(const PyLongObject *op)
{
assert(PyLong_Check(op));
bool is_small_int = (op->long_value.lv_tag & IMMORTALITY_BIT_MASK) != 0;
assert(PyLong_CheckExact(op) || (!is_small_int));
assert(_Py_IsImmortal(op) || (!is_small_int));
assert((_PyLong_IsCompact(op)
&& _PY_IS_SMALL_INT(_PyLong_CompactValue(op)))
|| (!is_small_int));
return is_small_int;
}

static inline Py_ssize_t
_PyLong_DigitCount(const PyLongObject *op)
{
Expand Down Expand Up @@ -293,7 +307,9 @@ _PyLong_SetDigitCount(PyLongObject *op, Py_ssize_t size)
#define NON_SIZE_MASK ~(uintptr_t)((1 << NON_SIZE_BITS) - 1)

static inline void
_PyLong_FlipSign(PyLongObject *op) {
_PyLong_FlipSign(PyLongObject *op)
{
assert(!_PyLong_IsSmallInt(op));
unsigned int flipped_sign = 2 - (op->long_value.lv_tag & SIGN_MASK);
op->long_value.lv_tag &= NON_SIZE_MASK;
op->long_value.lv_tag |= flipped_sign;
Expand Down
59 changes: 42 additions & 17 deletions InternalDocs/jit.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,54 @@ and this enables optimizations that span multiple instructions.
Historically, the adaptive interpreter was referred to as `tier 1` and
the JIT as `tier 2`. You will see remnants of this in the code.

## The Optimizer and Executors
## The Trace Recorder and Executors

The program begins running on the adaptive interpreter, until a `JUMP_BACKWARD`
instruction determines that it is "hot" because the counter in its
There are two interpreters in this section:
1. Adaptive interpreter (the default behavior)
2. Trace recording interpreter (enabled on JIT builds)

The program begins running on the adaptive interpreter, until a `JUMP_BACKWARD` or
`RESUME` instruction determines that it is "hot" because the counter in its
[inline cache](interpreter.md#inline-cache-entries) indicates that it
executed more than some threshold number of times (see
[`backoff_counter_triggers`](../Include/internal/pycore_backoff.h)).
It then calls the function `_PyOptimizer_Optimize()` in
It then calls the function `_PyJit_TryInitializeTracing` in
[`Python/optimizer.c`](../Python/optimizer.c), passing it the current
[frame](frames.md) and instruction pointer. `_PyOptimizer_Optimize()`
constructs an object of type
[`_PyExecutorObject`](../Include/internal/pycore_optimizer.h) which implements
an optimized version of the instruction trace beginning at this jump.

The optimizer determines where the trace ends, and the executor is set up
[frame](frames.md), instruction pointer and state.
The interpreter then switches into "tracing mode" via the macro
`ENTER_TRACING()`. On platforms that support computed goto and tail-calling
interpreters, the dispatch table is swapped out, while other platforms that do
not support either use a single flag in the opcode.
Execution between the normal interpreter and tracing interpreter are
interleaved via this dispatch mechanism. This means that while logically
there are two interpreters, the implementation appears to be a single
interpreter.

During tracing mode, after each interpreter instruction's `DISPATCH()`,
the interpreter jumps to the `TRACE_RECORD` instruction. This instruction
records the previous instruction executed and also any live values of the next
operation it may require. It then translates the previous instruction to
a sequence of micro-ops using `_PyJit_translate_single_bytecode_to_trace`.
To ensure that the adaptive interpreter instructions
and cache entries are up-to-date, the trace recording interpreter always resets
the adaptive counters of adaptive instructions it sees.
This forces a re-specialization of any new instruction should an instruction
deoptimize. Thus, feeding the trace recorder up-to-date information.
Finally, the `TRACE_RECORD` instruction decides when to stop tracing
using various heuristics.

Once trace recording concludes, `LEAVE_TRACING()` swaps out the dispatch
table/the opcode flag set earlier by `ENTER_TRACING()` is unset.
`stop_tracing_and_jit()` then calls `_PyOptimizer_Optimize()` which optimizes
the trace and constructs an
[`_PyExecutorObject`](../Include/internal/pycore_optimizer.h).

JIT execution is set up
to either return to the adaptive interpreter and resume execution, or
transfer control to another executor (see `_PyExitData` in
Include/internal/pycore_optimizer.h).
Include/internal/pycore_optimizer.h). When resuming to the adaptive interpreter,
a "side exit", generated by an `EXIT_IF` may trigger recording of another trace.
While a "deopt", generated by a `DEOPT_IF`, does not trigger recording.

The executor is stored on the [`code object`](code_objects.md) of the frame,
in the `co_executors` field which is an array of executors. The start
Expand All @@ -40,12 +70,7 @@ executor in `co_executors`.

The micro-op (abbreviated `uop` to approximate `μop`) optimizer is defined in
[`Python/optimizer.c`](../Python/optimizer.c) as `_PyOptimizer_Optimize`.
It translates an instruction trace into a sequence of micro-ops by replacing
each bytecode by an equivalent sequence of micro-ops (see
`_PyOpcode_macro_expansion` in
[pycore_opcode_metadata.h](../Include/internal/pycore_opcode_metadata.h)
which is generated from [`Python/bytecodes.c`](../Python/bytecodes.c)).
The micro-op sequence is then optimized by
It takes a micro-op sequence from the trace recorder and optimizes with
`_Py_uop_analyze_and_optimize` in
[`Python/optimizer_analysis.c`](../Python/optimizer_analysis.c)
and an instance of `_PyUOpExecutor_Type` is created to contain it.
Expand Down
16 changes: 8 additions & 8 deletions Lib/plistlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

The property list (.plist) file format is a simple XML pickle supporting
basic object types, like dictionaries, lists, numbers and strings.
Usually the top level object is a dictionary.
Usually the top level object is a dictionary or a frozen dictionary.

To write out a plist file, use the dump(value, file)
function. 'value' is the top level object, 'file' is
Expand Down Expand Up @@ -357,7 +357,7 @@ def write_value(self, value):
elif isinstance(value, float):
self.simple_element("real", repr(value))

elif isinstance(value, dict):
elif isinstance(value, (dict, frozendict)):
self.write_dict(value)

elif isinstance(value, (bytes, bytearray)):
Expand Down Expand Up @@ -715,7 +715,7 @@ def _flatten(self, value):
self._objidtable[id(value)] = refnum

# And finally recurse into containers
if isinstance(value, dict):
if isinstance(value, (dict, frozendict)):
keys = []
values = []
items = value.items()
Expand Down Expand Up @@ -836,7 +836,7 @@ def _write_object(self, value):
self._write_size(0xA0, s)
self._fp.write(struct.pack('>' + self._ref_format * s, *refs))

elif isinstance(value, dict):
elif isinstance(value, (dict, frozendict)):
keyRefs, valRefs = [], []

if self._sort_keys:
Expand Down Expand Up @@ -869,18 +869,18 @@ def _is_fmt_binary(header):
# Generic bits
#

_FORMATS={
FMT_XML: dict(
_FORMATS=frozendict({
FMT_XML: frozendict(
detect=_is_fmt_xml,
parser=_PlistParser,
writer=_PlistWriter,
),
FMT_BINARY: dict(
FMT_BINARY: frozendict(
detect=_is_fmt_binary,
parser=_BinaryPlistParser,
writer=_BinaryPlistWriter,
)
}
})


def load(fp, *, fmt=None, dict_type=dict, aware_datetime=False):
Expand Down
10 changes: 10 additions & 0 deletions Lib/test/test_capi/test_long.py
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,16 @@ def to_digits(num):
self.assertEqual(pylongwriter_create(negative, digits), num,
(negative, digits))

def test_bug_143050(self):
with support.adjust_int_max_str_digits(0):
# Bug coming from using _pylong.int_from_string(), that
# currently requires > 6000 decimal digits.
int('-' + '0' * 7000, 10)
_testcapi.test_immortal_small_ints()
# Test also nonzero small int
int('-' + '0' * 7000 + '123', 10)
_testcapi.test_immortal_small_ints()


if __name__ == "__main__":
unittest.main()
19 changes: 19 additions & 0 deletions Lib/test/test_plistlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,25 @@ def test_dict_members(self):
})
self.assertIsNot(pl2['first'], pl2['second'])

def test_frozendict(self):
pl = frozendict(
aString="Doodah",
anInt=728,
aDict=frozendict(
anotherString="hello",
aTrueValue=True,
),
aList=["A", "B", 12],
)

for fmt in ALL_FORMATS:
with self.subTest(fmt=fmt):
data = plistlib.dumps(pl, fmt=fmt)
pl2 = plistlib.loads(data)
self.assertEqual(pl2, dict(pl))
self.assertIsInstance(pl2, dict)
self.assertIsInstance(pl2['aDict'], dict)

def test_controlcharacters(self):
for i in range(128):
c = chr(i)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Support :class:`frozendict` in :mod:`plistlib`, for serialization only.
Patch by Hugo van Kemenade.
4 changes: 2 additions & 2 deletions Modules/_testcapi/immortal.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ test_immortal_small_ints(PyObject *self, PyObject *Py_UNUSED(ignored))
for (int i = -5; i <= 1024; i++) {
PyObject *obj = PyLong_FromLong(i);
assert(verify_immortality(obj));
int has_int_immortal_bit = ((PyLongObject *)obj)->long_value.lv_tag & IMMORTALITY_BIT_MASK;
int has_int_immortal_bit = _PyLong_IsSmallInt((PyLongObject *)obj);
assert(has_int_immortal_bit);
}
for (int i = 1025; i <= 1030; i++) {
PyObject *obj = PyLong_FromLong(i);
assert(obj);
int has_int_immortal_bit = ((PyLongObject *)obj)->long_value.lv_tag & IMMORTALITY_BIT_MASK;
int has_int_immortal_bit = _PyLong_IsSmallInt((PyLongObject *)obj);
assert(!has_int_immortal_bit);
Py_DECREF(obj);
}
Expand Down
20 changes: 5 additions & 15 deletions Objects/longobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -3119,11 +3119,11 @@ PyLong_FromString(const char *str, char **pend, int base)
}

/* Set sign and normalize */
if (sign < 0) {
_PyLong_FlipSign(z);
}
long_normalize(z);
z = maybe_small_long(z);
if (sign < 0) {
_PyLong_Negate(&z);
}

if (pend != NULL) {
*pend = (char *)str;
Expand Down Expand Up @@ -3623,21 +3623,11 @@ long_richcompare(PyObject *self, PyObject *other, int op)
Py_RETURN_RICHCOMPARE(result, 0, op);
}

static inline int
/// Return 1 if the object is one of the immortal small ints
_long_is_small_int(PyObject *op)
{
PyLongObject *long_object = (PyLongObject *)op;
int is_small_int = (long_object->long_value.lv_tag & IMMORTALITY_BIT_MASK) != 0;
assert((!is_small_int) || PyLong_CheckExact(op));
return is_small_int;
}

void
_PyLong_ExactDealloc(PyObject *self)
{
assert(PyLong_CheckExact(self));
if (_long_is_small_int(self)) {
if (_PyLong_IsSmallInt((PyLongObject *)self)) {
// See PEP 683, section Accidental De-Immortalizing for details
_Py_SetImmortal(self);
return;
Expand All @@ -3652,7 +3642,7 @@ _PyLong_ExactDealloc(PyObject *self)
static void
long_dealloc(PyObject *self)
{
if (_long_is_small_int(self)) {
if (_PyLong_IsSmallInt((PyLongObject *)self)) {
/* This should never get called, but we also don't want to SEGV if
* we accidentally decref small Ints out of existence. Instead,
* since small Ints are immortal, re-set the reference count.
Expand Down
Loading