[DTrace-devel] [PATCH REVIEW 4/4] consume, cg: implement speculations

Nick Alcock nick.alcock at oracle.com
Thu Sep 9 04:13:23 PDT 2021


This works by tracking live speculations in a specs map containing
dt_bpf_specs_t, each of which tracks the number of buffers written and
whether a commit/discard has been called on it.  (bpf/speculation.c,
which finds free speculations, is limited to 16 speculations and needs
adjusting when BPF loops work).  Successful speculate()s record the ID
of the active speculation in the clause header.  When a commit happens,
a COMMIT/DISCARD record is written.

At consume time, buffers with active speculate()s are hived off into a
list of dt_spec_bufs_t under a dt_spec_bufs_head_t (when peeking, this
is only done on the first peek, identified via the CONSUME_PEEK_START
peekflag); when a commit/discard hits for a given speculation, the efunc
invocation etc is suppressed if need be (discards can be followed by
data-recording actions, in which case the efunc is not suppressed), and
the specs map entry for this speculation is sucked into the
dt_spec_bufs_head and the head chained into the dt_spec_bufs_draining
list.  This list is traversed on every consume, and any new spec bufs
committed (as if just received from the ring buffer) or discarded
appropriately.

Signed-off-by: Nick Alcock <nick.alcock at oracle.com>
---
 bpf/Build                                     |   3 +
 bpf/speculation.c                             |  63 +++
 bpf/speculation_set_drainable.c               |  43 ++
 bpf/speculation_speculate.c                   |  47 ++
 libdtrace/dt_bpf.c                            |  22 +
 libdtrace/dt_bpf.h                            |   2 +
 libdtrace/dt_bpf_maps.h                       |   9 +
 libdtrace/dt_cc.c                             |   3 +
 libdtrace/dt_cg.c                             | 182 +++++--
 libdtrace/dt_consume.c                        | 513 ++++++++++++++++--
 libdtrace/dt_dlibs.c                          |   5 +
 libdtrace/dt_errtags.h                        |   2 +-
 libdtrace/dt_impl.h                           |  32 ++
 libdtrace/dt_open.c                           |  19 +-
 libdtrace/dt_peb.h                            |   1 +
 libdtrace/dtrace.h                            |  13 +-
 .../speculation/err.CommitWithInvalid.d       |  40 ++
 .../speculation/err.CommitWithInvalid.r       |   3 +
 .../err.D_ACT_SPEC.SpeculateWithCopyOut.d     |   2 +-
 .../err.D_ACT_SPEC.SpeculateWithCopyOutStr.d  |   2 +-
 .../err.D_COMM_COMM.CommitAftCommit.d         |  61 ---
 .../err.D_COMM_COMM.CommitAftCommit.r         |   2 -
 .../err.D_COMM_COMM.DisjointCommit.d          |  81 ---
 .../err.D_COMM_COMM.DisjointCommit.r          |   2 -
 ...tAftDiscard.d => err.DiscardWithInvalid.d} |  26 +-
 .../speculation/err.DiscardWithInvalid.r      |   3 +
 .../speculation/err.SpecSizeVariations1.d     |  59 --
 .../speculation/err.SpecSizeVariations1.r     |   2 -
 .../speculation/err.SpecSizeVariations2.d     |  59 --
 .../speculation/err.SpecSizeVariations2.r     |   2 -
 .../speculation/tst.CommitAfterDiscard.d      |   9 +-
 .../speculation/tst.CommitAfterDiscard.r      |   2 +-
 .../speculation/tst.CommitCommitCommit.d      |  58 ++
 .../speculation/tst.CommitCommitCommit.r      |   5 +
 .../speculation/tst.CommitDiscard4x.d         |  99 ++++
 .../speculation/tst.CommitDiscard4x.r         |   6 +
 .../speculation/tst.CommitWithInactive.d      |  38 ++
 .../speculation/tst.CommitWithInactive.r      |   1 +
 .../unittest/speculation/tst.CommitWithZero.d |  11 +-
 .../unittest/speculation/tst.CommitWithZero.r |   4 -
 .../speculation/tst.DataRecAftDiscard.d       |   5 +-
 .../speculation/tst.DiscardAftCommit.d        |  12 +-
 .../speculation/tst.DiscardAftCommit.r        |   2 +-
 .../speculation/tst.DiscardAftDataRec.d       |   3 +-
 .../speculation/tst.DiscardAftDiscard.d       |   7 +-
 ...AftDiscard.d => tst.DiscardWithInactive.d} |  26 +-
 .../speculation/tst.DiscardWithInactive.r     |   1 +
 .../speculation/tst.DiscardWithZero.d         |  12 +-
 .../speculation/tst.DiscardWithZero.r         |   4 -
 .../unittest/speculation/tst.ExitAftDiscard.d |   1 -
 test/unittest/speculation/tst.NoSpecBuffer.d  |   6 +-
 test/unittest/speculation/tst.NoSpecBuffer.r  |   2 -
 test/unittest/speculation/tst.SingleCPU.d     |  69 +++
 test/unittest/speculation/tst.SingleCPU.r     |  17 +
 .../speculation/tst.SpecSizeVariations3.d     |  59 --
 .../speculation/tst.SpecSizeVariations3.r     |   3 -
 .../speculation/tst.SpecSizeVariations4.d     |  16 +-
 .../speculation/tst.SpecSizeVariations4.r     |   2 +-
 .../speculation/tst.SpecSizeVariations5.d     |  12 +-
 .../speculation/tst.SpecSizeVariations5.r     |   8 +-
 .../speculation/tst.SpeculationCommit.d       |   5 +-
 ...mmit.d => tst.SpeculationCommitNotQuiet.d} |  11 +-
 .../tst.SpeculationCommitNotQuiet.r           |  10 +
 .../speculation/tst.SpeculationDefault.d      |  31 ++
 .../speculation/tst.SpeculationDefault.r      |   5 +
 .../tst.SpeculationDefaultCommit.d            |  38 ++
 .../tst.SpeculationDefaultCommit.r            |   6 +
 .../tst.SpeculationDefaultDiscard.d           |  37 ++
 .../tst.SpeculationDefaultDiscard.r           |   5 +
 .../speculation/tst.SpeculationDiscard.d      |   7 +-
 .../speculation/tst.SpeculationDiscard.r      |   2 +-
 ...ard.d => tst.SpeculationDiscardNotQuiet.d} |  10 +-
 .../tst.SpeculationDiscardNotQuiet.r          |   8 +
 test/unittest/speculation/tst.SpeculationID.d |   1 -
 .../speculation/tst.SpeculationWithZero.d     |   6 +-
 .../speculation/tst.SpeculationWithZero.r     |   3 -
 .../unittest/speculation/tst.TwoSpecBuffers.d |   8 +-
 .../unittest/speculation/tst.TwoSpecBuffers.r |   4 +-
 test/unittest/speculation/tst.negcommit.d     |   1 -
 test/unittest/speculation/tst.negcommit.r     |   2 +-
 test/unittest/speculation/tst.zerosize.d      |   1 +
 81 files changed, 1447 insertions(+), 557 deletions(-)
 create mode 100644 bpf/speculation.c
 create mode 100644 bpf/speculation_set_drainable.c
 create mode 100644 bpf/speculation_speculate.c
 create mode 100644 test/unittest/speculation/err.CommitWithInvalid.d
 create mode 100644 test/unittest/speculation/err.CommitWithInvalid.r
 delete mode 100644 test/unittest/speculation/err.D_COMM_COMM.CommitAftCommit.d
 delete mode 100644 test/unittest/speculation/err.D_COMM_COMM.CommitAftCommit.r
 delete mode 100644 test/unittest/speculation/err.D_COMM_COMM.DisjointCommit.d
 delete mode 100644 test/unittest/speculation/err.D_COMM_COMM.DisjointCommit.r
 copy test/unittest/speculation/{tst.ExitAftDiscard.d => err.DiscardWithInvalid.d} (51%)
 create mode 100644 test/unittest/speculation/err.DiscardWithInvalid.r
 delete mode 100644 test/unittest/speculation/err.SpecSizeVariations1.d
 delete mode 100644 test/unittest/speculation/err.SpecSizeVariations1.r
 delete mode 100644 test/unittest/speculation/err.SpecSizeVariations2.d
 delete mode 100644 test/unittest/speculation/err.SpecSizeVariations2.r
 create mode 100644 test/unittest/speculation/tst.CommitCommitCommit.d
 create mode 100644 test/unittest/speculation/tst.CommitCommitCommit.r
 create mode 100644 test/unittest/speculation/tst.CommitDiscard4x.d
 create mode 100644 test/unittest/speculation/tst.CommitDiscard4x.r
 create mode 100644 test/unittest/speculation/tst.CommitWithInactive.d
 create mode 100644 test/unittest/speculation/tst.CommitWithInactive.r
 copy test/unittest/speculation/{tst.ExitAftDiscard.d => tst.DiscardWithInactive.d} (51%)
 create mode 100644 test/unittest/speculation/tst.DiscardWithInactive.r
 create mode 100644 test/unittest/speculation/tst.SingleCPU.d
 create mode 100644 test/unittest/speculation/tst.SingleCPU.r
 delete mode 100644 test/unittest/speculation/tst.SpecSizeVariations3.d
 delete mode 100644 test/unittest/speculation/tst.SpecSizeVariations3.r
 copy test/unittest/speculation/{tst.SpeculationCommit.d => tst.SpeculationCommitNotQuiet.d} (78%)
 create mode 100644 test/unittest/speculation/tst.SpeculationCommitNotQuiet.r
 create mode 100644 test/unittest/speculation/tst.SpeculationDefault.d
 create mode 100644 test/unittest/speculation/tst.SpeculationDefault.r
 create mode 100644 test/unittest/speculation/tst.SpeculationDefaultCommit.d
 create mode 100644 test/unittest/speculation/tst.SpeculationDefaultCommit.r
 create mode 100644 test/unittest/speculation/tst.SpeculationDefaultDiscard.d
 create mode 100644 test/unittest/speculation/tst.SpeculationDefaultDiscard.r
 copy test/unittest/speculation/{tst.SpeculationDiscard.d => tst.SpeculationDiscardNotQuiet.d} (71%)
 create mode 100644 test/unittest/speculation/tst.SpeculationDiscardNotQuiet.r

This is the majority of the work: it could possibly be split into two
pieces (speculations cg+consume, peek_only revamp) but they are
entwined enough that it felt too annoying to do that.

diff --git a/bpf/Build b/bpf/Build
index e08a28b67771..46d90733bbda 100644
--- a/bpf/Build
+++ b/bpf/Build
@@ -26,6 +26,9 @@ bpf_dlib_SOURCES = \
 	get_bvar.c \
 	get_tvar.c set_tvar.c \
 	probe_error.c \
+	speculation.c \
+        speculation_speculate.c \
+	speculation_set_drainable.c \
 	strnlen.c \
 	varint.c
 
diff --git a/bpf/speculation.c b/bpf/speculation.c
new file mode 100644
index 000000000000..d42152d8075d
--- /dev/null
+++ b/bpf/speculation.c
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ */
+#include <linux/bpf.h>
+#include <stdint.h>
+#include <bpf-helpers.h>
+#include <dt_bpf_maps.h>
+
+#ifndef noinline
+# define noinline	__attribute__((noinline))
+#endif
+
+extern struct bpf_map_def specs;
+extern uint64_t NSPEC;
+
+noinline uint32_t dt_speculation(void)
+{
+	uint32_t id;
+	dt_bpf_specs_t zero;
+
+	__builtin_memset(&zero, 0, sizeof (dt_bpf_specs_t));
+
+#if 1 /* Loops are broken in BPF right now */
+#define SEARCH(n)							\
+	do {								\
+		if (n > (uint64_t) &NSPEC)				\
+			return 0;					\
+		id = (n);						\
+		if (bpf_map_update_elem(&specs, &id, &zero,		\
+			BPF_NOEXIST) == 0)				\
+			return id;					\
+	} while (0);
+
+	SEARCH(1);
+	SEARCH(2);
+	SEARCH(3);
+	SEARCH(4);
+	SEARCH(5);
+	SEARCH(6);
+	SEARCH(7);
+	SEARCH(8);
+	SEARCH(9);
+	SEARCH(10);
+	SEARCH(11);
+	SEARCH(12);
+	SEARCH(13);
+	SEARCH(14);
+	SEARCH(15);
+	SEARCH(16);
+#else
+	/*
+	 * Waiting on a verifier that can handle loops
+	 */
+	for (id = 1; id <= (uint64_t) &NSPEC; id++) {
+		if (bpf_map_update_elem(&specs, &id, &zero,
+					BPF_NOEXIST) == 0)
+			return id;
+	}
+#endif
+
+	return 0;
+}
diff --git a/bpf/speculation_set_drainable.c b/bpf/speculation_set_drainable.c
new file mode 100644
index 000000000000..55bb5ce45d14
--- /dev/null
+++ b/bpf/speculation_set_drainable.c
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ */
+#include <linux/bpf.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <bpf-helpers.h>
+#include <dt_bpf_maps.h>
+#include <dtrace/faults_defines.h>
+#include <asm/errno.h>
+#include <stdatomic.h>
+
+#include "probe_error.h"
+
+#ifndef noinline
+# define noinline	__attribute__((noinline))
+#endif
+
+extern struct bpf_map_def specs;
+extern uint64_t NSPEC;
+
+/* Returns -1 on error.  */
+
+noinline int32_t
+dt_speculation_set_drainable(dt_dctx_t *dctx, uint32_t id)
+{
+	dt_bpf_specs_t *spec;
+
+	/*
+	 * Arguably redundant to BPF_EXIST, but that doesn't seem to return
+	 * -EEXIST as one might hope.
+	 */
+	if ((spec = bpf_map_lookup_elem(&specs, &id)) == NULL) {
+		if (id <= (uint64_t) &NSPEC)
+			return 0;
+		dt_probe_error(dctx, -1, DTRACEFLT_ILLOP, 0);
+		return -1;
+	}
+	spec->draining = 1;
+
+	return 0;
+}
diff --git a/bpf/speculation_speculate.c b/bpf/speculation_speculate.c
new file mode 100644
index 000000000000..2cb8feefef79
--- /dev/null
+++ b/bpf/speculation_speculate.c
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ */
+#include <linux/bpf.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <bpf-helpers.h>
+#include <dt_bpf_maps.h>
+#include <dtrace/faults_defines.h>
+#include <asm/errno.h>
+#include <stdatomic.h>
+
+#include "probe_error.h"
+
+#ifndef noinline
+# define noinline	__attribute__((noinline))
+#endif
+
+extern struct bpf_map_def specs;
+
+/*
+ * We consider a speculation ID usable only if it exists in the speculation map
+ * (indicating that speculation() has returned it) with a a zero read value
+ * (indicating that neither commit() nor discard() have been called for it).
+ * We bump the written value by one to indicate that another speculative buffer
+ * is (or will soon be, once this clause terminates and its epilogue runs)
+ * available for draining.
+ */
+noinline int32_t
+dt_speculation_speculate(uint32_t id)
+{
+	dt_bpf_specs_t *spec;
+
+	if ((spec = bpf_map_lookup_elem(&specs, &id)) == NULL)
+		return -1;
+
+	/*
+	 * Spec already being drained: do not continue to emit new
+	 * data into it.
+	 */
+	if (spec->draining)
+		return -1;
+
+	__atomic_add_fetch(&spec->written, 1, __ATOMIC_RELAXED);
+	return 0;
+}
diff --git a/libdtrace/dt_bpf.c b/libdtrace/dt_bpf.c
index 8195cd07aac3..bd5e55acd9e2 100644
--- a/libdtrace/dt_bpf.c
+++ b/libdtrace/dt_bpf.c
@@ -71,6 +71,20 @@ int dt_bpf_map_lookup(int fd, const void *key, void *val)
 	return bpf(BPF_MAP_LOOKUP_ELEM, &attr);
 }
 
+/*
+ * Delete the given key from the map referenced by the given fd.
+ */
+int dt_bpf_map_delete(int fd, const void *key)
+{
+	union bpf_attr attr;
+
+	memset(&attr, 0, sizeof(attr));
+	attr.map_fd = fd;
+	attr.key = (uint64_t)(unsigned long)key;
+
+	return bpf(BPF_MAP_DELETE_ELEM, &attr);
+}
+
 /*
  * Store the (key, value) pair in the map referenced by the given fd.
  */
@@ -170,6 +184,9 @@ populate_probes_map(dtrace_hdl_t *dtp, int fd)
  *		element (key 0).  Every aggregation is stored with two copies
  *		of its data to provide a lockless latch-based mechanism for
  *		atomic reading and writing.
