diff mbox series

[v2,bpf-next,2/7] tools/memory-model: add BPF ringbuf MPSC litmus tests

Message ID 20200517195727.279322-3-andriin@fb.com
State Changes Requested
Delegated to: BPF Maintainers
Headers show
Series BPF ring buffer | expand

Commit Message

Andrii Nakryiko May 17, 2020, 7:57 p.m. UTC
Add 4 litmus tests for BPF ringbuf implementation, divided into two different
use cases.

First, two unbounded case, one with 1 producer and another with
2 producers, single consumer. All reservations are supposed to succeed.

Second, bounded case with only 1 record allowed in ring buffer at any given
time. Here failures to reserve space are expected. Again, 1- and 2- producer
cases, single consumer, are validated.

Just for the fun of it, I also wrote a 3-producer cases, it took *16 hours* to
validate, but came back successful as well. I'm not including it in this
patch, because it's not practical to run it. See output for all included
4 cases and one 3-producer one with bounded use case.

Each litmust test implements producer/consumer protocol for BPF ring buffer
implementation found in kernel/bpf/ringbuf.c. Due to limitations, all records
are assumed equal-sized and producer/consumer counters are incremented by 1.
This doesn't change the correctness of the algorithm, though.

Verification results:
/* 1p1c bounded case */
$ herd7 -unroll 0 -conf linux-kernel.cfg litmus-tests/mpsc-rb+1p1c+bounded.litmus
Test mpsc-rb+1p1c+bounded Allowed
States 2
0:rFail=0; 1:rFail=0; cx=0; dropped=0; len1=1; px=1;
0:rFail=0; 1:rFail=0; cx=1; dropped=0; len1=1; px=1;
Ok
Witnesses
Positive: 3 Negative: 0
Condition exists (0:rFail=0 /\ 1:rFail=0 /\ dropped=0 /\ px=1 /\ len1=1 /\ (cx=0 \/ cx=1))
Observation mpsc-rb+1p1c+bounded Always 3 0
Time mpsc-rb+1p1c+bounded 0.03
Hash=5bdad0f41557a641370e7fa6b8eb2f43

/* 2p1c bounded case */
$ herd7 -unroll 0 -conf linux-kernel.cfg litmus-tests/mpsc-rb+2p1c+bounded.litmus
Test mpsc-rb+2p1c+bounded Allowed
States 4
0:rFail=0; 1:rFail=0; 2:rFail=0; cx=0; dropped=1; len1=1; px=1;
0:rFail=0; 1:rFail=0; 2:rFail=0; cx=1; dropped=0; len1=1; px=2;
0:rFail=0; 1:rFail=0; 2:rFail=0; cx=1; dropped=1; len1=1; px=1;
0:rFail=0; 1:rFail=0; 2:rFail=0; cx=2; dropped=0; len1=1; px=2;
Ok
Witnesses
Positive: 22 Negative: 0
Condition exists (0:rFail=0 /\ 1:rFail=0 /\ 2:rFail=0 /\ len1=1 /\ (dropped=0 /\ px=2 /\ (cx=1 \/ cx=2) \/ dropped=1 /\ px=1 /\ (cx=0 \/ cx=1)))
Observation mpsc-rb+2p1c+bounded Always 22 0
Time mpsc-rb+2p1c+bounded 119.38
Hash=e2f8f442a02bf7d8c2988ba82cf002d2

/* 1p1c unbounded case */
$ herd7 -unroll 0 -conf linux-kernel.cfg litmus-tests/mpsc-rb+1p1c+unbound.litmus
Test mpsc-rb+1p1c+unbound Allowed
States 2
0:rFail=0; 1:rFail=0; cx=0; len1=1; px=1;
0:rFail=0; 1:rFail=0; cx=1; len1=1; px=1;
Ok
Witnesses
Positive: 3 Negative: 0
Condition exists (0:rFail=0 /\ 1:rFail=0 /\ px=1 /\ len1=1 /\ (cx=0 \/ cx=1))
Observation mpsc-rb+1p1c+unbound Always 3 0
Time mpsc-rb+1p1c+unbound 0.02
Hash=be9de6487d8e27c3d37802d122e4a87c

/* 2p1c unbounded case */
$ herd7 -unroll 0 -conf linux-kernel.cfg litmus-tests/mpsc-rb+2p1c+unbound.litmus
Test mpsc-rb+2p1c+unbound Allowed
States 3
0:rFail=0; 1:rFail=0; 2:rFail=0; cx=0; len1=1; len2=1; px=2;
0:rFail=0; 1:rFail=0; 2:rFail=0; cx=1; len1=1; len2=1; px=2;
0:rFail=0; 1:rFail=0; 2:rFail=0; cx=2; len1=1; len2=1; px=2;
Ok
Witnesses
Positive: 42 Negative: 0
Condition exists (0:rFail=0 /\ 1:rFail=0 /\ 2:rFail=0 /\ px=2 /\ len1=1 /\ len2=1 /\ (cx=0 \/ cx=1 \/ cx=2))
Observation mpsc-rb+2p1c+unbound Always 42 0
Time mpsc-rb+2p1c+unbound 39.19
Hash=f0352aba9bdc03dd0b1def7d0c4956fa

/* 3p1c bounded case */
$ herd7 -unroll 0 -conf linux-kernel.cfg mpsc-rb+3p1c+bounded.litmus
Test mpsc+ringbuf-spinlock Allowed
States 5
0:rFail=0; 1:rFail=0; 2:rFail=0; 3:rFail=0; cx=0; len1=1; len2=1; px=2;
0:rFail=0; 1:rFail=0; 2:rFail=0; 3:rFail=0; cx=1; len1=1; len2=1; px=2;
0:rFail=0; 1:rFail=0; 2:rFail=0; 3:rFail=0; cx=1; len1=1; len2=1; px=3;
0:rFail=0; 1:rFail=0; 2:rFail=0; 3:rFail=0; cx=2; len1=1; len2=1; px=2;
0:rFail=0; 1:rFail=0; 2:rFail=0; 3:rFail=0; cx=2; len1=1; len2=1; px=3;
Ok
Witnesses
Positive: 558 Negative: 0
Condition exists (0:rFail=0 /\ 1:rFail=0 /\ 2:rFail=0 /\ 3:rFail=0 /\ len1=1 /\ len2=1 /\ (px=2 /\ (cx=0 \/ cx=1 \/ cx=2) \/ px=3 /\ (cx=1 \/ cx=2)))
Observation mpsc+ringbuf-spinlock Always 558 0
Time mpsc+ringbuf-spinlock 57487.24
Hash=133977dba930d167b4e1b4a6923d5687

