[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