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

Kris Van Hees kris.van.hees at oracle.com
Wed Oct 13 07:49:45 PDT 2021


Some comments...

First off, in naming I think things are getting a little confusing...  What
you seem to describe in naming is that we have a collection of speculations,
where each speculation (identified by id) is represented in a htab with a
dt_spec_bufs_head and it contains a list of dt_spec_bufs elements.  That
seems to implement a structure where one or more speculation buffers are
linked together under a speculation buffers head, and that forms the data
to (hopefully) be committed at a later time for a specific speculation.

In DTrace documentation and the legacy code a speculation and a speculation
buffer are essentially the same thing.  The speculation buffer holds all the
data for a specific speculation.

Translating that into the new structure, I'd say that we have a speculation
buffer (represented by your dt_spec_bufs_head) that is identified by an id
(speculation id).  And it contains probe data chunks.

So maybe rename:
    dt_spec_bufs_head -> dt_spec_buf
    dt_spec_bufs      -> dt_spec_buf_data

and functions should probabyl be renamed to reflect this a bit better also.

Further comments are assuming this kind of renaming...

On Thu, Sep 09, 2021 at 12:13:23PM +0100, Nick Alcock wrote:
> 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 \

Just throw all of these in the same speculation.c source file.

>  	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;
> +}

As commented above, the above three files can be combined.

> 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)

This name is odd in comparison with the earlier dt_spec_bufs_* stuff.  Per my
comments before, maybe name this dt_spec_buf_create().

> +{
> +	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)

Maybe dt_spec_buf_add_data() ?  Or just t_spec_buf_add()

> +{
> +	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_buf_destroy()

> +{
> +	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

across - I make those typos all the time myself :)

> +	 * 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
> 
> 
> _______________________________________________
> DTrace-devel mailing list
> DTrace-devel at oss.oracle.com
> https://oss.oracle.com/mailman/listinfo/dtrace-devel



More information about the DTrace-devel mailing list