Cc: Paul E. McKenney <paulmck@kernel.org>
Signed-off-by: Andrii Nakryiko <andriin@fb.com>
---
 .../litmus-tests/mpsc-rb+1p1c+bounded.litmus  |  92 +++++++++++
 .../litmus-tests/mpsc-rb+1p1c+unbound.litmus  |  83 ++++++++++
 .../litmus-tests/mpsc-rb+2p1c+bounded.litmus  | 152 ++++++++++++++++++
 .../litmus-tests/mpsc-rb+2p1c+unbound.litmus  | 137 ++++++++++++++++
 4 files changed, 464 insertions(+)
 create mode 100644 tools/memory-model/litmus-tests/mpsc-rb+1p1c+bounded.litmus
 create mode 100644 tools/memory-model/litmus-tests/mpsc-rb+1p1c+unbound.litmus
 create mode 100644 tools/memory-model/litmus-tests/mpsc-rb+2p1c+bounded.litmus
 create mode 100644 tools/memory-model/litmus-tests/mpsc-rb+2p1c+unbound.litmus

Comments

Paul E. McKenney May 22, 2020, 12:34 a.m. UTC | #1
On Sun, May 17, 2020 at 12:57:22PM -0700, Andrii Nakryiko wrote:
> Add 4 litmus tests for BPF ringbuf implementation, divided into two different
> use cases.
> 
> First, two unbounded case, one with 1 producer and another with
> 2 producers, single consumer. All reservations are supposed to succeed.
> 
> Second, bounded case with only 1 record allowed in ring buffer at any given
> time. Here failures to reserve space are expected. Again, 1- and 2- producer
> cases, single consumer, are validated.
> 
> Just for the fun of it, I also wrote a 3-producer cases, it took *16 hours* to
> validate, but came back successful as well. I'm not including it in this
> patch, because it's not practical to run it. See output for all included
> 4 cases and one 3-producer one with bounded use case.
> 
> Each litmust test implements producer/consumer protocol for BPF ring buffer
> implementation found in kernel/bpf/ringbuf.c. Due to limitations, all records
> are assumed equal-sized and producer/consumer counters are incremented by 1.
> This doesn't change the correctness of the algorithm, though.

Very cool!!!

However, these should go into Documentation/litmus-tests/bpf-rb or similar.
Please take a look at Documentation/litmus-tests/ in -rcu, -tip, and
-next, including the README file.

The tools/memory-model/litmus-tests directory is for basic examples,
not for the more complex real-world ones like these guys.  ;-)

								Thanx, Paul

