PageRenderTime 50ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/rpython/rtyper/lltypesystem/llarena.py

https://bitbucket.org/pypy/pypy/
Python | 597 lines | 511 code | 40 blank | 46 comment | 64 complexity | 2ce16f747a8f586c6c37b4035cf7202c MD5 | raw file
Possible License(s): AGPL-3.0, BSD-3-Clause, Apache-2.0
  1. import array
  2. from rpython.rtyper.lltypesystem import llmemory
  3. from rpython.rlib.rarithmetic import is_valid_int
  4. from rpython.rtyper.lltypesystem.lloperation import llop
  5. # An "arena" is a large area of memory which can hold a number of
  6. # objects, not necessarily all of the same type or size. It's used by
  7. # some of our framework GCs. Addresses that point inside arenas support
  8. # direct arithmetic: adding and subtracting integers, and taking the
  9. # difference of two addresses. When not translated to C, the arena
  10. # keeps track of which bytes are used by what object to detect GC bugs;
  11. # it internally uses raw_malloc_usage() to estimate the number of bytes
  12. # it needs to reserve.
  13. class ArenaError(Exception):
  14. pass
  15. class Arena(object):
  16. _count_arenas = 0
  17. def __init__(self, nbytes, zero):
  18. Arena._count_arenas += 1
  19. self._arena_index = Arena._count_arenas
  20. self.nbytes = nbytes
  21. self.usagemap = array.array('c')
  22. self.objectptrs = {} # {offset: ptr-to-container}
  23. self.objectsizes = {} # {offset: size}
  24. self.freed = False
  25. self.protect_inaccessible = None
  26. self.reset(zero)
  27. def __repr__(self):
  28. return '<Arena #%d [%d bytes]>' % (self._arena_index, self.nbytes)
  29. def reset(self, zero, start=0, size=None):
  30. self.check()
  31. if size is None:
  32. stop = self.nbytes
  33. else:
  34. stop = start + llmemory.raw_malloc_usage(size)
  35. assert 0 <= start <= stop <= self.nbytes
  36. for offset, ptr in self.objectptrs.items():
  37. size = self.objectsizes[offset]
  38. if offset < start: # object is before the cleared area
  39. assert offset + size <= start, "object overlaps cleared area"
  40. elif offset + size > stop: # object is after the cleared area
  41. assert offset >= stop, "object overlaps cleared area"
  42. else:
  43. obj = ptr._obj
  44. obj.__arena_location__[0] = False # no longer valid
  45. del self.objectptrs[offset]
  46. del self.objectsizes[offset]
  47. obj._free()
  48. if zero in (1, 2):
  49. initialbyte = "0"
  50. else:
  51. initialbyte = "#"
  52. self.usagemap[start:stop] = array.array('c', initialbyte*(stop-start))
  53. def check(self):
  54. if self.freed:
  55. raise ArenaError("arena was already freed")
  56. if self.protect_inaccessible is not None:
  57. raise ArenaError("arena is currently arena_protect()ed")
  58. def _getid(self):
  59. address, length = self.usagemap.buffer_info()
  60. return address
  61. def getaddr(self, offset):
  62. if not (0 <= offset <= self.nbytes):
  63. raise ArenaError("Address offset is outside the arena")
  64. return fakearenaaddress(self, offset)
  65. def allocate_object(self, offset, size, letter='x'):
  66. self.check()
  67. bytes = llmemory.raw_malloc_usage(size)
  68. if offset + bytes > self.nbytes:
  69. raise ArenaError("object overflows beyond the end of the arena")
  70. zero = True
  71. for c in self.usagemap[offset:offset+bytes]:
  72. if c == '0':
  73. pass
  74. elif c == '#':
  75. zero = False
  76. else:
  77. raise ArenaError("new object overlaps a previous object")
  78. assert offset not in self.objectptrs
  79. addr2 = size._raw_malloc([], zero=zero)
  80. pattern = letter.upper() + letter*(bytes-1)
  81. self.usagemap[offset:offset+bytes] = array.array('c', pattern)
  82. self.setobject(addr2, offset, bytes)
  83. # common case: 'size' starts with a GCHeaderOffset. In this case
  84. # we can also remember that the real object starts after the header.
  85. while isinstance(size, RoundedUpForAllocation):
  86. size = size.basesize
  87. if (isinstance(size, llmemory.CompositeOffset) and
  88. isinstance(size.offsets[0], llmemory.GCHeaderOffset)):
  89. objaddr = addr2 + size.offsets[0]
  90. hdrbytes = llmemory.raw_malloc_usage(size.offsets[0])
  91. objoffset = offset + hdrbytes
  92. self.setobject(objaddr, objoffset, bytes - hdrbytes)
  93. return addr2
  94. def setobject(self, objaddr, offset, bytes):
  95. assert bytes > 0, ("llarena does not support GcStructs with no field"
  96. " or empty arrays")
  97. assert offset not in self.objectptrs
  98. self.objectptrs[offset] = objaddr.ptr
  99. self.objectsizes[offset] = bytes
  100. container = objaddr.ptr._obj
  101. container.__arena_location__ = [True, self, offset]
  102. def shrink_obj(self, offset, newsize):
  103. oldbytes = self.objectsizes[offset]
  104. newbytes = llmemory.raw_malloc_usage(newsize)
  105. assert newbytes <= oldbytes
  106. # fix self.objectsizes
  107. for i in range(newbytes):
  108. adr = offset + i
  109. if adr in self.objectsizes:
  110. assert self.objectsizes[adr] == oldbytes - i
  111. self.objectsizes[adr] = newbytes - i
  112. # fix self.usagemap
  113. for i in range(offset + newbytes, offset + oldbytes):
  114. assert self.usagemap[i] == 'x'
  115. self.usagemap[i] = '#'
  116. def mark_freed(self):
  117. self.freed = True # this method is a hook for tests
  118. def set_protect(self, inaccessible):
  119. if inaccessible:
  120. assert self.protect_inaccessible is None
  121. saved = []
  122. for ptr in self.objectptrs.values():
  123. obj = ptr._obj
  124. saved.append((obj, obj._protect()))
  125. self.protect_inaccessible = saved
  126. else:
  127. assert self.protect_inaccessible is not None
  128. saved = self.protect_inaccessible
  129. for obj, storage in saved:
  130. obj._unprotect(storage)
  131. self.protect_inaccessible = None
  132. class fakearenaaddress(llmemory.fakeaddress):
  133. def __init__(self, arena, offset):
  134. self.arena = arena
  135. self.offset = offset
  136. def _getptr(self):
  137. try:
  138. return self.arena.objectptrs[self.offset]
  139. except KeyError:
  140. self.arena.check()
  141. raise ArenaError("don't know yet what type of object "
  142. "is at offset %d" % (self.offset,))
  143. ptr = property(_getptr)
  144. def __repr__(self):
  145. return '<arenaaddr %s + %d>' % (self.arena, self.offset)
  146. def __add__(self, other):
  147. if is_valid_int(other):
  148. position = self.offset + other
  149. elif isinstance(other, llmemory.AddressOffset):
  150. # this is really some Do What I Mean logic. There are two
  151. # possible meanings: either we want to go past the current
  152. # object in the arena, or we want to take the address inside
  153. # the current object. Try to guess...
  154. bytes = llmemory.raw_malloc_usage(other)
  155. if (self.offset in self.arena.objectsizes and
  156. bytes < self.arena.objectsizes[self.offset]):
  157. # looks like we mean "inside the object"
  158. return llmemory.fakeaddress.__add__(self, other)
  159. position = self.offset + bytes
  160. else:
  161. return NotImplemented
  162. return self.arena.getaddr(position)
  163. def __sub__(self, other):
  164. if isinstance(other, llmemory.AddressOffset):
  165. other = llmemory.raw_malloc_usage(other)
  166. if is_valid_int(other):
  167. return self.arena.getaddr(self.offset - other)
  168. if isinstance(other, fakearenaaddress):
  169. if self.arena is not other.arena:
  170. raise ArenaError("The two addresses are from different arenas")
  171. return self.offset - other.offset
  172. return NotImplemented
  173. def __nonzero__(self):
  174. return True
  175. def compare_with_fakeaddr(self, other):
  176. other = other._fixup()
  177. if not other:
  178. return None, None
  179. obj = other.ptr._obj
  180. innerobject = False
  181. while not getattr(obj, '__arena_location__', (False,))[0]:
  182. obj = obj._parentstructure()
  183. if obj is None:
  184. return None, None # not found in the arena
  185. innerobject = True
  186. _, arena, offset = obj.__arena_location__
  187. if innerobject:
  188. # 'obj' is really inside the object allocated from the arena,
  189. # so it's likely that its address "should be" a bit larger than
  190. # what 'offset' says.
  191. # We could estimate the correct offset but it's a bit messy;
  192. # instead, let's check the answer doesn't depend on it
  193. if self.arena is arena:
  194. objectsize = arena.objectsizes[offset]
  195. if offset < self.offset < offset+objectsize:
  196. raise AssertionError(
  197. "comparing an inner address with a "
  198. "fakearenaaddress that points in the "
  199. "middle of the same object")
  200. offset += objectsize // 2 # arbitrary
  201. return arena, offset
  202. def __eq__(self, other):
  203. if isinstance(other, fakearenaaddress):
  204. arena = other.arena
  205. offset = other.offset
  206. elif isinstance(other, llmemory.fakeaddress):
  207. arena, offset = self.compare_with_fakeaddr(other)
  208. else:
  209. return llmemory.fakeaddress.__eq__(self, other)
  210. return self.arena is arena and self.offset == offset
  211. def __lt__(self, other):
  212. if isinstance(other, fakearenaaddress):
  213. arena = other.arena
  214. offset = other.offset
  215. elif isinstance(other, llmemory.fakeaddress):
  216. arena, offset = self.compare_with_fakeaddr(other)
  217. if arena is None:
  218. return False # self < other-not-in-any-arena => False
  219. # (arbitrarily)
  220. else:
  221. raise TypeError("comparing a %s and a %s" % (
  222. self.__class__.__name__, other.__class__.__name__))
  223. if self.arena is arena:
  224. return self.offset < offset
  225. else:
  226. return self.arena._getid() < arena._getid()
  227. def _cast_to_int(self, symbolic=False):
  228. assert not symbolic
  229. return self.arena._getid() + self.offset
  230. def getfakearenaaddress(addr):
  231. """Logic to handle test_replace_object_with_stub()."""
  232. if isinstance(addr, fakearenaaddress):
  233. return addr
  234. else:
  235. assert isinstance(addr, llmemory.fakeaddress)
  236. assert addr, "NULL address"
  237. # it must be possible to use the address of an already-freed
  238. # arena object
  239. obj = addr.ptr._getobj(check=False)
  240. return _oldobj_to_address(obj)
  241. def _oldobj_to_address(obj):
  242. obj = obj._normalizedcontainer(check=False)
  243. try:
  244. _, arena, offset = obj.__arena_location__
  245. except AttributeError:
  246. if obj._was_freed():
  247. msg = "taking address of %r, but it was freed"
  248. else:
  249. msg = "taking address of %r, but it is not in an arena"
  250. raise RuntimeError(msg % (obj,))
  251. return arena.getaddr(offset)
  252. class RoundedUpForAllocation(llmemory.AddressOffset):
  253. """A size that is rounded up in order to preserve alignment of objects
  254. following it. For arenas containing heterogenous objects.
  255. """
  256. def __init__(self, basesize, minsize):
  257. assert isinstance(basesize, llmemory.AddressOffset)
  258. assert isinstance(minsize, llmemory.AddressOffset) or minsize == 0
  259. self.basesize = basesize
  260. self.minsize = minsize
  261. def __repr__(self):
  262. return '< RoundedUpForAllocation %r %r >' % (self.basesize,
  263. self.minsize)
  264. def known_nonneg(self):
  265. return self.basesize.known_nonneg()
  266. def ref(self, ptr):
  267. return self.basesize.ref(ptr)
  268. def _raw_malloc(self, rest, zero):
  269. return self.basesize._raw_malloc(rest, zero=zero)
  270. def raw_memcopy(self, srcadr, dstadr):
  271. self.basesize.raw_memcopy(srcadr, dstadr)
  272. # ____________________________________________________________
  273. #
  274. # Public interface: arena_malloc(), arena_free(), arena_reset()
  275. # are similar to raw_malloc(), raw_free() and raw_memclear(), but
  276. # work with fakearenaaddresses on which arbitrary arithmetic is
  277. # possible even on top of the llinterpreter.
  278. # arena_new_view(ptr) is a no-op when translated, returns fresh view
  279. # on previous arena when run on top of llinterp
  280. def arena_malloc(nbytes, zero):
  281. """Allocate and return a new arena, optionally zero-initialized."""
  282. return Arena(nbytes, zero).getaddr(0)
  283. def arena_free(arena_addr):
  284. """Release an arena."""
  285. assert isinstance(arena_addr, fakearenaaddress)
  286. assert arena_addr.offset == 0
  287. arena_addr.arena.reset(False)
  288. assert not arena_addr.arena.objectptrs
  289. arena_addr.arena.mark_freed()
  290. def arena_reset(arena_addr, size, zero):
  291. """Free all objects in the arena, which can then be reused.
  292. This can also be used on a subrange of the arena.
  293. The value of 'zero' is:
  294. * 0: don't fill the area with zeroes
  295. * 1: clear, optimized for a very large area of memory
  296. * 2: clear, optimized for a small or medium area of memory
  297. * 3: fill with garbage
  298. * 4: large area of memory that can benefit from MADV_FREE
  299. (i.e. contains garbage, may be zero-filled or not)
  300. """
  301. arena_addr = getfakearenaaddress(arena_addr)
  302. arena_addr.arena.reset(zero, arena_addr.offset, size)
  303. def arena_reserve(addr, size, check_alignment=True):
  304. """Mark some bytes in an arena as reserved, and returns addr.
  305. For debugging this can check that reserved ranges of bytes don't
  306. overlap. The size must be symbolic; in non-translated version
  307. this is used to know what type of lltype object to allocate."""
  308. from rpython.memory.lltypelayout import memory_alignment
  309. addr = getfakearenaaddress(addr)
  310. letter = 'x'
  311. if llmemory.raw_malloc_usage(size) == 1:
  312. letter = 'b' # for Byte-aligned allocations
  313. elif check_alignment and (addr.offset & (memory_alignment-1)) != 0:
  314. raise ArenaError("object at offset %d would not be correctly aligned"
  315. % (addr.offset,))
  316. addr.arena.allocate_object(addr.offset, size, letter)
  317. def arena_shrink_obj(addr, newsize):
  318. """ Mark object as shorter than it was
  319. """
  320. addr = getfakearenaaddress(addr)
  321. addr.arena.shrink_obj(addr.offset, newsize)
  322. def round_up_for_allocation(size, minsize=0):
  323. """Round up the size in order to preserve alignment of objects
  324. following an object. For arenas containing heterogenous objects.
  325. If minsize is specified, it gives a minimum on the resulting size."""
  326. return _round_up_for_allocation(size, minsize)
  327. round_up_for_allocation._annenforceargs_ = [int, int]
  328. def _round_up_for_allocation(size, minsize): # internal
  329. return RoundedUpForAllocation(size, minsize)
  330. def arena_new_view(ptr):
  331. """Return a fresh memory view on an arena
  332. """
  333. return Arena(ptr.arena.nbytes, False).getaddr(0)
  334. def arena_protect(arena_addr, size, inaccessible):
  335. """For debugging, set or reset memory protection on an arena.
  336. For now, the starting point and size should reference the whole arena.
  337. The value of 'inaccessible' is a boolean.
  338. """
  339. arena_addr = getfakearenaaddress(arena_addr)
  340. assert arena_addr.offset == 0
  341. assert size == arena_addr.arena.nbytes
  342. arena_addr.arena.set_protect(inaccessible)
  343. # ____________________________________________________________
  344. #
  345. # Translation support: the functions above turn into the code below.
  346. # We can tweak these implementations to be more suited to very large
  347. # chunks of memory.
  348. import os, sys
  349. from rpython.rtyper.lltypesystem import rffi, lltype
  350. from rpython.rtyper.extfunc import register_external
  351. from rpython.rtyper.tool.rffi_platform import memory_alignment
  352. MEMORY_ALIGNMENT = memory_alignment()
  353. if os.name == 'posix':
  354. # The general Posix solution to clear a large range of memory that
  355. # was obtained with mmap() is to call mmap() again with MAP_FIXED.
  356. legacy_getpagesize = rffi.llexternal('getpagesize', [], rffi.INT,
  357. sandboxsafe=True, _nowrapper=True)
  358. class PosixPageSize:
  359. def __init__(self):
  360. self.pagesize = 0
  361. def _cleanup_(self):
  362. self.pagesize = 0
  363. def get(self):
  364. pagesize = self.pagesize
  365. if pagesize == 0:
  366. pagesize = rffi.cast(lltype.Signed, legacy_getpagesize())
  367. self.pagesize = pagesize
  368. return pagesize
  369. posixpagesize = PosixPageSize()
  370. def clear_large_memory_chunk(baseaddr, size):
  371. from rpython.rlib import rmmap
  372. pagesize = posixpagesize.get()
  373. if size > 2 * pagesize:
  374. lowbits = rffi.cast(lltype.Signed, baseaddr) & (pagesize - 1)
  375. if lowbits: # clear the initial misaligned part, if any
  376. partpage = pagesize - lowbits
  377. llmemory.raw_memclear(baseaddr, partpage)
  378. baseaddr += partpage
  379. size -= partpage
  380. length = size & -pagesize
  381. if rmmap.clear_large_memory_chunk_aligned(baseaddr, length):
  382. baseaddr += length # clearing worked
  383. size -= length
  384. if size > 0: # clear the final misaligned part, if any
  385. llmemory.raw_memclear(baseaddr, size)
  386. else:
  387. # XXX any better implementation on Windows?
  388. # Should use VirtualAlloc() to reserve the range of pages,
  389. # and commit some pages gradually with support from the GC.
  390. # Or it might be enough to decommit the pages and recommit
  391. # them immediately.
  392. clear_large_memory_chunk = llmemory.raw_memclear
  393. class PosixPageSize:
  394. def get(self):
  395. from rpython.rlib import rmmap
  396. return rmmap.PAGESIZE
  397. posixpagesize = PosixPageSize()
  398. def madvise_arena_free(baseaddr, size):
  399. from rpython.rlib import rmmap
  400. pagesize = posixpagesize.get()
  401. baseaddr = rffi.cast(lltype.Signed, baseaddr)
  402. aligned_addr = (baseaddr + pagesize - 1) & ~(pagesize - 1)
  403. size -= (aligned_addr - baseaddr)
  404. if size >= pagesize:
  405. rmmap.madvise_free(rffi.cast(rmmap.PTR, aligned_addr),
  406. size & ~(pagesize - 1))
  407. if os.name == "posix":
  408. from rpython.translator.tool.cbuild import ExternalCompilationInfo
  409. _eci = ExternalCompilationInfo(includes=['sys/mman.h'])
  410. raw_mprotect = rffi.llexternal('mprotect',
  411. [llmemory.Address, rffi.SIZE_T, rffi.INT],
  412. rffi.INT,
  413. sandboxsafe=True, _nowrapper=True,
  414. compilation_info=_eci)
  415. def llimpl_protect(addr, size, inaccessible):
  416. if inaccessible:
  417. prot = 0
  418. else:
  419. from rpython.rlib.rmmap import PROT_READ, PROT_WRITE
  420. prot = PROT_READ | PROT_WRITE
  421. raw_mprotect(addr, rffi.cast(rffi.SIZE_T, size),
  422. rffi.cast(rffi.INT, prot))
  423. # ignore potential errors
  424. has_protect = True
  425. elif os.name == 'nt':
  426. def llimpl_protect(addr, size, inaccessible):
  427. from rpython.rlib.rmmap import VirtualProtect, LPDWORD
  428. if inaccessible:
  429. from rpython.rlib.rmmap import PAGE_NOACCESS as newprotect
  430. else:
  431. from rpython.rlib.rmmap import PAGE_READWRITE as newprotect
  432. arg = lltype.malloc(LPDWORD.TO, 1, zero=True, flavor='raw')
  433. #does not release the GIL
  434. VirtualProtect(rffi.cast(rffi.VOIDP, addr),
  435. size, newprotect, arg)
  436. # ignore potential errors
  437. lltype.free(arg, flavor='raw')
  438. has_protect = True
  439. else:
  440. has_protect = False
  441. llimpl_malloc = rffi.llexternal('malloc', [lltype.Signed], llmemory.Address,
  442. sandboxsafe=True, _nowrapper=True)
  443. llimpl_free = rffi.llexternal('free', [llmemory.Address], lltype.Void,
  444. sandboxsafe=True, _nowrapper=True)
  445. def llimpl_arena_malloc(nbytes, zero):
  446. addr = llimpl_malloc(nbytes)
  447. if bool(addr):
  448. llimpl_arena_reset(addr, nbytes, zero)
  449. return addr
  450. llimpl_arena_malloc._always_inline_ = True
  451. register_external(arena_malloc, [int, int], llmemory.Address,
  452. 'll_arena.arena_malloc',
  453. llimpl=llimpl_arena_malloc,
  454. llfakeimpl=arena_malloc,
  455. sandboxsafe=True)
  456. register_external(arena_free, [llmemory.Address], None, 'll_arena.arena_free',
  457. llimpl=llimpl_free,
  458. llfakeimpl=arena_free,
  459. sandboxsafe=True)
  460. def llimpl_arena_reset(arena_addr, size, zero):
  461. if zero:
  462. if zero == 1:
  463. clear_large_memory_chunk(arena_addr, size)
  464. elif zero == 3:
  465. llop.raw_memset(lltype.Void, arena_addr, ord('#'), size)
  466. elif zero == 4:
  467. madvise_arena_free(arena_addr, size)
  468. else:
  469. llmemory.raw_memclear(arena_addr, size)
  470. llimpl_arena_reset._always_inline_ = True
  471. register_external(arena_reset, [llmemory.Address, int, int], None,
  472. 'll_arena.arena_reset',
  473. llimpl=llimpl_arena_reset,
  474. llfakeimpl=arena_reset,
  475. sandboxsafe=True)
  476. def llimpl_arena_reserve(addr, size):
  477. pass
  478. register_external(arena_reserve, [llmemory.Address, int], None,
  479. 'll_arena.arena_reserve',
  480. llimpl=llimpl_arena_reserve,
  481. llfakeimpl=arena_reserve,
  482. sandboxsafe=True)
  483. def llimpl_arena_shrink_obj(addr, newsize):
  484. pass
  485. register_external(arena_shrink_obj, [llmemory.Address, int], None,
  486. 'll_arena.arena_shrink_obj',
  487. llimpl=llimpl_arena_shrink_obj,
  488. llfakeimpl=arena_shrink_obj,
  489. sandboxsafe=True)
  490. def llimpl_round_up_for_allocation(size, minsize):
  491. return (max(size, minsize) + (MEMORY_ALIGNMENT-1)) & ~(MEMORY_ALIGNMENT-1)
  492. register_external(_round_up_for_allocation, [int, int], int,
  493. 'll_arena.round_up_for_allocation',
  494. llimpl=llimpl_round_up_for_allocation,
  495. llfakeimpl=round_up_for_allocation,
  496. sandboxsafe=True)
  497. def llimpl_arena_new_view(addr):
  498. return addr
  499. register_external(arena_new_view, [llmemory.Address], llmemory.Address,
  500. 'll_arena.arena_new_view', llimpl=llimpl_arena_new_view,
  501. llfakeimpl=arena_new_view, sandboxsafe=True)
  502. def llimpl_arena_protect(addr, size, inaccessible):
  503. if has_protect:
  504. # do some alignment
  505. start = rffi.cast(lltype.Signed, addr)
  506. end = start + size
  507. start = (start + 4095) & ~ 4095
  508. end = end & ~ 4095
  509. if end > start:
  510. llimpl_protect(rffi.cast(llmemory.Address, start), end-start,
  511. inaccessible)
  512. register_external(arena_protect, [llmemory.Address, lltype.Signed,
  513. lltype.Bool], lltype.Void,
  514. 'll_arena.arena_protect', llimpl=llimpl_arena_protect,
  515. llfakeimpl=arena_protect, sandboxsafe=True)
  516. def llimpl_getfakearenaaddress(addr):
  517. return addr
  518. register_external(getfakearenaaddress, [llmemory.Address], llmemory.Address,
  519. 'll_arena.getfakearenaaddress',
  520. llimpl=llimpl_getfakearenaaddress,
  521. llfakeimpl=getfakearenaaddress,
  522. sandboxsafe=True)