Author: Remi Meier <[email protected]>
Branch: stmgc-c4
Changeset: r66391:f37582685aca
Date: 2013-08-28 15:07 +0200
http://bitbucket.org/pypy/pypy/changeset/f37582685aca/
Log: merge static-write-barriers
diff --git a/rpython/jit/backend/tool/viewcode.py
b/rpython/jit/backend/tool/viewcode.py
--- a/rpython/jit/backend/tool/viewcode.py
+++ b/rpython/jit/backend/tool/viewcode.py
@@ -58,6 +58,9 @@
'arm': 'arm',
'arm_32': 'arm',
}
+ backend_to_machine = {
+ 'x86-64': 'i386:x86-64',
+ }
cmd = find_objdump()
objdump = ('%(command)s -w -M %(backend)s -b binary -m %(machine)s '
'--disassembler-options=intel-mnemonics '
@@ -66,12 +69,13 @@
f = open(tmpfile, 'wb')
f.write(data)
f.close()
+ backend = objdump_backend_option[backend_name]
p = subprocess.Popen(objdump % {
'command': cmd,
'file': tmpfile,
'origin': originaddr,
- 'backend': objdump_backend_option[backend_name],
- 'machine': 'i386' if not backend_name.startswith('arm') else 'arm',
+ 'backend': backend,
+ 'machine': backend_to_machine.get(backend, backend),
}, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
assert not p.returncode, ('Encountered an error running objdump: %s' %
@@ -239,7 +243,7 @@
self.backend_name = None
self.executable_name = None
- def parse(self, f, textonly=True, truncate_addr=True):
+ def parse(self, f, textonly=True):
for line in f:
line = line[line.find('#') + 1:].strip()
if line.startswith('BACKEND '):
@@ -251,9 +255,7 @@
if len(pieces) == 3:
continue # empty line
baseaddr = long(pieces[1][1:], 16)
- if truncate_addr:
- baseaddr &= 0xFFFFFFFFL
- elif baseaddr < 0:
+ if baseaddr < 0:
baseaddr += (2 * sys.maxint + 2)
offset = int(pieces[2][1:])
addr = baseaddr + offset
@@ -273,9 +275,7 @@
assert pieces[1].startswith('@')
assert pieces[2].startswith('+')
baseaddr = long(pieces[1][1:], 16)
- if truncate_addr:
- baseaddr &= 0xFFFFFFFFL
- elif baseaddr < 0:
+ if baseaddr < 0:
baseaddr += (2 * sys.maxint + 2)
offset = int(pieces[2][1:])
addr = baseaddr + offset
diff --git a/rpython/rtyper/lltypesystem/lloperation.py
b/rpython/rtyper/lltypesystem/lloperation.py
--- a/rpython/rtyper/lltypesystem/lloperation.py
+++ b/rpython/rtyper/lltypesystem/lloperation.py
@@ -624,6 +624,7 @@
'debug_reraise_traceback': LLOp(),
'debug_print_traceback': LLOp(),
'debug_nonnull_pointer': LLOp(canrun=True),
+ 'debug_stm_flush_barrier': LLOp(canrun=True),
# __________ instrumentation _________
'instrument_count': LLOp(),
diff --git a/rpython/rtyper/lltypesystem/opimpl.py
b/rpython/rtyper/lltypesystem/opimpl.py
--- a/rpython/rtyper/lltypesystem/opimpl.py
+++ b/rpython/rtyper/lltypesystem/opimpl.py
@@ -673,6 +673,9 @@
def op_nop(x):
pass
+def op_debug_stm_flush_barrier():
+ pass
+
# ____________________________________________________________
def get_op_impl(opname):
diff --git a/rpython/translator/stm/breakfinder.py
b/rpython/translator/stm/breakfinder.py
new file mode 100644
--- /dev/null
+++ b/rpython/translator/stm/breakfinder.py
@@ -0,0 +1,22 @@
+from rpython.translator.backendopt import graphanalyze
+from rpython.translator.simplify import get_funcobj
+
+
+TRANSACTION_BREAK = set([
+ 'stm_commit_transaction',
+ 'stm_begin_inevitable_transaction',
+ 'stm_perform_transaction',
+ ])
+
+
+class TransactionBreakAnalyzer(graphanalyze.BoolGraphAnalyzer):
+
+ def analyze_simple_operation(self, op, graphinfo):
+ return op.opname in TRANSACTION_BREAK
+
+ def analyze_external_call(self, op, seen=None):
+ # if 'funcobj' releases the GIL, then the GIL-releasing
+ # functions themselves will call stm_commit_transaction
+ # and stm_begin_inevitable_transaction. This case is
+ # covered above.
+ return False
diff --git a/rpython/translator/stm/funcgen.py
b/rpython/translator/stm/funcgen.py
--- a/rpython/translator/stm/funcgen.py
+++ b/rpython/translator/stm/funcgen.py
@@ -48,19 +48,27 @@
def stm_finalize(funcgen, op):
return 'stm_finalize();'
-_STM_BARRIER_FUNCS = { # XXX try to see if some combinations can be shorter
- 'P2R': 'stm_read_barrier',
- 'G2R': 'stm_read_barrier',
- 'O2R': 'stm_read_barrier',
- 'P2W': 'stm_write_barrier',
- 'G2W': 'stm_write_barrier',
- 'O2W': 'stm_write_barrier',
- 'R2W': 'stm_write_barrier',
- }
-
def stm_barrier(funcgen, op):
category_change = op.args[0].value
- funcname = _STM_BARRIER_FUNCS[category_change]
+ frm, middle, to = category_change
+ assert middle == '2'
+ assert frm < to
+ if to == 'W':
+ if frm >= 'V':
+ funcname = 'stm_repeat_write_barrier'
+ else:
+ funcname = 'stm_write_barrier'
+ elif to == 'V':
+ funcname = 'stm_write_barrier_noptr'
+ elif to == 'R':
+ if frm >= 'Q':
+ funcname = 'stm_repeat_read_barrier'
+ else:
+ funcname = 'stm_read_barrier'
+ elif to == 'I':
+ funcname = 'stm_immut_read_barrier'
+ else:
+ raise AssertionError(category_change)
assert op.args[1].concretetype == op.result.concretetype
arg = funcgen.expr(op.args[1])
result = funcgen.expr(op.result)
@@ -69,11 +77,20 @@
funcname, arg)
def stm_ptr_eq(funcgen, op):
- arg0 = funcgen.expr(op.args[0])
- arg1 = funcgen.expr(op.args[1])
+ args = [funcgen.expr(v) for v in op.args]
result = funcgen.expr(op.result)
+ # check for prebuilt arguments
+ for i, j in [(0, 1), (1, 0)]:
+ if isinstance(op.args[j], Constant):
+ if op.args[j].value: # non-NULL
+ return ('%s = stm_pointer_equal_prebuilt((gcptr)%s,
(gcptr)%s);'
+ % (result, args[i], args[j]))
+ else:
+ # this case might be unreachable, but better safe than sorry
+ return '%s = (%s == NULL);' % (result, args[i])
+ #
return '%s = stm_pointer_equal((gcptr)%s, (gcptr)%s);' % (
- result, arg0, arg1)
+ result, args[0], args[1])
def stm_become_inevitable(funcgen, op):
try:
diff --git a/rpython/translator/stm/src_stm/revision
b/rpython/translator/stm/src_stm/revision
--- a/rpython/translator/stm/src_stm/revision
+++ b/rpython/translator/stm/src_stm/revision
@@ -1,1 +1,1 @@
-63c2673c2045
+cdd017855adc+
diff --git a/rpython/translator/stm/test/test_writebarrier.py
b/rpython/translator/stm/test/test_writebarrier.py
--- a/rpython/translator/stm/test/test_writebarrier.py
+++ b/rpython/translator/stm/test/test_writebarrier.py
@@ -1,4 +1,6 @@
-from rpython.rtyper.lltypesystem import lltype, rffi
+from rpython.rlib.rstm import register_invoke_around_extcall
+from rpython.rtyper.lltypesystem import lltype, llmemory, rffi
+from rpython.rtyper.lltypesystem.lloperation import llop
from rpython.translator.stm.test.transform_support import BaseTestTransform
@@ -24,7 +26,7 @@
res = self.interpret(f1, [-5])
assert res == 42
assert len(self.writemode) == 0
- assert self.barriers == ['P2R']
+ assert self.barriers == ['I2R']
def test_simple_write(self):
X = lltype.GcStruct('X', ('foo', lltype.Signed))
@@ -37,7 +39,21 @@
self.interpret(f1, [4])
assert x1.foo == 4
assert len(self.writemode) == 1
- assert self.barriers == ['P2W']
+ assert self.barriers == ['I2V']
+
+ def test_simple_write_pointer(self):
+ T = lltype.GcStruct('T')
+ X = lltype.GcStruct('X', ('foo', lltype.Ptr(T)))
+ t1 = lltype.malloc(T, immortal=True)
+ x1 = lltype.malloc(X, immortal=True, zero=True)
+
+ def f1(n):
+ x1.foo = t1
+
+ self.interpret(f1, [4])
+ assert x1.foo == t1
+ assert len(self.writemode) == 1
+ assert self.barriers == ['I2W']
def test_multiple_reads(self):
X = lltype.GcStruct('X', ('foo', lltype.Signed),
@@ -58,7 +74,7 @@
res = self.interpret(f1, [4])
assert res == -81
assert len(self.writemode) == 0
- assert self.barriers == ['P2R']
+ assert self.barriers == ['I2R']
def test_malloc(self):
X = lltype.GcStruct('X', ('foo', lltype.Signed))
@@ -70,10 +86,9 @@
assert len(self.writemode) == 1
assert self.barriers == []
- def test_repeat_write_barrier_after_malloc(self):
+ def test_dont_repeat_write_barrier_after_malloc_if_not_a_ptr(self):
X = lltype.GcStruct('X', ('foo', lltype.Signed))
- x1 = lltype.malloc(X, immortal=True)
- x1.foo = 6
+ x1 = lltype.malloc(X, immortal=True, zero=True)
def f1(n):
x1.foo = n
lltype.malloc(X)
@@ -81,7 +96,22 @@
self.interpret(f1, [4])
assert len(self.writemode) == 2
- assert self.barriers == ['P2W', 'r2w']
+ assert self.barriers == ['I2V']
+
+ def test_repeat_write_barrier_after_malloc(self):
+ T = lltype.GcStruct('T')
+ X = lltype.GcStruct('X', ('foo', lltype.Ptr(T)))
+ t1 = lltype.malloc(T, immortal=True)
+ t2 = lltype.malloc(T, immortal=True)
+ x1 = lltype.malloc(X, immortal=True, zero=True)
+ def f1(n):
+ x1.foo = t1
+ lltype.malloc(X)
+ x1.foo = t2
+
+ self.interpret(f1, [4])
+ assert len(self.writemode) == 2
+ assert self.barriers == ['I2W', 'V2W']
def test_repeat_read_barrier_after_malloc(self):
X = lltype.GcStruct('X', ('foo', lltype.Signed))
@@ -95,7 +125,7 @@
self.interpret(f1, [4])
assert len(self.writemode) == 1
- assert self.barriers == ['P2R']
+ assert self.barriers == ['I2R']
def test_write_may_alias(self):
X = lltype.GcStruct('X', ('foo', lltype.Signed))
@@ -109,10 +139,10 @@
y = lltype.malloc(X, immortal=True)
res = self.interpret(f1, [x, y])
assert res == 36
- assert self.barriers == ['P2R', 'P2W', 'p2r']
+ assert self.barriers == ['A2R', 'A2V', 'q2r']
res = self.interpret(f1, [x, x])
assert res == 42
- assert self.barriers == ['P2R', 'P2W', 'P2R']
+ assert self.barriers == ['A2R', 'A2V', 'Q2R']
def test_write_cannot_alias(self):
X = lltype.GcStruct('X', ('foo', lltype.Signed))
@@ -127,37 +157,49 @@
y = lltype.malloc(Y, immortal=True)
res = self.interpret(f1, [x, y])
assert res == 36
- assert self.barriers == ['P2R', 'P2W']
+ assert self.barriers == ['A2R', 'A2V']
- def test_call_external_random_effects(self):
+ def test_call_external_release_gil(self):
X = lltype.GcStruct('X', ('foo', lltype.Signed))
def f1(p):
+ register_invoke_around_extcall()
x1 = p.foo
- external_stuff()
+ external_release_gil()
x2 = p.foo
return x1 * x2
x = lltype.malloc(X, immortal=True); x.foo = 6
res = self.interpret(f1, [x])
assert res == 36
- assert self.barriers == ['P2R', 'p2r']
+ assert self.barriers == ['A2R', 'I2R']
- def test_call_external_no_random_effects(self):
+ def test_call_external_any_gcobj(self):
X = lltype.GcStruct('X', ('foo', lltype.Signed))
- external_stuff = rffi.llexternal('external_stuff2', [], lltype.Void,
- _callable=lambda: None,
- random_effects_on_gcobjs=False,
- threadsafe=False)
def f1(p):
+ register_invoke_around_extcall()
x1 = p.foo
- external_stuff()
+ external_any_gcobj()
x2 = p.foo
return x1 * x2
x = lltype.malloc(X, immortal=True); x.foo = 6
res = self.interpret(f1, [x])
assert res == 36
- assert self.barriers == ['P2R']
+ assert self.barriers == ['A2R', 'q2r']
+
+ def test_call_external_safest(self):
+ X = lltype.GcStruct('X', ('foo', lltype.Signed))
+ def f1(p):
+ register_invoke_around_extcall()
+ x1 = p.foo
+ external_safest()
+ x2 = p.foo
+ return x1 * x2
+
+ x = lltype.malloc(X, immortal=True); x.foo = 6
+ res = self.interpret(f1, [x])
+ assert res == 36
+ assert self.barriers == ['A2R']
def test_pointer_compare_0(self):
X = lltype.GcStruct('X', ('foo', lltype.Signed))
@@ -190,10 +232,10 @@
y = lltype.malloc(X, immortal=True)
res = self.interpret(f1, [x, y])
assert res == 0
- assert self.barriers == ['P2W', '=']
+ assert self.barriers == ['A2V', '=']
res = self.interpret(f1, [x, x])
assert res == 1
- assert self.barriers == ['P2W', '=']
+ assert self.barriers == ['A2V', '=']
def test_pointer_compare_3(self):
X = lltype.GcStruct('X', ('foo', lltype.Signed))
@@ -204,10 +246,10 @@
y = lltype.malloc(X, immortal=True)
res = self.interpret(f1, [x, y])
assert res == 1
- assert self.barriers == ['P2W', '=']
+ assert self.barriers == ['A2V', '=']
res = self.interpret(f1, [x, x])
assert res == 0
- assert self.barriers == ['P2W', '=']
+ assert self.barriers == ['A2V', '=']
def test_pointer_compare_4(self):
X = lltype.GcStruct('X', ('foo', lltype.Signed))
@@ -219,10 +261,10 @@
y = lltype.malloc(X, immortal=True)
res = self.interpret(f1, [x, y])
assert res == 1
- assert self.barriers == ['P2W', 'P2W']
+ assert self.barriers == ['A2V', 'A2V']
res = self.interpret(f1, [x, x])
assert res == 0
- assert self.barriers == ['P2W', 'P2W']
+ assert self.barriers == ['A2V', 'A2V']
def test_simple_loop(self):
X = lltype.GcStruct('X', ('foo', lltype.Signed))
@@ -235,7 +277,7 @@
res = self.interpret(f1, [x, 5])
assert res == 0
# for now we get this. Later, we could probably optimize it
- assert self.barriers == ['P2W', 'p2w', 'p2w', 'p2w', 'p2w']
+ assert self.barriers == ['A2V', 'a2v', 'a2v', 'a2v', 'a2v']
def test_subclassing(self):
class X:
@@ -253,35 +295,210 @@
x = Z()
x.foo = 815
x.zbar = 'A'
- external_stuff()
+ llop.debug_stm_flush_barrier(lltype.Void)
result = x.foo # 1
if isinstance(x, Y): # 2
- result += x.ybar # 3
+ result += x.ybar # 3: optimized
return result
res = self.interpret(f1, [10])
assert res == 42 + 10
- assert self.barriers == ['p2r', 'p2r', 'p2r'] # from 3 blocks (could be
- # optimized later)
+ assert self.barriers == ['a2r', 'a2i']
res = self.interpret(f1, [-10])
assert res == 815
- assert self.barriers == ['p2r', 'p2r']
+ assert self.barriers == ['a2r', 'a2i']
+
+ def test_no_subclasses_2(self):
+ class Y(object):
+ pass
+ def handle(y):
+ y.ybar += 1
+ def make_y(i):
+ y = Y(); y.foo = 42; y.ybar = i
+ return y
+ def f1(i):
+ y = make_y(i)
+ llop.debug_stm_flush_barrier(lltype.Void)
+ prev = y.ybar # a2r
+ handle(y) # inside handle(): a2r, r2v
+ return prev + y.ybar # q2r
+
+ res = self.interpret(f1, [10])
+ assert res == 21
+ assert self.barriers == ['a2r', 'a2r', 'r2v', 'q2r']
+
+ def test_subclassing_2(self):
+ class X:
+ __slots__ = ['foo']
+ class Y(X):
+ pass
+ class Z(X):
+ pass
+ def handle(y):
+ y.ybar += 1
+ def f1(i):
+ if i > 5:
+ y = Y(); y.foo = 42; y.ybar = i
+ x = y
+ else:
+ x = Z(); x.foo = 815; x.zbar = 'A'
+ y = Y(); y.foo = -13; y.ybar = i
+ llop.debug_stm_flush_barrier(lltype.Void)
+ prev = x.foo # a2r
+ handle(y) # inside handle(): a2r, r2v
+ return prev + x.foo # q2r
+
+ res = self.interpret(f1, [10])
+ assert res == 84
+ assert self.barriers == ['a2r', 'a2r', 'r2v', 'q2r']
+
+ def test_subclassing_gcref(self):
+ Y = lltype.GcStruct('Y', ('foo', lltype.Signed),
+ ('ybar', lltype.Signed))
+ YPTR = lltype.Ptr(Y)
+ #
+ def handle(y):
+ y.ybar += 1
+ def f1(i):
+ if i > 5:
+ y = lltype.malloc(Y); y.foo = 52 - i; y.ybar = i
+ x = lltype.cast_opaque_ptr(llmemory.GCREF, y)
+ else:
+ y = lltype.nullptr(Y)
+ x = lltype.cast_opaque_ptr(llmemory.GCREF, y)
+ llop.debug_stm_flush_barrier(lltype.Void)
+ prev = lltype.cast_opaque_ptr(YPTR, x).foo # a2r
+ handle(y) # inside handle(): a2r, r2v
+ return prev + lltype.cast_opaque_ptr(YPTR, x).ybar # q2r?
+
+ res = self.interpret(f1, [10])
+ assert res == 42 + 11
+ assert self.barriers == ['a2r', 'a2r', 'r2v', 'a2r']
+ # Ideally we should get [... 'q2r'] but getting 'a2r' is not wrong
+ # either. This is because from a GCREF the only thing we can do is
+ # cast_opaque_ptr, which is not special-cased in writebarrier.py.
def test_write_barrier_repeated(self):
class X:
pass
x = X()
+ x2 = X()
+ x3 = X()
def f1(i):
- x.a = i # write barrier
+ x.a = x2 # write barrier
y = X() # malloc
- x.a += 1 # write barrier again
+ x.a = x3 # repeat write barrier
return y
res = self.interpret(f1, [10])
- assert self.barriers == ['P2W', 'r2w']
+ assert self.barriers == ['I2W', 'V2W']
+ def test_read_immutable(self):
+ class Foo:
+ _immutable_ = True
-external_stuff = rffi.llexternal('external_stuff', [], lltype.Void,
- _callable=lambda: None,
- random_effects_on_gcobjs=True,
- threadsafe=False)
+ def f1(n):
+ x = Foo()
+ llop.debug_stm_flush_barrier(lltype.Void)
+ if n > 1:
+ x.foo = n
+ llop.debug_stm_flush_barrier(lltype.Void)
+ return x.foo
+
+ res = self.interpret(f1, [4])
+ assert res == 4
+ assert self.barriers == ['a2v', 'a2i']
+
+ def test_read_immutable_prebuilt(self):
+ class Foo:
+ _immutable_ = True
+ x1 = Foo()
+ x1.foo = 42
+ x2 = Foo()
+ x2.foo = 81
+
+ def f1(n):
+ if n > 1:
+ return x2.foo
+ else:
+ return x1.foo
+
+ res = self.interpret(f1, [4])
+ assert res == 81
+ assert self.barriers == []
+
+ def test_isinstance(self):
+ class Base: pass
+ class A(Base): pass
+
+ def f1(n):
+ if n > 1:
+ x = Base()
+ else:
+ x = A()
+ return isinstance(x, A)
+
+ res = self.interpret(f1, [5])
+ assert res == False
+ assert self.barriers == ['a2i']
+ res = self.interpret(f1, [-5])
+ assert res == True
+ assert self.barriers == ['a2i']
+
+ def test_isinstance_gcremovetypeptr(self):
+ class Base: pass
+ class A(Base): pass
+
+ def f1(n):
+ if n > 1:
+ x = Base()
+ else:
+ x = A()
+ return isinstance(x, A)
+
+ res = self.interpret(f1, [5], gcremovetypeptr=True)
+ assert res == False
+ assert self.barriers == []
+ res = self.interpret(f1, [-5], gcremovetypeptr=True)
+ assert res == True
+ assert self.barriers == []
+
+ def test_infinite_loop_bug(self):
+ class A(object):
+ user_overridden_class = False
+
+ def stuff(self):
+ return 12.3
+
+ def immutable_unique_id(self):
+ if self.user_overridden_class:
+ return None
+ from rpython.rlib.longlong2float import float2longlong
+ from rpython.rlib.rarithmetic import r_ulonglong
+ from rpython.rlib.rbigint import rbigint
+ real = self.stuff()
+ imag = self.stuff()
+ real_b = rbigint.fromrarith_int(float2longlong(real))
+ imag_b =
rbigint.fromrarith_int(r_ulonglong(float2longlong(imag)))
+ val = real_b.lshift(64).or_(imag_b).lshift(3)
+ return val
+
+ def f():
+ return A().immutable_unique_id()
+
+ for i in range(10):
+ self.interpret(f, [], run=False)
+
+
+external_release_gil = rffi.llexternal('external_release_gil', [], lltype.Void,
+ _callable=lambda: None,
+ random_effects_on_gcobjs=True,
+ threadsafe=True) # GIL is released
+external_any_gcobj = rffi.llexternal('external_any_gcobj', [], lltype.Void,
+ _callable=lambda: None,
+ random_effects_on_gcobjs=True,
+ threadsafe=False) # GIL is not released
+external_safest = rffi.llexternal('external_safest', [], lltype.Void,
+ _callable=lambda: None,
+ random_effects_on_gcobjs=False,
+ threadsafe=False) # GIL is not released
diff --git a/rpython/translator/stm/test/test_ztranslated.py
b/rpython/translator/stm/test/test_ztranslated.py
--- a/rpython/translator/stm/test/test_ztranslated.py
+++ b/rpython/translator/stm/test/test_ztranslated.py
@@ -1,8 +1,6 @@
-import py
from rpython.rlib import rstm, rgc, objectmodel
-from rpython.rtyper.lltypesystem import lltype, llmemory, rffi, rclass
+from rpython.rtyper.lltypesystem import lltype, rffi
from rpython.rtyper.lltypesystem.lloperation import llop
-from rpython.rtyper.annlowlevel import cast_instance_to_base_ptr
from rpython.translator.stm.test.support import CompiledSTMTests
from rpython.translator.stm.test import targetdemo2
@@ -303,3 +301,31 @@
t, cbuilder = self.compile(main)
data = cbuilder.cmdexec('a b')
assert 'test ok\n' in data
+
+ def test_stm_pointer_equal(self):
+ class Foo:
+ pass
+ prebuilt_foo = Foo()
+ def make(n):
+ foo1 = Foo()
+ foo2 = Foo()
+ if n < 100:
+ return foo1, foo2, foo1, None
+ return None, None, None, foo1 # to annotate as "can be none"
+ def main(argv):
+ foo1, foo2, foo3, foo4 = make(len(argv))
+ assert foo1 is not prebuilt_foo
+ assert foo1 is not foo2
+ assert foo1 is foo3
+ assert foo4 is None
+ assert foo1 is not None
+ assert prebuilt_foo is not foo1
+ assert None is not foo1
+ assert None is foo4
+ print 'test ok'
+ return 0
+
+ main([])
+ t, cbuilder = self.compile(main)
+ data = cbuilder.cmdexec('')
+ assert 'test ok\n' in data
diff --git a/rpython/translator/stm/test/transform_support.py
b/rpython/translator/stm/test/transform_support.py
--- a/rpython/translator/stm/test/transform_support.py
+++ b/rpython/translator/stm/test/transform_support.py
@@ -2,7 +2,7 @@
from rpython.rtyper.llinterp import LLFrame
from rpython.rtyper.test.test_llinterp import get_interpreter, clear_tcache
from rpython.translator.stm.transform import STMTransformer
-from rpython.translator.stm.writebarrier import NEEDS_BARRIER
+from rpython.translator.stm.writebarrier import needs_barrier
from rpython.conftest import option
@@ -33,12 +33,12 @@
if isinstance(p, _stmptr):
return p._category
if not p:
- return 'N'
+ return None
if p._solid:
- return 'P' # allocated with immortal=True
+ return 'I' # allocated with immortal=True
raise AssertionError("unknown category on %r" % (p,))
- def interpret(self, fn, args):
+ def interpret(self, fn, args, gcremovetypeptr=False, run=True):
self.build_state()
clear_tcache()
interp, self.graph = get_interpreter(fn, args, view=False)
@@ -46,6 +46,7 @@
interp.frame_class = LLSTMFrame
#
self.translator = interp.typer.annotator.translator
+ self.translator.config.translation.gcremovetypeptr = gcremovetypeptr
self.stmtransformer = STMTransformer(self.translator)
if self.do_jit_driver:
self.stmtransformer.transform_jit_driver()
@@ -59,8 +60,9 @@
if self.do_jit_driver:
import py
py.test.skip("XXX how to test?")
- result = interp.eval_graph(self.graph, args)
- return result
+ if run:
+ result = interp.eval_graph(self.graph, args)
+ return result
class LLSTMFrame(LLFrame):
@@ -76,53 +78,90 @@
def check_category(self, p, expected):
cat = self.get_category_or_null(p)
- assert cat in 'NPRW'
+ assert cat is None or cat in 'AIQRVW'
+ if expected is not None:
+ assert cat is not None and cat >= expected
return cat
def op_stm_barrier(self, kind, obj):
frm, middledigit, to = kind
assert middledigit == '2'
cat = self.check_category(obj, frm)
- if not NEEDS_BARRIER[cat, to]:
+ if not needs_barrier(cat, to):
# a barrier, but with no effect
self.llinterpreter.tester.barriers.append(kind.lower())
return obj
else:
# a barrier, calling a helper
ptr2 = _stmptr(obj, to)
- if to == 'W':
+ if to >= 'V':
self.llinterpreter.tester.writemode.add(ptr2._obj)
self.llinterpreter.tester.barriers.append(kind)
return ptr2
def op_stm_ptr_eq(self, obj1, obj2):
- self.check_category(obj1, 'P')
- self.check_category(obj2, 'P')
+ self.check_category(obj1, None)
+ self.check_category(obj2, None)
self.llinterpreter.tester.barriers.append('=')
return obj1 == obj2
def op_getfield(self, obj, field):
- if not obj._TYPE.TO._immutable_field(field):
- self.check_category(obj, 'R')
+ if obj._TYPE.TO._gckind == 'gc':
+ if obj._TYPE.TO._immutable_field(field):
+ expected = 'I'
+ else:
+ expected = 'R'
+ self.check_category(obj, expected)
return LLFrame.op_getfield(self, obj, field)
def op_setfield(self, obj, fieldname, fieldvalue):
- if not obj._TYPE.TO._immutable_field(fieldname):
- self.check_category(obj, 'W')
- # convert R -> P all other pointers to the same object we can find
+ if obj._TYPE.TO._gckind == 'gc':
+ T = lltype.typeOf(fieldvalue)
+ if isinstance(T, lltype.Ptr) and T.TO._gckind == 'gc':
+ self.check_category(obj, 'W')
+ else:
+ self.check_category(obj, 'V')
+ # convert R -> Q all other pointers to the same object we can find
for p in self.all_stm_ptrs():
if p._category == 'R' and p._T == obj._T and p == obj:
- _stmptr._category.__set__(p, 'P')
+ _stmptr._category.__set__(p, 'Q')
return LLFrame.op_setfield(self, obj, fieldname, fieldvalue)
def op_cast_pointer(self, RESTYPE, obj):
- cat = self.check_category(obj, 'P')
- p = opimpl.op_cast_pointer(RESTYPE, obj)
- return _stmptr(p, cat)
+ if obj._TYPE.TO._gckind == 'gc':
+ cat = self.check_category(obj, None)
+ p = opimpl.op_cast_pointer(RESTYPE, obj)
+ return _stmptr(p, cat)
+ return lltype.cast_pointer(RESTYPE, obj)
op_cast_pointer.need_result_type = True
+ def op_cast_opaque_ptr(self, RESTYPE, obj):
+ if obj._TYPE.TO._gckind == 'gc':
+ cat = self.check_category(obj, None)
+ p = lltype.cast_opaque_ptr(RESTYPE, obj)
+ return _stmptr(p, cat)
+ return LLFrame.op_cast_opaque_ptr(self, RESTYPE, obj)
+ op_cast_opaque_ptr.need_result_type = True
+
def op_malloc(self, obj, flags):
+ assert flags['flavor'] == 'gc'
+ # convert all existing pointers W -> V
+ for p in self.all_stm_ptrs():
+ if p._category == 'W':
+ _stmptr._category.__set__(p, 'V')
p = LLFrame.op_malloc(self, obj, flags)
ptr2 = _stmptr(p, 'W')
self.llinterpreter.tester.writemode.add(ptr2._obj)
return ptr2
+
+ def transaction_break(self):
+ # convert -> I all other pointers to the same object we can find
+ for p in self.all_stm_ptrs():
+ if p._category > 'I':
+ _stmptr._category.__set__(p, 'I')
+
+ def op_stm_commit_transaction(self):
+ self.transaction_break()
+
+ def op_stm_begin_inevitable_transaction(self):
+ self.transaction_break()
diff --git a/rpython/translator/stm/transform.py
b/rpython/translator/stm/transform.py
--- a/rpython/translator/stm/transform.py
+++ b/rpython/translator/stm/transform.py
@@ -3,6 +3,7 @@
from rpython.translator.stm.inevitable import insert_turn_inevitable
from rpython.translator.stm.jitdriver import reorganize_around_jit_driver
from rpython.translator.stm.threadlocalref import transform_tlref
+from rpython.translator.stm.breakfinder import TransactionBreakAnalyzer
from rpython.translator.c.support import log
from rpython.memory.gctransform.framework import CollectAnalyzer
@@ -11,6 +12,7 @@
def __init__(self, translator):
self.translator = translator
+ self.barrier_counts = {}
def transform(self):
assert not hasattr(self.translator, 'stm_transformation_applied')
@@ -28,10 +30,14 @@
def transform_write_barrier(self):
self.write_analyzer = WriteAnalyzer(self.translator)
self.collect_analyzer = CollectAnalyzer(self.translator)
+ self.break_analyzer = TransactionBreakAnalyzer(self.translator)
for graph in self.translator.graphs:
insert_stm_barrier(self, graph)
+ for key, value in sorted(self.barrier_counts.items()):
+ log("%s: %d barriers" % (key, value[0]))
del self.write_analyzer
del self.collect_analyzer
+ del self.break_analyzer
def transform_turn_inevitable(self):
for graph in self.translator.graphs:
diff --git a/rpython/translator/stm/writebarrier.py
b/rpython/translator/stm/writebarrier.py
--- a/rpython/translator/stm/writebarrier.py
+++ b/rpython/translator/stm/writebarrier.py
@@ -1,5 +1,6 @@
from rpython.flowspace.model import SpaceOperation, Constant, Variable
from rpython.translator.unsimplify import varoftype, insert_empty_block
+from rpython.translator.unsimplify import insert_empty_startblock
from rpython.rtyper.lltypesystem import lltype
from rpython.translator.backendopt.writeanalyze import top_set
@@ -9,15 +10,6 @@
'malloc_nonmovable', 'malloc_nonmovable_varsize',
])
-NEEDS_BARRIER = {
- ('P', 'R'): True,
- ('P', 'W'): True,
- ('R', 'R'): False,
- ('R', 'W'): True,
- ('W', 'R'): False,
- ('W', 'W'): False,
- }
-
def unwraplist(list_v):
for v in list_v:
if isinstance(v, Constant):
@@ -42,153 +34,337 @@
return OUTER._immutable_interiorfield(unwraplist(op.args[1:-1]))
raise AssertionError(op)
+def needs_barrier(frm, to):
+ return to > frm
+
+def is_gc_ptr(T):
+ return isinstance(T, lltype.Ptr) and T.TO._gckind == 'gc'
+
+
+class Renaming(object):
+ def __init__(self, newvar, category):
+ self.newvar = newvar # a Variable or a Constant
+ self.TYPE = newvar.concretetype
+ self.category = category
+
+
+class BlockTransformer(object):
+
+ def __init__(self, stmtransformer, block):
+ self.stmtransformer = stmtransformer
+ self.block = block
+ self.patch = None
+ self.inputargs_category = [None] * len(block.inputargs)
+ self.inputargs_category_per_link = {}
+
+
+ def analyze_inside_block(self):
+ gcremovetypeptr = (
+ self.stmtransformer.translator.config.translation.gcremovetypeptr)
+ wants_a_barrier = {}
+ expand_comparison = set()
+ for op in self.block.operations:
+ is_getter = (op.opname in ('getfield', 'getarrayitem',
+ 'getinteriorfield') and
+ op.result.concretetype is not lltype.Void and
+ is_gc_ptr(op.args[0].concretetype))
+
+ if (gcremovetypeptr and op.opname in ('getfield', 'setfield') and
+ op.args[1].value == 'typeptr' and
+ op.args[0].concretetype.TO._hints.get('typeptr')):
+ # if gcremovetypeptr, we can access directly the typeptr
+ # field even on a stub
+ pass
+
+ elif (op.opname in ('getarraysize', 'getinteriorarraysize')
+ or (is_getter and is_immutable(op))):
+ # we can't leave getarraysize or the immutable getfields
+ # fully unmodified: we need at least immut_read_barrier
+ # to detect stubs.
+ wants_a_barrier[op] = 'I'
+
+ elif is_getter:
+ # the non-immutable getfields need a regular read barrier
+ wants_a_barrier[op] = 'R'
+
+ elif (op.opname in ('setfield', 'setarrayitem',
+ 'setinteriorfield') and
+ op.args[-1].concretetype is not lltype.Void and
+ is_gc_ptr(op.args[0].concretetype)):
+ # setfields need a regular write barrier
+ T = op.args[-1].concretetype
+ if is_gc_ptr(T):
+ wants_a_barrier[op] = 'W'
+ else:
+ # a write of a non-gc pointer doesn't need to check for
+ # the GCFLAG_WRITEBARRIER
+ wants_a_barrier[op] = 'V'
+
+ elif (op.opname in ('ptr_eq', 'ptr_ne') and
+ is_gc_ptr(op.args[0].concretetype)):
+ # GC pointer comparison might need special care
+ expand_comparison.add(op)
+ #
+ self.wants_a_barrier = wants_a_barrier
+ self.expand_comparison = expand_comparison
+
+
+ def flow_through_block(self, graphinfo):
+
+ def renfetch(v):
+ try:
+ return renamings[v]
+ except KeyError:
+ if isinstance(v, Variable):
+ ren = Renaming(v, 'A')
+ else:
+ ren = Renaming(v, 'I') # prebuilt objects cannot be stubs
+ renamings[v] = ren
+ return ren
+
+ def get_category_or_null(v):
+ # 'v' is an original variable here, or a constant
+ if isinstance(v, Constant) and not v.value: # a NULL constant
+ return None
+ if v in renamings:
+ return renamings[v].category
+ if isinstance(v, Constant):
+ return 'I'
+ else:
+ return 'A'
+
+ def renamings_get(v):
+ try:
+ ren = renamings[v]
+ except KeyError:
+ return v # unmodified
+ v2 = ren.newvar
+ if v2.concretetype == v.concretetype:
+ return v2
+ v3 = varoftype(v.concretetype)
+ newoperations.append(SpaceOperation('cast_pointer', [v2], v3))
+ if lltype.castable(ren.TYPE, v3.concretetype) > 0:
+ ren.TYPE = v3.concretetype
+ return v3
+
+ # note: 'renamings' maps old vars to new vars, but cast_pointers
+ # are done lazily. It means that the two vars may not have
+ # exactly the same type.
+ renamings = {} # {original-var: Renaming(newvar, category)}
+ newoperations = []
+ stmtransformer = self.stmtransformer
+
+ # make the initial trivial renamings needed to have some precise
+ # categories for the input args
+ for v, cat in zip(self.block.inputargs, self.inputargs_category):
+ if cat is not None and is_gc_ptr(v.concretetype):
+ renamings[v] = Renaming(v, cat)
+
+ for op in self.block.operations:
+ #
+ if (op.opname in ('cast_pointer', 'same_as') and
+ is_gc_ptr(op.result.concretetype)):
+ renamings[op.result] = renfetch(op.args[0])
+ continue
+ #
+ to = self.wants_a_barrier.get(op)
+ if to is not None:
+ ren = renfetch(op.args[0])
+ frm = ren.category
+ if needs_barrier(frm, to):
+ try:
+ b = stmtransformer.barrier_counts[frm, to]
+ except KeyError:
+ c_info = Constant('%s2%s' % (frm, to), lltype.Void)
+ b = [0, c_info]
+ stmtransformer.barrier_counts[frm, to] = b
+ b[0] += 1
+ c_info = b[1]
+ v = ren.newvar
+ w = varoftype(v.concretetype)
+ newop = SpaceOperation('stm_barrier', [c_info, v], w)
+ newoperations.append(newop)
+ ren.newvar = w
+ ren.category = to
+ #
+ newop = SpaceOperation(op.opname,
+ [renamings_get(v) for v in op.args],
+ op.result)
+ newoperations.append(newop)
+ #
+ if op in self.expand_comparison:
+ cats = (get_category_or_null(op.args[0]),
+ get_category_or_null(op.args[1]))
+ if None not in cats and (cats[0] < 'V' or cats[1] < 'V'):
+ if newop.opname == 'ptr_ne':
+ v = varoftype(lltype.Bool)
+ negop = SpaceOperation('bool_not', [v],
+ newop.result)
+ newoperations.append(negop)
+ newop.result = v
+ newop.opname = 'stm_ptr_eq'
+
+ if stmtransformer.break_analyzer.analyze(op):
+ # this operation can perform a transaction break:
+ # all pointers are lowered to 'I', because a non-
+ # stub cannot suddenly point to a stub, but we
+ # cannot guarantee anything more
+ for ren in renamings.values():
+ if ren.category > 'I':
+ ren.category = 'I'
+
+ if op.opname == 'debug_stm_flush_barrier':
+ for ren in renamings.values():
+ ren.category = 'A'
+
+ if stmtransformer.collect_analyzer.analyze(op):
+ # this operation can collect: we bring all 'W'
+ # categories back to 'V', because we would need
+ # a repeat_write_barrier on them afterwards
+ for ren in renamings.values():
+ if ren.category == 'W':
+ ren.category = 'V'
+
+ effectinfo = stmtransformer.write_analyzer.analyze(
+ op, graphinfo=graphinfo)
+ if effectinfo:
+ if effectinfo is top_set:
+ # this operation can perform random writes: any
+ # 'R'-category object falls back to 'Q' because
+ # we would need a repeat_read_barrier()
+ for ren in renamings.values():
+ if ren.category == 'R':
+ ren.category = 'Q'
+ else:
+ # the same, but only on objects of the right types
+ # -- we need to consider 'types' or any base type
+ types = set()
+ for entry in effectinfo:
+ TYPE = entry[1].TO
+ while TYPE is not None:
+ types.add(TYPE)
+ if not isinstance(TYPE, lltype.Struct):
+ break
+ _, TYPE = TYPE._first_struct()
+ for ren in renamings.values():
+ if ren.TYPE.TO in types and ren.category == 'R':
+ ren.category = 'Q'
+
+ if op.opname in MALLOCS:
+ assert op.result not in renamings
+ renamings[op.result] = Renaming(op.result, 'W')
+
+ if isinstance(self.block.exitswitch, Variable):
+ switchv = renamings_get(self.block.exitswitch)
+ else:
+ switchv = None
+ blockoperations = newoperations
+ linkoperations = []
+ for link in self.block.exits:
+ output_categories = []
+ for v in link.args:
+ if is_gc_ptr(v.concretetype):
+ cat = get_category_or_null(v)
+ else:
+ cat = None
+ output_categories.append(cat)
+ newoperations = []
+ newargs = [renamings_get(v) for v in link.args]
+ linkoperations.append((newargs, newoperations, output_categories))
+ #
+ # Record how we'd like to patch the block, but don't do any
+ # patching yet
+ self.patch = (blockoperations, switchv, linkoperations)
+
+
+ def update_targets(self, block_transformers):
+ (_, _, linkoperations) = self.patch
+ assert len(linkoperations) == len(self.block.exits)
+ targetbts = []
+ for link, (_, _, output_categories) in zip(self.block.exits,
+ linkoperations):
+ targetblock = link.target
+ if targetblock not in block_transformers:
+ continue # ignore the exit block
+ targetbt = block_transformers[targetblock]
+ targetbt.inputargs_category_per_link[link] = output_categories
+ if targetbt.update_inputargs_category():
+ targetbts.append(targetbt)
+ return set(targetbts)
+
+ def update_inputargs_category(self):
+ values = self.inputargs_category_per_link.values()
+ newcats = []
+ for i in range(len(self.block.inputargs)):
+ cat = None
+ for output_categories in values:
+ cat2 = output_categories[i]
+ if cat is None:
+ cat = cat2
+ elif cat2 is not None:
+ cat = min(cat, cat2)
+ newcats.append(cat)
+ if newcats != self.inputargs_category:
+ self.inputargs_category = newcats
+ return True
+ else:
+ return False
+
+
+ def patch_now(self):
+ if self.patch is None:
+ return
+ newoperations, switchv, linkoperations = self.patch
+ self.block.operations = newoperations
+ if switchv is not None:
+ self.block.exitswitch = switchv
+ assert len(linkoperations) == len(self.block.exits)
+ for link, (newargs, newoperations, _) in zip(self.block.exits,
+ linkoperations):
+ link.args[:] = newargs
+ if newoperations:
+ # must put them in a fresh block along the link
+ annotator = self.stmtransformer.translator.annotator
+ newblock = insert_empty_block(annotator, link,
+ newoperations)
+
def insert_stm_barrier(stmtransformer, graph):
"""This function uses the following characters for 'categories':
- * 'P': a general pointer
+ * 'A': any general pointer
+ * 'I': not a stub (immut_read_barrier was applied)
+ * 'Q': same as R, except needs a repeat_read_barrier
* 'R': the read barrier was applied
+ * 'V': same as W, except needs a repeat_write_barrier
* 'W': the write barrier was applied
+
+ The letters are chosen so that a barrier is needed to change a
+ pointer from category x to category y if and only if y > x.
"""
graphinfo = stmtransformer.write_analyzer.compute_graph_info(graph)
+ annotator = stmtransformer.translator.annotator
+ insert_empty_startblock(annotator, graph)
- def get_category(v):
- return category.get(v, 'P')
-
- def get_category_or_null(v):
- if isinstance(v, Constant) and not v.value:
- return 'N'
- return category.get(v, 'P')
-
- def renamings_get(v):
- if v not in renamings:
- return v
- v2 = renamings[v][0]
- if v2.concretetype == v.concretetype:
- return v2
- v3 = varoftype(v.concretetype)
- newoperations.append(SpaceOperation('cast_pointer', [v2], v3))
- return v3
+ block_transformers = {}
+ pending = set()
for block in graph.iterblocks():
if block.operations == ():
continue
- #
- wants_a_barrier = {}
- expand_comparison = set()
- for op in block.operations:
- # [1] XXX we can't leave getarraysize or the immutable getfields
- # fully unmodified. We'd need at least some lightweight
- # read barrier to detect stubs. For now we just put a
- # regular read barrier.
- if (op.opname in ('getfield', 'getarrayitem',
- 'getinteriorfield',
- 'getarraysize', 'getinteriorarraysize', # XXX [1]
- ) and
- op.result.concretetype is not lltype.Void and
- op.args[0].concretetype.TO._gckind == 'gc' and
- True): #not is_immutable(op)): XXX see [1]
- wants_a_barrier[op] = 'R'
- elif (op.opname in ('setfield', 'setarrayitem',
- 'setinteriorfield') and
- op.args[-1].concretetype is not lltype.Void and
- op.args[0].concretetype.TO._gckind == 'gc' and
- not is_immutable(op)):
- wants_a_barrier[op] = 'W'
- elif (op.opname in ('ptr_eq', 'ptr_ne') and
- op.args[0].concretetype.TO._gckind == 'gc'):
- expand_comparison.add(op)
- #
- if wants_a_barrier or expand_comparison:
- # note: 'renamings' maps old vars to new vars, but cast_pointers
- # are done lazily. It means that the two vars may not have
- # exactly the same type.
- renamings = {} # {original-var: [var-in-newoperations] (len 1)}
- category = {} # {var-in-newoperations: LETTER}
- newoperations = []
- for op in block.operations:
- #
- if op.opname == 'cast_pointer':
- v = op.args[0]
- renamings[op.result] = renamings.setdefault(v, [v])
- continue
- #
- to = wants_a_barrier.get(op)
- if to is not None:
- v = op.args[0]
- v_holder = renamings.setdefault(v, [v])
- v = v_holder[0]
- frm = get_category(v)
- if NEEDS_BARRIER[frm, to]:
- c_info = Constant('%s2%s' % (frm, to), lltype.Void)
- w = varoftype(v.concretetype)
- newop = SpaceOperation('stm_barrier', [c_info, v], w)
- newoperations.append(newop)
- v_holder[0] = w
- category[w] = to
- if to == 'W':
- # if any of the other vars in the same path
- # points to the same object, they must lose
- # their read-status now
- for u in block.getvariables():
- if get_category(u) == 'R' \
- and u.concretetype == v.concretetype:
- category[u] = 'P'
-
- #
- newop = SpaceOperation(op.opname,
- [renamings_get(v) for v in op.args],
- op.result)
- newoperations.append(newop)
- #
- if op in expand_comparison:
- cats = (get_category_or_null(newop.args[0]),
- get_category_or_null(newop.args[1]))
- if 'N' not in cats and cats != ('W', 'W'):
- if newop.opname == 'ptr_ne':
- v = varoftype(lltype.Bool)
- negop = SpaceOperation('bool_not', [v],
- newop.result)
- newoperations.append(negop)
- newop.result = v
- newop.opname = 'stm_ptr_eq'
+ bt = BlockTransformer(stmtransformer, block)
+ bt.analyze_inside_block()
+ block_transformers[block] = bt
+ pending.add(bt)
- if stmtransformer.collect_analyzer.analyze(op):
- # this operation can collect: we bring all 'W'
- # categories back to 'R', because we would need
- # another stm_write_barrier on them afterwards
- for v, cat in category.items():
- if cat == 'W':
- category[v] = 'R'
+ while pending:
+ # XXX sadly, this seems to be order-dependent. Picking the minimum
+ # of the blocks seems to be necessary, too, to avoid the situation
+ # of two blocks chasing each other around a loop :-(
+ bt = min(pending)
+ pending.remove(bt)
+ bt.flow_through_block(graphinfo)
+ pending |= bt.update_targets(block_transformers)
- effectinfo = stmtransformer.write_analyzer.analyze(
- op, graphinfo=graphinfo)
- if effectinfo:
- if effectinfo is top_set:
- # this operation can perform random writes: any
- # 'R'-category object falls back to 'P' because
- # we would need another stm_read_barrier()
- for v, cat in category.items():
- if cat == 'R':
- category[v] = 'P'
- else:
- # the same, but only on objects of the right types
- types = set([entry[1] for entry in effectinfo])
- for v in category.keys():
- if v.concretetype in types and category[v] == 'R':
- category[v] = 'P'
-
- if op.opname in MALLOCS:
- category[op.result] = 'W'
-
- block.operations = newoperations
- #
- for link in block.exits:
- newoperations = []
- for i, v in enumerate(link.args):
- link.args[i] = renamings_get(v)
- if newoperations:
- # must put them in a fresh block along the link
- annotator = stmtransformer.translator.annotator
- newblock = insert_empty_block(annotator, link,
- newoperations)
+ for bt in block_transformers.values():
+ bt.patch_now()
_______________________________________________
pypy-commit mailing list
[email protected]
http://mail.python.org/mailman/listinfo/pypy-commit