> Verification results:
> /* 1p1c bounded case */
> $ herd7 -unroll 0 -conf linux-kernel.cfg litmus-tests/mpsc-rb+1p1c+bounded.litmus
> Test mpsc-rb+1p1c+bounded Allowed
> States 2
> 0:rFail=0; 1:rFail=0; cx=0; dropped=0; len1=1; px=1;
> 0:rFail=0; 1:rFail=0; cx=1; dropped=0; len1=1; px=1;
> Ok
> Witnesses
> Positive: 3 Negative: 0
> Condition exists (0:rFail=0 /\ 1:rFail=0 /\ dropped=0 /\ px=1 /\ len1=1 /\ (cx=0 \/ cx=1))
> Observation mpsc-rb+1p1c+bounded Always 3 0
> Time mpsc-rb+1p1c+bounded 0.03
> Hash=5bdad0f41557a641370e7fa6b8eb2f43
> 
> /* 2p1c bounded case */
> $ herd7 -unroll 0 -conf linux-kernel.cfg litmus-tests/mpsc-rb+2p1c+bounded.litmus
> Test mpsc-rb+2p1c+bounded Allowed
> States 4
> 0:rFail=0; 1:rFail=0; 2:rFail=0; cx=0; dropped=1; len1=1; px=1;
> 0:rFail=0; 1:rFail=0; 2:rFail=0; cx=1; dropped=0; len1=1; px=2;
> 0:rFail=0; 1:rFail=0; 2:rFail=0; cx=1; dropped=1; len1=1; px=1;
> 0:rFail=0; 1:rFail=0; 2:rFail=0; cx=2; dropped=0; len1=1; px=2;
> Ok
> Witnesses
> Positive: 22 Negative: 0
> Condition exists (0:rFail=0 /\ 1:rFail=0 /\ 2:rFail=0 /\ len1=1 /\ (dropped=0 /\ px=2 /\ (cx=1 \/ cx=2) \/ dropped=1 /\ px=1 /\ (cx=0 \/ cx=1)))
> Observation mpsc-rb+2p1c+bounded Always 22 0
> Time mpsc-rb+2p1c+bounded 119.38
> Hash=e2f8f442a02bf7d8c2988ba82cf002d2
> 
> /* 1p1c unbounded case */
> $ herd7 -unroll 0 -conf linux-kernel.cfg litmus-tests/mpsc-rb+1p1c+unbound.litmus
> Test mpsc-rb+1p1c+unbound Allowed
> States 2
> 0:rFail=0; 1:rFail=0; cx=0; len1=1; px=1;
> 0:rFail=0; 1:rFail=0; cx=1; len1=1; px=1;
> Ok
> Witnesses
> Positive: 3 Negative: 0
> Condition exists (0:rFail=0 /\ 1:rFail=0 /\ px=1 /\ len1=1 /\ (cx=0 \/ cx=1))
> Observation mpsc-rb+1p1c+unbound Always 3 0
> Time mpsc-rb+1p1c+unbound 0.02
> Hash=be9de6487d8e27c3d37802d122e4a87c
> 
> /* 2p1c unbounded case */
> $ herd7 -unroll 0 -conf linux-kernel.cfg litmus-tests/mpsc-rb+2p1c+unbound.litmus
> Test mpsc-rb+2p1c+unbound Allowed
> States 3
> 0:rFail=0; 1:rFail=0; 2:rFail=0; cx=0; len1=1; len2=1; px=2;
> 0:rFail=0; 1:rFail=0; 2:rFail=0; cx=1; len1=1; len2=1; px=2;
> 0:rFail=0; 1:rFail=0; 2:rFail=0; cx=2; len1=1; len2=1; px=2;
> Ok
> Witnesses
> Positive: 42 Negative: 0
> Condition exists (0:rFail=0 /\ 1:rFail=0 /\ 2:rFail=0 /\ px=2 /\ len1=1 /\ len2=1 /\ (cx=0 \/ cx=1 \/ cx=2))
> Observation mpsc-rb+2p1c+unbound Always 42 0
> Time mpsc-rb+2p1c+unbound 39.19
> Hash=f0352aba9bdc03dd0b1def7d0c4956fa
> 
> /* 3p1c bounded case */
> $ herd7 -unroll 0 -conf linux-kernel.cfg mpsc-rb+3p1c+bounded.litmus
> Test mpsc+ringbuf-spinlock Allowed
> States 5
> 0:rFail=0; 1:rFail=0; 2:rFail=0; 3:rFail=0; cx=0; len1=1; len2=1; px=2;
> 0:rFail=0; 1:rFail=0; 2:rFail=0; 3:rFail=0; cx=1; len1=1; len2=1; px=2;
> 0:rFail=0; 1:rFail=0; 2:rFail=0; 3:rFail=0; cx=1; len1=1; len2=1; px=3;
> 0:rFail=0; 1:rFail=0; 2:rFail=0; 3:rFail=0; cx=2; len1=1; len2=1; px=2;
> 0:rFail=0; 1:rFail=0; 2:rFail=0; 3:rFail=0; cx=2; len1=1; len2=1; px=3;
> Ok
> Witnesses
> Positive: 558 Negative: 0
> Condition exists (0:rFail=0 /\ 1:rFail=0 /\ 2:rFail=0 /\ 3:rFail=0 /\ len1=1 /\ len2=1 /\ (px=2 /\ (cx=0 \/ cx=1 \/ cx=2) \/ px=3 /\ (cx=1 \/ cx=2)))
> Observation mpsc+ringbuf-spinlock Always 558 0
> Time mpsc+ringbuf-spinlock 57487.24
> Hash=133977dba930d167b4e1b4a6923d5687
> 
> Cc: Paul E. McKenney <paulmck@kernel.org>
> Signed-off-by: Andrii Nakryiko <andriin@fb.com>
> ---
>  .../litmus-tests/mpsc-rb+1p1c+bounded.litmus  |  92 +++++++++++
>  .../litmus-tests/mpsc-rb+1p1c+unbound.litmus  |  83 ++++++++++
>  .../litmus-tests/mpsc-rb+2p1c+bounded.litmus  | 152 ++++++++++++++++++
>  .../litmus-tests/mpsc-rb+2p1c+unbound.litmus  | 137 ++++++++++++++++
>  4 files changed, 464 insertions(+)
>  create mode 100644 tools/memory-model/litmus-tests/mpsc-rb+1p1c+bounded.litmus
>  create mode 100644 tools/memory-model/litmus-tests/mpsc-rb+1p1c+unbound.litmus
>  create mode 100644 tools/memory-model/litmus-tests/mpsc-rb+2p1c+bounded.litmus
>  create mode 100644 tools/memory-model/litmus-tests/mpsc-rb+2p1c+unbound.litmus
> 
> diff --git a/tools/memory-model/litmus-tests/mpsc-rb+1p1c+bounded.litmus b/tools/memory-model/litmus-tests/mpsc-rb+1p1c+bounded.litmus
> new file mode 100644
> index 000000000000..cafd17afe11e
> --- /dev/null
> +++ b/tools/memory-model/litmus-tests/mpsc-rb+1p1c+bounded.litmus
> @@ -0,0 +1,92 @@
> +C mpsc-rb+1p1c+bounded
> +
> +(*
> + * Result: Always
> + *
> + * This litmus test validates BPF ring buffer implementation under the
> + * following assumptions:
> + * - 1 producer;
> + * - 1 consumer;
> + * - ring buffer has capacity for only 1 record.
> + *
> + * Expectations:
> + * - 1 record pushed into ring buffer;
> + * - 0 or 1 element is consumed.
> + * - no failures.
> + *)
> +
> +{
> +	max_len = 1;
> +	len1 = 0;
> +	px = 0;
> +	cx = 0;
> +	dropped = 0;
> +}
> +
> +P0(int *len1, int *cx, int *px)
> +{
> +	int *rLenPtr;
> +	int rLen;
> +	int rPx;
> +	int rCx;
> +	int rFail;
> +
> +	rFail = 0;
> +	rCx = smp_load_acquire(cx);
> +
> +	rPx = smp_load_acquire(px);
> +	if (rCx < rPx) {
> +		if (rCx == 0)
> +			rLenPtr = len1;
> +		else
> +			rFail = 1;
> +
> +		rLen = smp_load_acquire(rLenPtr);
> +		if (rLen == 0) {
> +			rFail = 1;
> +		} else if (rLen == 1) {
> +			rCx = rCx + 1;
> +			smp_store_release(cx, rCx);
> +		}
> +	}
> +}
> +
> +P1(int *len1, spinlock_t *rb_lock, int *px, int *cx, int *dropped, int *max_len)
> +{
> +	int rPx;
> +	int rCx;
> +	int rFail;
> +	int *rLenPtr;
> +
> +	rFail = 0;
> +	rCx = smp_load_acquire(cx);
> +
> +	spin_lock(rb_lock);
> +
> +	rPx = *px;
> +	if (rPx - rCx >= *max_len) {
> +		atomic_inc(dropped);
> +		spin_unlock(rb_lock);
> +	} else {
> +		if (rPx == 0)
> +			rLenPtr = len1;
> +		else
> +			rFail = 1;
> +
> +		*rLenPtr = -1;
> +		smp_wmb();
> +		smp_store_release(px, rPx + 1);
> +
> +		spin_unlock(rb_lock);
> +
> +		smp_store_release(rLenPtr, 1);
> +	}
> +}
> +
> +exists (
> +	0:rFail=0 /\ 1:rFail=0
> +	/\
> +	(
> +		(dropped=0 /\ px=1 /\ len1=1 /\ (cx=0 \/ cx=1))
> +	)
> +)
> diff --git a/tools/memory-model/litmus-tests/mpsc-rb+1p1c+unbound.litmus b/tools/memory-model/litmus-tests/mpsc-rb+1p1c+unbound.litmus
> new file mode 100644
> index 000000000000..84f660598015
> --- /dev/null
> +++ b/tools/memory-model/litmus-tests/mpsc-rb+1p1c+unbound.litmus
> @@ -0,0 +1,83 @@
> +C mpsc-rb+1p1c+unbound
> +
> +(*
> + * Result: Always
> + *
> + * This litmus test validates BPF ring buffer implementation under the
> + * following assumptions:
> + * - 1 producer;
> + * - 1 consumer;
> + * - ring buffer capacity is unbounded.
> + *
> + * Expectations:
> + * - 1 record pushed into ring buffer;
> + * - 0 or 1 element is consumed.
> + * - no failures.
> + *)
> +
> +{
> +	len1 = 0;
> +	px = 0;
> +	cx = 0;
> +}
> +
> +P0(int *len1, int *cx, int *px)
> +{
> +	int *rLenPtr;
> +	int rLen;
> +	int rPx;
> +	int rCx;
> +	int rFail;
> +
> +	rFail = 0;
> +	rCx = smp_load_acquire(cx);
> +
> +	rPx = smp_load_acquire(px);
> +	if (rCx < rPx) {
> +		if (rCx == 0)
> +			rLenPtr = len1;
> +		else
> +			rFail = 1;
> +
> +		rLen = smp_load_acquire(rLenPtr);
> +		if (rLen == 0) {
> +			rFail = 1;
> +		} else if (rLen == 1) {
> +			rCx = rCx + 1;
> +			smp_store_release(cx, rCx);
> +		}
> +	}
> +}
> +
> +P1(int *len1, spinlock_t *rb_lock, int *px, int *cx)
> +{
> +	int rPx;
> +	int rCx;
> +	int rFail;
> +	int *rLenPtr;
> +
> +	rFail = 0;
> +	rCx = smp_load_acquire(cx);
> +
> +	spin_lock(rb_lock);
> +
> +	rPx = *px;
> +	if (rPx == 0)
> +		rLenPtr = len1;
> +	else
> +		rFail = 1;
> +
> +	*rLenPtr = -1;
> +	smp_wmb();
> +	smp_store_release(px, rPx + 1);
> +
> +	spin_unlock(rb_lock);
> +
> +	smp_store_release(rLenPtr, 1);
> +}
> +
> +exists (
> +	0:rFail=0 /\ 1:rFail=0
> +	/\ px=1 /\ len1=1
> +	/\ (cx=0 \/ cx=1)
> +)
> diff --git a/tools/memory-model/litmus-tests/mpsc-rb+2p1c+bounded.litmus b/tools/memory-model/litmus-tests/mpsc-rb+2p1c+bounded.litmus
> new file mode 100644
> index 000000000000..900104c4933b
> --- /dev/null
> +++ b/tools/memory-model/litmus-tests/mpsc-rb+2p1c+bounded.litmus
> @@ -0,0 +1,152 @@
> +C mpsc-rb+2p1c+bounded
> +
> +(*
> + * Result: Always
> + *
> + * This litmus test validates BPF ring buffer implementation under the
> + * following assumptions:
> + * - 2 identical producers;
> + * - 1 consumer;
> + * - ring buffer has capacity for only 1 record.
> + *
> + * Expectations:
> + * - either 1 or 2 records are pushed into ring buffer;
> + * - 0, 1, or 2 elements are consumed by consumer;
> + * - appropriate number of dropped records is recorded to satisfy ring buffer
> + *   size bounds;
> + * - no failures.
> + *)
> +
> +{
> +	max_len = 1;
> +	len1 = 0;
> +	px = 0;
> +	cx = 0;
> +	dropped = 0;
> +}
> +
> +P0(int *len1, int *cx, int *px)
> +{
> +	int *rLenPtr;
> +	int rLen;
> +	int rPx;
> +	int rCx;
> +	int rFail;
> +
> +	rFail = 0;
> +	rCx = smp_load_acquire(cx);
> +
> +	rPx = smp_load_acquire(px);
> +	if (rCx < rPx) {
> +		if (rCx == 0)
> +			rLenPtr = len1;
> +		else if (rCx == 1)
> +			rLenPtr = len1;
> +		else
> +			rFail = 1;
> +
> +		rLen = smp_load_acquire(rLenPtr);
> +		if (rLen == 0) {
> +			rFail = 1;
> +		} else if (rLen == 1) {
> +			rCx = rCx + 1;
> +			smp_store_release(cx, rCx);
> +		}
> +	}
> +
> +	rPx = smp_load_acquire(px);
> +	if (rCx < rPx) {
> +		if (rCx == 0)
> +			rLenPtr = len1;
> +		else if (rCx == 1)
> +			rLenPtr = len1;
> +		else
> +			rFail = 1;
> +
> +		rLen = smp_load_acquire(rLenPtr);
> +		if (rLen == 0) {
> +			rFail = 1;
> +		} else if (rLen == 1) {
> +			rCx = rCx + 1;
> +			smp_store_release(cx, rCx);
> +		}
> +	}
> +}
> +
> +P1(int *len1, spinlock_t *rb_lock, int *px, int *cx, int *dropped, int *max_len)
> +{
> +	int rPx;
> +	int rCx;
> +	int rFail;
> +	int *rLenPtr;
> +
> +	rFail = 0;
> +	rCx = smp_load_acquire(cx);
> +
> +	spin_lock(rb_lock);
> +
> +	rPx = *px;
> +	if (rPx - rCx >= *max_len) {
> +		atomic_inc(dropped);
> +		spin_unlock(rb_lock);
> +	} else {
> +		if (rPx == 0)
> +			rLenPtr = len1;
> +		else if (rPx == 1)
> +			rLenPtr = len1;
> +		else
> +			rFail = 1;
> +
> +		*rLenPtr = -1;
> +		smp_wmb();
> +		smp_store_release(px, rPx + 1);
> +
> +		spin_unlock(rb_lock);
> +
> +		smp_store_release(rLenPtr, 1);
> +	}
> +}
> +
> +P2(int *len1, spinlock_t *rb_lock, int *px, int *cx, int *dropped, int *max_len)
> +{
> +	int rPx;
> +	int rCx;
> +	int rFail;
> +	int *rLenPtr;
> +
> +	rFail = 0;
> +	rCx = smp_load_acquire(cx);
> +
> +	spin_lock(rb_lock);
> +
> +	rPx = *px;
> +	if (rPx - rCx >= *max_len) {
> +		atomic_inc(dropped);
> +		spin_unlock(rb_lock);
> +	} else {
> +		if (rPx == 0)
> +			rLenPtr = len1;
> +		else if (rPx == 1)
> +			rLenPtr = len1;
> +		else
> +			rFail = 1;
> +
> +		*rLenPtr = -1;
> +		smp_wmb();
> +		smp_store_release(px, rPx + 1);
> +
> +		spin_unlock(rb_lock);
> +
> +		smp_store_release(rLenPtr, 1);
> +	}
> +}
> +
> +exists (
> +	0:rFail=0 /\ 1:rFail=0 /\ 2:rFail=0 /\ len1=1
> +	/\
> +	(
> +		(dropped = 0 /\ px=2 /\ (cx=1 \/ cx=2))
> +		\/
> +		(dropped = 1 /\ px=1 /\ (cx=0 \/ cx=1))
> +	)
> +)
> diff --git a/tools/memory-model/litmus-tests/mpsc-rb+2p1c+unbound.litmus b/tools/memory-model/litmus-tests/mpsc-rb+2p1c+unbound.litmus
> new file mode 100644
> index 000000000000..83372e9eb079
> --- /dev/null
> +++ b/tools/memory-model/litmus-tests/mpsc-rb+2p1c+unbound.litmus
> @@ -0,0 +1,137 @@
> +C mpsc-rb+2p1c+unbound
> +
> +(*
> + * Result: Always
> + *
> + * This litmus test validates BPF ring buffer implementation under the
> + * following assumptions:
> + * - 2 identical producers;
> + * - 1 consumer;
> + * - ring buffer capacity is unbounded.
> + *
> + * Expectations:
> + * - 2 records pushed into ring buffer;
> + * - 0, 1, or 2 elements are consumed.
> + * - no failures.
> + *)
> +
> +{
> +	len1 = 0;
> +	len2 = 0;
> +	px = 0;
> +	cx = 0;
> +}
> +
> +P0(int *len1, int *len2, int *cx, int *px)
> +{
> +	int *rLenPtr;
> +	int rLen;
> +	int rPx;
> +	int rCx;
> +	int rFail;
> +
> +	rFail = 0;
> +	rCx = smp_load_acquire(cx);
> +
> +	rPx = smp_load_acquire(px);
> +	if (rCx < rPx) {
> +		if (rCx == 0)
> +			rLenPtr = len1;
> +		else if (rCx == 1)
> +			rLenPtr = len2;
> +		else
> +			rFail = 1;
> +
> +		rLen = smp_load_acquire(rLenPtr);
> +		if (rLen == 0) {
> +			rFail = 1;
> +		} else if (rLen == 1) {
> +			rCx = rCx + 1;
> +			smp_store_release(cx, rCx);
> +		}
> +	}
> +
> +	rPx = smp_load_acquire(px);
> +	if (rCx < rPx) {
> +		if (rCx == 0)
> +			rLenPtr = len1;
> +		else if (rCx == 1)
> +			rLenPtr = len2;
> +		else
> +			rFail = 1;
> +
> +		rLen = smp_load_acquire(rLenPtr);
> +		if (rLen == 0) {
> +			rFail = 1;
> +		} else if (rLen == 1) {
> +			rCx = rCx + 1;
> +			smp_store_release(cx, rCx);
> +		}
> +	}
> +}
> +
> +P1(int *len1, int *len2, spinlock_t *rb_lock, int *px, int *cx)
> +{
> +	int rPx;
> +	int rCx;
> +	int rFail;
> +	int *rLenPtr;
> +
> +	rFail = 0;
> +	rCx = smp_load_acquire(cx);
> +
> +	spin_lock(rb_lock);
> +
> +	rPx = *px;
> +	if (rPx == 0)
> +		rLenPtr = len1;
> +	else if (rPx == 1)
> +		rLenPtr = len2;
> +	else
> +		rFail = 1;
> +
> +	*rLenPtr = -1;
> +	smp_wmb();
> +	smp_store_release(px, rPx + 1);
> +
> +	spin_unlock(rb_lock);
> +
> +	smp_store_release(rLenPtr, 1);
> +}
> +
> +P2(int *len1, int *len2, spinlock_t *rb_lock, int *px, int *cx)
> +{
> +	int rPx;
> +	int rCx;
> +	int rFail;
> +	int *rLenPtr;
> +
> +	rFail = 0;
> +	rCx = smp_load_acquire(cx);
> +
> +	spin_lock(rb_lock);
> +
> +	rPx = *px;
> +	if (rPx == 0)
> +		rLenPtr = len1;
> +	else if (rPx == 1)
> +		rLenPtr = len2;
> +	else
> +		rFail = 1;
> +
> +	*rLenPtr = -1;
> +	smp_wmb();
> +	smp_store_release(px, rPx + 1);
> +
> +	spin_unlock(rb_lock);
> +
> +	smp_store_release(rLenPtr, 1);
> +}
> +
> +exists (
> +	0:rFail=0 /\ 1:rFail=0 /\ 2:rFail=0
> +	/\
> +	px=2 /\ len1=1 /\ len2=1
> +	/\
> +	(cx=0 \/ cx=1 \/ cx=2)
> +)
> -- 
> 2.24.1
>
Andrii Nakryiko May 22, 2020, 6:51 p.m. UTC | #2
On Thu, May 21, 2020 at 5:34 PM Paul E. McKenney <paulmck@kernel.org> wrote:
>
> On Sun, May 17, 2020 at 12:57:22PM -0700, Andrii Nakryiko wrote:
> > Add 4 litmus tests for BPF ringbuf implementation, divided into two different
> > use cases.
> >
> > First, two unbounded case, one with 1 producer and another with
> > 2 producers, single consumer. All reservations are supposed to succeed.
> >
> > Second, bounded case with only 1 record allowed in ring buffer at any given
> > time. Here failures to reserve space are expected. Again, 1- and 2- producer
> > cases, single consumer, are validated.
> >
> > Just for the fun of it, I also wrote a 3-producer cases, it took *16 hours* to
> > validate, but came back successful as well. I'm not including it in this
> > patch, because it's not practical to run it. See output for all included
> > 4 cases and one 3-producer one with bounded use case.
> >
> > Each litmust test implements producer/consumer protocol for BPF ring buffer
> > implementation found in kernel/bpf/ringbuf.c. Due to limitations, all records
> > are assumed equal-sized and producer/consumer counters are incremented by 1.
> > This doesn't change the correctness of the algorithm, though.
>
> Very cool!!!
>
> However, these should go into Documentation/litmus-tests/bpf-rb or similar.
> Please take a look at Documentation/litmus-tests/ in -rcu, -tip, and
> -next, including the README file.
>
> The tools/memory-model/litmus-tests directory is for basic examples,
> not for the more complex real-world ones like these guys.  ;-)

