[pypy-commit] pypy anntype2: hg merge default
rlamy
noreply at buildbot.pypy.org
Wed Nov 25 11:40:15 EST 2015
Author: Ronan Lamy <ronan.lamy at gmail.com>
Branch: anntype2
Changeset: r80961:3b7960428100
Date: 2015-11-25 16:40 +0000
http://bitbucket.org/pypy/pypy/changeset/3b7960428100/
Log: hg merge default
diff too long, truncating to 2000 out of 2617 lines
diff --git a/lib_pypy/cffi/api.py b/lib_pypy/cffi/api.py
--- a/lib_pypy/cffi/api.py
+++ b/lib_pypy/cffi/api.py
@@ -72,6 +72,7 @@
self._cdefsources = []
self._included_ffis = []
self._windows_unicode = None
+ self._init_once_cache = {}
if hasattr(backend, 'set_ffi'):
backend.set_ffi(self)
for name in backend.__dict__:
@@ -598,6 +599,30 @@
return recompile(self, module_name, source, tmpdir=tmpdir,
source_extension=source_extension, **kwds)
+ def init_once(self, func, tag):
+ # Read _init_once_cache[tag], which is either (False, lock) if
+ # we're calling the function now in some thread, or (True, result).
+ # Don't call setdefault() in most cases, to avoid allocating and
+ # immediately freeing a lock; but still use setdefaut() to avoid
+ # races.
+ try:
+ x = self._init_once_cache[tag]
+ except KeyError:
+ x = self._init_once_cache.setdefault(tag, (False, allocate_lock()))
+ # Common case: we got (True, result), so we return the result.
+ if x[0]:
+ return x[1]
+ # Else, it's a lock. Acquire it to serialize the following tests.
+ with x[1]:
+ # Read again from _init_once_cache the current status.
+ x = self._init_once_cache[tag]
+ if x[0]:
+ return x[1]
+ # Call the function and store the result back.
+ result = func()
+ self._init_once_cache[tag] = (True, result)
+ return result
+
def _load_backend_lib(backend, name, flags):
if name is None:
diff --git a/lib_pypy/cffi/parse_c_type.h b/lib_pypy/cffi/parse_c_type.h
--- a/lib_pypy/cffi/parse_c_type.h
+++ b/lib_pypy/cffi/parse_c_type.h
@@ -1,5 +1,6 @@
-/* See doc/misc/parse_c_type.rst in the source of CFFI for more information */
+/* This part is from file 'cffi/parse_c_type.h'. It is copied at the
+ beginning of C sources generated by CFFI's ffi.set_source(). */
typedef void *_cffi_opcode_t;
diff --git a/lib_pypy/datetime.py b/lib_pypy/datetime.py
--- a/lib_pypy/datetime.py
+++ b/lib_pypy/datetime.py
@@ -21,6 +21,8 @@
import math as _math
import struct as _struct
+_SENTINEL = object()
+
def _cmp(x, y):
return 0 if x == y else 1 if x > y else -1
@@ -31,6 +33,8 @@
MAXYEAR = 9999
_MINYEARFMT = 1900
+_MAX_DELTA_DAYS = 999999999
+
# Utility functions, adapted from Python's Demo/classes/Dates.py, which
# also assumes the current Gregorian calendar indefinitely extended in
# both directions. Difference: Dates.py calls January 1 of year 0 day
@@ -95,6 +99,15 @@
# pasting together 25 4-year cycles.
assert _DI100Y == 25 * _DI4Y - 1
+_US_PER_US = 1
+_US_PER_MS = 1000
+_US_PER_SECOND = 1000000
+_US_PER_MINUTE = 60000000
+_SECONDS_PER_DAY = 24 * 3600
+_US_PER_HOUR = 3600000000
+_US_PER_DAY = 86400000000
+_US_PER_WEEK = 604800000000
+
def _ord2ymd(n):
"ordinal -> (year, month, day), considering 01-Jan-0001 as day 1."
@@ -271,15 +284,17 @@
def _check_int_field(value):
if isinstance(value, int):
- return value
+ return int(value)
if not isinstance(value, float):
try:
value = value.__int__()
except AttributeError:
pass
else:
- if isinstance(value, (int, long)):
- return value
+ if isinstance(value, int):
+ return int(value)
+ elif isinstance(value, long):
+ return int(long(value))
raise TypeError('__int__ method should return an integer')
raise TypeError('an integer is required')
raise TypeError('integer argument expected, got float')
@@ -344,75 +359,79 @@
raise TypeError("can't compare '%s' to '%s'" % (
type(x).__name__, type(y).__name__))
-# This is a start at a struct tm workalike. Goals:
-#
-# + Works the same way across platforms.
-# + Handles all the fields datetime needs handled, without 1970-2038 glitches.
-#
-# Note: I suspect it's best if this flavor of tm does *not* try to
-# second-guess timezones or DST. Instead fold whatever adjustments you want
-# into the minutes argument (and the constructor will normalize).
+def _normalize_pair(hi, lo, factor):
+ if not 0 <= lo <= factor-1:
+ inc, lo = divmod(lo, factor)
+ hi += inc
+ return hi, lo
-class _tmxxx:
+def _normalize_datetime(y, m, d, hh, mm, ss, us, ignore_overflow=False):
+ # Normalize all the inputs, and store the normalized values.
+ ss, us = _normalize_pair(ss, us, 1000000)
+ mm, ss = _normalize_pair(mm, ss, 60)
+ hh, mm = _normalize_pair(hh, mm, 60)
+ d, hh = _normalize_pair(d, hh, 24)
+ y, m, d = _normalize_date(y, m, d, ignore_overflow)
+ return y, m, d, hh, mm, ss, us
- ordinal = None
+def _normalize_date(year, month, day, ignore_overflow=False):
+ # That was easy. Now it gets muddy: the proper range for day
+ # can't be determined without knowing the correct month and year,
+ # but if day is, e.g., plus or minus a million, the current month
+ # and year values make no sense (and may also be out of bounds
+ # themselves).
+ # Saying 12 months == 1 year should be non-controversial.
+ if not 1 <= month <= 12:
+ year, month = _normalize_pair(year, month-1, 12)
+ month += 1
+ assert 1 <= month <= 12
- def __init__(self, year, month, day, hour=0, minute=0, second=0,
- microsecond=0):
- # Normalize all the inputs, and store the normalized values.
- if not 0 <= microsecond <= 999999:
- carry, microsecond = divmod(microsecond, 1000000)
- second += carry
- if not 0 <= second <= 59:
- carry, second = divmod(second, 60)
- minute += carry
- if not 0 <= minute <= 59:
- carry, minute = divmod(minute, 60)
- hour += carry
- if not 0 <= hour <= 23:
- carry, hour = divmod(hour, 24)
- day += carry
+ # Now only day can be out of bounds (year may also be out of bounds
+ # for a datetime object, but we don't care about that here).
+ # If day is out of bounds, what to do is arguable, but at least the
+ # method here is principled and explainable.
+ dim = _days_in_month(year, month)
+ if not 1 <= day <= dim:
+ # Move day-1 days from the first of the month. First try to
+ # get off cheap if we're only one day out of range (adjustments
+ # for timezone alone can't be worse than that).
+ if day == 0: # move back a day
+ month -= 1
+ if month > 0:
+ day = _days_in_month(year, month)
+ else:
+ year, month, day = year-1, 12, 31
+ elif day == dim + 1: # move forward a day
+ month += 1
+ day = 1
+ if month > 12:
+ month = 1
+ year += 1
+ else:
+ ordinal = _ymd2ord(year, month, 1) + (day - 1)
+ year, month, day = _ord2ymd(ordinal)
- # That was easy. Now it gets muddy: the proper range for day
- # can't be determined without knowing the correct month and year,
- # but if day is, e.g., plus or minus a million, the current month
- # and year values make no sense (and may also be out of bounds
- # themselves).
- # Saying 12 months == 1 year should be non-controversial.
- if not 1 <= month <= 12:
- carry, month = divmod(month-1, 12)
- year += carry
- month += 1
- assert 1 <= month <= 12
+ if not ignore_overflow and not MINYEAR <= year <= MAXYEAR:
+ raise OverflowError("date value out of range")
+ return year, month, day
- # Now only day can be out of bounds (year may also be out of bounds
- # for a datetime object, but we don't care about that here).
- # If day is out of bounds, what to do is arguable, but at least the
- # method here is principled and explainable.
- dim = _days_in_month(year, month)
- if not 1 <= day <= dim:
- # Move day-1 days from the first of the month. First try to
- # get off cheap if we're only one day out of range (adjustments
- # for timezone alone can't be worse than that).
- if day == 0: # move back a day
- month -= 1
- if month > 0:
- day = _days_in_month(year, month)
- else:
- year, month, day = year-1, 12, 31
- elif day == dim + 1: # move forward a day
- month += 1
- day = 1
- if month > 12:
- month = 1
- year += 1
- else:
- self.ordinal = _ymd2ord(year, month, 1) + (day - 1)
- year, month, day = _ord2ymd(self.ordinal)
-
- self.year, self.month, self.day = year, month, day
- self.hour, self.minute, self.second = hour, minute, second
- self.microsecond = microsecond
+def _accum(tag, sofar, num, factor, leftover):
+ if isinstance(num, (int, long)):
+ prod = num * factor
+ rsum = sofar + prod
+ return rsum, leftover
+ if isinstance(num, float):
+ fracpart, intpart = _math.modf(num)
+ prod = int(intpart) * factor
+ rsum = sofar + prod
+ if fracpart == 0.0:
+ return rsum, leftover
+ assert isinstance(factor, (int, long))
+ fracpart, intpart = _math.modf(factor * fracpart)
+ rsum += int(intpart)
+ return rsum, leftover + fracpart
+ raise TypeError("unsupported type for timedelta %s component: %s" %
+ (tag, type(num)))
class timedelta(object):
"""Represent the difference between two datetime objects.
@@ -433,100 +452,42 @@
"""
__slots__ = '_days', '_seconds', '_microseconds', '_hashcode'
- def __new__(cls, days=0, seconds=0, microseconds=0,
- milliseconds=0, minutes=0, hours=0, weeks=0):
- # Doing this efficiently and accurately in C is going to be difficult
- # and error-prone, due to ubiquitous overflow possibilities, and that
- # C double doesn't have enough bits of precision to represent
- # microseconds over 10K years faithfully. The code here tries to make
- # explicit where go-fast assumptions can be relied on, in order to
- # guide the C implementation; it's way more convoluted than speed-
- # ignoring auto-overflow-to-long idiomatic Python could be.
+ def __new__(cls, days=_SENTINEL, seconds=_SENTINEL, microseconds=_SENTINEL,
+ milliseconds=_SENTINEL, minutes=_SENTINEL, hours=_SENTINEL, weeks=_SENTINEL):
+ x = 0
+ leftover = 0.0
+ if microseconds is not _SENTINEL:
+ x, leftover = _accum("microseconds", x, microseconds, _US_PER_US, leftover)
+ if milliseconds is not _SENTINEL:
+ x, leftover = _accum("milliseconds", x, milliseconds, _US_PER_MS, leftover)
+ if seconds is not _SENTINEL:
+ x, leftover = _accum("seconds", x, seconds, _US_PER_SECOND, leftover)
+ if minutes is not _SENTINEL:
+ x, leftover = _accum("minutes", x, minutes, _US_PER_MINUTE, leftover)
+ if hours is not _SENTINEL:
+ x, leftover = _accum("hours", x, hours, _US_PER_HOUR, leftover)
+ if days is not _SENTINEL:
+ x, leftover = _accum("days", x, days, _US_PER_DAY, leftover)
+ if weeks is not _SENTINEL:
+ x, leftover = _accum("weeks", x, weeks, _US_PER_WEEK, leftover)
+ if leftover != 0.0:
+ x += _round(leftover)
+ return cls._from_microseconds(x)
- # XXX Check that all inputs are ints, longs or floats.
+ @classmethod
+ def _from_microseconds(cls, us):
+ s, us = divmod(us, _US_PER_SECOND)
+ d, s = divmod(s, _SECONDS_PER_DAY)
+ return cls._create(d, s, us, False)
- # Final values, all integer.
- # s and us fit in 32-bit signed ints; d isn't bounded.
- d = s = us = 0
+ @classmethod
+ def _create(cls, d, s, us, normalize):
+ if normalize:
+ s, us = _normalize_pair(s, us, 1000000)
+ d, s = _normalize_pair(d, s, 24*3600)
- # Normalize everything to days, seconds, microseconds.
- days += weeks*7
- seconds += minutes*60 + hours*3600
- microseconds += milliseconds*1000
-
- # Get rid of all fractions, and normalize s and us.
- # Take a deep breath <wink>.
- if isinstance(days, float):
- dayfrac, days = _math.modf(days)
- daysecondsfrac, daysecondswhole = _math.modf(dayfrac * (24.*3600.))
- assert daysecondswhole == int(daysecondswhole) # can't overflow
- s = int(daysecondswhole)
- assert days == int(days)
- d = int(days)
- else:
- daysecondsfrac = 0.0
- d = days
- assert isinstance(daysecondsfrac, float)
- assert abs(daysecondsfrac) <= 1.0
- assert isinstance(d, (int, long))
- assert abs(s) <= 24 * 3600
- # days isn't referenced again before redefinition
-
- if isinstance(seconds, float):
- secondsfrac, seconds = _math.modf(seconds)
- assert seconds == int(seconds)
- seconds = int(seconds)
- secondsfrac += daysecondsfrac
- assert abs(secondsfrac) <= 2.0
- else:
- secondsfrac = daysecondsfrac
- # daysecondsfrac isn't referenced again
- assert isinstance(secondsfrac, float)
- assert abs(secondsfrac) <= 2.0
-
- assert isinstance(seconds, (int, long))
- days, seconds = divmod(seconds, 24*3600)
- d += days
- s += int(seconds) # can't overflow
- assert isinstance(s, int)
- assert abs(s) <= 2 * 24 * 3600
- # seconds isn't referenced again before redefinition
-
- usdouble = secondsfrac * 1e6
- assert abs(usdouble) < 2.1e6 # exact value not critical
- # secondsfrac isn't referenced again
-
- if isinstance(microseconds, float):
- microseconds = _round(microseconds + usdouble)
- seconds, microseconds = divmod(microseconds, 1000000)
- days, seconds = divmod(seconds, 24*3600)
- d += days
- s += int(seconds)
- microseconds = int(microseconds)
- else:
- microseconds = int(microseconds)
- seconds, microseconds = divmod(microseconds, 1000000)
- days, seconds = divmod(seconds, 24*3600)
- d += days
- s += int(seconds)
- microseconds = _round(microseconds + usdouble)
- assert isinstance(s, int)
- assert isinstance(microseconds, int)
- assert abs(s) <= 3 * 24 * 3600
- assert abs(microseconds) < 3.1e6
-
- # Just a little bit of carrying possible for microseconds and seconds.
- seconds, us = divmod(microseconds, 1000000)
- s += seconds
- days, s = divmod(s, 24*3600)
- d += days
-
- assert isinstance(d, (int, long))
- assert isinstance(s, int) and 0 <= s < 24*3600
- assert isinstance(us, int) and 0 <= us < 1000000
-
- if abs(d) > 999999999:
- raise OverflowError("timedelta # of days is too large: %d" % d)
+ if not -_MAX_DELTA_DAYS <= d <= _MAX_DELTA_DAYS:
+ raise OverflowError("days=%d; must have magnitude <= %d" % (d, _MAX_DELTA_DAYS))
self = object.__new__(cls)
self._days = d
@@ -535,6 +496,10 @@
self._hashcode = -1
return self
+ def _to_microseconds(self):
+ return ((self._days * _SECONDS_PER_DAY + self._seconds) * _US_PER_SECOND +
+ self._microseconds)
+
def __repr__(self):
module = "datetime." if self.__class__ is timedelta else ""
if self._microseconds:
@@ -562,8 +527,7 @@
def total_seconds(self):
"""Total seconds in the duration."""
- return ((self.days * 86400 + self.seconds) * 10**6 +
- self.microseconds) / 10**6
+ return self._to_microseconds() / 10**6
# Read-only field accessors
@property
@@ -585,36 +549,37 @@
if isinstance(other, timedelta):
# for CPython compatibility, we cannot use
# our __class__ here, but need a real timedelta
- return timedelta(self._days + other._days,
- self._seconds + other._seconds,
- self._microseconds + other._microseconds)
+ return timedelta._create(self._days + other._days,
+ self._seconds + other._seconds,
+ self._microseconds + other._microseconds,
+ True)
return NotImplemented
- __radd__ = __add__
-
def __sub__(self, other):
if isinstance(other, timedelta):
# for CPython compatibility, we cannot use
# our __class__ here, but need a real timedelta
- return timedelta(self._days - other._days,
- self._seconds - other._seconds,
- self._microseconds - other._microseconds)
- return NotImplemented
-
- def __rsub__(self, other):
- if isinstance(other, timedelta):
- return -self + other
+ return timedelta._create(self._days - other._days,
+ self._seconds - other._seconds,
+ self._microseconds - other._microseconds,
+ True)
return NotImplemented
def __neg__(self):
# for CPython compatibility, we cannot use
# our __class__ here, but need a real timedelta
- return timedelta(-self._days,
- -self._seconds,
- -self._microseconds)
+ return timedelta._create(-self._days,
+ -self._seconds,
+ -self._microseconds,
+ True)
def __pos__(self):
- return self
+ # for CPython compatibility, we cannot use
+ # our __class__ here, but need a real timedelta
+ return timedelta._create(self._days,
+ self._seconds,
+ self._microseconds,
+ False)
def __abs__(self):
if self._days < 0:
@@ -623,25 +588,18 @@
return self
def __mul__(self, other):
- if isinstance(other, (int, long)):
- # for CPython compatibility, we cannot use
- # our __class__ here, but need a real timedelta
- return timedelta(self._days * other,
- self._seconds * other,
- self._microseconds * other)
- return NotImplemented
+ if not isinstance(other, (int, long)):
+ return NotImplemented
+ usec = self._to_microseconds()
+ return timedelta._from_microseconds(usec * other)
__rmul__ = __mul__
- def _to_microseconds(self):
- return ((self._days * (24*3600) + self._seconds) * 1000000 +
- self._microseconds)
-
def __div__(self, other):
if not isinstance(other, (int, long)):
return NotImplemented
usec = self._to_microseconds()
- return timedelta(0, 0, usec // other)
+ return timedelta._from_microseconds(usec // other)
__floordiv__ = __div__
@@ -705,9 +663,8 @@
def __reduce__(self):
return (self.__class__, self._getstate())
-timedelta.min = timedelta(-999999999)
-timedelta.max = timedelta(days=999999999, hours=23, minutes=59, seconds=59,
- microseconds=999999)
+timedelta.min = timedelta(-_MAX_DELTA_DAYS)
+timedelta.max = timedelta(_MAX_DELTA_DAYS, 24*3600-1, 1000000-1)
timedelta.resolution = timedelta(microseconds=1)
class date(object):
@@ -948,32 +905,29 @@
# Computations
- def _checkOverflow(self, year):
- if not MINYEAR <= year <= MAXYEAR:
- raise OverflowError("date +/-: result year %d not in %d..%d" %
- (year, MINYEAR, MAXYEAR))
+ def _add_timedelta(self, other, factor):
+ y, m, d = _normalize_date(
+ self._year,
+ self._month,
+ self._day + other.days * factor)
+ return date(y, m, d)
def __add__(self, other):
"Add a date to a timedelta."
if isinstance(other, timedelta):
- t = _tmxxx(self._year,
- self._month,
- self._day + other.days)
- self._checkOverflow(t.year)
- result = date(t.year, t.month, t.day)
- return result
+ return self._add_timedelta(other, 1)
return NotImplemented
__radd__ = __add__
def __sub__(self, other):
"""Subtract two dates, or a date and a timedelta."""
- if isinstance(other, timedelta):
- return self + timedelta(-other.days)
if isinstance(other, date):
days1 = self.toordinal()
days2 = other.toordinal()
- return timedelta(days1 - days2)
+ return timedelta._create(days1 - days2, 0, 0, False)
+ if isinstance(other, timedelta):
+ return self._add_timedelta(other, -1)
return NotImplemented
def weekday(self):
@@ -1340,7 +1294,7 @@
offset = self._tzinfo.utcoffset(None)
offset = _check_utc_offset("utcoffset", offset)
if offset is not None:
- offset = timedelta(minutes=offset)
+ offset = timedelta._create(0, offset * 60, 0, True)
return offset
# Return an integer (or None) instead of a timedelta (or None).
@@ -1378,7 +1332,7 @@
offset = self._tzinfo.dst(None)
offset = _check_utc_offset("dst", offset)
if offset is not None:
- offset = timedelta(minutes=offset)
+ offset = timedelta._create(0, offset * 60, 0, True)
return offset
# Return an integer (or None) instead of a timedelta (or None).
@@ -1505,18 +1459,24 @@
A timezone info object may be passed in as well.
"""
+ _check_tzinfo_arg(tz)
+ converter = _time.localtime if tz is None else _time.gmtime
+ self = cls._from_timestamp(converter, timestamp, tz)
+ if tz is not None:
+ self = tz.fromutc(self)
+ return self
- _check_tzinfo_arg(tz)
+ @classmethod
+ def utcfromtimestamp(cls, t):
+ "Construct a UTC datetime from a POSIX timestamp (like time.time())."
+ return cls._from_timestamp(_time.gmtime, t, None)
- converter = _time.localtime if tz is None else _time.gmtime
-
- if isinstance(timestamp, int):
- us = 0
- else:
- t_full = timestamp
- timestamp = int(_math.floor(timestamp))
- frac = t_full - timestamp
- us = _round(frac * 1e6)
+ @classmethod
+ def _from_timestamp(cls, converter, timestamp, tzinfo):
+ t_full = timestamp
+ timestamp = int(_math.floor(timestamp))
+ frac = t_full - timestamp
+ us = _round(frac * 1e6)
# If timestamp is less than one microsecond smaller than a
# full second, us can be rounded up to 1000000. In this case,
@@ -1527,32 +1487,7 @@
us = 0
y, m, d, hh, mm, ss, weekday, jday, dst = converter(timestamp)
ss = min(ss, 59) # clamp out leap seconds if the platform has them
- result = cls(y, m, d, hh, mm, ss, us, tz)
- if tz is not None:
- result = tz.fromutc(result)
- return result
-
- @classmethod
- def utcfromtimestamp(cls, t):
- "Construct a UTC datetime from a POSIX timestamp (like time.time())."
- if isinstance(t, int):
- us = 0
- else:
- t_full = t
- t = int(_math.floor(t))
- frac = t_full - t
- us = _round(frac * 1e6)
-
- # If timestamp is less than one microsecond smaller than a
- # full second, us can be rounded up to 1000000. In this case,
- # roll over to seconds, otherwise, ValueError is raised
- # by the constructor.
- if us == 1000000:
- t += 1
- us = 0
- y, m, d, hh, mm, ss, weekday, jday, dst = _time.gmtime(t)
- ss = min(ss, 59) # clamp out leap seconds if the platform has them
- return cls(y, m, d, hh, mm, ss, us)
+ return cls(y, m, d, hh, mm, ss, us, tzinfo)
@classmethod
def now(cls, tz=None):
@@ -1594,9 +1529,9 @@
hh, mm, ss = self.hour, self.minute, self.second
offset = self._utcoffset()
if offset: # neither None nor 0
- tm = _tmxxx(y, m, d, hh, mm - offset)
- y, m, d = tm.year, tm.month, tm.day
- hh, mm = tm.hour, tm.minute
+ mm -= offset
+ y, m, d, hh, mm, ss, _ = _normalize_datetime(
+ y, m, d, hh, mm, ss, 0, ignore_overflow=True)
return _build_struct_time(y, m, d, hh, mm, ss, 0)
def date(self):
@@ -1730,7 +1665,7 @@
offset = self._tzinfo.utcoffset(self)
offset = _check_utc_offset("utcoffset", offset)
if offset is not None:
- offset = timedelta(minutes=offset)
+ offset = timedelta._create(0, offset * 60, 0, True)
return offset
# Return an integer (or None) instead of a timedelta (or None).
@@ -1768,7 +1703,7 @@
offset = self._tzinfo.dst(self)
offset = _check_utc_offset("dst", offset)
if offset is not None:
- offset = timedelta(minutes=offset)
+ offset = timedelta._create(0, offset * 60, 0, True)
return offset
# Return an integer (or None) instead of a timedelta (or None).
@@ -1859,22 +1794,22 @@
return -1
return diff and 1 or 0
+ def _add_timedelta(self, other, factor):
+ y, m, d, hh, mm, ss, us = _normalize_datetime(
+ self._year,
+ self._month,
+ self._day + other.days * factor,
+ self._hour,
+ self._minute,
+ self._second + other.seconds * factor,
+ self._microsecond + other.microseconds * factor)
+ return datetime(y, m, d, hh, mm, ss, us, tzinfo=self._tzinfo)
+
def __add__(self, other):
"Add a datetime and a timedelta."
if not isinstance(other, timedelta):
return NotImplemented
- t = _tmxxx(self._year,
- self._month,
- self._day + other.days,
- self._hour,
- self._minute,
- self._second + other.seconds,
- self._microsecond + other.microseconds)
- self._checkOverflow(t.year)
- result = datetime(t.year, t.month, t.day,
- t.hour, t.minute, t.second,
- t.microsecond, tzinfo=self._tzinfo)
- return result
+ return self._add_timedelta(other, 1)
__radd__ = __add__
@@ -1882,16 +1817,15 @@
"Subtract two datetimes, or a datetime and a timedelta."
if not isinstance(other, datetime):
if isinstance(other, timedelta):
- return self + -other
+ return self._add_timedelta(other, -1)
return NotImplemented
- days1 = self.toordinal()
- days2 = other.toordinal()
- secs1 = self._second + self._minute * 60 + self._hour * 3600
- secs2 = other._second + other._minute * 60 + other._hour * 3600
- base = timedelta(days1 - days2,
- secs1 - secs2,
- self._microsecond - other._microsecond)
+ delta_d = self.toordinal() - other.toordinal()
+ delta_s = (self._hour - other._hour) * 3600 + \
+ (self._minute - other._minute) * 60 + \
+ (self._second - other._second)
+ delta_us = self._microsecond - other._microsecond
+ base = timedelta._create(delta_d, delta_s, delta_us, True)
if self._tzinfo is other._tzinfo:
return base
myoff = self._utcoffset()
diff --git a/pypy/doc/whatsnew-head.rst b/pypy/doc/whatsnew-head.rst
--- a/pypy/doc/whatsnew-head.rst
+++ b/pypy/doc/whatsnew-head.rst
@@ -15,4 +15,17 @@
Fix the cpyext tests on OSX by linking with -flat_namespace
.. branch: anntype
+
Refactor and improve exception analysis in the annotator.
+
+.. branch: posita/2193-datetime-timedelta-integrals
+
+Fix issue #2193. ``isinstance(..., int)`` => ``isinstance(..., numbers.Integral)``
+to allow for alternate ``int``-like implementations (e.g., ``future.types.newint``)
+
+.. branch: faster-rstruct
+
+Improve the performace of struct.unpack, which now directly reads inside the
+string buffer and directly casts the bytes to the appropriate type, when
+allowed. Unpacking of floats and doubles is about 15 times faster now, while
+for integer types it's up to ~50% faster for 64bit integers.
diff --git a/pypy/module/_cffi_backend/ffi_obj.py b/pypy/module/_cffi_backend/ffi_obj.py
--- a/pypy/module/_cffi_backend/ffi_obj.py
+++ b/pypy/module/_cffi_backend/ffi_obj.py
@@ -49,6 +49,8 @@
ACCEPT_CDATA = ACCEPT_CDATA
w_gc_wref_remove = None
+ w_init_once_cache = None
+ jit_init_once_cache = None
@jit.dont_look_inside
def __init__(self, space, src_ctx):
@@ -585,6 +587,59 @@
return w_result
+ def descr_init_once(self, w_func, w_tag):
+ """XXX document me"""
+ #
+ # first, a fast-path for the JIT which only works if the very
+ # same w_tag object is passed; then it turns into no code at all
+ try:
+ return self._init_once_elidable(w_tag)
+ except KeyError:
+ return self._init_once_slowpath(w_func, w_tag)
+
+ @jit.elidable
+ def _init_once_elidable(self, w_tag):
+ jit_cache = self.jit_init_once_cache
+ if jit_cache is not None:
+ return jit_cache[w_tag]
+ else:
+ raise KeyError
+
+ @jit.dont_look_inside
+ def _init_once_slowpath(self, w_func, w_tag):
+ space = self.space
+ w_cache = self.w_init_once_cache
+ if w_cache is None:
+ w_cache = self.space.newdict()
+ jit_cache = {}
+ self.w_init_once_cache = w_cache
+ self.jit_init_once_cache = jit_cache
+ #
+ # get the lock or result from cache[tag]
+ w_res = space.finditem(w_cache, w_tag)
+ if w_res is None:
+ w_res = W_InitOnceLock(space)
+ w_res = space.call_method(w_cache, 'setdefault', w_tag, w_res)
+ if not isinstance(w_res, W_InitOnceLock):
+ return w_res
+ with w_res.lock:
+ w_res = space.finditem(w_cache, w_tag)
+ if w_res is None or isinstance(w_res, W_InitOnceLock):
+ w_res = space.call_function(w_func)
+ self.jit_init_once_cache[w_tag] = w_res
+ space.setitem(w_cache, w_tag, w_res)
+ else:
+ # the real result was put in the dict while we were
+ # waiting for lock.__enter__() above
+ pass
+ return w_res
+
+
+class W_InitOnceLock(W_Root):
+ def __init__(self, space):
+ self.lock = space.allocate_lock()
+
+
@jit.dont_look_inside
def make_plain_ffi_object(space, w_ffitype=None):
if w_ffitype is None:
@@ -641,6 +696,7 @@
from_handle = interp2app(W_FFIObject.descr_from_handle),
gc = interp2app(W_FFIObject.descr_gc),
getctype = interp2app(W_FFIObject.descr_getctype),
+ init_once = interp2app(W_FFIObject.descr_init_once),
integer_const = interp2app(W_FFIObject.descr_integer_const),
memmove = interp2app(W_FFIObject.descr_memmove),
new = interp2app(W_FFIObject.descr_new),
diff --git a/pypy/module/_cffi_backend/test/test_ffi_obj.py b/pypy/module/_cffi_backend/test/test_ffi_obj.py
--- a/pypy/module/_cffi_backend/test/test_ffi_obj.py
+++ b/pypy/module/_cffi_backend/test/test_ffi_obj.py
@@ -447,3 +447,19 @@
assert int(ffi.cast("_Bool", ffi.cast(type, 42))) == 1
assert int(ffi.cast("bool", ffi.cast(type, 42))) == 1
assert int(ffi.cast("_Bool", ffi.cast(type, 0))) == 0
+
+ def test_init_once(self):
+ import _cffi_backend as _cffi1_backend
+ def do_init():
+ seen.append(1)
+ return 42
+ ffi = _cffi1_backend.FFI()
+ seen = []
+ for i in range(3):
+ res = ffi.init_once(do_init, "tag1")
+ assert res == 42
+ assert seen == [1]
+ for i in range(3):
+ res = ffi.init_once(do_init, "tag2")
+ assert res == 42
+ assert seen == [1, 1]
diff --git a/pypy/module/micronumpy/compile.py b/pypy/module/micronumpy/compile.py
--- a/pypy/module/micronumpy/compile.py
+++ b/pypy/module/micronumpy/compile.py
@@ -8,6 +8,7 @@
from pypy.interpreter.error import OperationError
from rpython.rlib.objectmodel import specialize, instantiate
from rpython.rlib.nonconst import NonConstant
+from rpython.rlib.rarithmetic import base_int
from pypy.module.micronumpy import boxes, ufuncs
from pypy.module.micronumpy.arrayops import where
from pypy.module.micronumpy.ndarray import W_NDimArray
@@ -178,7 +179,7 @@
return BoolObject(obj)
elif isinstance(obj, int):
return IntObject(obj)
- elif isinstance(obj, long):
+ elif isinstance(obj, base_int):
return LongObject(obj)
elif isinstance(obj, W_Root):
return obj
@@ -196,31 +197,31 @@
return self.float(f)
def le(self, w_obj1, w_obj2):
- assert isinstance(w_obj1, boxes.W_GenericBox)
- assert isinstance(w_obj2, boxes.W_GenericBox)
+ assert isinstance(w_obj1, boxes.W_GenericBox)
+ assert isinstance(w_obj2, boxes.W_GenericBox)
return w_obj1.descr_le(self, w_obj2)
def lt(self, w_obj1, w_obj2):
- assert isinstance(w_obj1, boxes.W_GenericBox)
- assert isinstance(w_obj2, boxes.W_GenericBox)
+ assert isinstance(w_obj1, boxes.W_GenericBox)
+ assert isinstance(w_obj2, boxes.W_GenericBox)
return w_obj1.descr_lt(self, w_obj2)
def ge(self, w_obj1, w_obj2):
- assert isinstance(w_obj1, boxes.W_GenericBox)
- assert isinstance(w_obj2, boxes.W_GenericBox)
+ assert isinstance(w_obj1, boxes.W_GenericBox)
+ assert isinstance(w_obj2, boxes.W_GenericBox)
return w_obj1.descr_ge(self, w_obj2)
def add(self, w_obj1, w_obj2):
- assert isinstance(w_obj1, boxes.W_GenericBox)
- assert isinstance(w_obj2, boxes.W_GenericBox)
+ assert isinstance(w_obj1, boxes.W_GenericBox)
+ assert isinstance(w_obj2, boxes.W_GenericBox)
return w_obj1.descr_add(self, w_obj2)
def sub(self, w_obj1, w_obj2):
return self.wrap(1)
def mul(self, w_obj1, w_obj2):
- assert isinstance(w_obj1, boxes.W_GenericBox)
- assert isinstance(w_obj2, boxes.W_GenericBox)
+ assert isinstance(w_obj1, boxes.W_GenericBox)
+ assert isinstance(w_obj2, boxes.W_GenericBox)
return w_obj1.descr_mul(self, w_obj2)
def pow(self, w_obj1, w_obj2, _):
@@ -836,7 +837,7 @@
elif self.name == 'reshape':
w_arg = self.args[1]
assert isinstance(w_arg, ArrayConstant)
- order = -1
+ order = -1
w_res = arr.reshape(interp.space, w_arg.wrap(interp.space), order)
else:
assert False
diff --git a/pypy/module/micronumpy/descriptor.py b/pypy/module/micronumpy/descriptor.py
--- a/pypy/module/micronumpy/descriptor.py
+++ b/pypy/module/micronumpy/descriptor.py
@@ -816,8 +816,8 @@
def _usefields(space, w_dict, align):
# Only for testing, a shortened version of the real _usefields
allfields = []
- for fname in w_dict.iterkeys().iterator:
- obj = _get_list_or_none(space, w_dict, fname)
+ for fname_w in space.unpackiterable(w_dict):
+ obj = _get_list_or_none(space, w_dict, space.str_w(fname_w))
num = space.int_w(obj[1])
if align:
alignment = 0
@@ -828,8 +828,8 @@
title = space.wrap(obj[2])
else:
title = space.w_None
- allfields.append((space.wrap(fname), format, num, title))
- allfields.sort(key=lambda x: x[2])
+ allfields.append((fname_w, format, num, title))
+ #allfields.sort(key=lambda x: x[2])
names = [space.newtuple([x[0], x[3]]) for x in allfields]
formats = [x[1] for x in allfields]
offsets = [x[2] for x in allfields]
@@ -853,12 +853,14 @@
aligned_w = _get_val_or_none(space, w_dict, 'aligned')
itemsize_w = _get_val_or_none(space, w_dict, 'itemsize')
if names_w is None or formats_w is None:
- if we_are_translated():
+ try:
return get_appbridge_cache(space).call_method(space,
'numpy.core._internal', '_usefields', Arguments(space,
[w_dict, space.wrap(alignment >= 0)]))
- else:
- return _usefields(space, w_dict, alignment >= 0)
+ except OperationError as e:
+ if e.match(space, space.w_ImportError):
+ return _usefields(space, w_dict, alignment >= 0)
+ raise
n = len(names_w)
if (n != len(formats_w) or
(offsets_w is not None and n != len(offsets_w)) or
@@ -898,16 +900,17 @@
def dtype_from_spec(space, w_spec, alignment):
- if we_are_translated():
+ w_lst = w_spec
+ try:
w_lst = get_appbridge_cache(space).call_method(space,
'numpy.core._internal', '_commastring', Arguments(space, [w_spec]))
- else:
+ except OperationError as e:
+ if not e.match(space, space.w_ImportError):
+ raise
# handle only simple cases for testing
if space.isinstance_w(w_spec, space.w_str):
spec = [s.strip() for s in space.str_w(w_spec).split(',')]
w_lst = space.newlist([space.wrap(s) for s in spec])
- elif space.isinstance_w(w_spec, space.w_list):
- w_lst = w_spec
if not space.isinstance_w(w_lst, space.w_list) or space.len_w(w_lst) < 1:
raise oefmt(space.w_RuntimeError,
"_commastring is not returning a list with len >= 1")
diff --git a/pypy/module/pypyjit/test_pypy_c/test_struct.py b/pypy/module/pypyjit/test_pypy_c/test_struct.py
--- a/pypy/module/pypyjit/test_pypy_c/test_struct.py
+++ b/pypy/module/pypyjit/test_pypy_c/test_struct.py
@@ -19,7 +19,8 @@
import struct
i = 1
while i < n:
- x = struct.unpack("i", struct.pack("i", i))[0] # ID: struct
+ buf = struct.pack("i", i) # ID: pack
+ x = struct.unpack("i", buf)[0] # ID: unpack
i += x / i
return i
@@ -29,7 +30,7 @@
loop, = log.loops_by_filename(self.filepath)
# This could, of course stand some improvement, to remove all these
# arithmatic ops, but we've removed all the core overhead.
- assert loop.match_by_id("struct", """
+ assert loop.match_by_id("pack", """
guard_not_invalidated(descr=...)
# struct.pack
%s
@@ -40,17 +41,22 @@
i17 = int_and(i16, 255)
i19 = int_rshift(i16, 8)
i20 = int_and(i19, 255)
+ """ % extra)
+ # the newstr and the strsetitems are because the string is forced,
+ # which is in turn because the optimizer doesn't know how to handle a
+ # getarrayitem_gc_i on a virtual string. It could be improved, but it
+ # is also true that in real life cases struct.unpack is called on
+ # strings which come from the outside, so it's a minor issue.
+ assert loop.match_by_id("unpack", """
# struct.unpack
- i22 = int_lshift(i14, 8)
- i23 = int_or(i11, i22)
- i25 = int_lshift(i17, 16)
- i26 = int_or(i23, i25)
- i28 = int_ge(i20, 128)
- guard_false(i28, descr=...)
- i30 = int_lshift(i20, 24)
- i31 = int_or(i26, i30)
- """ % extra)
+ p88 = newstr(4)
+ strsetitem(p88, 0, i11)
+ strsetitem(p88, 1, i14)
+ strsetitem(p88, 2, i17)
+ strsetitem(p88, 3, i20)
+ i91 = getarrayitem_gc_i(p88, 0, descr=<ArrayS 4>)
+ """)
def test_struct_object(self):
def main(n):
@@ -58,7 +64,8 @@
s = struct.Struct("i")
i = 1
while i < n:
- x = s.unpack(s.pack(i))[0] # ID: struct
+ buf = s.pack(i) # ID: pack
+ x = s.unpack(buf)[0] # ID: unpack
i += x / i
return i
@@ -66,7 +73,7 @@
assert log.result == main(1000)
loop, = log.loops_by_filename(self.filepath)
- assert loop.match_by_id('struct', """
+ assert loop.match_by_id('pack', """
guard_not_invalidated(descr=...)
# struct.pack
%s
@@ -77,14 +84,14 @@
i17 = int_and(i16, 255)
i19 = int_rshift(i16, 8)
i20 = int_and(i19, 255)
+ """ % extra)
+ assert loop.match_by_id('unpack', """
# struct.unpack
- i22 = int_lshift(i14, 8)
- i23 = int_or(i11, i22)
- i25 = int_lshift(i17, 16)
- i26 = int_or(i23, i25)
- i28 = int_ge(i20, 128)
- guard_false(i28, descr=...)
- i30 = int_lshift(i20, 24)
- i31 = int_or(i26, i30)
- """ % extra)
+ p88 = newstr(4)
+ strsetitem(p88, 0, i11)
+ strsetitem(p88, 1, i14)
+ strsetitem(p88, 2, i17)
+ strsetitem(p88, 3, i20)
+ i91 = getarrayitem_gc_i(p88, 0, descr=<ArrayS 4>)
+ """)
diff --git a/pypy/module/struct/formatiterator.py b/pypy/module/struct/formatiterator.py
--- a/pypy/module/struct/formatiterator.py
+++ b/pypy/module/struct/formatiterator.py
@@ -149,3 +149,13 @@
@specialize.argtype(1)
def appendobj(self, value):
self.result_w.append(self.space.wrap(value))
+
+ def get_pos(self):
+ return self.pos
+
+ def get_buffer_as_string_maybe(self):
+ string, pos = self.buf.as_str_and_offset_maybe()
+ return string, pos+self.pos
+
+ def skip(self, size):
+ self.read(size) # XXX, could avoid taking the slice
diff --git a/pypy/module/struct/test/test_struct.py b/pypy/module/struct/test/test_struct.py
--- a/pypy/module/struct/test/test_struct.py
+++ b/pypy/module/struct/test/test_struct.py
@@ -462,3 +462,29 @@
assert self.struct.unpack_from("ii", b, 2) == (17, 42)
b[:sz] = self.struct.pack("ii", 18, 43)
assert self.struct.unpack_from("ii", b) == (18, 43)
+
+
+class AppTestFastPath(object):
+ spaceconfig = dict(usemodules=['struct', '__pypy__'])
+
+ def setup_class(cls):
+ from rpython.rlib.rstruct import standardfmttable
+ standardfmttable.ALLOW_SLOWPATH = False
+ #
+ cls.w_struct = cls.space.appexec([], """():
+ import struct
+ return struct
+ """)
+ cls.w_bytebuffer = cls.space.appexec([], """():
+ import __pypy__
+ return __pypy__.bytebuffer
+ """)
+
+ def teardown_class(cls):
+ from rpython.rlib.rstruct import standardfmttable
+ standardfmttable.ALLOW_SLOWPATH = True
+
+ def test_unpack_from(self):
+ buf = self.struct.pack("iii", 0, 42, 43)
+ offset = self.struct.calcsize("i")
+ assert self.struct.unpack_from("ii", buf, offset) == (42, 43)
diff --git a/pypy/module/test_lib_pypy/cffi_tests/cffi0/backend_tests.py b/pypy/module/test_lib_pypy/cffi_tests/cffi0/backend_tests.py
--- a/pypy/module/test_lib_pypy/cffi_tests/cffi0/backend_tests.py
+++ b/pypy/module/test_lib_pypy/cffi_tests/cffi0/backend_tests.py
@@ -1810,3 +1810,35 @@
assert lib.EE1 == 0
assert lib.EE2 == 0
assert lib.EE3 == 1
+
+ def test_init_once(self):
+ def do_init():
+ seen.append(1)
+ return 42
+ ffi = FFI()
+ seen = []
+ for i in range(3):
+ res = ffi.init_once(do_init, "tag1")
+ assert res == 42
+ assert seen == [1]
+ for i in range(3):
+ res = ffi.init_once(do_init, "tag2")
+ assert res == 42
+ assert seen == [1, 1]
+
+ def test_init_once_multithread(self):
+ import thread, time
+ def do_init():
+ seen.append('init!')
+ time.sleep(1)
+ seen.append('init done')
+ return 7
+ ffi = FFI()
+ seen = []
+ for i in range(6):
+ def f():
+ res = ffi.init_once(do_init, "tag")
+ seen.append(res)
+ thread.start_new_thread(f, ())
+ time.sleep(1.5)
+ assert seen == ['init!', 'init done'] + 6 * [7]
diff --git a/pypy/module/test_lib_pypy/cffi_tests/cffi0/test_function.py b/pypy/module/test_lib_pypy/cffi_tests/cffi0/test_function.py
--- a/pypy/module/test_lib_pypy/cffi_tests/cffi0/test_function.py
+++ b/pypy/module/test_lib_pypy/cffi_tests/cffi0/test_function.py
@@ -487,7 +487,7 @@
ffi = FFI(backend=self.Backend())
ffi.cdef("double __stdcall sin(double x);") # stdcall ignored
m = ffi.dlopen(lib_m)
- if (sys.platform == 'win32' and sys.maxint < 2**32 and
+ if (sys.platform == 'win32' and sys.maxsize < 2**32 and
self.Backend is not CTypesBackend):
assert "double(__stdcall *)(double)" in str(ffi.typeof(m.sin))
else:
diff --git a/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_ffi_obj.py b/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_ffi_obj.py
--- a/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_ffi_obj.py
+++ b/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_ffi_obj.py
@@ -194,6 +194,11 @@
yp = ffi.new_handle([6, 4, 2])
assert ffi.from_handle(yp) == [6, 4, 2]
+def test_handle_unique():
+ ffi = _cffi1_backend.FFI()
+ assert ffi.new_handle(None) is not ffi.new_handle(None)
+ assert ffi.new_handle(None) != ffi.new_handle(None)
+
def test_ffi_cast():
ffi = _cffi1_backend.FFI()
assert ffi.cast("int(*)(int)", 0) == ffi.NULL
@@ -416,3 +421,37 @@
assert int(ffi.cast("_Bool", ffi.cast(type, 42))) == 1
assert int(ffi.cast("bool", ffi.cast(type, 42))) == 1
assert int(ffi.cast("_Bool", ffi.cast(type, 0))) == 0
+
+def test_init_once():
+ def do_init():
+ seen.append(1)
+ return 42
+ ffi = _cffi1_backend.FFI()
+ seen = []
+ for i in range(3):
+ res = ffi.init_once(do_init, "tag1")
+ assert res == 42
+ assert seen == [1]
+ for i in range(3):
+ res = ffi.init_once(do_init, "tag2")
+ assert res == 42
+ assert seen == [1, 1]
+
+def test_init_once_multithread():
+ import thread, time
+ def do_init():
+ print 'init!'
+ seen.append('init!')
+ time.sleep(1)
+ seen.append('init done')
+ print 'init done'
+ return 7
+ ffi = _cffi1_backend.FFI()
+ seen = []
+ for i in range(6):
+ def f():
+ res = ffi.init_once(do_init, "tag")
+ seen.append(res)
+ thread.start_new_thread(f, ())
+ time.sleep(1.5)
+ assert seen == ['init!', 'init done'] + 6 * [7]
diff --git a/pypy/module/test_lib_pypy/test_datetime.py b/pypy/module/test_lib_pypy/test_datetime.py
--- a/pypy/module/test_lib_pypy/test_datetime.py
+++ b/pypy/module/test_lib_pypy/test_datetime.py
@@ -170,14 +170,23 @@
self.value = value
def __int__(self):
return self.value
+ class SubInt(int): pass
+ class SubLong(long): pass
+ dt10 = datetime.datetime(10, 10, 10, 10, 10, 10, 10)
for xx in [10L,
decimal.Decimal(10),
decimal.Decimal('10.9'),
Number(10),
- Number(10L)]:
- assert datetime.datetime(10, 10, 10, 10, 10, 10, 10) == \
- datetime.datetime(xx, xx, xx, xx, xx, xx, xx)
+ Number(10L),
+ SubInt(10),
+ SubLong(10),
+ Number(SubInt(10)),
+ Number(SubLong(10))]:
+ dtxx = datetime.datetime(xx, xx, xx, xx, xx, xx, xx)
+ assert dt10 == dtxx
+ assert type(dtxx.month) is int
+ assert type(dtxx.second) is int
with py.test.raises(TypeError) as e:
datetime.datetime(10, 10, '10')
@@ -242,6 +251,70 @@
naive == aware
assert str(e.value) == "can't compare offset-naive and offset-aware times"
+ def test_future_types_newint(self):
+ try:
+ from future.types.newint import newint
+ except ImportError:
+ py.test.skip('requires future')
+
+ dt_from_ints = datetime.datetime(2015, 12, 31, 12, 34, 56)
+ dt_from_newints = datetime.datetime(newint(2015), newint(12), newint(31), newint(12), newint(34), newint(56))
+ dt_from_mixed = datetime.datetime(2015, newint(12), 31, newint(12), 34, newint(56))
+ assert dt_from_ints == dt_from_newints
+ assert dt_from_newints == dt_from_mixed
+ assert dt_from_mixed == dt_from_ints
+
+ d_from_int = datetime.date.fromtimestamp(1431216000)
+ d_from_newint = datetime.date.fromtimestamp(newint(1431216000))
+ assert d_from_int == d_from_newint
+
+ dt_from_int = datetime.datetime.fromtimestamp(1431216000)
+ dt_from_newint = datetime.datetime.fromtimestamp(newint(1431216000))
+ assert dt_from_int == dt_from_newint
+
+ dtu_from_int = datetime.datetime.utcfromtimestamp(1431216000)
+ dtu_from_newint = datetime.datetime.utcfromtimestamp(newint(1431216000))
+ assert dtu_from_int == dtu_from_newint
+
+ td_from_int = datetime.timedelta(16565)
+ tds_from_int = datetime.timedelta(seconds=1431216000)
+ td_from_newint = datetime.timedelta(newint(16565))
+ tds_from_newint = datetime.timedelta(seconds=newint(1431216000))
+ assert td_from_int == tds_from_int
+ assert td_from_int == td_from_newint
+ assert td_from_int == tds_from_newint
+ assert tds_from_int == td_from_newint
+ assert tds_from_int == tds_from_newint
+ assert td_from_newint == tds_from_newint
+
+ td_mul_int_int = td_from_int * 2
+ td_mul_int_newint = td_from_int * newint(2)
+ td_mul_newint_int = td_from_newint * 2
+ td_mul_newint_newint = td_from_newint * newint(2)
+ assert td_mul_int_int == td_mul_int_newint
+ assert td_mul_int_int == td_mul_newint_int
+ assert td_mul_int_int == td_mul_newint_newint
+ assert td_mul_int_newint == td_mul_newint_int
+ assert td_mul_int_newint == td_mul_newint_newint
+ assert td_mul_newint_int == td_mul_newint_newint
+
+ td_div_int_int = td_from_int / 3600
+ td_div_int_newint = td_from_int / newint(3600)
+ td_div_newint_int = td_from_newint / 3600
+ td_div_newint_newint = td_from_newint / newint(3600)
+ assert td_div_int_int == td_div_int_newint
+ assert td_div_int_int == td_div_newint_int
+ assert td_div_int_int == td_div_newint_newint
+ assert td_div_int_newint == td_div_newint_int
+ assert td_div_int_newint == td_div_newint_newint
+ assert td_div_newint_int == td_div_newint_newint
+
+ def test_return_types(self):
+ td = datetime.timedelta(5)
+ assert type(td.total_seconds()) is float
+ class sub(datetime.timedelta): pass
+ assert type(+sub()) is datetime.timedelta
+
class TestDatetimeHost(BaseTestDatetime):
def setup_class(cls):
diff --git a/rpython/jit/backend/llgraph/runner.py b/rpython/jit/backend/llgraph/runner.py
--- a/rpython/jit/backend/llgraph/runner.py
+++ b/rpython/jit/backend/llgraph/runner.py
@@ -638,9 +638,18 @@
return array.getlength()
def bh_getarrayitem_gc(self, a, index, descr):
- a = support.cast_arg(lltype.Ptr(descr.A), a)
+ assert index >= 0
+ if descr.A is descr.OUTERA:
+ a = support.cast_arg(lltype.Ptr(descr.A), a)
+ else:
+ # we use rffi.cast instead of support.cast_arg because the types
+ # might not be "compatible" enough from the lltype point of
+ # view. In particular, this happens when we use
+ # str_storage_getitem, in which an rpy_string is casted to
+ # rpy_string_as_Signed (or similar)
+ a = rffi.cast(lltype.Ptr(descr.OUTERA), a)
+ a = getattr(a, descr.OUTERA._arrayfld)
array = a._obj
- assert index >= 0
return support.cast_result(descr.A.OF, array.getitem(index))
bh_getarrayitem_gc_pure_i = bh_getarrayitem_gc
diff --git a/rpython/jit/backend/x86/test/test_strstorage.py b/rpython/jit/backend/x86/test/test_strstorage.py
new file mode 100644
--- /dev/null
+++ b/rpython/jit/backend/x86/test/test_strstorage.py
@@ -0,0 +1,8 @@
+from rpython.jit.backend.x86.test.test_basic import Jit386Mixin
+from rpython.jit.metainterp.test.test_strstorage import TestStrStorage as _TestStrStorage
+
+
+class TestStrStorage(Jit386Mixin, _TestStrStorage):
+ # for the individual tests see
+ # ====> ../../../metainterp/test/test_strstorage.py
+ pass
diff --git a/rpython/jit/codewriter/jtransform.py b/rpython/jit/codewriter/jtransform.py
--- a/rpython/jit/codewriter/jtransform.py
+++ b/rpython/jit/codewriter/jtransform.py
@@ -1008,12 +1008,11 @@
return SpaceOperation('getarrayitem_gc_i',
[op.args[0], v_index, bytearraydescr],
op.result)
- else:
+ elif op.result.concretetype is lltype.Void:
+ return
+ elif isinstance(op.args[0].concretetype.TO, lltype.GcArray):
+ # special-case 1: GcArray of Struct
v_inst, v_index, c_field = op.args
- if op.result.concretetype is lltype.Void:
- return
- # only GcArray of Struct supported
- assert isinstance(v_inst.concretetype.TO, lltype.GcArray)
STRUCT = v_inst.concretetype.TO.OF
assert isinstance(STRUCT, lltype.Struct)
descr = self.cpu.interiorfielddescrof(v_inst.concretetype.TO,
@@ -1022,6 +1021,20 @@
kind = getkind(op.result.concretetype)[0]
return SpaceOperation('getinteriorfield_gc_%s' % kind, args,
op.result)
+ elif isinstance(op.args[0].concretetype.TO, lltype.GcStruct):
+ # special-case 2: GcStruct with Array field
+ v_inst, c_field, v_index = op.args
+ STRUCT = v_inst.concretetype.TO
+ ARRAY = getattr(STRUCT, c_field.value)
+ assert isinstance(ARRAY, lltype.Array)
+ arraydescr = self.cpu.arraydescrof(STRUCT)
+ kind = getkind(op.result.concretetype)[0]
+ assert kind in ('i', 'f')
+ return SpaceOperation('getarrayitem_gc_%s' % kind,
+ [op.args[0], v_index, arraydescr],
+ op.result)
+ else:
+ assert False, 'not supported'
def rewrite_op_setinteriorfield(self, op):
assert len(op.args) == 4
@@ -1130,10 +1143,13 @@
def rewrite_op_force_cast(self, op):
v_arg = op.args[0]
v_result = op.result
- assert not self._is_gc(v_arg)
-
if v_arg.concretetype == v_result.concretetype:
return
+ elif self._is_gc(v_arg) and self._is_gc(v_result):
+ # cast from GC to GC is always fine
+ return
+ else:
+ assert not self._is_gc(v_arg)
float_arg = v_arg.concretetype in [lltype.Float, lltype.SingleFloat]
float_res = v_result.concretetype in [lltype.Float, lltype.SingleFloat]
diff --git a/rpython/jit/metainterp/optimizeopt/heap.py b/rpython/jit/metainterp/optimizeopt/heap.py
--- a/rpython/jit/metainterp/optimizeopt/heap.py
+++ b/rpython/jit/metainterp/optimizeopt/heap.py
@@ -535,10 +535,16 @@
cf.do_setfield(self, op)
def optimize_GETARRAYITEM_GC_I(self, op):
+ # When using str_storage_getitem it might happen that op.getarg(0) is
+ # a virtual string, NOT an array. In that case, we cannot cache the
+ # getarrayitem as if it were an array, obviously. In theory we could
+ # improve by writing special code to interpter the buffer of the
+ # virtual string as if it were an array, but it looks complicate,
+ # fragile and not worth it.
arrayinfo = self.ensure_ptr_info_arg0(op)
indexb = self.getintbound(op.getarg(1))
cf = None
- if indexb.is_constant():
+ if indexb.is_constant() and not arrayinfo.is_vstring():
index = indexb.getint()
arrayinfo.getlenbound(None).make_gt_const(index)
# use the cache on (arraydescr, index), which is a constant
@@ -555,7 +561,7 @@
self.make_nonnull(op.getarg(0))
self.emit_operation(op)
# the remember the result of reading the array item
- if cf is not None:
+ if cf is not None and not arrayinfo.is_vstring():
arrayinfo.setitem(op.getdescr(), indexb.getint(),
self.get_box_replacement(op.getarg(0)),
self.get_box_replacement(op), cf,
diff --git a/rpython/jit/metainterp/optimizeopt/info.py b/rpython/jit/metainterp/optimizeopt/info.py
--- a/rpython/jit/metainterp/optimizeopt/info.py
+++ b/rpython/jit/metainterp/optimizeopt/info.py
@@ -24,6 +24,9 @@
def is_virtual(self):
return False
+ def is_vstring(self):
+ return False
+
def is_precise(self):
return False
diff --git a/rpython/jit/metainterp/optimizeopt/unroll.py b/rpython/jit/metainterp/optimizeopt/unroll.py
--- a/rpython/jit/metainterp/optimizeopt/unroll.py
+++ b/rpython/jit/metainterp/optimizeopt/unroll.py
@@ -314,9 +314,16 @@
args, virtuals = target_virtual_state.make_inputargs_and_virtuals(
args, self.optimizer)
short_preamble = target_token.short_preamble
- extra = self.inline_short_preamble(args + virtuals, args,
- short_preamble, self.optimizer.patchguardop,
- target_token, label_op)
+ try:
+ extra = self.inline_short_preamble(args + virtuals, args,
+ short_preamble, self.optimizer.patchguardop,
+ target_token, label_op)
+ except KeyError:
+ # SHOULD NOT OCCUR BUT DOES: WHY?? issue #2185
+ self.optimizer.metainterp_sd.logger_ops.log_short_preamble([],
+ short_preamble, {})
+ raise
+
self.send_extra_operation(jump_op.copy_and_change(rop.JUMP,
args=args + extra,
descr=target_token))
diff --git a/rpython/jit/metainterp/optimizeopt/virtualize.py b/rpython/jit/metainterp/optimizeopt/virtualize.py
--- a/rpython/jit/metainterp/optimizeopt/virtualize.py
+++ b/rpython/jit/metainterp/optimizeopt/virtualize.py
@@ -271,8 +271,10 @@
self.emit_operation(op)
def optimize_GETARRAYITEM_GC_I(self, op):
+ # When using str_storage_getitem we op.getarg(0) is a string, NOT an
+ # array, hence the check. In that case, it will be forced
opinfo = self.getptrinfo(op.getarg(0))
- if opinfo and opinfo.is_virtual():
+ if opinfo and opinfo.is_virtual() and not opinfo.is_vstring():
indexbox = self.get_constant_box(op.getarg(1))
if indexbox is not None:
item = opinfo.getitem(op.getdescr(), indexbox.getint())
diff --git a/rpython/jit/metainterp/optimizeopt/vstring.py b/rpython/jit/metainterp/optimizeopt/vstring.py
--- a/rpython/jit/metainterp/optimizeopt/vstring.py
+++ b/rpython/jit/metainterp/optimizeopt/vstring.py
@@ -62,6 +62,9 @@
self.mode = mode
self.length = length
+ def is_vstring(self):
+ return True
+
def getlenbound(self, mode):
from rpython.jit.metainterp.optimizeopt import intutils
diff --git a/rpython/jit/metainterp/test/test_strstorage.py b/rpython/jit/metainterp/test/test_strstorage.py
new file mode 100644
--- /dev/null
+++ b/rpython/jit/metainterp/test/test_strstorage.py
@@ -0,0 +1,53 @@
+import py
+import sys
+import struct
+from rpython.rtyper.lltypesystem import lltype, rffi
+from rpython.rlib.strstorage import str_storage_getitem
+from rpython.rlib.test.test_strstorage import BaseStrStorageTest
+from rpython.jit.codewriter import longlong
+from rpython.jit.metainterp.history import getkind
+from rpython.jit.metainterp.test.support import LLJitMixin
+
+class TestStrStorage(BaseStrStorageTest, LLJitMixin):
+
+ # for the individual tests see
+ # ====> ../../../rlib/test/test_strstorage.py
+
+ def str_storage_getitem(self, TYPE, buf, offset):
+ def f():
+ return str_storage_getitem(TYPE, buf, offset)
+ res = self.interp_operations(f, [], supports_singlefloats=True)
+ #
+ kind = getkind(TYPE)[0] # 'i' or 'f'
+ self.check_operations_history({'getarrayitem_gc_%s' % kind: 1,
+ 'finish': 1})
+ #
+ if TYPE == lltype.SingleFloat:
+ # interp_operations returns the int version of r_singlefloat, but
+ # our tests expects to receive an r_singlefloat: let's convert it
+ # back!
+ return longlong.int2singlefloat(res)
+ return res
+
+ def str_storage_supported(self, TYPE):
+ py.test.skip('this is not a JIT test')
+
+ def test_force_virtual_str_storage(self):
+ byteorder = sys.byteorder
+ size = rffi.sizeof(lltype.Signed)
+ def f(val):
+ if byteorder == 'little':
+ x = chr(val) + '\x00'*(size-1)
+ else:
+ x = '\x00'*(size-1) + chr(val)
+ return str_storage_getitem(lltype.Signed, x, 0)
+ res = self.interp_operations(f, [42], supports_singlefloats=True)
+ assert res == 42
+ self.check_operations_history({
+ 'newstr': 1, # str forcing
+ 'strsetitem': 1, # str forcing
+ 'call_pure_r': 1, # str forcing (copystrcontent)
+ 'guard_no_exception': 1, # str forcing
+ 'getarrayitem_gc_i': 1, # str_storage_getitem
+ 'finish': 1
+ })
diff --git a/rpython/rlib/buffer.py b/rpython/rlib/buffer.py
--- a/rpython/rlib/buffer.py
+++ b/rpython/rlib/buffer.py
@@ -22,6 +22,14 @@
# May be overridden.
return self.getslice(0, self.getlength(), 1, self.getlength())
+ def as_str_and_offset_maybe(self):
+ """
+ If the buffer is backed by a string, return a pair (string, offset), where
+ offset is the offset inside the string where the buffer start.
+ Else, return (None, 0).
+ """
+ return None, 0
+
def getitem(self, index):
"Returns the index'th character in the buffer."
raise NotImplementedError # Must be overriden. No bounds checks.
@@ -66,6 +74,9 @@
def as_str(self):
return self.value
+ def as_str_and_offset_maybe(self):
+ return self.value, 0
+
def getitem(self, index):
return self.value[index]
@@ -99,6 +110,12 @@
else:
return 0
+ def as_str_and_offset_maybe(self):
+ string, offset = self.buffer.as_str_and_offset_maybe()
+ if string is not None:
+ return string, offset+self.offset
+ return None, 0
+
def getitem(self, index):
return self.buffer.getitem(self.offset + index)
diff --git a/rpython/rlib/rstruct/nativefmttable.py b/rpython/rlib/rstruct/nativefmttable.py
--- a/rpython/rlib/rstruct/nativefmttable.py
+++ b/rpython/rlib/rstruct/nativefmttable.py
@@ -8,15 +8,15 @@
from rpython.rlib.objectmodel import specialize
from rpython.rlib.rarithmetic import r_singlefloat, widen
from rpython.rlib.rstruct import standardfmttable as std
+from rpython.rlib.rstruct.standardfmttable import native_is_bigendian
from rpython.rlib.rstruct.error import StructError
from rpython.rlib.unroll import unrolling_iterable
+from rpython.rlib.strstorage import str_storage_getitem
from rpython.rtyper.lltypesystem import lltype, rffi
from rpython.rtyper.tool import rffi_platform
from rpython.translator.tool.cbuild import ExternalCompilationInfo
-native_is_bigendian = struct.pack("=i", 1) == struct.pack(">i", 1)
-
native_fmttable = {
'x': std.standard_fmttable['x'],
'c': std.standard_fmttable['c'],
@@ -27,9 +27,6 @@
# ____________________________________________________________
-double_buf = lltype.malloc(rffi.DOUBLEP.TO, 1, flavor='raw', immortal=True)
-float_buf = lltype.malloc(rffi.FLOATP.TO, 1, flavor='raw', immortal=True)
-
range_8_unroll = unrolling_iterable(list(reversed(range(8))))
range_4_unroll = unrolling_iterable(list(reversed(range(4))))
@@ -45,14 +42,6 @@
fmtiter.result.append(chr(value & 0xff))
value >>= 8
- at specialize.argtype(0)
-def unpack_double(fmtiter):
- input = fmtiter.read(sizeof_double)
- p = rffi.cast(rffi.CCHARP, double_buf)
- for i in range(sizeof_double):
- p[i] = input[i]
- doubleval = double_buf[0]
- fmtiter.appendobj(doubleval)
def pack_float(fmtiter):
doubleval = fmtiter.accept_float_arg()
@@ -68,16 +57,6 @@
fmtiter.result.append(chr(value & 0xff))
value >>= 8
- at specialize.argtype(0)
-def unpack_float(fmtiter):
- input = fmtiter.read(sizeof_float)
- p = rffi.cast(rffi.CCHARP, float_buf)
- for i in range(sizeof_float):
- p[i] = input[i]
- floatval = float_buf[0]
- doubleval = float(floatval)
- fmtiter.appendobj(doubleval)
-
# ____________________________________________________________
#
# Use rffi_platform to get the native sizes and alignments from the C compiler
@@ -134,10 +113,10 @@
if fmtchar == 'f':
pack = pack_float
- unpack = unpack_float
+ unpack = std.unpack_float
elif fmtchar == 'd':
pack = pack_double
- unpack = unpack_double
+ unpack = std.unpack_double
elif fmtchar == '?':
pack = std.pack_bool
unpack = std.unpack_bool
diff --git a/rpython/rlib/rstruct/runpack.py b/rpython/rlib/rstruct/runpack.py
--- a/rpython/rlib/rstruct/runpack.py
+++ b/rpython/rlib/rstruct/runpack.py
@@ -38,6 +38,12 @@
def appendobj(self, value):
self.value = value
+
+ def get_buffer_as_string_maybe(self):
+ return self.mr.input, self.mr.inputpos
+
+ def skip(self, size):
+ self.read(size) # XXX, could avoid taking the slice
ReaderForPos.__name__ = 'ReaderForPos%d' % pos
return ReaderForPos
@@ -88,13 +94,6 @@
exec source.compile() in miniglobals
self.unpack = miniglobals['unpack'] # override not-rpython version
- def unpack(self, s):
- # NOT_RPYTHON
- res = unpack(self.fmt, s)
- if len(res) == 1:
- return res[0]
- return res
-
def _freeze_(self):
assert self.formats
self._create_unpacking_func()
@@ -103,6 +102,7 @@
def create_unpacker(unpack_str):
fmtiter = FrozenUnpackIterator(unpack_str)
fmtiter.interpret(unpack_str)
+ assert fmtiter._freeze_()
return fmtiter
create_unpacker._annspecialcase_ = 'specialize:memo'
diff --git a/rpython/rlib/rstruct/standardfmttable.py b/rpython/rlib/rstruct/standardfmttable.py
--- a/rpython/rlib/rstruct/standardfmttable.py
+++ b/rpython/rlib/rstruct/standardfmttable.py
@@ -12,7 +12,12 @@
from rpython.rlib.rstruct import ieee
from rpython.rlib.rstruct.error import StructError, StructOverflowError
from rpython.rlib.unroll import unrolling_iterable
+from rpython.rlib.strstorage import str_storage_getitem, str_storage_supported
+from rpython.rlib import rarithmetic
+from rpython.rtyper.lltypesystem import rffi
+native_is_bigendian = struct.pack("=i", 1) == struct.pack(">i", 1)
+native_is_ieee754 = float.__getformat__('double').startswith('IEEE')
def pack_pad(fmtiter, count):
fmtiter.result.append_multiple_char('\x00', count)
@@ -126,6 +131,24 @@
# ____________________________________________________________
+USE_FASTPATH = True # set to False by some tests
+ALLOW_SLOWPATH = True # set to False by some tests
+
+class CannotUnpack(Exception):
+ pass
+
+ at specialize.memo()
+def unpack_fastpath(TYPE):
+ @specialize.argtype(0)
+ def do_unpack_fastpath(fmtiter):
+ size = rffi.sizeof(TYPE)
+ strbuf, pos = fmtiter.get_buffer_as_string_maybe()
+ if strbuf is None or pos % size != 0 or not USE_FASTPATH:
+ raise CannotUnpack
+ fmtiter.skip(size)
+ return str_storage_getitem(TYPE, strbuf, pos)
+ return do_unpack_fastpath
+
@specialize.argtype(0)
def unpack_pad(fmtiter, count):
fmtiter.read(count)
@@ -153,15 +176,54 @@
end = count
fmtiter.appendobj(data[1:end])
-def make_float_unpacker(size):
+def make_ieee_unpacker(TYPE):
@specialize.argtype(0)
- def unpacker(fmtiter):
- data = fmtiter.read(size)
- fmtiter.appendobj(ieee.unpack_float(data, fmtiter.bigendian))
- return unpacker
+ def unpack_ieee(fmtiter):
+ size = rffi.sizeof(TYPE)
+ if fmtiter.bigendian != native_is_bigendian or not native_is_ieee754:
+ # fallback to the very slow unpacking code in ieee.py
+ data = fmtiter.read(size)
+ fmtiter.appendobj(ieee.unpack_float(data, fmtiter.bigendian))
+ return
+ if not str_storage_supported(TYPE):
+ # this happens e.g. on win32 and ARM32: we cannot read the string
+ # content as an array of doubles because it's not properly
+ # aligned. But we can read a longlong and convert to float
+ assert TYPE == rffi.DOUBLE
+ assert rffi.sizeof(TYPE) == 8
+ return unpack_longlong2float(fmtiter)
+ try:
+ # fast path
+ val = unpack_fastpath(TYPE)(fmtiter)
+ except CannotUnpack:
+ # slow path, take the slice
+ input = fmtiter.read(size)
+ val = str_storage_getitem(TYPE, input, 0)
+ fmtiter.appendobj(float(val))
+ return unpack_ieee
+
+ at specialize.argtype(0)
+def unpack_longlong2float(fmtiter):
+ from rpython.rlib.rstruct.runpack import runpack
+ from rpython.rlib.longlong2float import longlong2float
+ s = fmtiter.read(8)
+ llval = runpack('q', s) # this is a bit recursive, I know
+ doubleval = longlong2float(llval)
+ fmtiter.appendobj(doubleval)
+
+
+unpack_double = make_ieee_unpacker(rffi.DOUBLE)
+unpack_float = make_ieee_unpacker(rffi.FLOAT)
# ____________________________________________________________
+def get_rffi_int_type(size, signed):
+ for TYPE in rffi.platform.numbertype_to_rclass:
+ if (rffi.sizeof(TYPE) == size and
+ rarithmetic.is_signed_integer_type(TYPE) == signed):
+ return TYPE
+ raise KeyError("Cannot find an int type size=%d, signed=%d" % (size, signed))
+
def make_int_unpacker(size, signed, _memo={}):
try:
return _memo[size, signed]
@@ -180,9 +242,30 @@
else:
inttype = r_ulonglong
unroll_range_size = unrolling_iterable(range(size))
+ TYPE = get_rffi_int_type(size, signed)
+
+ @specialize.argtype(0)
+ def unpack_int_fastpath_maybe(fmtiter):
+ if fmtiter.bigendian != native_is_bigendian or not str_storage_supported(TYPE):
+ return False
+ try:
+ intvalue = unpack_fastpath(TYPE)(fmtiter)
+ except CannotUnpack:
+ return False
+ if not signed and size < native_int_size:
+ intvalue = rarithmetic.intmask(intvalue)
+ intvalue = inttype(intvalue)
+ fmtiter.appendobj(intvalue)
+ return True
@specialize.argtype(0)
def unpack_int(fmtiter):
+ if unpack_int_fastpath_maybe(fmtiter):
+ return
+ # slow path
+ if not ALLOW_SLOWPATH:
+ # we enter here only on some tests
+ raise ValueError("fastpath not taken :(")
intvalue = inttype(0)
s = fmtiter.read(size)
idx = 0
@@ -217,9 +300,9 @@
'p':{ 'size' : 1, 'pack' : pack_pascal, 'unpack' : unpack_pascal,
'needcount' : True },
'f':{ 'size' : 4, 'pack' : make_float_packer(4),
- 'unpack' : make_float_unpacker(4)},
+ 'unpack' : unpack_float},
'd':{ 'size' : 8, 'pack' : make_float_packer(8),
- 'unpack' : make_float_unpacker(8)},
+ 'unpack' : unpack_double},
'?':{ 'size' : 1, 'pack' : pack_bool, 'unpack' : unpack_bool},
}
diff --git a/rpython/rlib/rstruct/test/test_runpack.py b/rpython/rlib/rstruct/test/test_runpack.py
--- a/rpython/rlib/rstruct/test/test_runpack.py
+++ b/rpython/rlib/rstruct/test/test_runpack.py
@@ -1,5 +1,6 @@
from rpython.rtyper.test.tool import BaseRtypingTest
from rpython.rlib.rstruct.runpack import runpack
+from rpython.rlib.rstruct import standardfmttable
from rpython.rlib.rarithmetic import LONG_BIT
import struct
@@ -37,3 +38,63 @@
return runpack(">d", "testtest")
assert fn() == struct.unpack(">d", "testtest")[0]
assert self.interpret(fn, []) == struct.unpack(">d", "testtest")[0]
+
+ def test_native_floats(self):
+ """
+ Check the 'd' and 'f' format characters on native packing.
+ """
+ d_data = struct.pack("df", 12.34, 12.34)
+ def fn():
+ d, f = runpack("@df", d_data)
+ return d, f
+ #
+ # direct test
+ d, f = fn()
+ assert d == 12.34 # no precision lost
+ assert f != 12.34 # precision lost
+ assert abs(f - 12.34) < 1E-6
+ #
+ # translated test
+ res = self.interpret(fn, [])
+ d = res.item0
+ f = res.item1 # convert from r_singlefloat
+ assert d == 12.34 # no precision lost
+ assert f != 12.34 # precision lost
+ assert abs(f - 12.34) < 1E-6
+
+ def test_unpack_standard_little(self):
+ def unpack(fmt, data):
+ def fn():
+ return runpack(fmt, data)
+ return self.interpret(fn, [])
+ #
+ assert unpack("<i", 'DCBA') == 0x41424344
+ assert unpack("<i", '\xfd\xff\xff\xff') == -3
+ assert unpack("<i", '\x00\x00\x00\x80') == -2147483648
+ assert unpack("<I", 'DCB\x81') == 0x81424344
+ assert unpack("<q", 'HGFEDCBA') == 0x4142434445464748
+ assert unpack("<q", 'HHIJKLM\xbe') == -0x41B2B3B4B5B6B7B8
+ assert unpack("<Q", 'HGFEDCB\x81') == 0x8142434445464748
+
+ def test_unpack_standard_big(self):
+ def unpack(fmt, data):
+ def fn():
+ return runpack(fmt, data)
+ return self.interpret(fn, [])
+ #
+ assert unpack(">i", 'ABCD') == 0x41424344
+ assert unpack(">i", '\xff\xff\xff\xfd') == -3
+ assert unpack(">i", '\x80\x00\x00\x00') == -2147483648
+ assert unpack(">I", '\x81BCD') == 0x81424344
+ assert unpack(">q", 'ABCDEFGH') == 0x4142434445464748
+ assert unpack(">q", '\xbeMLKJIHH') == -0x41B2B3B4B5B6B7B8
+ assert unpack(">Q", '\x81BCDEFGH') == 0x8142434445464748
+
+
+class TestNoFastPath(TestRStruct):
+
+ def setup_method(self, meth):
+ standardfmttable.USE_FASTPATH = False
+
+ def teardown_method(self, meth):
+ standardfmttable.USE_FASTPATH = True
diff --git a/rpython/rlib/strstorage.py b/rpython/rlib/strstorage.py
new file mode 100644
--- /dev/null
+++ b/rpython/rlib/strstorage.py
@@ -0,0 +1,62 @@
+# Support for str_storage: i.e., reading primitive types out of RPython string
+#
+# There are various possible ways to implement it, however not all of them are
+# easily supported by the JIT:
+#
+# 1. use _get_raw_str_buf and cast the chars buffer to RAW_STORAGE_PTR: this
+# works well without the JIT, but the cast to RAW_STORAGE_PTR needs to
+# happen inside a short "no GC" section (like the one in
+# rstr.py:copy_string_contents), which has no chance to work during
+# tracing
+#
+# 2. use llop.raw_load: despite the name, llop.raw_load DOES support reading
+# from GC pointers. However:
+#
+# a. we would like to use a CompositeOffset as the offset (using the
+# same logic as in rstr.py:_get_raw_str_buf), but this is not (yet)
+# supported before translation: it works only if you pass an actual
+# integer
+#
+# b. raw_load from a GC pointer is not (yet) supported by the
+# JIT. There are plans to introduce a gc_load operation: when it
+# will be there, we could fix the issue above and actually use it to
+# implement str_storage_getitem
+#
+# 3. the actual solution: cast rpy_string to a GcStruct which has the very
+# same layout, with the only difference that its 'chars' field is no
+# longer an Array(Char) but e.e. an Array(Signed). Then, we just need to
+# read the appropriate index into the array
+
+from rpython.rtyper.lltypesystem import lltype, rffi, llmemory
+from rpython.rtyper.lltypesystem.rstr import STR, _get_raw_str_buf
+from rpython.rtyper.annlowlevel import llstr
+from rpython.rlib.objectmodel import specialize, we_are_translated
+
+ at specialize.memo()
+def _rpy_string_as_type(TP):
More information about the pypy-commit
mailing list