+ * - specs:     Map associating speculation IDs with a dt_bpf_specs_t struct
+ *		giving the number of buffers speculated into for this
+ *		speculation, and the number drained by userspace.
  * - buffers:	Perf event output buffer map, associating a perf event output
  *		buffer with each CPU.  The map is indexed by CPU id.
  * - cpuinfo:	CPU information map, associating a cpuinfo_t structure with
@@ -264,6 +281,11 @@ dt_bpf_gmap_create(dtrace_hdl_t *dtp)
 			return -1;	/* dt_errno is set for us */
 	}
 
+	if (create_gmap(dtp, "specs", BPF_MAP_TYPE_HASH,
+		sizeof(uint32_t), sizeof(dt_bpf_specs_t),
+		dtp->dt_options[DTRACEOPT_NSPEC]) == -1)
+		return -1;		/* dt_errno is set for us */
+
 	if (create_gmap(dtp, "buffers", BPF_MAP_TYPE_PERF_EVENT_ARRAY,
 			sizeof(uint32_t), sizeof(uint32_t),
 			dtp->dt_conf.num_online_cpus) == -1)
diff --git a/libdtrace/dt_bpf.h b/libdtrace/dt_bpf.h
index 2882520fd701..287178c11d9b 100644
--- a/libdtrace/dt_bpf.h
+++ b/libdtrace/dt_bpf.h
@@ -26,6 +26,7 @@ extern "C" {
 #define DT_CONST_STRSZ	6
 #define DT_CONST_STKSIZ	7
 #define DT_CONST_BOOTTM	8
+#define DT_CONST_NSPEC	9
 
 extern int perf_event_open(struct perf_event_attr *attr, pid_t pid, int cpu,
 			   int group_fd, unsigned long flags);
@@ -34,6 +35,7 @@ extern int bpf(enum bpf_cmd cmd, union bpf_attr *attr);
 extern int dt_bpf_gmap_create(struct dtrace_hdl *);
 extern int dt_bpf_map_lookup(int fd, const void *key, void *val);
 extern int dt_bpf_map_update(int fd, const void *key, const void *val);
+extern int dt_bpf_map_delete(int fd, const void *key);
 extern int dt_bpf_load_progs(struct dtrace_hdl *, uint_t);
 
 #ifdef	__cplusplus
diff --git a/libdtrace/dt_bpf_maps.h b/libdtrace/dt_bpf_maps.h
index aa088ce8ee80..e33748b7aa50 100644
--- a/libdtrace/dt_bpf_maps.h
+++ b/libdtrace/dt_bpf_maps.h
@@ -12,6 +12,8 @@
 extern "C" {
 #endif
 
+#include <stddef.h>
+
 typedef struct dt_bpf_probe	dt_bpf_probe_t;
 struct dt_bpf_probe {
 	size_t		prv;		/* probeprov string offset in strtab */
@@ -20,6 +22,13 @@ struct dt_bpf_probe {
 	size_t		prb;		/* probename string offset in strtab */
 };
 
+typedef struct dt_bpf_specs	dt_bpf_specs_t;
+struct dt_bpf_specs {
+	uint64_t	written;	/* number of spec buffers written */
+	uint32_t	draining;	/* 1 if userspace has been asked to
+					 * drain this buffer */
+};
+
 #ifdef  __cplusplus
 }
 #endif
diff --git a/libdtrace/dt_cc.c b/libdtrace/dt_cc.c
index 97c7d5e2a0a1..a25f354815dc 100644
--- a/libdtrace/dt_cc.c
+++ b/libdtrace/dt_cc.c
@@ -2352,6 +2352,9 @@ dt_link_construct(dtrace_hdl_t *dtp, const dt_probe_t *prp, dtrace_difo_t *dp,
 				nrp->dofr_data =
 					dtp->dt_options[DTRACEOPT_STRSIZE];
 				continue;
+			case DT_CONST_NSPEC:
+				nrp->dofr_data = dtp->dt_options[DTRACEOPT_NSPEC];
+				continue;
 			case DT_CONST_STKSIZ:
 				nrp->dofr_data = sizeof(uint64_t)
 				    * dtp->dt_options[DTRACEOPT_MAXFRAMES];
diff --git a/libdtrace/dt_cg.c b/libdtrace/dt_cg.c
index b94f4485f295..43ed2351d51c 100644
--- a/libdtrace/dt_cg.c
+++ b/libdtrace/dt_cg.c
@@ -488,8 +488,9 @@ dt_cg_tramp_error(dt_pcb_t *pcb)
  *
  *	1. Store the base pointer to the output data buffer in %r9.
  *	2. Initialize the machine state (dctx->mst).
- *	3. Store the epid and tag at [%r9 + 0] and [%r9 + 4] respectively.
- *	4. Evaluate the predicate expression and return if false.
+ *	3. Store the epid at [%r9 + 0].
+ *	4. Store 0 to indicate no active speculation at [%r9 + 4].
+ *	5. Evaluate the predicate expression and return if false.
  *
  * The dt_program() function will always return 0.
  */
@@ -539,11 +540,11 @@ dt_cg_prologue(dt_pcb_t *pcb, dt_node_t *pred)
 	emite(dlp, BPF_STORE_IMM(BPF_W, BPF_REG_9, 0, -1), epid);
 
 	/*
-	 *	dctx->mst->tag = 0;	// stw [%r0 + DMST_TAG], 0
+	 *	Set the speculation ID field to zero to indicate no active
+	 *	speculation.
 	 *	*((uint32_t *)&buf[4]) = 0;
 	 *				// stw [%r9 + 4], 0
 	 */
-	emit(dlp,  BPF_STORE_IMM(BPF_W, BPF_REG_0, DMST_TAG, 0));
 	emit(dlp,  BPF_STORE_IMM(BPF_W, BPF_REG_9, 4, 0));
 
 	/*
@@ -587,8 +588,10 @@ dt_cg_epilogue(dt_pcb_t *pcb)
 	 * Output the buffer if:
 	 *   - data-recording action, or
 	 *   - default action (no clause specified)
+	 *   - committing or discarding a speculation
 	 */
-	if (pcb->pcb_stmt->dtsd_clauseflags & DT_CLSFLAG_DATAREC) {
+	if (pcb->pcb_stmt->dtsd_clauseflags & DT_CLSFLAG_DATAREC ||
+	    pcb->pcb_stmt->dtsd_clauseflags & DT_CLSFLAG_COMMIT_DISCARD) {
 		dt_ident_t *buffers = dt_dlib_get_map(pcb->pcb_hdl, "buffers");
 
 		assert(buffers != NULL);
@@ -923,14 +926,16 @@ dt_cg_clsflags(dt_pcb_t *pcb, dtrace_actkind_t kind, const dt_node_t *dnp)
 		*cfp |= DT_CLSFLAG_DESTRUCT;
 
 	if (kind == DTRACEACT_COMMIT) {
-		if (*cfp & DT_CLSFLAG_COMMIT)
-			dnerror(dnp, D_COMM_COMM,
-			    "commit( ) may not follow commit( )\n");
 		if (*cfp & (DT_CLSFLAG_DATAREC | DT_CLSFLAG_AGGREGATION))
 			dnerror(dnp, D_COMM_DREC, "commit( ) may "
 			    "not follow data-recording action(s)\n");
 
-		*cfp |= DT_CLSFLAG_COMMIT;
+		*cfp |= DT_CLSFLAG_COMMIT | DT_CLSFLAG_COMMIT_DISCARD;
+		return;
+	}
+
+	if (kind == DTRACEACT_DISCARD) {
+		*cfp |= DT_CLSFLAG_COMMIT_DISCARD;
 		return;
 	}
 
@@ -987,8 +992,7 @@ dt_cg_clsflags(dt_pcb_t *pcb, dtrace_actkind_t kind, const dt_node_t *dnp)
 		dnerror(dnp, D_DREC_COMM,
 		    "data-recording actions may not follow commit( )\n");
 
-	if (!(*cfp & DT_CLSFLAG_SPECULATE))
-		*cfp |= DT_CLSFLAG_DATAREC;
+	*cfp |= DT_CLSFLAG_DATAREC;
 }
 
 static void
@@ -1037,6 +1041,50 @@ dt_cg_act_clear(dt_pcb_t *pcb, dt_node_t *dnp, dtrace_actkind_t kind)
 	dt_cg_store_val(pcb, anp, DTRACEACT_LIBACT, NULL, DT_ACT_CLEAR);
 }
 
+/*
+ * Mark a speculation as committed/discarded and ready for draining.
+ *
+ * The speculation ID must be in IDREG (true for both commit and discard).
+ *
+ * If the speculation does not exist, nothing will be done: the consumer has to
+ * detect this itself if the speculation is inactive (out-of-range values
+ * fault and do not write a commit/discard record).
+ */
+static void
+dt_cg_spec_set_drainable(dt_pcb_t *pcb, dt_node_t *dnp, int idreg)
+{
+	dt_regset_t	*drp = pcb->pcb_regs;
+	dt_irlist_t	*dlp = &pcb->pcb_ir;
+	dt_ident_t	*idp;
+	uint_t		lbl_ok = dt_irlist_label(dlp);
+
+	idp = dt_dlib_get_func(yypcb->pcb_hdl, "dt_speculation_set_drainable");
+	assert(idp != NULL);
+
+	/*
+	 *	spec = dt_speculation_set_drainable(ctx, id);
+	 *				// call dt_speculation_commit_discard
+	 *				//     (%r1 ... %r5 clobbered)
+	 *	if (rc == 0)		// jeq %r0, 0, lbl_ok
+	 *		goto ok;
+	 *	return rc;		// mov %dn_reg, %r0
+	 * ok:
+	 */
+
+	if (dt_regset_xalloc_args(drp) == -1)
+		longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
+	dt_regset_xalloc(drp, BPF_REG_0);
+	emit(dlp,  BPF_LOAD(BPF_DW, BPF_REG_1, BPF_REG_FP, DT_STK_DCTX));
+	emit(dlp,  BPF_MOV_REG(BPF_REG_2, idreg));
+	emite(dlp, BPF_CALL_FUNC(idp->di_id), idp);
+	emit(dlp,  BPF_BRANCH_IMM(BPF_JEQ, BPF_REG_0, 0, lbl_ok));
+	emit(dlp,  BPF_RETURN());
+	emitl(dlp, lbl_ok,
+		   BPF_NOP());
+	dt_regset_free(drp, BPF_REG_0);
+	dt_regset_free_args(drp);
+}
+
 /*
  * Signal that the speculation with the given id should be committed to the
  * tracing output.
@@ -1047,16 +1095,12 @@ dt_cg_act_clear(dt_pcb_t *pcb, dt_node_t *dnp, dtrace_actkind_t kind)
 static void
 dt_cg_act_commit(dt_pcb_t *pcb, dt_node_t *dnp, dtrace_actkind_t kind)
 {
-	dt_irlist_t	*dlp = &pcb->pcb_ir;
-	uint_t		off;
-
-	dt_cg_node(dnp->dn_args, &pcb->pcb_ir, pcb->pcb_regs);
-
-	off = dt_rec_add(pcb->pcb_hdl, dt_cg_fill_gap, DTRACEACT_COMMIT,
-			 sizeof(uint64_t), sizeof(uint64_t), NULL,
-			 DT_ACT_COMMIT);
+	dt_regset_t	*drp = pcb->pcb_regs;
 
-	emit(dlp, BPF_STORE(BPF_DW, BPF_REG_9, off, BPF_REG_0)); /* FIXME */
+	dt_cg_node(dnp->dn_args, &pcb->pcb_ir, drp);
+	dt_cg_spec_set_drainable(pcb, dnp, dnp->dn_args->dn_reg);
+	dt_regset_free(drp, dnp->dn_args->dn_reg);
+	dt_cg_store_val(pcb, dnp->dn_args, DTRACEACT_COMMIT, NULL, DT_ACT_COMMIT);
 }
 
 static void
@@ -1094,16 +1138,12 @@ dt_cg_act_denormalize(dt_pcb_t *pcb, dt_node_t *dnp, dtrace_actkind_t kind)
 static void
 dt_cg_act_discard(dt_pcb_t *pcb, dt_node_t *dnp, dtrace_actkind_t kind)
 {
-	dt_irlist_t	*dlp = &pcb->pcb_ir;
-	uint_t		off;
-
-	dt_cg_node(dnp->dn_args, &pcb->pcb_ir, pcb->pcb_regs);
-
-	off = dt_rec_add(pcb->pcb_hdl, dt_cg_fill_gap, DTRACEACT_DISCARD,
-			 sizeof(uint64_t), sizeof(uint64_t), NULL,
-			 DT_ACT_DISCARD);
+	dt_regset_t	*drp = pcb->pcb_regs;
 
-	emit(dlp, BPF_STORE(BPF_DW, BPF_REG_9, off, BPF_REG_0)); /* FIXME */
+	dt_cg_node(dnp->dn_args, &pcb->pcb_ir, drp);
+	dt_cg_spec_set_drainable(pcb, dnp, dnp->dn_args->dn_reg);
+	dt_regset_free(drp, dnp->dn_args->dn_reg);
+	dt_cg_store_val(pcb, dnp->dn_args, DTRACEACT_DISCARD, NULL, DT_ACT_DISCARD);
 }
 
 /*
@@ -1407,22 +1447,48 @@ dt_cg_act_setopt(dt_pcb_t *pcb, dt_node_t *dnp, dtrace_actkind_t kind)
  * Signal that subsequent tracing output in the current clause should be kept
  * back pending a commit() or discard() for the speculation with the given id.
  *
- * Stores:
- *	integer (4 bytes)		-- speculation ID
+ * Updates the specid in the output buffer header, rather than emitting a new
+ * record into it.
  */
 static void
 dt_cg_act_speculate(dt_pcb_t *pcb, dt_node_t *dnp, dtrace_actkind_t kind)
 {
 	dt_irlist_t	*dlp = &pcb->pcb_ir;
-	uint_t		off;
+	dt_regset_t	*drp = pcb->pcb_regs;
+	dt_ident_t	*idp;
+	uint_t		lbl_exit = pcb->pcb_exitlbl;
 
-	dt_cg_node(dnp->dn_args, &pcb->pcb_ir, pcb->pcb_regs);
+	idp = dt_dlib_get_func(yypcb->pcb_hdl, "dt_speculation_speculate");
+	assert(idp != NULL);
+
+	dt_cg_node(dnp->dn_args, dlp, drp);
+	dnp->dn_reg = dnp->dn_args->dn_reg;
+
+	if (dt_regset_xalloc_args(drp) == -1)
+		longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
 
-	off = dt_rec_add(pcb->pcb_hdl, dt_cg_fill_gap, DTRACEACT_SPECULATE,
-			 sizeof(uint64_t), sizeof(uint64_t), NULL,
-			 DT_ACT_SPECULATE);
+	/*
+	 *	rc = dt_speculation_speculate(spec);
+	 *				// lddw %r1, %dn_reg
+	 *				// call dt_speculation_speculate
+	 *				//     (%r1 ... %r5 clobbered)
+	 *				//     (%r0 = error return)
+	 *	if (rc != 0)		// jne %r0, 0, lbl_exit
+	 *		goto exit;
+	 *	*((uint32_t *)&buf[4]) = spec;	// mov [%r9 + 4], %dn_reg
+	 *	exit:			// nop
+	 */
 
-	emit(dlp, BPF_STORE(BPF_DW, BPF_REG_9, off, BPF_REG_0)); /* FIXME */
+	emit(dlp, BPF_STORE(BPF_W, BPF_REG_FP, DT_STK_SPILL(0),
+		dnp->dn_reg));
+	emit(dlp, BPF_MOV_REG(BPF_REG_1, dnp->dn_reg));
+	dt_regset_xalloc(drp, BPF_REG_0);
+	emite(dlp, BPF_CALL_FUNC(idp->di_id), idp);
+	dt_regset_free_args(drp);
+	emit(dlp, BPF_BRANCH_IMM(BPF_JNE, BPF_REG_0, 0, lbl_exit));
+	emit(dlp, BPF_STORE(BPF_W, BPF_REG_9, 4, dnp->dn_reg));
+	dt_regset_free(drp, BPF_REG_0);
+	dt_regset_free(drp, dnp->dn_reg);
 }
 
 static void
@@ -3011,12 +3077,39 @@ dt_cg_array_op(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
 	emit(dlp, BPF_ALU64_REG((dnp->dn_flags & DT_NF_SIGNED) ? BPF_ARSH : BPF_RSH, dnp->dn_reg, n));
 }
 
+/*
+ * Get and return a new speculation ID.  These are unallocated entries in the
+ * specs map, obtained by calling dt_speculation().  Return zero if none is
+ * available.  TODO: add a drop in this case?
+ */
 static void
 dt_cg_subr_speculation(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
 {
+	dt_ident_t	*idp;
+
+	idp = dt_dlib_get_func(yypcb->pcb_hdl, "dt_speculation");
+	assert(idp != NULL);
+
 	TRACE_REGSET("    subr-speculation:Begin");
-	dnp->dn_reg = dt_regset_alloc(drp);
-	emit(dlp, BPF_MOV_IMM(dnp->dn_reg, 0));
+	if ((dnp->dn_reg = dt_regset_alloc(drp)) == -1)
+		longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
+
+
+	/*
+	 *	spec = dt_speculation();
+	 *				// call dt_speculation
+	 *				//     (%r1 ... %r5 clobbered)
+	 *				//     (%r0 = speculation ID, or 0)
+	 *	return rc;		// mov %dn_reg, %r0
+	 */
+
+	if (dt_regset_xalloc_args(drp) == -1)
+		longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
+	dt_regset_xalloc(drp, BPF_REG_0);
+	emite(dlp, BPF_CALL_FUNC(idp->di_id), idp);
+	emit(dlp,  BPF_MOV_REG(dnp->dn_reg, BPF_REG_0));
+	dt_regset_free(drp, BPF_REG_0);
+	dt_regset_free_args(drp);
 	TRACE_REGSET("    subr-speculation:End  ");
 }
 
@@ -4887,6 +4980,9 @@ dt_cg(dt_pcb_t *pcb, dt_node_t *dnp)
 		dxp->dx_ident->di_id = dt_regset_alloc(pcb->pcb_regs);
 		dt_cg_node(dnp, &pcb->pcb_ir, pcb->pcb_regs);
 	} else if (dnp->dn_kind == DT_NODE_CLAUSE) {
+		int nonspec_acts = 0;
+		int speculative = 0;
+
 		dt_cg_prologue(pcb, dnp->dn_pred);
 
 		for (act = dnp->dn_acts; act != NULL; act = act->dn_list) {
@@ -4902,6 +4998,11 @@ dt_cg(dt_pcb_t *pcb, dt_node_t *dnp)
 					actdp->fun(pcb, act->dn_expr,
 						   actdp->kind);
 				}
+
+				if (actdp->kind == DTRACEACT_SPECULATE)
+					speculative = 1;
+				else
+					nonspec_acts++;
 				break;
 			}
 			case DT_NODE_AGG:
@@ -4926,7 +5027,8 @@ dt_cg(dt_pcb_t *pcb, dt_node_t *dnp)
 					"statement\n", dnp->dn_kind);
 			}
 		}