Oh, ok, I didn't realize there are more litmus tests under
Documentation/litmus-tests... Might have saved me some time (more
examples to learn from!) when I was writing mine :) Will check those
and move everything.

>
>                                                                 Thanx, Paul
>

[...]
Andrii Nakryiko May 25, 2020, 11:33 p.m. UTC | #3
On Fri, May 22, 2020 at 11:51 AM Andrii Nakryiko
<andrii.nakryiko@gmail.com> wrote:
>
> On Thu, May 21, 2020 at 5:34 PM Paul E. McKenney <paulmck@kernel.org> wrote:
> >
> > On Sun, May 17, 2020 at 12:57:22PM -0700, Andrii Nakryiko wrote:
> > > Add 4 litmus tests for BPF ringbuf implementation, divided into two different
> > > use cases.
> > >
> > > First, two unbounded case, one with 1 producer and another with
> > > 2 producers, single consumer. All reservations are supposed to succeed.
> > >
> > > Second, bounded case with only 1 record allowed in ring buffer at any given
> > > time. Here failures to reserve space are expected. Again, 1- and 2- producer
> > > cases, single consumer, are validated.
> > >
> > > Just for the fun of it, I also wrote a 3-producer cases, it took *16 hours* to
> > > validate, but came back successful as well. I'm not including it in this
> > > patch, because it's not practical to run it. See output for all included
> > > 4 cases and one 3-producer one with bounded use case.
> > >
> > > Each litmust test implements producer/consumer protocol for BPF ring buffer
> > > implementation found in kernel/bpf/ringbuf.c. Due to limitations, all records
> > > are assumed equal-sized and producer/consumer counters are incremented by 1.
> > > This doesn't change the correctness of the algorithm, though.
> >
> > Very cool!!!
> >
> > However, these should go into Documentation/litmus-tests/bpf-rb or similar.
> > Please take a look at Documentation/litmus-tests/ in -rcu, -tip, and
> > -next, including the README file.
> >
> > The tools/memory-model/litmus-tests directory is for basic examples,
> > not for the more complex real-world ones like these guys.  ;-)
>
> Oh, ok, I didn't realize there are more litmus tests under
> Documentation/litmus-tests... Might have saved me some time (more
> examples to learn from!) when I was writing mine :) Will check those
> and move everything.
>

