diff --git a/devito/ir/support/guards.py b/devito/ir/support/guards.py index 8a3842ddfd..54e0dce95c 100644 --- a/devito/ir/support/guards.py +++ b/devito/ir/support/guards.py @@ -278,6 +278,14 @@ class Guards(frozendict): construction of a new object, leaving self intact. """ + def __init__(self, *args, **kwargs): + mapper = dict(*args, **kwargs) + + # A guard that reduces to `true` is pointless, hence never set + mapper = {d: v for d, v in mapper.items() if v != true} + + super().__init__(mapper) + def get(self, d, v=true): return super().get(d, v) diff --git a/devito/passes/iet/languages/C.py b/devito/passes/iet/languages/C.py index c5ac413ac4..0f687280ac 100644 --- a/devito/passes/iet/languages/C.py +++ b/devito/passes/iet/languages/C.py @@ -89,6 +89,13 @@ class CPrinter(BasePrinter, C99CodePrinter): def _print_ImaginaryUnit(self, expr): return '_Complex_I' + # C99 has no `true`/`false` without `stdbool.h`, which is never included + def _print_BooleanTrue(self, expr): + return '1' + + def _print_BooleanFalse(self, expr): + return '0' + def _print_ListInitializer(self, expr): li = super()._print_ListInitializer(expr) if expr.dtype: diff --git a/tests/test_buffering.py b/tests/test_buffering.py index 29c864e848..fa9b7d3562 100644 --- a/tests/test_buffering.py +++ b/tests/test_buffering.py @@ -1179,3 +1179,34 @@ def test_buffering_multi_cond(factor): # The last sample is on the subsampled grid, hence no extra slot expected[-2:] = [ntmod - 1, 0] assert np.allclose(f_all.data[:, 11, 11], expected) + + +def test_true_condition_single_buffer(): + """ + A condition folded to `true` (`time` is nonnegative, so `time >= 0` is + `true`) imposes no guard. Reads of `f` under it and unguarded reads of `f` + must thus share one buffer. + """ + grid = Grid(shape=(4, 4)) + time = grid.time_dim + nt = 4 + + f = TimeFunction(name='f', grid=grid, time_order=0, space_order=0, save=nt) + g = TimeFunction(name='g', grid=grid, time_order=0, space_order=0) + h = TimeFunction(name='h', grid=grid, time_order=0, space_order=0) + for i in range(nt): + f.data[i] = i + + ctrue = ConditionalDimension(name='ctrue', parent=time, condition=time >= 0, + relation='strict') + + eqs = [Eq(g, g + f, implicit_dims=ctrue), Eq(h, h + 2*f)] + + op = Operator(eqs, opt='buffering', name='TrueCondition') + + buffers = [i for i in FindSymbols().visit(op.body) if i.is_Array and i._mem_heap] + assert len(buffers) == 1 + + op.apply(time_m=0, time_M=nt-1) + assert np.allclose(g.data[0], 6.) + assert np.allclose(h.data[0], 12.) diff --git a/tests/test_dimension.py b/tests/test_dimension.py index 3e28210fe5..70defe4452 100644 --- a/tests/test_dimension.py +++ b/tests/test_dimension.py @@ -3,7 +3,7 @@ import numpy as np import pytest -from sympy import And, Or +from sympy import And, Or, false from conftest import assert_blocking, assert_structure, opts_tiling, skipif from devito import ( # noqa @@ -2128,6 +2128,27 @@ def test_blocking_w_guard(self): 't,x0_blk0,y0_blk0,x,y,z'], 't,x0_blk0,y0_blk0,x,y,z,z') + def test_false_condition(self): + """ + A condition that SymPy folds to `false` (`time` is nonnegative) must + still be printed as valid C. + """ + grid = Grid(shape=(4, 4)) + time = grid.time_dim + + condition = time < 0 + assert condition is false + ct = ConditionalDimension(name='ct', parent=time, condition=condition) + + T = TimeFunction(name='T', grid=grid, time_order=0) + f = TimeFunction(name='f', grid=grid, time_order=0, save=2) + + op = Operator([Eq(f, T + 1, implicit_dims=ct), Eq(T, T + 1)], + name='FalseCondition') + op.apply(time_M=1) + + assert np.all(f.data[:, 1, 1] == [0, 0]) + class TestCustomDimension: diff --git a/tests/test_ir.py b/tests/test_ir.py index 9a7b2e9fea..ad553af9da 100644 --- a/tests/test_ir.py +++ b/tests/test_ir.py @@ -1,6 +1,6 @@ import numpy as np import pytest -from sympy import S +from sympy import And, Or, S from conftest import EVAL, skipif # noqa from devito import ( # noqa @@ -12,7 +12,7 @@ from devito.ir.clusters import Cluster, ClusterGroup from devito.ir.equations import LoweredEq from devito.ir.equations.algorithms import dimension_sort -from devito.ir.iet import FindNodes, Iteration +from devito.ir.iet import Conditional, FindNodes, Iteration from devito.ir.stree import stree_build from devito.ir.support.basic import ( AFFINE, IRREGULAR, REGULAR, IterationInstance, Relation, Scope, TimedAccess, Vector, @@ -1716,6 +1716,35 @@ def test_local_sums(self): class TestGuards: + @pytest.mark.parametrize('relation', [And, Or, 'strict']) + def test_true_never_set(self, relation): + """ + A guard that reduces to `true` is pointless, hence never set. E.g., + `time >= 0` folds to `true` as `time` is nonnegative. + """ + grid = Grid(shape=(4, 4)) + time = grid.time_dim + + ct = ConditionalDimension(name='ct', parent=time, condition=time >= 0, + relation=relation) + + f = TimeFunction(name='f', grid=grid, time_order=0, save=4) + f1 = TimeFunction(name='f', grid=grid, time_order=0, save=4) + + eqn = Eq(f, time + 1, implicit_dims=ct) + + op0 = Operator(eqn, opt='noop') + op1 = Operator(eqn) + + for op in (op0, op1): + assert not FindNodes(Conditional).visit(op) + + op0.apply(time_M=3) + op1.apply(time_M=3, f=f1) + + assert np.all(f.data[:, 1, 1] == [1, 2, 3, 4]) + assert np.all(f.data == f1.data) + def test_guard_overflow(self): """ A toy test showing how to create a Guard to prevent writes to buffers