-		if (dnp->dn_acts == NULL)
+		if (dnp->dn_acts == NULL ||
+		    (speculative && nonspec_acts == 0))
 			pcb->pcb_stmt->dtsd_clauseflags |= DT_CLSFLAG_DATAREC;
 
 		dt_cg_epilogue(pcb);
diff --git a/libdtrace/dt_consume.c b/libdtrace/dt_consume.c
index 5075254cb399..8296a0051065 100644
--- a/libdtrace/dt_consume.c
+++ b/libdtrace/dt_consume.c
@@ -364,6 +364,37 @@ dt_stddev(uint64_t *data, uint64_t normal)
 	return dt_sqrt_128(diff);
 }
 
+static uint32_t
+dt_spec_bufs_hval(const dt_spec_bufs_head_t *head)
+{
+	uint32_t g = 0, hval = 0;
+	uint32_t *p = (uint32_t *) &head->dtsh_id;
+
+	while ((uintptr_t) p < (((uintptr_t) &head->dtsh_id) +
+				sizeof(head->dtsh_id))) {
+		hval = (hval << 4) + *p++;
+		g = hval & 0xf0000000;
+		if (g != 0) {
+			hval ^= (g >> 24);
+			hval ^= g;
+		}
+	}
+
+	return hval;
+}
+
+static int
+dt_spec_bufs_cmp(const dt_spec_bufs_head_t *p,
+		 const dt_spec_bufs_head_t *q)
+{
+	if (p->dtsh_id == q->dtsh_id)
+		return 0;
+	return 1;
+}
+
+DEFINE_HE_STD_LINK_FUNCS(dt_spec_bufs, dt_spec_bufs_head_t, dtsh_he);
+DEFINE_HTAB_STD_OPS(dt_spec_bufs);
+
 static int
 dt_flowindent(dtrace_hdl_t *dtp, dtrace_probedata_t *data, dtrace_epid_t last,
 	      dtrace_epid_t next)
@@ -1935,31 +1966,217 @@ dt_print_trace(dtrace_hdl_t *dtp, FILE *fp, dtrace_recdesc_t *rec,
 	return dt_print_rawbytes(dtp, fp, data, rec->dtrd_size);
 }
 