Ok, so Documentation/litmus-tests is not present in bpf-next, so I
guess I'll have to split this patch out and post it separately. BTW,
it's not in -rcu tree either, should I post this against linux-next
tree directly?


> >
> >                                                                 Thanx, Paul
> >
>
> [...]
Paul E. McKenney May 26, 2020, 3:05 a.m. UTC | #4
On Mon, May 25, 2020 at 04:33:15PM -0700, Andrii Nakryiko wrote:
> On Fri, May 22, 2020 at 11:51 AM Andrii Nakryiko
> <andrii.nakryiko@gmail.com> wrote:
> >
> > On Thu, May 21, 2020 at 5:34 PM Paul E. McKenney <paulmck@kernel.org> wrote:
> > >
> > > On Sun, May 17, 2020 at 12:57:22PM -0700, Andrii Nakryiko wrote:
> > > > Add 4 litmus tests for BPF ringbuf implementation, divided into two different
> > > > use cases.
> > > >
> > > > First, two unbounded case, one with 1 producer and another with
> > > > 2 producers, single consumer. All reservations are supposed to succeed.
> > > >
> > > > Second, bounded case with only 1 record allowed in ring buffer at any given
> > > > time. Here failures to reserve space are expected. Again, 1- and 2- producer
> > > > cases, single consumer, are validated.
> > > >
> > > > Just for the fun of it, I also wrote a 3-producer cases, it took *16 hours* to
> > > > validate, but came back successful as well. I'm not including it in this
> > > > patch, because it's not practical to run it. See output for all included
> > > > 4 cases and one 3-producer one with bounded use case.
> > > >
> > > > Each litmust test implements producer/consumer protocol for BPF ring buffer
> > > > implementation found in kernel/bpf/ringbuf.c. Due to limitations, all records
> > > > are assumed equal-sized and producer/consumer counters are incremented by 1.
> > > > This doesn't change the correctness of the algorithm, though.
> > >
> > > Very cool!!!
> > >
> > > However, these should go into Documentation/litmus-tests/bpf-rb or similar.
> > > Please take a look at Documentation/litmus-tests/ in -rcu, -tip, and
> > > -next, including the README file.
> > >
> > > The tools/memory-model/litmus-tests directory is for basic examples,
> > > not for the more complex real-world ones like these guys.  ;-)
> >
> > Oh, ok, I didn't realize there are more litmus tests under
> > Documentation/litmus-tests... Might have saved me some time (more
> > examples to learn from!) when I was writing mine :) Will check those
> > and move everything.
> 
> Ok, so Documentation/litmus-tests is not present in bpf-next, so I
> guess I'll have to split this patch out and post it separately. BTW,
> it's not in -rcu tree either, should I post this against linux-next
> tree directly?

It is on branch "dev" of -rcu, though yet not on branch "master".

							Thanx, Paul
diff mbox series

Patch

diff --git a/tools/memory-model/litmus-tests/mpsc-rb+1p1c+bounded.litmus b/tools/memory-model/litmus-tests/mpsc-rb+1p1c+bounded.litmus
new file mode 100644
index 000000000000..cafd17afe11e
--- /dev/null
+++ b/tools/memory-model/litmus-tests/mpsc-rb+1p1c+bounded.litmus
@@ -0,0 +1,92 @@ 
+C mpsc-rb+1p1c+bounded
+
+(*
+ * Result: Always
+ *
+ * This litmus test validates BPF ring buffer implementation under the
+ * following assumptions:
+ * - 1 producer;
+ * - 1 consumer;
+ * - ring buffer has capacity for only 1 record.
+ *
+ * Expectations:
+ * - 1 record pushed into ring buffer;
+ * - 0 or 1 element is consumed.
+ * - no failures.
+ *)
+
+{
+	max_len = 1;
+	len1 = 0;
+	px = 0;
+	cx = 0;
+	dropped = 0;
+}
+
+P0(int *len1, int *cx, int *px)
+{
+	int *rLenPtr;
+	int rLen;
+	int rPx;
+	int rCx;
+	int rFail;
+
+	rFail = 0;
+	rCx = smp_load_acquire(cx);
+
+	rPx = smp_load_acquire(px);
+	if (rCx < rPx) {
+		if (rCx == 0)
+			rLenPtr = len1;
+		else
+			rFail = 1;
+
+		rLen = smp_load_acquire(rLenPtr);
+		if (rLen == 0) {
+			rFail = 1;
+		} else if (rLen == 1) {
+			rCx = rCx + 1;
+			smp_store_release(cx, rCx);
+		}
+	}
+}
+
+P1(int *len1, spinlock_t *rb_lock, int *px, int *cx, int *dropped, int *max_len)
+{
+	int rPx;
+	int rCx;
+	int rFail;
+	int *rLenPtr;
+
+	rFail = 0;
+	rCx = smp_load_acquire(cx);
+
+	spin_lock(rb_lock);
+
+	rPx = *px;
+	if (rPx - rCx >= *max_len) {
+		atomic_inc(dropped);
+		spin_unlock(rb_lock);
+	} else {
+		if (rPx == 0)
+			rLenPtr = len1;
+		else
+			rFail = 1;
+
+		*rLenPtr = -1;
+		smp_wmb();
+		smp_store_release(px, rPx + 1);
+
+		spin_unlock(rb_lock);
+
+		smp_store_release(rLenPtr, 1);
+	}
+}
+
+exists (
+	0:rFail=0 /\ 1:rFail=0
+	/\
+	(
+		(dropped=0 /\ px=1 /\ len1=1 /\ (cx=0 \/ cx=1))
+	)
+)
diff --git a/tools/memory-model/litmus-tests/mpsc-rb+1p1c+unbound.litmus b/tools/memory-model/litmus-tests/mpsc-rb+1p1c+unbound.litmus
new file mode 100644
index 000000000000..84f660598015
--- /dev/null
+++ b/tools/memory-model/litmus-tests/mpsc-rb+1p1c+unbound.litmus
@@ -0,0 +1,83 @@ 
+C mpsc-rb+1p1c+unbound
+
+(*
+ * Result: Always
+ *
+ * This litmus test validates BPF ring buffer implementation under the
+ * following assumptions:
+ * - 1 producer;
+ * - 1 consumer;
+ * - ring buffer capacity is unbounded.
+ *
+ * Expectations:
+ * - 1 record pushed into ring buffer;
+ * - 0 or 1 element is consumed.
+ * - no failures.
+ *)
+
+{
+	len1 = 0;
+	px = 0;
+	cx = 0;
+}
+
+P0(int *len1, int *cx, int *px)
+{
+	int *rLenPtr;
+	int rLen;
+	int rPx;
+	int rCx;
+	int rFail;
+
+	rFail = 0;
+	rCx = smp_load_acquire(cx);
+
+	rPx = smp_load_acquire(px);
+	if (rCx < rPx) {
+		if (rCx == 0)
+			rLenPtr = len1;
+		else
+			rFail = 1;
+
+		rLen = smp_load_acquire(rLenPtr);
+		if (rLen == 0) {
+			rFail = 1;
+		} else if (rLen == 1) {
+			rCx = rCx + 1;
+			smp_store_release(cx, rCx);
+		}
+	}
+}
+
+P1(int *len1, spinlock_t *rb_lock, int *px, int *cx)
+{
+	int rPx;
+	int rCx;
+	int rFail;
+	int *rLenPtr;
+
+	rFail = 0;
+	rCx = smp_load_acquire(cx);
+
+	spin_lock(rb_lock);
+
+	rPx = *px;
+	if (rPx == 0)
+		rLenPtr = len1;
+	else
+		rFail = 1;
+
+	*rLenPtr = -1;
+	smp_wmb();
+	smp_store_release(px, rPx + 1);
+
+	spin_unlock(rb_lock);
+
+	smp_store_release(rLenPtr, 1);
+}
+
+exists (
+	0:rFail=0 /\ 1:rFail=0
+	/\ px=1 /\ len1=1
+	/\ (cx=0 \/ cx=1)
+)
diff --git a/tools/memory-model/litmus-tests/mpsc-rb+2p1c+bounded.litmus b/tools/memory-model/litmus-tests/mpsc-rb+2p1c+bounded.litmus
new file mode 100644
index 000000000000..900104c4933b
--- /dev/null
+++ b/tools/memory-model/litmus-tests/mpsc-rb+2p1c+bounded.litmus
@@ -0,0 +1,152 @@ 
+C mpsc-rb+2p1c+bounded
+
+(*
+ * Result: Always
+ *
+ * This litmus test validates BPF ring buffer implementation under the
+ * following assumptions:
+ * - 2 identical producers;
+ * - 1 consumer;
+ * - ring buffer has capacity for only 1 record.
+ *
+ * Expectations:
+ * - either 1 or 2 records are pushed into ring buffer;
+ * - 0, 1, or 2 elements are consumed by consumer;
+ * - appropriate number of dropped records is recorded to satisfy ring buffer
+ *   size bounds;
+ * - no failures.
+ *)
+
+{
+	max_len = 1;
+	len1 = 0;
+	px = 0;
+	cx = 0;
+	dropped = 0;
+}
+
+P0(int *len1, int *cx, int *px)
+{
+	int *rLenPtr;
+	int rLen;
+	int rPx;
+	int rCx;
+	int rFail;
+
+	rFail = 0;
+	rCx = smp_load_acquire(cx);
+
+	rPx = smp_load_acquire(px);
+	if (rCx < rPx) {
+		if (rCx == 0)
+			rLenPtr = len1;
+		else if (rCx == 1)
+			rLenPtr = len1;
+		else
+			rFail = 1;
+
+		rLen = smp_load_acquire(rLenPtr);
+		if (rLen == 0) {
+			rFail = 1;
+		} else if (rLen == 1) {
+			rCx = rCx + 1;
+			smp_store_release(cx, rCx);
+		}
+	}
+
+	rPx = smp_load_acquire(px);
+	if (rCx < rPx) {
+		if (rCx == 0)
+			rLenPtr = len1;
+		else if (rCx == 1)
+			rLenPtr = len1;
+		else
+			rFail = 1;
+
+		rLen = smp_load_acquire(rLenPtr);
+		if (rLen == 0) {
+			rFail = 1;
+		} else if (rLen == 1) {
+			rCx = rCx + 1;
+			smp_store_release(cx, rCx);
+		}
+	}
+}
+
+P1(int *len1, spinlock_t *rb_lock, int *px, int *cx, int *dropped, int *max_len)
+{
+	int rPx;
+	int rCx;
+	int rFail;
+	int *rLenPtr;
+
+	rFail = 0;
+	rCx = smp_load_acquire(cx);
+
+	spin_lock(rb_lock);
+
+	rPx = *px;
+	if (rPx - rCx >= *max_len) {
+		atomic_inc(dropped);
+		spin_unlock(rb_lock);
+	} else {
+		if (rPx == 0)
+			rLenPtr = len1;
+		else if (rPx == 1)
+			rLenPtr = len1;
+		else
+			rFail = 1;
+
+		*rLenPtr = -1;
+		smp_wmb();
+		smp_store_release(px, rPx + 1);
+
+		spin_unlock(rb_lock);
+
+		smp_store_release(rLenPtr, 1);
+	}
+}
+
+P2(int *len1, spinlock_t *rb_lock, int *px, int *cx, int *dropped, int *max_len)
+{
+	int rPx;
+	int rCx;
+	int rFail;
+	int *rLenPtr;
+
+	rFail = 0;
+	rCx = smp_load_acquire(cx);
+
+	spin_lock(rb_lock);
+
+	rPx = *px;
+	if (rPx - rCx >= *max_len) {
+		atomic_inc(dropped);
+		spin_unlock(rb_lock);
+	} else {
+		if (rPx == 0)
+			rLenPtr = len1;
+		else if (rPx == 1)
+			rLenPtr = len1;
+		else
+			rFail = 1;
+
+		*rLenPtr = -1;
+		smp_wmb();
+		smp_store_release(px, rPx + 1);
+
+		spin_unlock(rb_lock);
+
+		smp_store_release(rLenPtr, 1);
+	}
+}
+
+exists (
+	0:rFail=0 /\ 1:rFail=0 /\ 2:rFail=0 /\ len1=1
+	/\
+	(
+		(dropped = 0 /\ px=2 /\ (cx=1 \/ cx=2))
+		\/
+		(dropped = 1 /\ px=1 /\ (cx=0 \/ cx=1))
+	)
+)
diff --git a/tools/memory-model/litmus-tests/mpsc-rb+2p1c+unbound.litmus b/tools/memory-model/litmus-tests/mpsc-rb+2p1c+unbound.litmus
new file mode 100644
index 000000000000..83372e9eb079
--- /dev/null
+++ b/tools/memory-model/litmus-tests/mpsc-rb+2p1c+unbound.litmus
@@ -0,0 +1,137 @@ 
+C mpsc-rb+2p1c+unbound
+
+(*
+ * Result: Always
+ *
+ * This litmus test validates BPF ring buffer implementation under the
+ * following assumptions:
+ * - 2 identical producers;
+ * - 1 consumer;
+ * - ring buffer capacity is unbounded.
+ *
+ * Expectations:
+ * - 2 records pushed into ring buffer;
+ * - 0, 1, or 2 elements are consumed.
+ * - no failures.
+ *)
+
+{
+	len1 = 0;
+	len2 = 0;
+	px = 0;
+	cx = 0;
+}
+
+P0(int *len1, int *len2, int *cx, int *px)
+{
+	int *rLenPtr;
+	int rLen;
+	int rPx;
+	int rCx;
+	int rFail;
+
+	rFail = 0;
+	rCx = smp_load_acquire(cx);
+
+	rPx = smp_load_acquire(px);
+	if (rCx < rPx) {
+		if (rCx == 0)
+			rLenPtr = len1;
+		else if (rCx == 1)
+			rLenPtr = len2;
+		else
+			rFail = 1;
+
+		rLen = smp_load_acquire(rLenPtr);
+		if (rLen == 0) {
+			rFail = 1;
+		} else if (rLen == 1) {
+			rCx = rCx + 1;
+			smp_store_release(cx, rCx);
+		}
+	}
+
+	rPx = smp_load_acquire(px);
+	if (rCx < rPx) {
+		if (rCx == 0)
+			rLenPtr = len1;
+		else if (rCx == 1)
+			rLenPtr = len2;
+		else
+			rFail = 1;
+
+		rLen = smp_load_acquire(rLenPtr);
+		if (rLen == 0) {
+			rFail = 1;
+		} else if (rLen == 1) {
+			rCx = rCx + 1;
+			smp_store_release(cx, rCx);
+		}
+	}
+}
+
+P1(int *len1, int *len2, spinlock_t *rb_lock, int *px, int *cx)
+{
+	int rPx;
+	int rCx;
+	int rFail;
+	int *rLenPtr;
+
+	rFail = 0;
+	rCx = smp_load_acquire(cx);
+
+	spin_lock(rb_lock);
+
+	rPx = *px;
+	if (rPx == 0)
+		rLenPtr = len1;
+	else if (rPx == 1)
+		rLenPtr = len2;
+	else
+		rFail = 1;
+
+	*rLenPtr = -1;
+	smp_wmb();
+	smp_store_release(px, rPx + 1);
+
+	spin_unlock(rb_lock);
+
+	smp_store_release(rLenPtr, 1);
+}
+
+P2(int *len1, int *len2, spinlock_t *rb_lock, int *px, int *cx)
+{
+	int rPx;
+	int rCx;
+	int rFail;
+	int *rLenPtr;
+
+	rFail = 0;
+	rCx = smp_load_acquire(cx);
+
+	spin_lock(rb_lock);
+
+	rPx = *px;
+	if (rPx == 0)
+		rLenPtr = len1;
+	else if (rPx == 1)
+		rLenPtr = len2;
+	else
+		rFail = 1;
+
+	*rLenPtr = -1;
+	smp_wmb();
+	smp_store_release(px, rPx + 1);
+
+	spin_unlock(rb_lock);
+
+	smp_store_release(rLenPtr, 1);
+}
+
+exists (
+	0:rFail=0 /\ 1:rFail=0 /\ 2:rFail=0
+	/\
+	px=2 /\ len1=1 /\ len2=1
+	/\
+	(cx=0 \/ cx=1 \/ cx=2)
+)