+/*
+ * The lifecycle of speculation buffers is as follows:
+ *
+ *  - They are created upon speculation() as entries in the specs map mapping
+ *    from the speculation ID to a dt_bpf_specs_t entry with read, written,
+ *    and draining values all zero.
+ *
+ *  - speculate() verifies the existence of the requested speculation entry in
+ *    the specs map, and that draining has not been set in it, and atomically
+ *    bumps the written value.
+ *
+ *  - Speculations drain from the perf ring buffer into dt_spec_bufs, one
+ *    dt_spec_buf per ring-buffer of data fetched from the kernel with a nonzero
+ *    specid (one speculated clause); these are chained into dt_spec_buf_heads,
+ *    one per speculation ID, and these are chained into dtp->dt_spec_bufs
+ *    instead of being consumed.
+ *
+ *  - commit / discard set the specs map entry's drained value to 1, which
+ *    indicates that it is drainable by userspace and prevents further
+ *    speculate()s, and  record a single entry in the output buffer with the
+ *    committed/discarded ID attached
+ *
+ *  - Non-speculated buffers (with a zero specid) are scanned by
+ *    dt_consume_one_buffer for commit / discard; the first found for a given
+ *    live speculation triggers draining, chaining the spec buf head into
+ *    dtp->dt_spec_bufs_draining.  dt_spec_bufs_draining contains a counter of
+ *    the number of buffers drained ("read", mirroring "written" in the specs
+ *    map).
+ *
+ *  - dt_spec_bufs_draining is traversed on every buffer consume and any
+ *    speculations that are committing have their speculation buffers processed
+ *    as if they had just been received (but keeping their original CPU number);
+ *    both entries that are committing and those that are discarding are then
+ *    removed, leaving the buffer head behind to be filled out by future
+ *    ring-buffer fetches, and the number removed recorded by incrementing
+ *    dtsh_draining_t.read.  Buffers for which read >= written are removed from
+ *    dt_spec_bufs_draining and have their buffer heads removed from
+ *    dt_spec_bufs and their ID removed from the specs map, freeing them for
+ *    recycling by future calls to speculation().
+ */
+
+/*
+ * Member of the dtsh_draining_list.
+ */
+typedef struct dtsh_draining {
+	dt_list_t dtsd_list;
+	dt_spec_bufs_head_t *dtsd_dtsh;
+	uint64_t dtsd_read;
+} dtsh_draining_t;
+
+static dt_spec_bufs_head_t *
+dt_create_spec_buf_head(dtrace_hdl_t *dtp, int32_t spec)
+{
+	dt_spec_bufs_head_t *dtsh;
+
+	dtsh = dt_zalloc(dtp, sizeof(struct dt_spec_bufs_head));
+	if (!dtsh)
+		goto oom;
+	dtsh->dtsh_id = spec;
+
+	if (dt_htab_insert(dtp->dt_specs_byid, dtsh) < 0)
+		goto oom;
+	dt_list_append(&dtp->dt_spec_bufs, dtsh);
+	return dtsh;
+oom:
+	dt_free(dtp, dtsh);
+	dt_set_errno(dtp, EDT_NOMEM);
+	return NULL;
+}
+
+static dt_spec_bufs_t *
+dt_create_spec_buf(dtrace_hdl_t *dtp, dt_spec_bufs_head_t *dtsh,
+		   dtrace_epid_t epid, unsigned int cpu,
+		   dtrace_datadesc_t *datadesc, char *data,
+		   uint32_t size)
+{
+	dt_spec_bufs_t *dtsb;
+
+	dtsb = dt_zalloc(dtp, sizeof(struct dt_spec_bufs));
+	if (!dtsb)
+		goto oom;
+
+	dtsb->dtsb_cpu = cpu;
+	dtsb->dtsb_size = size;
+	dtsb->dtsb_data = dt_alloc(dtp, size);
+	if (!dtsb->dtsb_data)
+		goto oom;
+
+	dtsh->dtsh_size += size;
+	memcpy(dtsb->dtsb_data, data, size);
+
+	dt_list_append(&dtsh->dtsh_dtsb_list, dtsb);
+	return dtsb;
+
+oom:
+	dt_free(dtp, dtsb);
+	dt_set_errno(dtp, EDT_NOMEM);
+	return NULL;
+}
+
+/*
+ * Remove a speculation's buffers and possibly the speculation head and
+ * the record of it in the BPF specs map (which frees the ID for reuse).
+ *
+ * Note: if the speculation is being committed, it will also be interned on the
+ * dtp->dt_spec_bufs_draining list.  Such speculations must be removed via
+ * dt_gc_speculations, which ultimately calls this function.
+ */
+static void
+dt_destroy_spec_bufs(dtrace_hdl_t *dtp, dt_spec_bufs_head_t *dtsh,
+	int remove_specid)
+{
+	dt_spec_bufs_t	*dtsb;
+
+	while ((dtsb = dt_list_next(&dtsh->dtsh_dtsb_list)) != NULL) {
+
+		dt_list_delete(&dtsh->dtsh_dtsb_list, dtsb);
+		dt_free(dtp, dtsb->dtsb_data);
+		dt_free(dtp, dtsb);
+	}
+
+	dtsh->dtsh_size = 0;
+
+	if (remove_specid) {
+		dt_ident_t *idp = dt_dlib_get_map(dtp, "specs");
+
+		if (idp)
+			dt_bpf_map_delete(idp->di_id, &dtsh->dtsh_id);
+		dt_list_delete(&dtp->dt_spec_bufs, dtsh);
+		dt_free(dtp, dtsh);
+	}
+}
+
+/*
+ * Peeking flags (values for the peekflag parameter for functions that have
+ * one).
+ *
+ * These let you process a single buffer more than once.  The first call
+ * should pass CONSUME_PEEK_START: this suppresses deletion of consumed records.
+ * Subsequent calls should pass CONSUME_PEEK; this does as CONSUME_PEEK_START
+ * does, but also suppresses hiving off of speculative buffers (because they
+ * have already been hived off under CONSUME_PEEK_START).  The final call should
+ * pass CONSUME_PEEK_FINISH; this does a normal buffer consumption (with
+ * deletion) except that speculative hiving-off is again suppressd.
+ *
+ * These are not bit-flags: pass only one.
+ */
+#define CONSUME_PEEK_START 0x1
+#define CONSUME_PEEK 0x2
+#define CONSUME_PEEK_FINISH 0x3
+
 static dtrace_workstatus_t
 dt_consume_one_buffer(dtrace_hdl_t *dtp, FILE *fp, char *data, uint32_t size,
 		      dtrace_probedata_t *pdat, dtrace_consume_probe_f *efunc,
 		      dtrace_consume_rec_f *rfunc, int flow, int quiet,
-		      dtrace_epid_t *last, void *arg);
+		      int peekflags, dtrace_epid_t *last, int committing,
+		      void *arg);
+
+/*
+ * Commit one speculation.
+ */
+static dtrace_workstatus_t
+dt_commit_one_spec(dtrace_hdl_t *dtp, FILE *fp, dt_spec_bufs_head_t *dtsh,
+		   dtrace_probedata_t *pdat, dtrace_consume_probe_f *efunc,
+		   dtrace_consume_rec_f *rfunc, int flow, int quiet,
+		   int peekflags, dtrace_epid_t *last, void *arg)
+{
+	dt_spec_bufs_t 	*dtsb;
+
+	for (dtsb = dt_list_next(&dtsh->dtsh_dtsb_list);
+	     dtsb != NULL; dtsb = dt_list_next(dtsb)) {
+		dtrace_workstatus_t ret;
+		dtrace_probedata_t specpdat;
+
+		memcpy(&specpdat, pdat, sizeof(dtrace_probedata_t));
+		specpdat.dtpda_cpu = dtsb->dtsb_cpu;
+		ret = dt_consume_one_buffer(dtp, fp, dtsb->dtsb_data,
+					    dtsb->dtsb_size, &specpdat,
+					    efunc, rfunc, flow, quiet,
+					    peekflags, last, 1, arg);
+		if (ret != DTRACE_WORKSTATUS_OKAY)
+			return ret;
+	}
+
+	return DTRACE_WORKSTATUS_OKAY;
+}
 
 static dtrace_workstatus_t
 dt_consume_one_buffer(dtrace_hdl_t *dtp, FILE *fp, char *data, uint32_t size,
 		      dtrace_probedata_t *pdat, dtrace_consume_probe_f *efunc,
 		      dtrace_consume_rec_f *rfunc, int flow, int quiet,
-		      dtrace_epid_t *last, void *arg)
+		      int peekflags, dtrace_epid_t *last, int committing,
+		      void *arg)
 {
 	dtrace_epid_t		epid;
+	dt_spec_bufs_head_t	tmpl;
+	dt_spec_bufs_head_t	*dtsh;
+	int			specid;
 	int			i;
 	int			rval;
+	dtrace_workstatus_t	ret;
+	int			commit_discard_seen, only_commit_discards;
+	int			data_recording = 1;
 
 
 	epid = ((uint32_t *)data)[0];
-#ifdef FIXME
-	tag = ((uint32_t *)data)[1];		/* for future use */
-#endif
+	specid = ((uint32_t *)data)[1];
 
 	/*
 	 * Fill in the epid and address of the epid in the buffer.  We need to
-	 * pass this to the efunc.
+	 * pass this to the efunc and possibly to create speculations.
 	 */
 	pdat->dtpda_epid = epid;
 	pdat->dtpda_data = data;
@@ -1978,30 +2195,115 @@ dt_consume_one_buffer(dtrace_hdl_t *dtp, FILE *fp, char *data, uint32_t size,
 			return DTRACE_WORKSTATUS_ERROR;
 	}
 
-	if (flow)
-		dt_flowindent(dtp, pdat, *last, DTRACE_EPIDNONE);
+	/*
+	 * If speculating (and not peeking), hive this buffer off into a
+	 * suitable spec buf, creating the spec buf head if need be.  No need to
+	 * do anything else with this buffer until a commit or discard is seen
+	 * for it (in some other, non-speculated buffer).
+	 */
 
-	rval = (*efunc)(pdat, arg);
+	if (!committing && specid != 0) {
+		size_t cursz = 0;
 
-	if (flow) {
-		if (pdat->dtpda_flow == DTRACEFLOW_ENTRY)
-			pdat->dtpda_indent += 2;
-	}
+		/*
+		 * If peeking, we dealt with this buffer earlier: don't
+		 * re-speculate it again.
+		 */
+		if (peekflags == CONSUME_PEEK ||
+		    peekflags == CONSUME_PEEK_FINISH)
+			return DTRACE_WORKSTATUS_OKAY;
+
+		tmpl.dtsh_id = specid;
+		dtsh = dt_htab_lookup(dtp->dt_specs_byid, &tmpl);
+
+		/*
+		 * Discard when over the specsize.
+		 *
+		 * TODO: add a drop when OOM or > specsize -- and also on OOM in
+		 * any of the consuming functions.
+		 */
+
+		if (dtsh)
+			cursz = dtsh->dtsh_size;
+
+		if (cursz + size > dtp->dt_options[DTRACEOPT_SPECSIZE])
+			return DTRACE_WORKSTATUS_OKAY;
+
+		if (!dtsh) {
+			if ((dtsh = dt_create_spec_buf_head(dtp, specid)) == NULL)
+				return -1;
+		}
+
+		if (dt_create_spec_buf(dtp, dtsh, epid, pdat->dtpda_cpu,
+				       pdat->dtpda_ddesc, data, size) == NULL)
+			return -1;
 
-	switch (rval) {
-	case DTRACE_CONSUME_NEXT:
 		return DTRACE_WORKSTATUS_OKAY;
-	case DTRACE_CONSUME_DONE:
-		return DTRACE_WORKSTATUS_DONE;
-	case DTRACE_CONSUME_ABORT:
-		return dt_set_errno(dtp, EDT_DIRABORT);
-	case DTRACE_CONSUME_THIS:
-		break;
-	default:
-		return dt_set_errno(dtp, EDT_BADRVAL);
 	}
 
 	/*
+	 * First, scan for commit/discard.  Track whether we have seen discards,
+	 * and whether we have seen anything else, to determine whether this
+	 * clause should be considered data-recording from the user's
+	 * perspective.  (A clause with only a discard is not data-recording.
+	 * Commits cannot share a clause with data-recording actions at all: see
+	 * dt_cg_clsflags.)
+	 *
+	 * Do nothing of this if committing speculated buffers, since speculated
+	 * buffers cannot contain commits or discards.
+	 */
+	commit_discard_seen = 0;
+	only_commit_discards = 1;
+	for (i = 0; !committing && i < pdat->dtpda_ddesc->dtdd_nrecs; i++) {
+		dtrace_recdesc_t	*rec;
+		dtrace_actkind_t	act;
+
+		rec = &pdat->dtpda_ddesc->dtdd_recs[i];
+		act = rec->dtrd_action;
+
+		if (act == DTRACEACT_COMMIT || act == DTRACEACT_DISCARD) {
+			commit_discard_seen = 1;
+		} else
+			only_commit_discards = 0;
+	}
+
+	/*
+	 * If this clause is a commit or discard, and no other actions have been
+	 * seen, this is not a data-recording clause, and we should not call the
+	 * efunc or rfunc at all.
+	 */
+
+	if (commit_discard_seen && only_commit_discards)
+		data_recording = 0;
+
+	if (data_recording) {
+		if (flow)
+			dt_flowindent(dtp, pdat, *last, DTRACE_EPIDNONE);
+
+		rval = (*efunc)(pdat, arg);
+
+		if (flow) {
+			if (pdat->dtpda_flow == DTRACEFLOW_ENTRY)
+				pdat->dtpda_indent += 2;
+		}
+
+		switch (rval) {
+		case DTRACE_CONSUME_NEXT:
+			return DTRACE_WORKSTATUS_OKAY;
+		case DTRACE_CONSUME_DONE:
+			return DTRACE_WORKSTATUS_DONE;
+		case DTRACE_CONSUME_ABORT:
+			return dt_set_errno(dtp, EDT_DIRABORT);
+		case DTRACE_CONSUME_THIS:
+			break;
+		default:
+			return dt_set_errno(dtp, EDT_BADRVAL);
+		}
+	}
+
+	/*
+	 * Now scan for data-recording actions.
+	 *
 	 * FIXME: This code is temporary.
 	 */
 	for (i = 0; i < pdat->dtpda_ddesc->dtdd_nrecs; i++) {
@@ -2037,6 +2339,49 @@ dt_consume_one_buffer(dtrace_hdl_t *dtp, FILE *fp, char *data, uint32_t size,
 			}
 		}
 
+		if (act == DTRACEACT_COMMIT || act == DTRACEACT_DISCARD) {
+			/*
+			 * Committing or discarding.  If this is the first
+			 * commit/discard we've seen for this speculation,
+			 * arrange to drain it until enough CPUs have passed by
+			 * that all must have been drained.  Out-of-range IDs
+			 * are automatically ignored by the code below, since
+			 * they will have no dtsh entries.
+			 */
+
+			assert(specid == 0);
+
+			tmpl.dtsh_id = *(uint32_t *)recdata;
+			dtsh = dt_htab_lookup(dtp->dt_specs_byid, &tmpl);
+
+			/*
+			 * Speculation exists and is not already being drained.
+			 */
+			if (dtsh && !dtsh->dtsh_spec.draining) {
+				dtsh_draining_t *dtsd;
+				dt_bpf_specs_t spec;
+				dt_ident_t *idp = dt_dlib_get_map(dtp, "specs");
+				int rval;
+
+				rval = dt_bpf_map_lookup(idp->di_id, &tmpl.dtsh_id, &spec);
+				if (rval != 0)
+					return dt_set_errno(dtp, -rval);
+
+				assert(spec.draining);
+				dtsd = dt_zalloc(dtp, sizeof(dtsh_draining_t));
+				if (!dtsd)
+					return dt_set_errno(dtp, EDT_NOMEM);
+				dtsd->dtsd_dtsh = dtsh;
+				dtsh->dtsh_committing = (act == DTRACEACT_COMMIT);
+				memcpy(&dtsh->dtsh_spec, &spec,
+				    sizeof(dt_bpf_specs_t));
+				dt_list_append(&dtp->dt_spec_bufs_draining, dtsd);
+			}
+			continue;
+		}
+
+		assert(data_recording);
+
 		rval = (*rfunc)(pdat, rec, arg);
 
 		if (rval == DTRACE_CONSUME_NEXT)
@@ -2122,9 +2467,49 @@ dt_consume_one_buffer(dtrace_hdl_t *dtp, FILE *fp, char *data, uint32_t size,
 	 * that we're done processing this EPID.  The return value is ignored in
 	 * this case. XXX should we respect at least DTRACE_CONSUME_ABORT?
 	 */
-	(*rfunc)(pdat, NULL, arg);
+	if (data_recording) {
+		(*rfunc)(pdat, NULL, arg);
 
-	*last = epid;
+		*last = epid;
+	}
+
+	/*
+	 * If we're not committing a speculative buffer already, commit any spec
+	 * buffers that are marked as committing and draining and have any
+	 * content to drain.
+	 */
+	if (!committing) {
+		dtsh_draining_t *dtsd, *next;
+
+		for (dtsd = dt_list_next(&dtp->dt_spec_bufs_draining);
+		     dtsd != NULL; dtsd = next) {
+
+			dtsh = dtsd->dtsd_dtsh;
+			dtsd->dtsd_read += dt_list_length(&dtsd->dtsd_dtsh->dtsh_dtsb_list);
+
+			if (dtsh->dtsh_committing) {
+				if ((ret = dt_commit_one_spec(dtp, fp, dtsh,
+							      pdat, efunc, rfunc,
+							      flow, quiet, peekflags,
+							      last, arg)) !=
+				    DTRACE_WORKSTATUS_OKAY) {
+					dt_destroy_spec_bufs(dtp, dtsh, 0);
+					return ret;
+				}
+			}
+
+			next = dt_list_next(dtsd);
+
+			if (dtsd->dtsd_read < dtsd->dtsd_dtsh->dtsh_spec.written)
+				dt_destroy_spec_bufs(dtp, dtsh, 0);
+			else {
+				dt_destroy_spec_bufs(dtp, dtsh, 1);
+				dt_htab_delete(dtp->dt_specs_byid, dtsd->dtsd_dtsh);
+				dt_list_delete(&dtp->dt_spec_bufs_draining, dtsd);
+				dt_free(dtp, dtsd);
+			}
+		}
+	}
 
 	return DTRACE_WORKSTATUS_OKAY;
 }
@@ -2132,7 +2517,7 @@ dt_consume_one_buffer(dtrace_hdl_t *dtp, FILE *fp, char *data, uint32_t size,
 static dtrace_workstatus_t
 dt_consume_one(dtrace_hdl_t *dtp, FILE *fp, char *buf,
 	       dtrace_probedata_t *pdat, dtrace_consume_probe_f *efunc,
-	       dtrace_consume_rec_f *rfunc, int flow, int quiet,
+	       dtrace_consume_rec_f *rfunc, int flow, int quiet, int peekflags,
 	       dtrace_epid_t *last, void *arg)
 {
 	char				*data = buf;
@@ -2151,7 +2536,7 @@ dt_consume_one(dtrace_hdl_t *dtp, FILE *fp, char *buf,
 		 *	uint32_t			size;
 		 *	uint32_t			pad;
 		 *	uint32_t			epid;
-		 *	uint32_t			tag;
+		 *	uint32_t			specid;
 		 *	uint64_t			data[n];
 		 * }
 		 * and 'data' points to the 'size' member at this point.
@@ -2170,7 +2555,8 @@ dt_consume_one(dtrace_hdl_t *dtp, FILE *fp, char *buf,
 		size -= sizeof(uint32_t);
 
 		return dt_consume_one_buffer(dtp, fp, data, size, pdat, efunc,
-					     rfunc, flow, quiet, last, arg);
+					     rfunc, flow, quiet, peekflags,
+					     last, 0, arg);
 	} else if (hdr->type == PERF_RECORD_LOST) {
 #ifdef FIXME
 		uint64_t	lost;
@@ -2195,7 +2581,7 @@ dt_consume_one(dtrace_hdl_t *dtp, FILE *fp, char *buf,
 int
 dt_consume_cpu(dtrace_hdl_t *dtp, FILE *fp, dt_peb_t *peb,
 	       dtrace_consume_probe_f *efunc, dtrace_consume_rec_f *rfunc,
-	       int peek_only, void *arg)
+	       int peekflags, void *arg)
 {
 	struct perf_event_mmap_page	*rb_page = (void *)peb->base;
 	struct perf_event_header	*hdr;
@@ -2214,6 +2600,11 @@ dt_consume_cpu(dtrace_hdl_t *dtp, FILE *fp, dt_peb_t *peb,
 
 	/*
 	 * Clear the probe data, and fill in data independent fields.
+	 *
+	 * Initializing more fields here (or anywhere above
+	 * dt_consume_one_buffer) may require the addition of new fields to
+	 * dt_spec_bufs, if you want the original value to be preserved acorss
+	 * speculate/commit.
 	 */
 	memset(&pdat, 0, sizeof(pdat));
 	pdat.dtpda_handle = dtp;
@@ -2226,7 +2617,12 @@ dt_consume_cpu(dtrace_hdl_t *dtp, FILE *fp, dt_peb_t *peb,
 	base = peb->base + pebset->page_size;
 
 	do {
-		head = ring_buffer_read_head(rb_page);
+		if (peekflags == CONSUME_PEEK || peekflags == CONSUME_PEEK_FINISH)
+			head = peb->last_head;
+		else {
+			head = ring_buffer_read_head(rb_page);
+			peb->last_head = head;
+		}
 		tail = rb_page->data_tail;
 
 		if (head == tail)
@@ -2262,7 +2658,8 @@ dt_consume_cpu(dtrace_hdl_t *dtp, FILE *fp, dt_peb_t *peb,
 			}
 
 			rval = dt_consume_one(dtp, fp, event, &pdat, efunc,
-					      rfunc, flow, quiet, &last, arg);
+					      rfunc, flow, quiet, peekflags,
+					      &last, arg);
 			if (rval == DTRACE_WORKSTATUS_DONE)
 				return DTRACE_WORKSTATUS_OKAY;
 			if (rval != DTRACE_WORKSTATUS_OKAY)
@@ -2271,9 +2668,9 @@ dt_consume_cpu(dtrace_hdl_t *dtp, FILE *fp, dt_peb_t *peb,
 			tail += hdr->size;
 		} while (tail != head);
 
-		if (!peek_only)
+		if (peekflags == 0 || peekflags == CONSUME_PEEK_FINISH)
 			ring_buffer_write_tail(rb_page, tail);
-	} while (!peek_only);
+	} while (peekflags == 0);
 
 	return DTRACE_WORKSTATUS_OKAY;
 }
@@ -2420,7 +2817,8 @@ dt_consume_begin(dtrace_hdl_t *dtp, FILE *fp, struct epoll_event *events,
 	dtp->dt_errarg = &begin;
 
 	rval = dt_consume_cpu(dtp, fp, bpeb, dt_consume_begin_probe,
-			      dt_consume_begin_record, 1, &begin);
+			      dt_consume_begin_record, CONSUME_PEEK_START,
+			      &begin);
 
 	dtp->dt_errhdlr = begin.dtbgn_errhdlr;
 	dtp->dt_errarg = begin.dtbgn_errarg;
@@ -2454,7 +2852,8 @@ dt_consume_begin(dtrace_hdl_t *dtp, FILE *fp, struct epoll_event *events,
 	dtp->dt_errarg = &begin;
 
 	rval = dt_consume_cpu(dtp, fp, bpeb, dt_consume_begin_probe,
-			      dt_consume_begin_record, 0, &begin);
+			      dt_consume_begin_record, CONSUME_PEEK_FINISH,
+			      &begin);
 
 	dtp->dt_errhdlr = begin.dtbgn_errhdlr;
 	dtp->dt_errarg = begin.dtbgn_errarg;
@@ -2514,12 +2913,41 @@ dt_consume_proc_exits(dtrace_hdl_t *dtp)
 	pthread_mutex_unlock(&dph->dph_lock);
 }
 
+
+int
+dt_consume_init(dtrace_hdl_t *dtp)
+{
+	dtp->dt_specs_byid = dt_htab_create(dtp, &dt_spec_bufs_htab_ops);
+
+	if (!dtp->dt_specs_byid)
+		return dt_set_errno(dtp, EDT_NOMEM);
+	return 0;
+}
+
+void
+dt_consume_fini(dtrace_hdl_t *dtp)
+{
+	dt_spec_bufs_head_t *dtsh;
+	dtsh_draining_t *dtsd;
+
+	while ((dtsd = dt_list_next(&dtp->dt_spec_bufs_draining)) != NULL) {
+		dt_list_delete(&dtp->dt_spec_bufs_draining, dtsd);
+		dt_free(dtp, dtsd);
+	}
+
+	while ((dtsh = dt_list_next(&dtp->dt_spec_bufs)) != NULL)
+		dt_destroy_spec_bufs(dtp, dtsh, 1);
+
+	dt_htab_destroy(dtp, dtp->dt_specs_byid);
+}
+
 dtrace_workstatus_t
 dtrace_consume(dtrace_hdl_t *dtp, FILE *fp, dtrace_consume_probe_f *pf,
 	       dtrace_consume_rec_f *rf, void *arg)
 {
 	dtrace_optval_t		timeout = dtp->dt_options[DTRACEOPT_SWITCHRATE];
 	struct epoll_event	events[dtp->dt_conf.num_online_cpus];
+	int			drained = 0;
 	int			i, cnt;
 	dtrace_workstatus_t	rval;
 
@@ -2585,6 +3013,7 @@ dtrace_consume(dtrace_hdl_t *dtp, FILE *fp, dtrace_consume_probe_f *pf,
 	 * by one.  If tracing has stopped, skip the CPU on which the END probe
 	 * executed because we want to process that one last.
 	 */
+drain:
 	for (i = 0; i < cnt; i++) {
 		dt_peb_t	*peb = events[i].data.ptr;
 
@@ -2598,6 +3027,20 @@ dtrace_consume(dtrace_hdl_t *dtp, FILE *fp, dtrace_consume_probe_f *pf,
 			return rval;
 	}
 
+	/*
+	 * If a commit or discard has come in, loop twice, because if it was a
+	 * commit the commit probably wasn't the first in the CPU list, and it is
+	 * quite likely that an earlier CPU contains some of the speculative
+	 * content we want to commit.  Circle round and process it once more to
+	 * pick this up.  This means users don't find themselves with committed
+	 * speculative content routinely split between one consume loop and the
+	 * next.
+	 */
+	if (!drained && dt_list_next(&dtp->dt_spec_bufs_draining) != NULL) {
+		drained = 1;
+		goto drain;
+	}
+
 	/*
 	 * If tracing has not been stopped, we are done here.
 	 */
diff --git a/libdtrace/dt_dlibs.c b/libdtrace/dt_dlibs.c
index 04becfa6a016..fa85cd446036 100644
--- a/libdtrace/dt_dlibs.c
+++ b/libdtrace/dt_dlibs.c
@@ -59,6 +59,9 @@ static const dt_ident_t		dt_bpf_symbols[] = {
 	DT_BPF_SYMBOL(dt_get_string, DT_IDENT_SYMBOL),
 	DT_BPF_SYMBOL(dt_get_tvar, DT_IDENT_SYMBOL),
 	DT_BPF_SYMBOL(dt_set_tvar, DT_IDENT_SYMBOL),
+	DT_BPF_SYMBOL(dt_speculation, DT_IDENT_SYMBOL),
+	DT_BPF_SYMBOL(dt_speculation_speculate, DT_IDENT_SYMBOL),
+	DT_BPF_SYMBOL(dt_speculation_set_drainable, DT_IDENT_SYMBOL),
 	DT_BPF_SYMBOL(dt_strnlen, DT_IDENT_SYMBOL),
 	/* BPF maps */
 	DT_BPF_SYMBOL(aggs, DT_IDENT_PTR),
@@ -68,6 +71,7 @@ static const dt_ident_t		dt_bpf_symbols[] = {
 	DT_BPF_SYMBOL(lvars, DT_IDENT_PTR),
 	DT_BPF_SYMBOL(mem, DT_IDENT_PTR),
 	DT_BPF_SYMBOL(probes, DT_IDENT_PTR),
+	DT_BPF_SYMBOL(specs, DT_IDENT_PTR),
 	DT_BPF_SYMBOL(state, DT_IDENT_PTR),
 	DT_BPF_SYMBOL(strtab, DT_IDENT_PTR),
 	DT_BPF_SYMBOL(tvars, DT_IDENT_PTR),
@@ -80,6 +84,7 @@ static const dt_ident_t		dt_bpf_symbols[] = {
 	DT_BPF_SYMBOL_ID(STRSZ, DT_IDENT_SCALAR, DT_CONST_STRSZ),
 	DT_BPF_SYMBOL_ID(STKSIZ, DT_IDENT_SCALAR, DT_CONST_STKSIZ),
 	DT_BPF_SYMBOL_ID(BOOTTM, DT_IDENT_SCALAR, DT_CONST_BOOTTM),
+	DT_BPF_SYMBOL_ID(NSPEC, DT_IDENT_SCALAR, DT_CONST_NSPEC),
 	/* End-of-list marker */
 	{ NULL, }
 };
diff --git a/libdtrace/dt_errtags.h b/libdtrace/dt_errtags.h
index f5b3bab72959..83257a148d0f 100644
--- a/libdtrace/dt_errtags.h
+++ b/libdtrace/dt_errtags.h
@@ -148,7 +148,7 @@ typedef enum {
 	D_DECL_PROTO_VOID,		/* void must be sole parameter */
 	D_DECL_PROTO_NAME,		/* void parameter may not have a name */
 	D_DECL_PROTO_FORM,		/* parameter name has no formal */
-	D_COMM_COMM,			/* commit() after commit() */
+	D_OBSOLETE1,			/* (was commit() after commit()) */
 	D_COMM_DREC,			/* commit() after data action */
 	D_SPEC_SPEC,			/* speculate() after speculate() */
 	D_SPEC_COMM,			/* speculate() after commit() */
diff --git a/libdtrace/dt_impl.h b/libdtrace/dt_impl.h
index bd8d9943c6fa..fb8cc0447a6c 100644
--- a/libdtrace/dt_impl.h
+++ b/libdtrace/dt_impl.h
@@ -31,6 +31,7 @@
 extern "C" {
 #endif
 
+#include <dt_bpf_maps.h>
 #include <dt_parser.h>
 #include <dt_regset.h>
 #include <dt_strtab.h>
@@ -225,6 +226,31 @@ typedef struct dt_lib_depend {
 	dt_list_t dtld_dependents;	/* linked-list of lib dependents */
 } dt_lib_depend_t;
 
+typedef struct dt_spec_bufs {
+	dt_list_t dtsb_list;		/* linked-list forward/back pointers */
+	unsigned int dtsb_cpu;		/* cpu for data */
+	char *dtsb_data;		/* data for later processing */
+	uint32_t dtsb_size;		/* size of data */
+} dt_spec_bufs_t;
+
+typedef struct dt_spec_bufs_head {
+	dt_list_t dtsh_list;		/* list of dt_spec_bufs_heads */
+	int32_t dtsh_id;		/* speculation ID */
+	size_t dtsh_size;		/* size of all buffers in this spec */
+	int dtsh_committing;		/* when draining, nonzero if commit */
+	dt_bpf_specs_t dtsh_spec;	/* bpf-side specs record for this spec
+					   (buffer read/write counts).  */
+	dt_list_t dtsh_dtsb_list;	/* list of dt_spec_bufs */
+	struct dt_hentry dtsh_he;	/* htab links */
+} dt_spec_bufs_head_t;
+
+/*
+ * This will be raised much higher in future: right now it is nailed low
+ * because the search-for-free-speculation code is unrolled rather than being a
+ * proper loop, due to limitations in the BPF verifier.
+ */
+#define DT_MAX_NSPECS 16		/* sanity upper bound on speculations */
+
 typedef uint32_t dt_version_t;		/* encoded version (see below) */
 
 struct dtrace_hdl {
@@ -365,6 +391,9 @@ struct dtrace_hdl {
 	hrtime_t dt_laststatus;	/* last status */
 	hrtime_t dt_lastswitch;	/* last switch of buffer data */
 	hrtime_t dt_lastagg;	/* last snapshot of aggregation data */
+	dt_list_t dt_spec_bufs; /* List of spec bufs */
+	dt_list_t dt_spec_bufs_draining; /* List of spec bufs being drained */
+	dt_htab_t *dt_specs_byid;/* spec ID -> list of dt_spec_bufs_head_t */
 	char *dt_sprintf_buf;	/* buffer for dtrace_sprintf() */
 	int dt_sprintf_buflen;	/* length of dtrace_sprintf() buffer */
 	pthread_mutex_t dt_sprintf_lock; /* lock for dtrace_sprintf() buffer */
@@ -729,6 +758,9 @@ extern int dt_aggregate_go(dtrace_hdl_t *);
 extern int dt_aggregate_init(dtrace_hdl_t *);
 extern void dt_aggregate_destroy(dtrace_hdl_t *);
 
+extern int dt_consume_init(dtrace_hdl_t *);
+extern void dt_consume_fini(dtrace_hdl_t *);
+
 extern dtrace_datadesc_t *dt_datadesc_hold(dtrace_datadesc_t *ddp);
 extern void dt_datadesc_release(dtrace_hdl_t *, dtrace_datadesc_t *);
 extern dtrace_datadesc_t *dt_datadesc_create(dtrace_hdl_t *);
diff --git a/libdtrace/dt_open.c b/libdtrace/dt_open.c
index 867a61a30cc5..d3908c0be05a 100644
--- a/libdtrace/dt_open.c
+++ b/libdtrace/dt_open.c
@@ -790,7 +790,16 @@ dt_vopen(int version, int flags, int *errp,
 	 */
 	dtp->dt_options[DTRACEOPT_STRSIZE] = 256;
 
-	/* Set the default value of maxframes. */
+	/*
+	 * Set the default speculation size and number of simultaneously active
+	 * speculations.
+	 */
+	dtp->dt_options[DTRACEOPT_SPECSIZE] = 1024 * 1024 * 4;
+	dtp->dt_options[DTRACEOPT_NSPEC] = 1;
+
+	/*
+	 * Set the default value of maxframes.
+	 */
 	fd = fopen("/proc/sys/kernel/perf_event_max_stack", "r");
 	assert(fd);
 	if (fscanf(fd, "%lu", &dtp->dt_options[DTRACEOPT_MAXFRAMES]) != 1)
@@ -1108,6 +1117,12 @@ dt_vopen(int version, int flags, int *errp,
 	 */
 	dt_dlib_init(dtp);
 
+	/*
+	 * Initialize consume handling, e.g. storage of uncommitted speculations.
+	 */
+	if (dt_consume_init(dtp) < 0)
+		return set_open_errno(dtp, errp, dtp->dt_errno);
+
 	/*
 	 * Initialize the collection of probes that is made available by the
 	 * known providers.
@@ -1196,6 +1211,8 @@ dtrace_close(dtrace_hdl_t *dtp)
 
 	dt_free(dtp, dtp->dt_xlatormap);
 
+	dt_consume_fini(dtp);
+
 	for (idp = dtp->dt_externs; idp != NULL; idp = ndp) {
 		ndp = idp->di_next;
 		dt_ident_destroy(idp);
diff --git a/libdtrace/dt_peb.h b/libdtrace/dt_peb.h
index 961db93c3837..ab327ec775a2 100644
--- a/libdtrace/dt_peb.h
+++ b/libdtrace/dt_peb.h
@@ -26,6 +26,7 @@ typedef struct dt_peb {
 	int		fd;		/* fd of perf output buffer */
 	char		*base;		/* address of buffer */
 	char		*endp;		/* address of end of buffer */
+	uint64_t	last_head;	/* last known head, for peeking */
 } dt_peb_t;
 
 /*
diff --git a/libdtrace/dtrace.h b/libdtrace/dtrace.h
index 036876facbc0..a05cc4a9f21b 100644
--- a/libdtrace/dtrace.h
+++ b/libdtrace/dtrace.h
@@ -145,12 +145,13 @@ typedef struct dtrace_stmtdesc {
 } dtrace_stmtdesc_t;
 
 /* dtsd clause flags */
-#define DT_CLSFLAG_DATAREC	1	/* data-recording */
-#define DT_CLSFLAG_SPECULATE	2	/* speculate */
-#define DT_CLSFLAG_COMMIT	4	/* commit */
-#define DT_CLSFLAG_EXIT		8	/* exit */
-#define DT_CLSFLAG_DESTRUCT	16	/* destructive */
-#define DT_CLSFLAG_AGGREGATION	32	/* aggregation */
+#define DT_CLSFLAG_DATAREC		1	/* data-recording */
+#define DT_CLSFLAG_SPECULATE		2	/* speculate */
+#define DT_CLSFLAG_COMMIT		4	/* commit */
+#define DT_CLSFLAG_COMMIT_DISCARD	8	/* commit/discard */
+#define DT_CLSFLAG_EXIT			16	/* exit */
+#define DT_CLSFLAG_DESTRUCT		32	/* destructive */
+#define DT_CLSFLAG_AGGREGATION		64	/* aggregation */
 
 typedef int dtrace_stmt_f(dtrace_hdl_t *dtp, dtrace_prog_t *pgp,
     dtrace_stmtdesc_t *sdp, void *data);
diff --git a/test/unittest/speculation/err.CommitWithInvalid.d b/test/unittest/speculation/err.CommitWithInvalid.d
new file mode 100644
index 000000000000..7dc094d4a21e
--- /dev/null
+++ b/test/unittest/speculation/err.CommitWithInvalid.d
@@ -0,0 +1,40 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2006, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: When commit() is called with an out-of-range buffer number,
+ * a fault is raised.
+ *
+ * SECTION: Speculative Tracing/Committing a Speculation
+ */
+#pragma D option quiet
+#pragma D option nspec=8
+
+BEGIN
+{
+	self->i = 0;
+}
+
+BEGIN
+{
+	commit(1024);
+}
+
+BEGIN
+{
+	trace("This should not be seen");
+}
+
+BEGIN
+{
+	exit(0);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/speculation/err.CommitWithInvalid.r b/test/unittest/speculation/err.CommitWithInvalid.r
new file mode 100644
index 000000000000..a9fd33ad9d30
--- /dev/null
+++ b/test/unittest/speculation/err.CommitWithInvalid.r
@@ -0,0 +1,3 @@
+
+-- @@stderr --
+dtrace: error on enabled probe ID 4 (ID 1: dtrace:::BEGIN): illegal operation in action #2
diff --git a/test/unittest/speculation/err.D_ACT_SPEC.SpeculateWithCopyOut.d b/test/unittest/speculation/err.D_ACT_SPEC.SpeculateWithCopyOut.d
index aae3e4ff84c1..908e9b9c703f 100644
--- a/test/unittest/speculation/err.D_ACT_SPEC.SpeculateWithCopyOut.d
+++ b/test/unittest/speculation/err.D_ACT_SPEC.SpeculateWithCopyOut.d
@@ -5,7 +5,7 @@
  * Licensed under the Universal Permissive License v 1.0 as shown at
  * http://oss.oracle.com/licenses/upl.
  */
-/* @@xfail: dtv2 */
+/* @@skip: dtv2, no copyout yet */
 /*
  * ASSERTION: Destructive actions may never be speculative.
  *
diff --git a/test/unittest/speculation/err.D_ACT_SPEC.SpeculateWithCopyOutStr.d b/test/unittest/speculation/err.D_ACT_SPEC.SpeculateWithCopyOutStr.d
index c75d15067c8c..1b23ec3bcbe3 100644
--- a/test/unittest/speculation/err.D_ACT_SPEC.SpeculateWithCopyOutStr.d
+++ b/test/unittest/speculation/err.D_ACT_SPEC.SpeculateWithCopyOutStr.d
@@ -5,7 +5,7 @@
  * Licensed under the Universal Permissive License v 1.0 as shown at
  * http://oss.oracle.com/licenses/upl.
  */
-/* @@xfail: dtv2 */
+/* @@skip: dtv2, no copyoutstr yet */
 /*
  * ASSERTION: Destructive actions may never be speculative.
  *
diff --git a/test/unittest/speculation/err.D_COMM_COMM.CommitAftCommit.d b/test/unittest/speculation/err.D_COMM_COMM.CommitAftCommit.d
deleted file mode 100644
index 4b20022ccc19..000000000000
--- a/test/unittest/speculation/err.D_COMM_COMM.CommitAftCommit.d
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Oracle Linux DTrace.
- * Copyright (c) 2007, 2020, Oracle and/or its affiliates. All rights reserved.
- * Licensed under the Universal Permissive License v 1.0 as shown at
- * http://oss.oracle.com/licenses/upl.
- */
-
-/*
- * ASSERTION:
- * A clause cannot contain multiple commit() calls to same buffer.
- *
- * SECTION: Speculative Tracing/Committing a Speculation;
- *	Options and Tunables/cleanrate
- */
-#pragma D option quiet
-#pragma D option cleanrate=3000hz
-
-BEGIN
-{
-	self->i = 0;
-	var1 = 0;
-}
-
-profile:::tick-1sec
-/!var1/
-{
-	var1 = speculation();
-	printf("Speculation ID: %d\n", var1);
-}
-
-profile:::tick-1sec
-/var1/
-{
-	speculate(var1);
-	printf("Speculating on id: %d\n", var1);
-	self->i++;
-}
-
-profile:::tick-1sec
-/(!self->i)/
-{
-}
-
-profile:::tick-1sec
-/(self->i)/
-{
-	commit(var1);
-	commit(var1);
-	exit(0);
-}
-
-END
-{
-	printf("Succesfully commited both buffers");
-	exit(0);
-}
-
-ERROR
-{
-	exit(0);
-}
diff --git a/test/unittest/speculation/err.D_COMM_COMM.CommitAftCommit.r b/test/unittest/speculation/err.D_COMM_COMM.CommitAftCommit.r
deleted file mode 100644
index a687cd0b6d34..000000000000
--- a/test/unittest/speculation/err.D_COMM_COMM.CommitAftCommit.r
+++ /dev/null
@@ -1,2 +0,0 @@
--- @@stderr --
-dtrace: failed to compile script test/unittest/speculation/err.D_COMM_COMM.CommitAftCommit.d: [D_COMM_COMM] line 48: commit( ) may not follow commit( )
diff --git a/test/unittest/speculation/err.D_COMM_COMM.DisjointCommit.d b/test/unittest/speculation/err.D_COMM_COMM.DisjointCommit.d
deleted file mode 100644
index 24c7ae7d6231..000000000000
--- a/test/unittest/speculation/err.D_COMM_COMM.DisjointCommit.d
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Oracle Linux DTrace.
- * Copyright (c) 2007, 2020, Oracle and/or its affiliates. All rights reserved.
- * Licensed under the Universal Permissive License v 1.0 as shown at
- * http://oss.oracle.com/licenses/upl.
- */
-
-/*
- * ASSERTION:
- * A clause cannot contain multiple commit() calls to disjoint buffers.
- *
- * SECTION: Speculative Tracing/Committing a Speculation;
- *	Options and Tunables/cleanrate
- */
-#pragma D option quiet
-#pragma D option cleanrate=3000hz
-
-BEGIN
-{
-	self->i = 0;
-	self->j = 0;
-	self->commit = 0;
-	var1 = 0;
-	var2 = 0;
-}
-
-BEGIN
-{
-	var1 = speculation();
-	printf("Speculation ID: %d\n", var1);
-}
-
-BEGIN
-{
-	var2 = speculation();
-	printf("Speculation ID: %d\n", var2);
-}
-
-BEGIN
-/var1/
-{
-	speculate(var1);
-	printf("Speculating on id: %d\n", var1);
-	self->i++;
-}
-
-BEGIN
-/var2/
-{
-	speculate(var2);
-	printf("Speculating on id: %d", var2);
-	self->j++;
-
-}
-
-BEGIN
-/(self->i) && (self->j)/
-{
-	commit(var1);
-	commit(var2);
-	self->commit++;
-}
-
-BEGIN
-/self->commit/
-{
-	printf("Succesfully commited both buffers");
-	exit(0);
-}
-
-BEGIN
-/!self->commit/
-{
-	printf("Couldnt commit both buffers");
-	exit(1);
-}
-
-ERROR
-{
-	exit(1);
-}
diff --git a/test/unittest/speculation/err.D_COMM_COMM.DisjointCommit.r b/test/unittest/speculation/err.D_COMM_COMM.DisjointCommit.r
deleted file mode 100644
index ae18ddf58396..000000000000
--- a/test/unittest/speculation/err.D_COMM_COMM.DisjointCommit.r
+++ /dev/null
@@ -1,2 +0,0 @@
--- @@stderr --
-dtrace: failed to compile script test/unittest/speculation/err.D_COMM_COMM.DisjointCommit.d: [D_COMM_COMM] line 60: commit( ) may not follow commit( )
diff --git a/test/unittest/speculation/tst.ExitAftDiscard.d b/test/unittest/speculation/err.DiscardWithInvalid.d
similarity index 51%
copy from test/unittest/speculation/tst.ExitAftDiscard.d
copy to test/unittest/speculation/err.DiscardWithInvalid.d
index 758c1554a0af..15ad11908646 100644
--- a/test/unittest/speculation/tst.ExitAftDiscard.d
+++ b/test/unittest/speculation/err.DiscardWithInvalid.d
@@ -1,36 +1,40 @@
 /*
  * Oracle Linux DTrace.
- * Copyright (c) 2006, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2006, 2021, Oracle and/or its affiliates. All rights reserved.
  * Licensed under the Universal Permissive License v 1.0 as shown at
  * http://oss.oracle.com/licenses/upl.
  */
-/* @@xfail: dtv2 */
 
 /*
- * ASSERTION: Using exit after discard should work fine.
+ * ASSERTION: When discard() is called with an out-of-range buffer number,
+ * a fault is raised.
  *
  * SECTION: Speculative Tracing/Discarding a Speculation
- *
  */
 #pragma D option quiet
+#pragma D option nspec=8
 
 BEGIN
 {
 	self->i = 0;
-	self->spec = speculation();
 }
 
 BEGIN
-/self->spec/
 {
-	speculate(self->spec);
-	self->i++;
-	printf("self->i: %d\n", self->i);
+	discard(1024);
+}
+
+BEGIN
+{
+	trace("This should not be seen");
 }
 
 BEGIN
-/self->i/
 {
-	discard(self->spec);
 	exit(0);
 }
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/speculation/err.DiscardWithInvalid.r b/test/unittest/speculation/err.DiscardWithInvalid.r
new file mode 100644
index 000000000000..a9fd33ad9d30
--- /dev/null
+++ b/test/unittest/speculation/err.DiscardWithInvalid.r
@@ -0,0 +1,3 @@
+
+-- @@stderr --
+dtrace: error on enabled probe ID 4 (ID 1: dtrace:::BEGIN): illegal operation in action #2
diff --git a/test/unittest/speculation/err.SpecSizeVariations1.d b/test/unittest/speculation/err.SpecSizeVariations1.d
deleted file mode 100644
index 05fb2cf632e9..000000000000
--- a/test/unittest/speculation/err.SpecSizeVariations1.d
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Oracle Linux DTrace.
- * Copyright (c) 2006, 2020, Oracle and/or its affiliates. All rights reserved.
- * Licensed under the Universal Permissive License v 1.0 as shown at
- * http://oss.oracle.com/licenses/upl.
- */
-/* @@xfail: dtv2 */
-
-/*
- * ASSERTION:
- * Verify the behavior of variations in specsize.
- *
- * SECTION: Speculative Tracing/Options and Tuning;
- *	Options and Tunables/specsize
- *
- */
-
-#pragma D option quiet
-#pragma D option specsize=0
-
-BEGIN
-{
-	self->speculateFlag = 0;
-	self->commitFlag = 0;
-	self->spec = speculation();
-	printf("Speculative buffer ID: %d\n", self->spec);
-}
-
-BEGIN
-{
-	speculate(self->spec);
-	printf("Lots of data\n");
-	printf("Has to be crammed into this buffer\n");
-	printf("Until it overflows\n");
-	printf("And causes flops\n");
-	self->speculateFlag++;
-
-}
-
-BEGIN
-/1 <= self->speculateFlag/
-{
-	commit(self->spec);
-	self->commitFlag++;
-}
-
-BEGIN
-/1 <= self->commitFlag/
-{
-	printf("Statement was executed\n");
-	exit(0);
-}
-
-BEGIN
-/1 > self->commitFlag/
-{
-	printf("Statement wasn't executed\n");
-	exit(1);
-}
diff --git a/test/unittest/speculation/err.SpecSizeVariations1.r b/test/unittest/speculation/err.SpecSizeVariations1.r
deleted file mode 100644
index daca8b5083ff..000000000000
--- a/test/unittest/speculation/err.SpecSizeVariations1.r
+++ /dev/null
@@ -1,2 +0,0 @@
--- @@stderr --
-dtrace: could not enable tracing: Enabling exceeds size of buffer
diff --git a/test/unittest/speculation/err.SpecSizeVariations2.d b/test/unittest/speculation/err.SpecSizeVariations2.d
deleted file mode 100644
index 9685d2400e58..000000000000
--- a/test/unittest/speculation/err.SpecSizeVariations2.d
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Oracle Linux DTrace.
- * Copyright (c) 2006, 2020, Oracle and/or its affiliates. All rights reserved.
- * Licensed under the Universal Permissive License v 1.0 as shown at
- * http://oss.oracle.com/licenses/upl.
- */
-/* @@xfail: dtv2 */
-
-/*
- * ASSERTION:
- * Verify the behavior of variations in specsize.
- *
- * SECTION: Speculative Tracing/Options and Tuning;
- *	Options and Tunables/specsize
- *
- */
-
-#pragma D option quiet
-#pragma D option specsize=7
-
-BEGIN
-{
-	self->speculateFlag = 0;
-	self->commitFlag = 0;
-	self->spec = speculation();
-	printf("Speculative buffer ID: %d\n", self->spec);
-}
-
-BEGIN
-{
-	speculate(self->spec);
-	printf("Lots of data\n");
-	printf("Has to be crammed into this buffer\n");
-	printf("Until it overflows\n");
-	printf("And causes flops\n");
-	self->speculateFlag++;
-
-}
-
-BEGIN
-/1 <= self->speculateFlag/
-{
-	commit(self->spec);
-	self->commitFlag++;
-}
-
-BEGIN
-/1 <= self->commitFlag/
-{
-	printf("Statement was executed\n");
-	exit(0);
-}
-
-BEGIN
-/1 > self->commitFlag/
-{
-	printf("Statement wasn't executed\n");
-	exit(1);
-}
diff --git a/test/unittest/speculation/err.SpecSizeVariations2.r b/test/unittest/speculation/err.SpecSizeVariations2.r
deleted file mode 100644
index daca8b5083ff..000000000000
--- a/test/unittest/speculation/err.SpecSizeVariations2.r
+++ /dev/null
@@ -1,2 +0,0 @@
--- @@stderr --
-dtrace: could not enable tracing: Enabling exceeds size of buffer
diff --git a/test/unittest/speculation/tst.CommitAfterDiscard.d b/test/unittest/speculation/tst.CommitAfterDiscard.d
index b07222179e16..49456cdd4521 100644
--- a/test/unittest/speculation/tst.CommitAfterDiscard.d
+++ b/test/unittest/speculation/tst.CommitAfterDiscard.d
@@ -4,19 +4,16 @@
  * Licensed under the Universal Permissive License v 1.0 as shown at
  * http://oss.oracle.com/licenses/upl.
  */
-/* @@xfail: dtv2 */
 
 /*
  * ASSERTION:
  * Call to commit() on a buffer after it has been discarded is silently
  * ignored.
  *
- * SECTION: Speculative Tracing/Committing a Speculation;
- *	Options and Tunables/cleanrate
+ * SECTION: Speculative Tracing/Committing a Speculation
  *
  */
 #pragma D option quiet
-#pragma D option cleanrate=3000hz
 
 BEGIN
 {
@@ -48,7 +45,7 @@ BEGIN
 BEGIN
 /self->commit/
 {
-	printf("Commited a discarded buffer\n");
+	printf("Committed a discarded buffer\n");
 	exit(0);
 }
 
@@ -56,7 +53,7 @@ BEGIN
 BEGIN
 /!self->commit/
 {
-	printf("Couldnt commit a discarded buffer\n");
+	printf("Couldn't commit a discarded buffer\n");
 	exit(1);
 }
 
diff --git a/test/unittest/speculation/tst.CommitAfterDiscard.r b/test/unittest/speculation/tst.CommitAfterDiscard.r
index 3674e24e5bc4..8ae7a38ec525 100644
--- a/test/unittest/speculation/tst.CommitAfterDiscard.r
+++ b/test/unittest/speculation/tst.CommitAfterDiscard.r
@@ -1,3 +1,3 @@
 Speculation ID: 1
-Commited a discarded buffer
+Committed a discarded buffer
 
diff --git a/test/unittest/speculation/tst.CommitCommitCommit.d b/test/unittest/speculation/tst.CommitCommitCommit.d
new file mode 100644
index 000000000000..e91d0d05db22
--- /dev/null
+++ b/test/unittest/speculation/tst.CommitCommitCommit.d
@@ -0,0 +1,58 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: A clause can have multiple commits.
+ *
+ * SECTION: Speculative Tracing/Committing a Speculation
+ *
+ */
+#pragma D option quiet
+#pragma D option nspec=3
+
+BEGIN
+{
+	i = speculation();
+	j = speculation();
+	k = speculation();
+	printf("Speculation IDs: %d %d %d\n", i, j, k);
+}
+
+BEGIN
+{
+	speculate(i);
+	printf("Speculating on id: %d\n", i);
+}
+
+BEGIN
+{
+	speculate(j);
+	printf("Speculating on id: %d\n", j);
+}
+
+BEGIN
+{
+	speculate(k);
+	printf("Speculating on id: %d\n", k);
+}
+
+BEGIN
+{
+	commit(k);
+	commit(j);
+	commit(i);
+}
+
+BEGIN
+{
+	exit(0);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/speculation/tst.CommitCommitCommit.r b/test/unittest/speculation/tst.CommitCommitCommit.r
new file mode 100644
index 000000000000..bce7ae388a20
--- /dev/null
+++ b/test/unittest/speculation/tst.CommitCommitCommit.r
@@ -0,0 +1,5 @@
+Speculation IDs: 1 2 3
+Speculating on id: 3
+Speculating on id: 2
+Speculating on id: 1
+
diff --git a/test/unittest/speculation/tst.CommitDiscard4x.d b/test/unittest/speculation/tst.CommitDiscard4x.d
new file mode 100644
index 000000000000..3a1f5b04f907
--- /dev/null
+++ b/test/unittest/speculation/tst.CommitDiscard4x.d
@@ -0,0 +1,99 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: A clause can have multiple commits and discards.
+ *
+ * SECTION: Speculative Tracing/Committing a Speculation
+ *
+ */
+#pragma D option quiet
+#pragma D option nspec=8
+
+BEGIN
+{
+	a = speculation();
+	b = speculation();
+	c = speculation();
+	d = speculation();
+	e = speculation();
+	f = speculation();
+	g = speculation();
+	h = speculation();
+	printf("Speculation IDs: %d %d %d %d %d %d %d %d\n",
+	       a, b, c, d, e, f, g, h);
+}
+
+BEGIN
+{
+	speculate(a);
+	printf("Speculating on id: %d\n", a);
+}
+
+BEGIN
+{
+	speculate(b);
+	printf("Speculating on id: %d\n", b);
+}
+
+BEGIN
+{
+	speculate(c);
+	printf("Speculating on id: %d\n", c);
+}
+
+BEGIN
+{
+	speculate(d);
+	printf("Speculating on id: %d\n", d);
+}
+
+BEGIN
+{
+	speculate(e);
+	printf("Speculating on id: %d\n", e);
+}
+
+BEGIN
+{
+	speculate(f);
+	printf("Speculating on id: %d\n", f);
+}
+
+BEGIN
+{
+	speculate(g);
+	printf("Speculating on id: %d\n", g);
+}
+
+BEGIN
+{
+	speculate(h);
+	printf("Speculating on id: %d\n", h);
+}
+
+BEGIN
+{
+	commit(h);
+	discard(g);
+	commit(f);
+	discard(e);
+	commit(d);
+	discard(c);
+	commit(b);
+	discard(a);
+}
+
+BEGIN
+{
+	exit(0);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/speculation/tst.CommitDiscard4x.r b/test/unittest/speculation/tst.CommitDiscard4x.r
new file mode 100644
index 000000000000..b6bbe9d26851
--- /dev/null
+++ b/test/unittest/speculation/tst.CommitDiscard4x.r
@@ -0,0 +1,6 @@
+Speculation IDs: 1 2 3 4 5 6 7 8
+Speculating on id: 8
+Speculating on id: 6
+Speculating on id: 4
+Speculating on id: 2
+
diff --git a/test/unittest/speculation/tst.CommitWithInactive.d b/test/unittest/speculation/tst.CommitWithInactive.d
new file mode 100644
index 000000000000..17bfe52ba7cf
--- /dev/null
+++ b/test/unittest/speculation/tst.CommitWithInactive.d
@@ -0,0 +1,38 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2006, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: When commit() is called with an inactive buffer number,
+ * it is ignored.
+ *
+ * SECTION: Speculative Tracing/Comitting a Speculation
+ */
+#pragma D option quiet
+BEGIN
+{
+	self->i = 0;
+}
+
+BEGIN
+{
+	commit(1);
+}
+
+BEGIN
+{
+	trace("This should be seen");
+}
+
+BEGIN
+{
+	exit(0);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/speculation/tst.CommitWithInactive.r b/test/unittest/speculation/tst.CommitWithInactive.r
new file mode 100644
index 000000000000..5c6e69bdcfe1
--- /dev/null
+++ b/test/unittest/speculation/tst.CommitWithInactive.r
@@ -0,0 +1 @@
+This should be seen
diff --git a/test/unittest/speculation/tst.CommitWithZero.d b/test/unittest/speculation/tst.CommitWithZero.d
index dbc6dc06e8b8..9246d1de64d4 100644
--- a/test/unittest/speculation/tst.CommitWithZero.d
+++ b/test/unittest/speculation/tst.CommitWithZero.d
@@ -4,30 +4,23 @@
  * Licensed under the Universal Permissive License v 1.0 as shown at
  * http://oss.oracle.com/licenses/upl.
  */
-/* @@xfail: dtv2 */
 
 /*
  * ASSERTION: An Id of zero though invalid may be passed to speculate(),
  * commit() and discard() without any ill effects.
  *
- * SECTION: Speculative Tracing/Creating a Speculation;
- *	Options and Tunables/cleanrate
+ * SECTION: Speculative Tracing/Creating a Speculation
  */
 #pragma D option quiet
-#pragma D option cleanrate=4000hz
 
 BEGIN
 {
 	self->commitFlag = 0;
-	self->var1 = speculation();
-	printf("Speculative buffer ID: %d\n", self->var1);
-	self->spec = speculation();
-	printf("Speculative buffer ID: %d\n", self->spec);
 }
 
 BEGIN
 {
-	commit(self->spec);
+	commit(0);
 	self->commitFlag++;
 }
 
diff --git a/test/unittest/speculation/tst.CommitWithZero.r b/test/unittest/speculation/tst.CommitWithZero.r
index 507400da55a4..7534ce772b29 100644
--- a/test/unittest/speculation/tst.CommitWithZero.r
+++ b/test/unittest/speculation/tst.CommitWithZero.r
@@ -1,6 +1,2 @@
-Speculative buffer ID: 1
-Speculative buffer ID: 0
 commit(), self->commitFlag = 1
 
--- @@stderr --
-dtrace: 1 failed speculation (no speculative buffer available)
diff --git a/test/unittest/speculation/tst.DataRecAftDiscard.d b/test/unittest/speculation/tst.DataRecAftDiscard.d
index e262604e5910..23601df39ff9 100644
--- a/test/unittest/speculation/tst.DataRecAftDiscard.d
+++ b/test/unittest/speculation/tst.DataRecAftDiscard.d
@@ -4,17 +4,14 @@
  * Licensed under the Universal Permissive License v 1.0 as shown at
  * http://oss.oracle.com/licenses/upl.
  */
-/* @@xfail: dtv2 */
 
 /*
  * ASSERTION:
  * Data recording actions may follow discard.
  *
- * SECTION: Speculative Tracing/Discarding a Speculation;
- *	Options and Tunables/cleanrate
+ * SECTION: Speculative Tracing/Discarding a Speculation
  */
 #pragma D option quiet
-#pragma D option cleanrate=2000hz
 
 BEGIN
 {
diff --git a/test/unittest/speculation/tst.DiscardAftCommit.d b/test/unittest/speculation/tst.DiscardAftCommit.d
index ca4a0645c00f..7b7e0e6896c1 100644
--- a/test/unittest/speculation/tst.DiscardAftCommit.d
+++ b/test/unittest/speculation/tst.DiscardAftCommit.d
@@ -4,18 +4,16 @@
  * Licensed under the Universal Permissive License v 1.0 as shown at
  * http://oss.oracle.com/licenses/upl.
  */
-/* @@xfail: dtv2 */
 
 /*
  * ASSERTION:
- * Can call discard() on a buffer after it has been commited.
+ * Can call discard() on a buffer after it has been committed.
  *
- * SECTION: Speculative Tracing/Discarding a Speculation;
- *	Options and Tunables/cleanrate
+ * SECTION: Speculative Tracing/Discarding a Speculation
  *
  */
+
 #pragma D option quiet
-#pragma D option cleanrate=3000hz
 
 BEGIN
 {
@@ -47,7 +45,7 @@ BEGIN
 BEGIN
 /self->discard/
 {
-	printf("Discarded a commited buffer\n");
+	printf("Discarded a committed buffer\n");
 	exit(0);
 }
 
@@ -55,7 +53,7 @@ BEGIN
 BEGIN
 /!self->discard/
 {
-	printf("Couldnt discard a commited buffer\n");
+	printf("Couldn't discard a committed buffer\n");
 	exit(1);
 }
 
diff --git a/test/unittest/speculation/tst.DiscardAftCommit.r b/test/unittest/speculation/tst.DiscardAftCommit.r
index 3f960663fd8c..004727149781 100644
--- a/test/unittest/speculation/tst.DiscardAftCommit.r
+++ b/test/unittest/speculation/tst.DiscardAftCommit.r
@@ -1,5 +1,5 @@
 Speculation ID: 1
 This statement and the following are speculative!!
 Speculating on id: 1
-Discarded a commited buffer
+Discarded a committed buffer
 
diff --git a/test/unittest/speculation/tst.DiscardAftDataRec.d b/test/unittest/speculation/tst.DiscardAftDataRec.d
index 3d7df87ef838..740016648c11 100644
--- a/test/unittest/speculation/tst.DiscardAftDataRec.d
+++ b/test/unittest/speculation/tst.DiscardAftDataRec.d
@@ -4,11 +4,10 @@
  * Licensed under the Universal Permissive License v 1.0 as shown at
  * http://oss.oracle.com/licenses/upl.
  */
-/* @@xfail: dtv2 */
 
 /*
  * ASSERTION:
- * Discard may not follow data recording actions.
+ * Discard may follow data recording actions.
  *
  * SECTION: Speculative Tracing/Discarding a Speculation
  *
diff --git a/test/unittest/speculation/tst.DiscardAftDiscard.d b/test/unittest/speculation/tst.DiscardAftDiscard.d
index d6231e2cca5c..1f5ae2b9740c 100644
--- a/test/unittest/speculation/tst.DiscardAftDiscard.d
+++ b/test/unittest/speculation/tst.DiscardAftDiscard.d
@@ -4,18 +4,15 @@
  * Licensed under the Universal Permissive License v 1.0 as shown at
  * http://oss.oracle.com/licenses/upl.
  */
-/* @@xfail: dtv2 */
 
 /*
  * ASSERTION:
  * Can call discard on an already discarded buffer.
  *
- * SECTION: Speculative Tracing/Discarding a Speculation;
- *	Options and Tunables/cleanrate
+ * SECTION: Speculative Tracing/Discarding a Speculation
  *
  */
 #pragma D option quiet
-#pragma D option cleanrate=3000hz
 
 BEGIN
 {
@@ -55,7 +52,7 @@ BEGIN
 BEGIN
 /(!self->discard2) || (!self->discard1)/
 {
-	printf("Couldnt discard a discarded buffer\n");
+	printf("Couldn't discard a discarded buffer\n");
 	exit(1);
 }
 
diff --git a/test/unittest/speculation/tst.ExitAftDiscard.d b/test/unittest/speculation/tst.DiscardWithInactive.d
similarity index 51%
copy from test/unittest/speculation/tst.ExitAftDiscard.d
copy to test/unittest/speculation/tst.DiscardWithInactive.d
index 758c1554a0af..4228244bfa3d 100644
--- a/test/unittest/speculation/tst.ExitAftDiscard.d
+++ b/test/unittest/speculation/tst.DiscardWithInactive.d
@@ -1,36 +1,38 @@
 /*
  * Oracle Linux DTrace.
- * Copyright (c) 2006, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2006, 2021, Oracle and/or its affiliates. All rights reserved.
  * Licensed under the Universal Permissive License v 1.0 as shown at
  * http://oss.oracle.com/licenses/upl.
  */
-/* @@xfail: dtv2 */
 
 /*
- * ASSERTION: Using exit after discard should work fine.
+ * ASSERTION: When discard() is called with an inactive buffer number,
+ * it is ignored.
  *
  * SECTION: Speculative Tracing/Discarding a Speculation
- *
  */
 #pragma D option quiet
-
 BEGIN
 {
 	self->i = 0;
-	self->spec = speculation();
 }
 
 BEGIN
-/self->spec/
 {
-	speculate(self->spec);
-	self->i++;
-	printf("self->i: %d\n", self->i);
+	discard(1);
+}
+
+BEGIN
+{
+	trace("This should be seen");
 }
 
 BEGIN
-/self->i/
 {
-	discard(self->spec);
 	exit(0);
 }
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/speculation/tst.DiscardWithInactive.r b/test/unittest/speculation/tst.DiscardWithInactive.r
new file mode 100644
index 000000000000..5c6e69bdcfe1
--- /dev/null
+++ b/test/unittest/speculation/tst.DiscardWithInactive.r
@@ -0,0 +1 @@
+This should be seen
diff --git a/test/unittest/speculation/tst.DiscardWithZero.d b/test/unittest/speculation/tst.DiscardWithZero.d
index 8ff2d43d9fe5..98c1f1bfde8e 100644
--- a/test/unittest/speculation/tst.DiscardWithZero.d
+++ b/test/unittest/speculation/tst.DiscardWithZero.d
@@ -4,31 +4,23 @@
  * Licensed under the Universal Permissive License v 1.0 as shown at
  * http://oss.oracle.com/licenses/upl.
  */
-/* @@xfail: dtv2 */
 
 /*
  * ASSERTION: An Id of zero though invalid may be passed to speculate(),
  * commit() and discard() without any ill effects.
  *
- * SECTION: Speculative Tracing/Creating a Speculation;
- *	Options and Tunables/cleanrate
+ * SECTION: Speculative Tracing/Creating a Speculation
  */
 #pragma D option quiet
-#pragma D option cleanrate=4000hz
 
 BEGIN
 {
 	self->discardFlag = 0;
-	self->var1 = speculation();
-	printf("Speculative buffer ID: %d\n", self->var1);
-	self->spec = speculation();
-	printf("Speculative buffer ID: %d\n", self->spec);
 }
 
 BEGIN
-/0 == self->spec/
 {
-	discard(self->spec);
+	discard(0);
 	self->discardFlag++;
 }
 
diff --git a/test/unittest/speculation/tst.DiscardWithZero.r b/test/unittest/speculation/tst.DiscardWithZero.r
index e4a7cb7f9528..f356261e2522 100644
--- a/test/unittest/speculation/tst.DiscardWithZero.r
+++ b/test/unittest/speculation/tst.DiscardWithZero.r
@@ -1,6 +1,2 @@
-Speculative buffer ID: 1
-Speculative buffer ID: 0
 discard(), self->discardFlag = 1
 
--- @@stderr --
-dtrace: 1 failed speculation (no speculative buffer available)
diff --git a/test/unittest/speculation/tst.ExitAftDiscard.d b/test/unittest/speculation/tst.ExitAftDiscard.d
index 758c1554a0af..cbe32a572ee4 100644
--- a/test/unittest/speculation/tst.ExitAftDiscard.d
+++ b/test/unittest/speculation/tst.ExitAftDiscard.d
@@ -4,7 +4,6 @@
  * Licensed under the Universal Permissive License v 1.0 as shown at
  * http://oss.oracle.com/licenses/upl.
  */
-/* @@xfail: dtv2 */
 
 /*
  * ASSERTION: Using exit after discard should work fine.
diff --git a/test/unittest/speculation/tst.NoSpecBuffer.d b/test/unittest/speculation/tst.NoSpecBuffer.d
index a3a2107c7132..da138e364f30 100644
--- a/test/unittest/speculation/tst.NoSpecBuffer.d
+++ b/test/unittest/speculation/tst.NoSpecBuffer.d
@@ -5,18 +5,18 @@
  * http://oss.oracle.com/licenses/upl.
  */
 
-/* @@xfail: dtv2 */
 /* @@trigger: none */
 
 /*
  * ASSERTION:
- * The number of speculative buffers defaults to one. If no speculative buffer
- * is available when speculation is called, an ID of zero is returned.
+ * If no speculative buffer is available when speculation is called,
+ * an ID of zero is returned.
  *
  * SECTION: Speculative Tracing/Creating a Speculation
  *
  */
 #pragma D option quiet
+#pragma D option nspec=1
 
 BEGIN
 {
diff --git a/test/unittest/speculation/tst.NoSpecBuffer.r b/test/unittest/speculation/tst.NoSpecBuffer.r
index 8d6f9d2c8946..88d21d7ca4f7 100644
--- a/test/unittest/speculation/tst.NoSpecBuffer.r
+++ b/test/unittest/speculation/tst.NoSpecBuffer.r
@@ -1,5 +1,3 @@
 Speculative buffer ID: 1
 Speculative buffer ID: 0
 i: 2	self->spec: 0
--- @@stderr --
-dtrace: 1 failed speculation (no speculative buffer available)
diff --git a/test/unittest/speculation/tst.SingleCPU.d b/test/unittest/speculation/tst.SingleCPU.d
new file mode 100644
index 000000000000..a1cb4612a81b
--- /dev/null
+++ b/test/unittest/speculation/tst.SingleCPU.d
@@ -0,0 +1,69 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Verify many speculations (single-CPU case).
+ *
+ * SECTION: Speculative Tracing
+ */
+
+#pragma D option quiet
+
+/* This test should take only 10s, but is taking much longer than the 10s
+   one would expect (48s here).  Boosting timeout temporarily.  */
+/* @@timeout: 120 */
+
+BEGIN
+{
+	n = 0;
+}
+
+/*
+ * Each tick, n is incremented.  Which clause is used rotates modulo 4.
+ *   0: get a specid
+ *   1: speculate some output
+ *   2: speculate some other output
+ *   3: commit (sometimes) or discard (usually)
+ */
+
+tick-10ms
+/ (n & 3) == 0 /
+{
+	i = speculation();
+}
+
+tick-10ms
+/ (n & 3) == 1 /
+{
+	speculate(i);
+	printf("%4d %4d", n, i);
+}
+
+tick-10ms
+/ (n & 3) == 2 /
+{
+	speculate(i);
+	printf("%4d hello world\n", n);
+}
+
+tick-10ms
+/ (n & 3) == 3 && (n & 63) == 3 /
+{
+	commit(i);
+}
+
+tick-10ms
+/ (n & 3) == 3 && (n & 63) != 3 /
+{
+	discard(i);
+}
+
+tick-10ms
+/ n++ >= 1000 /
+{
+	exit(0);
+}
diff --git a/test/unittest/speculation/tst.SingleCPU.r b/test/unittest/speculation/tst.SingleCPU.r
new file mode 100644
index 000000000000..3659925d10b0
--- /dev/null
+++ b/test/unittest/speculation/tst.SingleCPU.r
@@ -0,0 +1,17 @@
+   1    1   2 hello world
+  65    1  66 hello world
+ 129    1 130 hello world
+ 193    1 194 hello world
+ 257    1 258 hello world
+ 321    1 322 hello world
+ 385    1 386 hello world
+ 449    1 450 hello world
+ 513    1 514 hello world
+ 577    1 578 hello world
+ 641    1 642 hello world
+ 705    1 706 hello world
+ 769    1 770 hello world
+ 833    1 834 hello world
+ 897    1 898 hello world
+ 961    1 962 hello world
+
diff --git a/test/unittest/speculation/tst.SpecSizeVariations3.d b/test/unittest/speculation/tst.SpecSizeVariations3.d
deleted file mode 100644
index 9ba2d1ebf9d9..000000000000
--- a/test/unittest/speculation/tst.SpecSizeVariations3.d
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Oracle Linux DTrace.
- * Copyright (c) 2006, 2020, Oracle and/or its affiliates. All rights reserved.
- * Licensed under the Universal Permissive License v 1.0 as shown at
- * http://oss.oracle.com/licenses/upl.
- */
-/* @@xfail: dtv2 */
-
-/*
- * ASSERTION:
- * Verify the behavior of speculations with changes in specsize.
- *
- * SECTION: Speculative Tracing/Options and Tuning;
- *	Options and Tunables/specsize
- *
- */
-
-#pragma D option quiet
-#pragma D option specsize=8
-
-BEGIN
-{
-	self->speculateFlag = 0;
-	self->commitFlag = 0;
-	self->spec = speculation();
-	printf("Speculative buffer ID: %d\n", self->spec);
-}
-
-BEGIN
-{
-	speculate(self->spec);
-	printf("Lots of data\n");
-	printf("Has to be crammed into this buffer\n");
-	printf("Until it overflows\n");
-	printf("And causes flops\n");
-	self->speculateFlag++;
-
-}
-
-BEGIN
-/1 <= self->speculateFlag/
-{
-	commit(self->spec);
-	self->commitFlag++;
-}
-
-BEGIN
-/1 <= self->commitFlag/
-{
-	printf("Statement was executed\n");
-	exit(1);
-}
-
-BEGIN
-/1 > self->commitFlag/
-{
-	printf("Statement wasn't executed\n");
-	exit(0);
-}
diff --git a/test/unittest/speculation/tst.SpecSizeVariations3.r b/test/unittest/speculation/tst.SpecSizeVariations3.r
deleted file mode 100644
index 41ccfc14c78c..000000000000
--- a/test/unittest/speculation/tst.SpecSizeVariations3.r
+++ /dev/null
@@ -1,3 +0,0 @@
-Speculative buffer ID: 1
-Statement wasn't executed
-
diff --git a/test/unittest/speculation/tst.SpecSizeVariations4.d b/test/unittest/speculation/tst.SpecSizeVariations4.d
index 342ea316a219..17c6eab6118c 100644
--- a/test/unittest/speculation/tst.SpecSizeVariations4.d
+++ b/test/unittest/speculation/tst.SpecSizeVariations4.d
@@ -4,7 +4,6 @@
  * Licensed under the Universal Permissive License v 1.0 as shown at
  * http://oss.oracle.com/licenses/upl.
  */
-/* @@xfail: dtv2 */
 
 /*
  * ASSERTION:
@@ -18,8 +17,11 @@
 #pragma D option quiet
 #pragma D option specsize=39
 
+long long x;
+
 BEGIN
 {
+	x = 123456789;
 	self->speculateFlag = 0;
 	self->commitFlag = 0;
 	self->spec = speculation();
@@ -29,10 +31,10 @@ BEGIN
 BEGIN
 {
 	speculate(self->spec);
-	printf("Lots of data\n");
-	printf("Has to be crammed into this buffer\n");
-	printf("Until it overflows\n");
-	printf("And causes flops\n");
+	printf("%lld: Lots of data\n", x);
+	printf("%lld: Has to be crammed into this buffer\n", x);
+	printf("%lld: Until it overflows\n", x);
+	printf("%lld: And causes flops\n", x);
 	self->speculateFlag++;
 
 }
@@ -48,12 +50,12 @@ BEGIN
 /1 <= self->commitFlag/
 {
 	printf("Statement was executed\n");
-	exit(1);
+	exit(0);
 }
 
 BEGIN
 /1 > self->commitFlag/
 {
 	printf("Statement wasn't executed\n");
-	exit(0);
+	exit(1);
 }
diff --git a/test/unittest/speculation/tst.SpecSizeVariations4.r b/test/unittest/speculation/tst.SpecSizeVariations4.r
index 41ccfc14c78c..719e2441d27d 100644
--- a/test/unittest/speculation/tst.SpecSizeVariations4.r
+++ b/test/unittest/speculation/tst.SpecSizeVariations4.r
@@ -1,3 +1,3 @@
 Speculative buffer ID: 1
-Statement wasn't executed
+Statement was executed
 
diff --git a/test/unittest/speculation/tst.SpecSizeVariations5.d b/test/unittest/speculation/tst.SpecSizeVariations5.d
index 4e90fbb1feca..cfbd885a5cdd 100644
--- a/test/unittest/speculation/tst.SpecSizeVariations5.d
+++ b/test/unittest/speculation/tst.SpecSizeVariations5.d
@@ -4,7 +4,6 @@
  * Licensed under the Universal Permissive License v 1.0 as shown at
  * http://oss.oracle.com/licenses/upl.
  */
-/* @@xfail: dtv2 */
 
 /*
  * ASSERTION:
@@ -18,8 +17,11 @@
 #pragma D option quiet
 #pragma D option specsize=40
 
+long long x;
+
 BEGIN
 {
+	x = 123456789;
 	self->speculateFlag = 0;
 	self->commitFlag = 0;
 	self->spec = speculation();
@@ -29,10 +31,10 @@ BEGIN
 BEGIN
 {
 	speculate(self->spec);
-	printf("Lots of data\n");
-	printf("Has to be crammed into this buffer\n");
-	printf("Until it overflows\n");
-	printf("And causes flops\n");
+	printf("%lld: Lots of data\n", x);
+	printf("%lld: Has to be crammed into this buffer\n", x);
+	printf("%lld: Until it overflows\n", x);
+	printf("%lld: And causes flops\n", x);
 	self->speculateFlag++;
 
 }
diff --git a/test/unittest/speculation/tst.SpecSizeVariations5.r b/test/unittest/speculation/tst.SpecSizeVariations5.r
index da44024d5ffe..d09013a2ae19 100644
--- a/test/unittest/speculation/tst.SpecSizeVariations5.r
+++ b/test/unittest/speculation/tst.SpecSizeVariations5.r
@@ -1,7 +1,7 @@
 Speculative buffer ID: 1
-Lots of data
-Has to be crammed into this buffer
-Until it overflows
-And causes flops
+123456789: Lots of data
+123456789: Has to be crammed into this buffer
+123456789: Until it overflows
+123456789: And causes flops
 Statement was executed
 
diff --git a/test/unittest/speculation/tst.SpeculationCommit.d b/test/unittest/speculation/tst.SpeculationCommit.d
index 554f5339db11..957042367b08 100644
--- a/test/unittest/speculation/tst.SpeculationCommit.d
+++ b/test/unittest/speculation/tst.SpeculationCommit.d
@@ -4,18 +4,15 @@
  * Licensed under the Universal Permissive License v 1.0 as shown at
  * http://oss.oracle.com/licenses/upl.
  */
-/* @@xfail: dtv2 */
 
 /*
  * ASSERTION: Test the normal behavior of speculate() and commit().
  *
  * SECTION: Speculative Tracing/Committing a Speculation;
- *	Actions and Subroutines/speculation();
- *	Options and Tunables/cleanrate
+ *	Actions and Subroutines/speculation()
  *
  */
 #pragma D option quiet
-#pragma D option cleanrate=2000hz
 
 BEGIN
 {
diff --git a/test/unittest/speculation/tst.SpeculationCommit.d b/test/unittest/speculation/tst.SpeculationCommitNotQuiet.d
similarity index 78%
copy from test/unittest/speculation/tst.SpeculationCommit.d
copy to test/unittest/speculation/tst.SpeculationCommitNotQuiet.d
index 554f5339db11..322eefeac799 100644
--- a/test/unittest/speculation/tst.SpeculationCommit.d
+++ b/test/unittest/speculation/tst.SpeculationCommitNotQuiet.d
@@ -1,21 +1,18 @@
 /*
  * Oracle Linux DTrace.
- * Copyright (c) 2007, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.
  * Licensed under the Universal Permissive License v 1.0 as shown at
  * http://oss.oracle.com/licenses/upl.
  */
-/* @@xfail: dtv2 */
 
 /*
- * ASSERTION: Test the normal behavior of speculate() and commit().
+ * ASSERTION: Test the normal behavior of speculate() and commit() with
+ * quiet mode turned off.
  *
  * SECTION: Speculative Tracing/Committing a Speculation;
- *	Actions and Subroutines/speculation();
- *	Options and Tunables/cleanrate
+ *	Actions and Subroutines/speculation()
  *
  */
-#pragma D option quiet
-#pragma D option cleanrate=2000hz
 
 BEGIN
 {
diff --git a/test/unittest/speculation/tst.SpeculationCommitNotQuiet.r b/test/unittest/speculation/tst.SpeculationCommitNotQuiet.r
new file mode 100644
index 000000000000..635f849a4df1
--- /dev/null
+++ b/test/unittest/speculation/tst.SpeculationCommitNotQuiet.r
@@ -0,0 +1,10 @@
+                   FUNCTION:NAME
+                          :BEGIN Speculation ID: 1
+
+                          :BEGIN Called speculate on id: 1
+
+                          :BEGIN Succesfully tested buffer commit
+
+-- @@stderr --
+dtrace: script 'test/unittest/speculation/tst.SpeculationCommitNotQuiet.d' matched 5 probes
+
diff --git a/test/unittest/speculation/tst.SpeculationDefault.d b/test/unittest/speculation/tst.SpeculationDefault.d
new file mode 100644
index 000000000000..aeeeb64d9956
--- /dev/null
+++ b/test/unittest/speculation/tst.SpeculationDefault.d
@@ -0,0 +1,31 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Test the normal behavior of a speculative default clause
+ * with quiet mode turned off.
+ *
+ * SECTION: Actions and Subroutines/speculation()
+ *
+ */
+
+BEGIN
+{
+	self->spec = speculation();
+}
+
+BEGIN
+/self->spec/
+{
+	speculate(self->spec);
+}
+
+BEGIN
+/self->spec/
+{
+	exit(0);
+}
diff --git a/test/unittest/speculation/tst.SpeculationDefault.r b/test/unittest/speculation/tst.SpeculationDefault.r
new file mode 100644
index 000000000000..b39c2480397c
--- /dev/null
+++ b/test/unittest/speculation/tst.SpeculationDefault.r
@@ -0,0 +1,5 @@
+                   FUNCTION:NAME
+                          :BEGIN 
+
+-- @@stderr --
+dtrace: script 'test/unittest/speculation/tst.SpeculationDefault.d' matched 3 probes
diff --git a/test/unittest/speculation/tst.SpeculationDefaultCommit.d b/test/unittest/speculation/tst.SpeculationDefaultCommit.d
new file mode 100644
index 000000000000..6a8e20434c93
--- /dev/null
+++ b/test/unittest/speculation/tst.SpeculationDefaultCommit.d
@@ -0,0 +1,38 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Test the normal behavior when committing a speculative
+ * default clause with quiet mode turned off.
+ *
+ * SECTION: Speculative Tracing/Committing a Speculation;
+ *	Actions and Subroutines/speculation()
+ *
+ */
+
+BEGIN
+{
+	self->spec = speculation();
+}
+
+BEGIN
+/self->spec/
+{
+	speculate(self->spec);
+}
+
+BEGIN
+/self->spec/
+{
+	commit(self->spec);
+}
+
+BEGIN
+/self->spec/
+{
+	exit(0);
+}
diff --git a/test/unittest/speculation/tst.SpeculationDefaultCommit.r b/test/unittest/speculation/tst.SpeculationDefaultCommit.r
new file mode 100644
index 000000000000..8d09be3ad3e4
--- /dev/null
+++ b/test/unittest/speculation/tst.SpeculationDefaultCommit.r
@@ -0,0 +1,6 @@
+                   FUNCTION:NAME
+                          :BEGIN 
+                          :BEGIN 
+
+-- @@stderr --
+dtrace: script 'test/unittest/speculation/tst.SpeculationDefaultCommit.d' matched 4 probes
diff --git a/test/unittest/speculation/tst.SpeculationDefaultDiscard.d b/test/unittest/speculation/tst.SpeculationDefaultDiscard.d
new file mode 100644
index 000000000000..3292ca982638
--- /dev/null
+++ b/test/unittest/speculation/tst.SpeculationDefaultDiscard.d
@@ -0,0 +1,37 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Test the normal behavior when discarding a speculative
+ * default clause with quiet mode turned off.
+ *
+ * SECTION: Speculative Tracing/Discarding a Speculation;
+ *	Actions and Subroutines/speculation()
+ */
+
+BEGIN
+{
+	self->spec = speculation();
+}
+
+BEGIN
+/self->spec/
+{
+	speculate(self->spec);
+}
+
+BEGIN
+/self->spec/
+{
+	discard(self->spec);
+}
+
+BEGIN
+/self->spec/
+{
+	exit(0);
+}
diff --git a/test/unittest/speculation/tst.SpeculationDefaultDiscard.r b/test/unittest/speculation/tst.SpeculationDefaultDiscard.r
new file mode 100644
index 000000000000..156d6b6ecba8
--- /dev/null
+++ b/test/unittest/speculation/tst.SpeculationDefaultDiscard.r
@@ -0,0 +1,5 @@
+                   FUNCTION:NAME
+                          :BEGIN 
+
+-- @@stderr --
+dtrace: script 'test/unittest/speculation/tst.SpeculationDefaultDiscard.d' matched 4 probes
diff --git a/test/unittest/speculation/tst.SpeculationDiscard.d b/test/unittest/speculation/tst.SpeculationDiscard.d
index 8803324a81bb..132cc13beedb 100644
--- a/test/unittest/speculation/tst.SpeculationDiscard.d
+++ b/test/unittest/speculation/tst.SpeculationDiscard.d
@@ -4,17 +4,14 @@
  * Licensed under the Universal Permissive License v 1.0 as shown at
  * http://oss.oracle.com/licenses/upl.
  */
-/* @@xfail: dtv2 */
 
 /*
  * ASSERTION: Test the normal behavior of speculate() and discard().
  *
- * SECTION: Speculative Tracing/Discarding a Speculation;
- *	Options and Tunables/cleanrate
+ * SECTION: Speculative Tracing/Discarding a Speculation
  *
  */
 #pragma D option quiet
-#pragma D option cleanrate=2000hz
 
 BEGIN
 {
@@ -42,7 +39,7 @@ BEGIN
 BEGIN
 /(1 == self->discard)/
 {
-	printf("Succesfully tested buffer discard\n");
+	printf("Successfully tested buffer discard\n");
 	exit(0);
 }
 
diff --git a/test/unittest/speculation/tst.SpeculationDiscard.r b/test/unittest/speculation/tst.SpeculationDiscard.r
index 8b960a0dbd18..80e8b50fadc1 100644
--- a/test/unittest/speculation/tst.SpeculationDiscard.r
+++ b/test/unittest/speculation/tst.SpeculationDiscard.r
@@ -1,3 +1,3 @@
 Speculation ID: 1
-Succesfully tested buffer discard
+Successfully tested buffer discard
 
diff --git a/test/unittest/speculation/tst.SpeculationDiscard.d b/test/unittest/speculation/tst.SpeculationDiscardNotQuiet.d
similarity index 71%
copy from test/unittest/speculation/tst.SpeculationDiscard.d
copy to test/unittest/speculation/tst.SpeculationDiscardNotQuiet.d
index 8803324a81bb..23013c6e5ff1 100644
--- a/test/unittest/speculation/tst.SpeculationDiscard.d
+++ b/test/unittest/speculation/tst.SpeculationDiscardNotQuiet.d
@@ -1,20 +1,16 @@
 /*
  * Oracle Linux DTrace.
- * Copyright (c) 2007, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.
  * Licensed under the Universal Permissive License v 1.0 as shown at
  * http://oss.oracle.com/licenses/upl.
  */
-/* @@xfail: dtv2 */
 
 /*
  * ASSERTION: Test the normal behavior of speculate() and discard().
  *
- * SECTION: Speculative Tracing/Discarding a Speculation;
- *	Options and Tunables/cleanrate
+ * SECTION: Speculative Tracing/Discarding a Speculation
  *
  */
-#pragma D option quiet
-#pragma D option cleanrate=2000hz
 
 BEGIN
 {
@@ -42,7 +38,7 @@ BEGIN
 BEGIN
 /(1 == self->discard)/
 {
-	printf("Succesfully tested buffer discard\n");
+	printf("Successfully tested buffer discard\n");
 	exit(0);
 }
 
diff --git a/test/unittest/speculation/tst.SpeculationDiscardNotQuiet.r b/test/unittest/speculation/tst.SpeculationDiscardNotQuiet.r
new file mode 100644
index 000000000000..be17cec0cec9
--- /dev/null
+++ b/test/unittest/speculation/tst.SpeculationDiscardNotQuiet.r
@@ -0,0 +1,8 @@
+                   FUNCTION:NAME
+                          :BEGIN Speculation ID: 1
+
+                          :BEGIN Successfully tested buffer discard
+
+-- @@stderr --
+dtrace: script 'test/unittest/speculation/tst.SpeculationDiscardNotQuiet.d' matched 5 probes
+
diff --git a/test/unittest/speculation/tst.SpeculationID.d b/test/unittest/speculation/tst.SpeculationID.d
index 21ef5ac64b55..f55d5fda44f5 100644
--- a/test/unittest/speculation/tst.SpeculationID.d
+++ b/test/unittest/speculation/tst.SpeculationID.d
@@ -4,7 +4,6 @@
  * Licensed under the Universal Permissive License v 1.0 as shown at
  * http://oss.oracle.com/licenses/upl.
  */
-/* @@xfail: dtv2 */
 
 /*
  * ASSERTION:
diff --git a/test/unittest/speculation/tst.SpeculationWithZero.d b/test/unittest/speculation/tst.SpeculationWithZero.d
index 959b734a9b75..04991b40f5a2 100644
--- a/test/unittest/speculation/tst.SpeculationWithZero.d
+++ b/test/unittest/speculation/tst.SpeculationWithZero.d
@@ -4,7 +4,6 @@
  * Licensed under the Universal Permissive License v 1.0 as shown at
  * http://oss.oracle.com/licenses/upl.
  */
-/* @@xfail: dtv2 */
 
 /*
  * ASSERTION:
@@ -19,14 +18,11 @@
 BEGIN
 {
 	self->speculateFlag = 0;
-	self->spec = speculation();
-	self->spec = speculation();
-	printf("Speculative buffer ID: %d\n", self->spec);
 }
 
 BEGIN
 {
-	speculate(self->spec);
+	speculate(0);
 	self->speculateFlag++;
 }
 
diff --git a/test/unittest/speculation/tst.SpeculationWithZero.r b/test/unittest/speculation/tst.SpeculationWithZero.r
index 9165c854920c..35952e0a25e9 100644
--- a/test/unittest/speculation/tst.SpeculationWithZero.r
+++ b/test/unittest/speculation/tst.SpeculationWithZero.r
@@ -1,5 +1,2 @@
-Speculative buffer ID: 0
 Statement wasn't executed
 
--- @@stderr --
-dtrace: 1 failed speculation (no speculative buffer available)
diff --git a/test/unittest/speculation/tst.TwoSpecBuffers.d b/test/unittest/speculation/tst.TwoSpecBuffers.d
index 2df996927c15..25e73b0662c7 100644
--- a/test/unittest/speculation/tst.TwoSpecBuffers.d
+++ b/test/unittest/speculation/tst.TwoSpecBuffers.d
@@ -4,12 +4,11 @@
  * Licensed under the Universal Permissive License v 1.0 as shown at
  * http://oss.oracle.com/licenses/upl.
  */
-/* @@xfail: dtv2 */
 
 /*
  * ASSERTION:
- * Increasing the value of nspec to two should will increase the number of
- * speculative buffers to two.
+ * Increasing the value of nspec to two should set the number of
+ * speculative buffers to two: getting a third should fail.
  *
  * SECTION: Speculative Tracing/Options and Tuning;
  *		Options and Tunables/nspec
@@ -17,7 +16,6 @@
  */
 
 #pragma D option quiet
-#pragma D option cleanrate=3000hz
 #pragma D option nspec=2
 
 BEGIN
@@ -40,7 +38,7 @@ BEGIN
 BEGIN
 /var1 && var2 && (!var3)/
 {
-	printf("Succesfully got two speculative buffers");
+	printf("Successfully got two speculative buffers");
 	exit(0);
 }
 
diff --git a/test/unittest/speculation/tst.TwoSpecBuffers.r b/test/unittest/speculation/tst.TwoSpecBuffers.r
index 3a35b4673ee4..0c6bd3d47fff 100644
--- a/test/unittest/speculation/tst.TwoSpecBuffers.r
+++ b/test/unittest/speculation/tst.TwoSpecBuffers.r
@@ -1,6 +1,4 @@
 Speculation ID: 1
 Speculation ID: 2
 Speculation ID: 0
-Succesfully got two speculative buffers
--- @@stderr --
-dtrace: 1 failed speculation (no speculative buffer available)
+Successfully got two speculative buffers
diff --git a/test/unittest/speculation/tst.negcommit.d b/test/unittest/speculation/tst.negcommit.d
index cafb0a149bc7..5a680a84f866 100644
--- a/test/unittest/speculation/tst.negcommit.d
+++ b/test/unittest/speculation/tst.negcommit.d
@@ -4,7 +4,6 @@
  * Licensed under the Universal Permissive License v 1.0 as shown at
  * http://oss.oracle.com/licenses/upl.
  */
-/* @@xfail: dtv2 */
 
 BEGIN
 {
diff --git a/test/unittest/speculation/tst.negcommit.r b/test/unittest/speculation/tst.negcommit.r
index 716afcb52d70..6bb75d1462ba 100644
--- a/test/unittest/speculation/tst.negcommit.r
+++ b/test/unittest/speculation/tst.negcommit.r
@@ -3,4 +3,4 @@
 
 -- @@stderr --
 dtrace: script 'test/unittest/speculation/tst.negcommit.d' matched 2 probes
-dtrace: error on enabled probe ID 1 (ID 1: dtrace:::BEGIN): illegal operation in action #1
+dtrace: error on enabled probe ID 2 (ID 1: dtrace:::BEGIN): illegal operation in action #1
diff --git a/test/unittest/speculation/tst.zerosize.d b/test/unittest/speculation/tst.zerosize.d
index 74d1ace9e1e3..03d3b8497aa9 100644
--- a/test/unittest/speculation/tst.zerosize.d
+++ b/test/unittest/speculation/tst.zerosize.d
@@ -4,6 +4,7 @@
  * Licensed under the Universal Permissive License v 1.0 as shown at
  * http://oss.oracle.com/licenses/upl.
  */
+
 /* @@xfail: dtv2 */
 
 #pragma D option destructive
-- 
2.33.0.256.gb827f06fa9




More information about the DTrace-devel mailing list