[DTrace-devel] [PATCH 5/6 v2] Implement associative array support

Kris Van Hees kris.van.hees at oracle.com
Thu Mar 10 06:14:06 UTC 2022


Associative arrays (global or TLS) are variables that are indexed with
a tuple (one or more values).  The underlying storage is provided by
the dvar (dynamic variables) BPF hash map.  Since dvar elements are
indexed using a unique 64-bit ID, and given that it provides storage
for both regular TLS variables and associate array elements, the
algorithms to calculate the ID are designed to provide orthogonal
value ranges.

TLS variable: dvar key has high order bit 0
Associative array element: dvar key has high order bit 1

The dvar key for an associative array element is determined based on the
tuple content values.  A new BPF hash map (tuples) associates the
concatenation of the tuple values as key with the address of the hash
map element (which is guaranteed to be unique).  Since BPF maps are
allocated in kernel space, these addresses will always have high order
bit 1.

To ensure uniqueness between global associate array elements and TLS
associate array elements, and to ensure uniqueness between elements
with the same tuple index in different arrays, the key in the tuples
map is determined based on a rewritten tuple:

    [ variable ID, original tuple values, TLS variable key or 0 ]

While variable IDs are not unique between variable kinds, the final
component in the rewritten tuple is the TLS variable key (for TLS
associative arrays - never 0) or 0 (for global associative arrays).

Various new tests are added to exercise the new functionality.

Running out of dynamic variable space is not being reported as a drop
yet due to lack of drop counter support.  It generates an error
instead, and therefore the test for the drop reporting remains XFAIL.

Signed-off-by: Kris Van Hees <kris.van.hees at oracle.com>
---
 bpf/get_dvar.c                                |  74 ++-
 libdtrace/dt_bpf.c                            |  19 +-
 libdtrace/dt_bpf.h                            |   3 +-
 libdtrace/dt_cc.c                             |   5 +-
 libdtrace/dt_cg.c                             | 458 ++++++++++++------
 libdtrace/dt_dctx.h                           |  18 +-
 libdtrace/dt_dlibs.c                          |   2 +
 libdtrace/dt_ident.c                          |   9 +-
 libdtrace/dt_impl.h                           |   1 +
 test/unittest/assocs/err.limited_space.d      |  34 ++
 test/unittest/assocs/err.limited_space.r      |   6 +
 test/unittest/assocs/err.tupoflow.d           |   3 +-
 test/unittest/assocs/err.tupoflow.r           |   2 +-
 test/unittest/assocs/tst.clean-tuple.d        |  29 ++
 test/unittest/assocs/tst.cpyarray.d           |   3 +-
 test/unittest/assocs/tst.gvar-postdec.d       |  22 +
 test/unittest/assocs/tst.gvar-postinc.d       |  22 +
 test/unittest/assocs/tst.gvar-predec.d        |  22 +
 test/unittest/assocs/tst.gvar-preinc.d        |  22 +
 test/unittest/assocs/tst.init-str.d           |  29 ++
 test/unittest/assocs/tst.init.d               |  21 +
 test/unittest/assocs/tst.misc.d               |   3 +-
 test/unittest/assocs/tst.orthogonality.d      |   3 +-
 test/unittest/assocs/tst.store_zero_deletes.d |  35 ++
 test/unittest/assocs/tst.store_zero_deletes.r |   1 +
 test/unittest/assocs/tst.tvar-postdec.d       |  22 +
 test/unittest/assocs/tst.tvar-postinc.d       |  22 +
 test/unittest/assocs/tst.tvar-predec.d        |  22 +
 test/unittest/assocs/tst.tvar-preinc.d        |  22 +
 29 files changed, 758 insertions(+), 176 deletions(-)
 create mode 100644 test/unittest/assocs/err.limited_space.d
 create mode 100644 test/unittest/assocs/err.limited_space.r
 create mode 100644 test/unittest/assocs/tst.clean-tuple.d
 create mode 100644 test/unittest/assocs/tst.gvar-postdec.d
 create mode 100644 test/unittest/assocs/tst.gvar-postinc.d
 create mode 100644 test/unittest/assocs/tst.gvar-predec.d
 create mode 100644 test/unittest/assocs/tst.gvar-preinc.d
 create mode 100644 test/unittest/assocs/tst.init-str.d
 create mode 100644 test/unittest/assocs/tst.init.d
 create mode 100644 test/unittest/assocs/tst.store_zero_deletes.d
 create mode 100644 test/unittest/assocs/tst.store_zero_deletes.r
 create mode 100644 test/unittest/assocs/tst.tvar-postdec.d
 create mode 100644 test/unittest/assocs/tst.tvar-postinc.d
 create mode 100644 test/unittest/assocs/tst.tvar-predec.d
 create mode 100644 test/unittest/assocs/tst.tvar-preinc.d

diff --git a/bpf/get_dvar.c b/bpf/get_dvar.c
index 14f21783..25ce37f4 100644
--- a/bpf/get_dvar.c
+++ b/bpf/get_dvar.c
@@ -11,8 +11,24 @@
 #endif
 
 extern struct bpf_map_def dvars;
+extern struct bpf_map_def tuples;
 extern uint64_t NCPUS;
 
+/*
+ * Dynamic variables are identified using a unique 64-bit key.  Three diferent
+ * categories of dynamic variables are supported in DTrace:
+ *
+ * 1. Thread-local storage (TLS) variables:
+ *	dvar key = TLS key (highest bit = 0)
+ * 2. Global associative array elements:
+ *	dvar key = &tuples[var id, tuple, (uint64_t)0] (highest bit = 1)
+ * 2. TLS associative array elements:
+ *	dvar key = &tuples[var id, tuple, TLS key] (highest bit = 1)
+ *
+ * Given that the TLS key can never be 0, uniqueness of the dvar key is
+ * guaranteed in this scheme.
+ */
+
 noinline uint64_t dt_tlskey(uint32_t id)
 {
 	uint64_t	key;
@@ -25,7 +41,7 @@ noinline uint64_t dt_tlskey(uint32_t id)
 		key += (uint32_t)(uint64_t)&NCPUS;
 
 	key++;
-	key = (key << 32) | id;
+	key = ((key & 0x7fffffff) << 32) | id;
 
 	return key;
 }
@@ -81,3 +97,59 @@ noinline void *dt_get_tvar(uint32_t id, uint64_t store, uint64_t nval)
 {
 	return dt_get_dvar(dt_tlskey(id), store, nval);
 }
+
+noinline void *dt_get_assoc(uint32_t id, const char *tuple,
+			    uint64_t store, uint64_t nval)
+{
+	uint64_t	*valp;
+	uint64_t	val;
+	uint64_t	dflt_val = 0;
+
+	/*
+	 * Place the variable ID at the beginning of the tuple.
+	 */
+	*(uint32_t *)tuple = id;
+
+	/*
+	 * Look for the tuple in the tuples map.
+	 */
+	valp = bpf_map_lookup_elem(&tuples, tuple);
+	if (valp == 0) {
+		/*
+		 * Not found.  If we are not storing a value (i.e. performing a
+		 * load), return the default value (0).  If we are trying to
+		 * delete an associative array element, we don't have to do
+		 * anything because it does not exist anyway.
+		 */
+		if (!store || !nval)
+			return 0;
+
+		/*
+		 * Create the tuple and use the address of the value as the
+		 * actual value.
+		 */
+		if (bpf_map_update_elem(&tuples, tuple, &dflt_val, BPF_ANY) < 0)
+			return 0;
+		valp = bpf_map_lookup_elem(&tuples, tuple);
+		if (valp == 0)
+			return 0;
+		*valp = (uint64_t)valp;
+		if (bpf_map_update_elem(&tuples, tuple, valp, BPF_ANY) < 0)
+			return 0;
+
+		val = *valp;
+	} else {
+		/*
+		 * Record the value (used as key into the dvars map), and if we
+		 * are storing a zero-value (deleting the element), delete the
+		 * tuple.  The associated dynamic variable will be delete by
+		 * the dt_get_dvar() call.
+		 */
+		val = *valp;
+
+		if (store && !nval)
+			bpf_map_delete_elem(&tuples, tuple);
+	}
+	
+	return dt_get_dvar(val, store, nval);
+}
diff --git a/libdtrace/dt_bpf.c b/libdtrace/dt_bpf.c
index 159c4722..ade09b15 100644
--- a/libdtrace/dt_bpf.c
+++ b/libdtrace/dt_bpf.c
@@ -224,6 +224,11 @@ populate_probes_map(dtrace_hdl_t *dtp, int fd)
  *		tracing session.
  * - lvars:	Local variables map.  This is a per-CPU map with a singleton
  *		element (key 0) addressed by variable offset.
+ * - tuples:	Tuple-to-id map.  This is a global hash map indexed with a
+ *		tuple.  The value associated with the tuple key is an id that
+ *		is used to index the dvars map.  The key size is determined as
+ *		the largest tuple used across all programs in the tracing
+ *		session.
  */
 int
 dt_bpf_gmap_create(dtrace_hdl_t *dtp)
@@ -249,9 +254,10 @@ dt_bpf_gmap_create(dtrace_hdl_t *dtp)
 	/* Determine sizes for global, local, and TLS maps. */
 	gvarsz = P2ROUNDUP(dt_idhash_datasize(dtp->dt_globals), 8);
 	lvarsz = P2ROUNDUP(dtp->dt_maxlvaralloc, 8);
+
 	if (dtp->dt_maxdvarsize)
-		dvarc = (dtp->dt_options[DTRACEOPT_DYNVARSIZE] /
-			 dtp->dt_maxdvarsize) + 1;
+		dvarc = dtp->dt_options[DTRACEOPT_DYNVARSIZE] /
+			dtp->dt_maxdvarsize;
 
 	/* Create global maps as long as there are no errors. */
 	dtp->dt_stmap_fd = create_gmap(dtp, "state", BPF_MAP_TYPE_ARRAY,
@@ -356,8 +362,10 @@ dt_bpf_gmap_create(dtrace_hdl_t *dtp)
 		int	fd;
 		char	dflt[dtp->dt_maxdvarsize];
 
+		/* Allocate one extra element for the default value. */
 		fd = create_gmap(dtp, "dvars", BPF_MAP_TYPE_HASH,
-				 sizeof(uint64_t), dtp->dt_maxdvarsize, dvarc);
+				 sizeof(uint64_t), dtp->dt_maxdvarsize,
+				 dvarc + 1);
 		if (fd == -1)
 			return -1;	/* dt_errno is set for us */
 
@@ -366,6 +374,11 @@ dt_bpf_gmap_create(dtrace_hdl_t *dtp)
 		dt_bpf_map_update(fd, &key, &dflt);
 	}
 
+	if (dtp->dt_maxtuplesize > 0 &&
+	    create_gmap(dtp, "tuples", BPF_MAP_TYPE_HASH,
+			dtp->dt_maxtuplesize, sizeof(uint64_t), dvarc) == -1)
+		return -1;		/* dt_errno is set for us */
+
 	/* Populate the 'cpuinfo' map. */
 	dt_bpf_map_update(ci_mapfd, &key, dtp->dt_conf.cpus);
 
diff --git a/libdtrace/dt_bpf.h b/libdtrace/dt_bpf.h
index 5b9bae80..237546e8 100644
--- a/libdtrace/dt_bpf.h
+++ b/libdtrace/dt_bpf.h
@@ -1,6 +1,6 @@
 /*
  * Oracle Linux DTrace.
- * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2022, 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.
  */
@@ -28,6 +28,7 @@ extern "C" {
 #define DT_CONST_BOOTTM	8
 #define DT_CONST_NSPEC	9
 #define DT_CONST_NCPUS	10
+#define DT_CONST_TUPSZ	11
 
 extern int perf_event_open(struct perf_event_attr *attr, pid_t pid, int cpu,
 			   int group_fd, unsigned long flags);
diff --git a/libdtrace/dt_cc.c b/libdtrace/dt_cc.c
index ce824eb5..49901710 100644
--- a/libdtrace/dt_cc.c
+++ b/libdtrace/dt_cc.c
@@ -1,6 +1,6 @@
 /*
  * Oracle Linux DTrace.
- * Copyright (c) 2008, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 2022, 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.
  */
@@ -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_TUPSZ:
+				nrp->dofr_data = DMEM_TUPLE_SZ(dtp);
+				continue;
 			case DT_CONST_NSPEC:
 				nrp->dofr_data = dtp->dt_options[DTRACEOPT_NSPEC];
 				continue;
diff --git a/libdtrace/dt_cg.c b/libdtrace/dt_cg.c
index 5300c9f6..ec4056a0 100644
--- a/libdtrace/dt_cg.c
+++ b/libdtrace/dt_cg.c
@@ -2319,6 +2319,217 @@ dt_cg_store(dt_node_t *src, dt_irlist_t *dlp, dt_regset_t *drp, dt_node_t *dst)
 	}
 }
 
+/*
+ * Generate code for a typecast or for argument promotion from the type of the
+ * actual to the type of the formal.  We need to generate code for casts when
+ * a scalar type is being narrowed or changing signed-ness.  We first shift the
+ * desired bits high (losing excess bits if narrowing) and then shift them down
+ * using logical shift (unsigned result) or arithmetic shift (signed result).
+ */
+static void
+dt_cg_typecast(const dt_node_t *src, const dt_node_t *dst,
+    dt_irlist_t *dlp, dt_regset_t *drp)
+{
+	size_t srcsize;
+	size_t dstsize;
+	int n;
+
+	/* If the destination type is '@' (any type) we need not cast. */
+	if (dst->dn_ctfp == NULL && dst->dn_type == CTF_ERR)
+		return;
+
+	srcsize = dt_node_type_size(src);
+	dstsize = dt_node_type_size(dst);
+
+	if (dstsize < srcsize)
+		n = sizeof(uint64_t) * NBBY - dstsize * NBBY;
+	else
+		n = sizeof(uint64_t) * NBBY - srcsize * NBBY;
+
+	if (dt_node_is_scalar(dst) && n != 0 && (dstsize < srcsize ||
+	    (src->dn_flags & DT_NF_SIGNED) ^ (dst->dn_flags & DT_NF_SIGNED))) {
+		emit(dlp, BPF_MOV_REG(dst->dn_reg, src->dn_reg));
+		emit(dlp, BPF_ALU64_IMM(BPF_LSH, dst->dn_reg, n));
+		emit(dlp, BPF_ALU64_IMM((dst->dn_flags & DT_NF_SIGNED) ? BPF_ARSH : BPF_RSH, dst->dn_reg, n));
+	}
+}
+
+/*
+ * Generate code to push the specified argument list on to the tuple stack.
+ * We use this routine for handling the index tuple for associative arrays.
+ * We must first generate code for all subexpressions because any subexpression
+ * could itself require the use of the tuple assembly area and we only provide
+ * one.
+ *
+ * Since the number of tuple components is unknown, we do not want to waste
+ * registers on storing the subexpression values.  So, instead, we store the
+ * subexpression values on the stack.
+ *
+ * Once code for all subexpressions has been generated, we assemble the tuple.
+ *
+ * Note that we leave space at the beginning of the tuple for a uint32_t value,
+ * and at the end space for a uint64_t value.
+ */
+static void
+dt_cg_arglist(dt_ident_t *idp, dt_node_t *args, dt_irlist_t *dlp,
+	      dt_regset_t *drp)
+{
+	dtrace_hdl_t		*dtp = yypcb->pcb_hdl;
+	const dt_idsig_t	*isp = idp->di_data;
+	dt_node_t		*dnp;
+	dt_ident_t		*tupsz = dt_dlib_get_var(dtp, "TUPSZ");
+	int			i;
+	int			treg, areg;
+	uint_t			tuplesize = sizeof(uint32_t);
+	size_t			strsize = dtp->dt_options[DTRACEOPT_STRSIZE];
+
+	TRACE_REGSET("      arglist: Begin");
+
+	for (dnp = args, i = 0; dnp != NULL; dnp = dnp->dn_list, i++) {
+		dt_cg_node(dnp, dlp, drp);
+		emit(dlp, BPF_STORE(BPF_DW, BPF_REG_FP, DT_STK_SP(i), dnp->dn_reg));
+		dt_regset_free(drp, dnp->dn_reg);
+	}
+
+	if (i > dtp->dt_conf.dtc_diftupregs)
+		longjmp(yypcb->pcb_jmpbuf, EDT_NOTUPREG);
+
+	TRACE_REGSET("      arglist: Stack");
+
+	if ((treg = dt_regset_alloc(drp)) == -1)
+		longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
+
+	emit(dlp, BPF_LOAD(BPF_DW, treg, BPF_REG_FP, DT_STK_DCTX));
+	emit(dlp, BPF_LOAD(BPF_DW, treg, treg, DCTX_MEM));
+
+	/*
+	 * We need to clear the tuple assembly area in case the previous tuple
+	 * was larger than the one we will construct, because otherwise we end
+	 * up with trailing garbage.
+	 */
+	if (dt_regset_xalloc_args(drp) == -1)
+		longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
+
+	emit(dlp,  BPF_MOV_REG(BPF_REG_1, treg));
+	emit(dlp,  BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, DMEM_TUPLE(dtp)));
+	emite(dlp, BPF_MOV_IMM(BPF_REG_2, -1), tupsz);
+	emit(dlp,  BPF_MOV_REG(BPF_REG_3, BPF_REG_1));
+	emite(dlp, BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, -1), tupsz);
+	dt_regset_xalloc(drp, BPF_REG_0);
+	emit(dlp,  BPF_CALL_HELPER(BPF_FUNC_probe_read));
+	dt_regset_free_args(drp);
+	dt_regset_free(drp, BPF_REG_0);
+
+	emit(dlp, BPF_ALU64_IMM(BPF_ADD, treg, DMEM_TUPLE(dtp) + sizeof(uint32_t)));
+
+	if ((areg = dt_regset_alloc(drp)) == -1)
+		longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
+
+	for (dnp = args, i = 0; dnp != NULL; dnp = dnp->dn_list, i++) {
+		dtrace_diftype_t	t;
+		size_t			size;
+
+		dt_node_diftype(yypcb->pcb_hdl, dnp, &t);
+		size = t.dtdt_size;
+
+		/* Append the value to the tuple assembly area. */
+		if (t.dtdt_size == 0)
+			continue;
+
+		emit(dlp,  BPF_LOAD(BPF_DW, areg, BPF_REG_FP, DT_STK_SP(i)));
+
+		isp->dis_args[i].dn_reg = areg;
+		dt_cg_typecast(dnp, &isp->dis_args[i], dlp, drp);
+		isp->dis_args[i].dn_reg = -1;
+
+		if (dt_node_is_scalar(dnp) || dt_node_is_float(dnp)) {
+			assert(size > 0 && size <= 8 &&
+			       (size & (size - 1)) == 0);
+
+			emit(dlp,  BPF_STORE(ldstw[size], treg, 0, areg));
+			emit(dlp,  BPF_ALU64_IMM(BPF_ADD, treg, size));
+
+			tuplesize += size;
+		} else if (dt_node_is_string(dnp)) {
+			uint_t	lbl_valid = dt_irlist_label(dlp);
+
+			if (dt_regset_xalloc_args(drp) == -1)
+				longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
+
+			emit(dlp,  BPF_MOV_REG(BPF_REG_1, treg));
+			emit(dlp,  BPF_MOV_IMM(BPF_REG_2, strsize + 1));
+			emit(dlp,  BPF_MOV_REG(BPF_REG_3, areg));
+			dt_cg_tstring_free(yypcb, dnp);
+			dt_regset_xalloc(drp, BPF_REG_0);
+			emit(dlp,  BPF_CALL_HELPER(BPF_FUNC_probe_read_str));
+			dt_regset_free_args(drp);
+			emit(dlp,  BPF_BRANCH_IMM(BPF_JSGE, BPF_REG_0, 0, lbl_valid));
+			dt_cg_probe_error(yypcb, -1, DTRACEFLT_BADADDR, 0);
+			emitl(dlp, lbl_valid,
+				   BPF_ALU64_REG(BPF_ADD, treg, BPF_REG_0));
+			dt_regset_free(drp, BPF_REG_0);
+
+			tuplesize += size + 1;
+		} else if (t.dtdt_flags & DIF_TF_BYREF) {
+			uint_t	lbl_valid = dt_irlist_label(dlp);
+
+			if (dt_regset_xalloc_args(drp) == -1)
+				longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
+
+			emit(dlp,  BPF_MOV_REG(BPF_REG_1, treg));
+			emit(dlp,  BPF_MOV_IMM(BPF_REG_2, size));
+			emit(dlp,  BPF_MOV_REG(BPF_REG_3, areg));
+			dt_regset_xalloc(drp, BPF_REG_0);
+			emit(dlp,  BPF_CALL_HELPER(BPF_FUNC_probe_read));
+			dt_regset_free_args(drp);
+			emit(dlp,  BPF_BRANCH_IMM(BPF_JEQ, BPF_REG_0, 0, lbl_valid));
+			dt_cg_probe_error(yypcb, -1, DTRACEFLT_BADADDR, 0);
+			emitl(dlp, lbl_valid,
+				   BPF_ALU64_IMM(BPF_ADD, treg, size));
+			dt_regset_free(drp, BPF_REG_0);
+
+			tuplesize += size;
+		} else
+			assert(0);	/* We shouldn't be able to get here. */
+	}
+
+	dt_regset_free(drp, areg);
+
+	TRACE_REGSET("      arglist: Tuple");
+
+	/* Add room for an optional TLS key (or 0). */
+	tuplesize += sizeof(uint64_t);
+
+	if (idp->di_flags & DT_IDFLG_TLS) {
+		dt_ident_t	*idp = dt_dlib_get_func(dtp, "dt_tlskey");
+		uint_t		varid = idp->di_id - DIF_VAR_OTHER_UBASE;
+
+		assert(idp != NULL);
+
+		if (dt_regset_xalloc_args(drp) == -1)
+			longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
+
+		emit(dlp,  BPF_MOV_IMM(BPF_REG_1, varid));
+		dt_regset_xalloc(drp, BPF_REG_0);
+		emite(dlp, BPF_CALL_FUNC(idp->di_id), idp);
+		dt_regset_free_args(drp);
+		emit(dlp,  BPF_STORE(BPF_DW, treg, 0, BPF_REG_0));
+		dt_regset_free(drp, BPF_REG_0);
+	} else
+		emit(dlp,  BPF_STORE_IMM(BPF_DW, treg, 0, 0));
+
+	if (tuplesize > dtp->dt_maxtuplesize)
+		dtp->dt_maxtuplesize = tuplesize;
+
+	emit(dlp, BPF_LOAD(BPF_DW, treg, BPF_REG_FP, DT_STK_DCTX));
+	emit(dlp, BPF_LOAD(BPF_DW, treg, treg, DCTX_MEM));
+	emit(dlp, BPF_ALU64_IMM(BPF_ADD, treg, DMEM_TUPLE(dtp)));
+
+	args->dn_reg = treg;
+
+	TRACE_REGSET("      arglist: End  ");
+}
+
 /*
  * dnp = node of the assignment
  *   dn_left = identifier node for the destination (idp = identifier)
@@ -2334,6 +2545,63 @@ dt_cg_store_var(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp,
 
 	idp->di_flags |= DT_IDFLG_DIFW;
 
+	/* Associative (global or TLS) array */
+	if (idp->di_kind == DT_IDENT_ARRAY) {
+		dt_cg_arglist(idp, dnp->dn_left->dn_args, dlp, drp);
+
+		varid = idp->di_id - DIF_VAR_OTHER_UBASE;
+		size = idp->di_size;
+		idp = dt_dlib_get_func(yypcb->pcb_hdl, "dt_get_assoc");
+		assert(idp != NULL);
+
+		if (dt_regset_xalloc_args(drp) == -1)
+			longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
+
+		emit(dlp,  BPF_MOV_IMM(BPF_REG_1, varid));
+		emit(dlp,  BPF_MOV_REG(BPF_REG_2, dnp->dn_left->dn_args->dn_reg));
+		dt_regset_free(drp, dnp->dn_left->dn_args->dn_reg);
+		emit(dlp,  BPF_MOV_IMM(BPF_REG_3, 1));
+		emit(dlp,  BPF_MOV_REG(BPF_REG_4, dnp->dn_reg));
+		dt_regset_xalloc(drp, BPF_REG_0);
+		emite(dlp, BPF_CALL_FUNC(idp->di_id), idp);
+		dt_regset_free_args(drp);
+		lbl_done = dt_irlist_label(dlp);
+		emit(dlp,  BPF_BRANCH_IMM(BPF_JEQ, dnp->dn_reg, 0, lbl_done));
+
+		if ((reg = dt_regset_alloc(drp)) == -1)
+			longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
+
+		emit(dlp,  BPF_MOV_REG(reg, BPF_REG_0));
+		dt_regset_free(drp, BPF_REG_0);
+
+		dt_cg_check_notnull(dlp, drp, reg);
+
+		if (dnp->dn_flags & DT_NF_REF) {
+			size_t	srcsz;
+
+			/*
+			 * Determine the amount of data to be copied.  It is
+			 * the lesser of the size of the identifier and the
+			 * size of the data being copied in.
+			 */
+			srcsz = dt_node_type_size(dnp->dn_right);
+			size = MIN(srcsz, size);
+
+			dt_cg_memcpy(dlp, drp, reg, dnp->dn_reg, size);
+		} else {
+			assert(size > 0 && size <= 8 &&
+			       (size & (size - 1)) == 0);
+
+			emit(dlp, BPF_STORE(ldstw[size], reg, 0, dnp->dn_reg));
+		}
+
+		dt_regset_free(drp, reg);
+
+		emitl(dlp, lbl_done,
+			   BPF_NOP());
+		return;
+	}
+
 	/* global and local variables (that is, not thread-local) */
 	if (!(idp->di_flags & DT_IDFLG_TLS)) {
 		if ((reg = dt_regset_alloc(drp)) == -1)
@@ -2425,103 +2693,6 @@ dt_cg_store_var(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp,
 		   BPF_NOP());
 }
 
-/*
- * Generate code for a typecast or for argument promotion from the type of the
- * actual to the type of the formal.  We need to generate code for casts when
- * a scalar type is being narrowed or changing signed-ness.  We first shift the
- * desired bits high (losing excess bits if narrowing) and then shift them down
- * using logical shift (unsigned result) or arithmetic shift (signed result).
- */
-static void
-dt_cg_typecast(const dt_node_t *src, const dt_node_t *dst,
-    dt_irlist_t *dlp, dt_regset_t *drp)
-{
-	size_t srcsize;
-	size_t dstsize;
-	int n;
-
-	/* If the destination type is '@' (any type) we need not cast. */
-	if (dst->dn_ctfp == NULL && dst->dn_type == CTF_ERR)
-		return;
-
-	srcsize = dt_node_type_size(src);
-	dstsize = dt_node_type_size(dst);
-
-	if (dstsize < srcsize)
-		n = sizeof(uint64_t) * NBBY - dstsize * NBBY;
-	else
-		n = sizeof(uint64_t) * NBBY - srcsize * NBBY;
-
-	if (dt_node_is_scalar(dst) && n != 0 && (dstsize < srcsize ||
-	    (src->dn_flags & DT_NF_SIGNED) ^ (dst->dn_flags & DT_NF_SIGNED))) {
-		emit(dlp, BPF_MOV_REG(dst->dn_reg, src->dn_reg));
-		emit(dlp, BPF_ALU64_IMM(BPF_LSH, dst->dn_reg, n));
-		emit(dlp, BPF_ALU64_IMM((dst->dn_flags & DT_NF_SIGNED) ? BPF_ARSH : BPF_RSH, dst->dn_reg, n));
-	}
-}
-
-/*
- * Generate code to push the specified argument list on to the tuple stack.
- * We use this routine for handling subroutine calls and associative arrays.
- * We must first generate code for all subexpressions before loading the stack
- * because any subexpression could itself require the use of the tuple stack.
- * This holds a number of registers equal to the number of arguments, but this
- * is not a huge problem because the number of arguments can't exceed the
- * number of tuple register stack elements anyway.  At most one extra register
- * is required (either by dt_cg_typecast() or for dtdt_size, below).  This
- * implies that a DIF implementation should offer a number of general purpose
- * registers at least one greater than the number of tuple registers.
- */
-static void
-dt_cg_arglist(dt_ident_t *idp, dt_node_t *args,
-    dt_irlist_t *dlp, dt_regset_t *drp)
-{
-	const dt_idsig_t *isp = idp->di_data;
-	dt_node_t *dnp;
-	int i = 0;
-
-	for (dnp = args; dnp != NULL; dnp = dnp->dn_list)
-		dt_cg_node(dnp, dlp, drp);
-
-	for (dnp = args; dnp != NULL; dnp = dnp->dn_list, i++) {
-		dtrace_diftype_t t;
-		uint_t op;
-		int reg;
-
-		dt_node_diftype(yypcb->pcb_hdl, dnp, &t);
-
-		isp->dis_args[i].dn_reg = dnp->dn_reg; /* re-use register */
-		dt_cg_typecast(dnp, &isp->dis_args[i], dlp, drp);
-		isp->dis_args[i].dn_reg = -1;
-
-		if (t.dtdt_flags & DIF_TF_BYREF)
-			op = DIF_OP_PUSHTR;
-		else
-			op = DIF_OP_PUSHTV;
-
-		if (t.dtdt_size != 0) {
-			if ((reg = dt_regset_alloc(drp)) == -1)
-				longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
-			dt_cg_setx(dlp, reg, t.dtdt_size);
-		} else
-			reg = DIF_REG_R0;
-
-#if 0
-		instr = DIF_INSTR_PUSHTS(op, t.dtdt_kind, reg, dnp->dn_reg);
-		dt_irlist_append(dlp, dt_cg_node_alloc(DT_LBL_NONE, instr));
-#else
-		emit(dlp, BPF_CALL_FUNC(op));
-#endif
-		dt_regset_free(drp, dnp->dn_reg);
-
-		if (reg != DIF_REG_R0)
-			dt_regset_free(drp, reg);
-	}
-
-	if (i > yypcb->pcb_hdl->dt_conf.dtc_diftupregs)
-		longjmp(yypcb->pcb_jmpbuf, EDT_NOTUPREG);
-}
-
 static void
 dt_cg_arithmetic_op(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp,
 		    uint_t op)
@@ -3138,9 +3309,6 @@ dt_cg_asgn_op(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
 	if (dnp->dn_left->dn_kind == DT_NODE_VAR) {
 		idp = dt_ident_resolve(dnp->dn_left->dn_ident);
 
-		if (idp->di_kind == DT_IDENT_ARRAY)
-			dt_cg_arglist(idp, dnp->dn_left->dn_args, dlp, drp);
-
 		dt_cg_store_var(dnp, dlp, drp, idp);
 
 		/*
@@ -3168,75 +3336,59 @@ dt_cg_asgn_op(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
 static void
 dt_cg_assoc_op(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
 {
-	ssize_t base;
+	dt_ident_t	*idp = dt_dlib_get_func(yypcb->pcb_hdl, "dt_get_assoc");
+	uint_t		varid;
 
+	TRACE_REGSET("    assoc_op: Begin");
+
+	assert(idp != NULL);
 	assert(dnp->dn_kind == DT_NODE_VAR);
 	assert(!(dnp->dn_ident->di_flags & DT_IDFLG_LOCAL));
 	assert(dnp->dn_args != NULL);
 
+	dnp->dn_ident->di_flags |= DT_IDFLG_DIFR;
+
 	dt_cg_arglist(dnp->dn_ident, dnp->dn_args, dlp, drp);
 
 	if ((dnp->dn_reg = dt_regset_alloc(drp)) == -1)
 		longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
+	if (dt_regset_xalloc_args(drp) == -1)
+		longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
 
-	if (dnp->dn_ident->di_flags & DT_IDFLG_TLS)
-		base = 0x2000;
-	else
-		base = 0x3000;
+	varid = dnp->dn_ident->di_id - DIF_VAR_OTHER_UBASE;
 
-	dnp->dn_ident->di_flags |= DT_IDFLG_DIFR;
-	emit(dlp, BPF_LOAD(BPF_DW, dnp->dn_reg, BPF_REG_FP, base + dnp->dn_ident->di_id));
+	emit(dlp,  BPF_MOV_IMM(BPF_REG_1, varid));
+	emit(dlp,  BPF_MOV_REG(BPF_REG_2, dnp->dn_args->dn_reg));
+	dt_regset_free(drp, dnp->dn_args->dn_reg);
+	emit(dlp,  BPF_MOV_IMM(BPF_REG_3, 0));
+	emit(dlp,  BPF_MOV_IMM(BPF_REG_4, 0));
+	dt_regset_xalloc(drp, BPF_REG_0);
+	emite(dlp, BPF_CALL_FUNC(idp->di_id), idp);
+	dt_regset_free_args(drp);
 
-	/*
-	 * If the associative array is a pass-by-reference type, then we are
-	 * loading its value as a pointer to either load or store through it.
-	 * The array element in question may not have been faulted in yet, in
-	 * which case DIF_OP_LD*AA will return zero.  We append an epilogue
-	 * of instructions similar to the following:
-	 *
-	 *	  ld?aa	 id, %r1	! base ld?aa instruction above
-	 *	  tst	 %r1		! start of epilogue
-	 *   +--- bne	 label
-	 *   |    setx	 size, %r1
-	 *   |    allocs %r1, %r1
-	 *   |    st?aa	 id, %r1
-	 *   |    ld?aa	 id, %r1
-	 *   v
-	 * label: < rest of code >
-	 *
-	 * The idea is that we allocs a zero-filled chunk of scratch space and
-	 * do a DIF_OP_ST*AA to fault in and initialize the array element, and
-	 * then reload it to get the faulted-in address of the new variable
-	 * storage.  This isn't cheap, but pass-by-ref associative array values
-	 * are (thus far) uncommon and the allocs cost only occurs once.  If
-	 * this path becomes important to DTrace users, we can improve things
-	 * by adding a new DIF opcode to fault in associative array elements.
-	 */
 	if (dnp->dn_flags & DT_NF_REF) {
-#ifdef FIXME
-		uint_t stvop = op == DIF_OP_LDTAA ? DIF_OP_STTAA : DIF_OP_STGAA;
-		uint_t label = dt_irlist_label(dlp);
-
-		emit(dlp, BPF_BRANCH_IMM(BPF_JNE, dnp->dn_reg, 0, label));
-
-		dt_cg_setx(dlp, dnp->dn_reg, dt_node_type_size(dnp));
-		instr = DIF_INSTR_ALLOCS(dnp->dn_reg, dnp->dn_reg);
-		dt_irlist_append(dlp, dt_cg_node_alloc(DT_LBL_NONE, instr));
-
-		dnp->dn_ident->di_flags |= DT_IDFLG_DIFW;
-		instr = DIF_INSTR_STV(stvop, dnp->dn_ident->di_id, dnp->dn_reg);
-		dt_irlist_append(dlp, dt_cg_node_alloc(DT_LBL_NONE, instr));
-
-		instr = DIF_INSTR_LDV(op, dnp->dn_ident->di_id, dnp->dn_reg);
-		dt_irlist_append(dlp, dt_cg_node_alloc(DT_LBL_NONE, instr));
+		emit(dlp,  BPF_MOV_REG(dnp->dn_reg, BPF_REG_0));
+		dt_regset_free(drp, BPF_REG_0);
+	} else {
+		size_t	size = dt_node_type_size(dnp);
+		uint_t	lbl_notnull = dt_irlist_label(dlp);
+		uint_t	lbl_done = dt_irlist_label(dlp);
+
+		assert(size > 0 && size <= 8 &&
+		       (size & (size - 1)) == 0);
+
+		emit(dlp,  BPF_BRANCH_IMM(BPF_JNE, BPF_REG_0, 0, lbl_notnull));
+		emit(dlp,  BPF_MOV_IMM(dnp->dn_reg, 0));
+		emit(dlp,  BPF_JUMP(lbl_done));
+		emitl(dlp, lbl_notnull,
+			   BPF_LOAD(ldstw[size], dnp->dn_reg, BPF_REG_0, 0));
+		dt_regset_free(drp, BPF_REG_0);
 
-		emitl(dlp, label,
+		emitl(dlp, lbl_done,
 			   BPF_NOP());
-#else
-		xyerror(D_UNKNOWN, "internal error -- no support for "
-			"associative arrays yet\n");
-#endif
 	}
+
+	TRACE_REGSET("    assoc_op: End  ");
 }
 
 static void
diff --git a/libdtrace/dt_dctx.h b/libdtrace/dt_dctx.h
index 0f9050d3..a8d2d7c4 100644
--- a/libdtrace/dt_dctx.h
+++ b/libdtrace/dt_dctx.h
@@ -72,9 +72,13 @@ typedef struct dt_dctx {
  *                       +----------------+----------------+
  *                  0 -> | Stack          :     tstring    | \
  *                       |   trace     (shared)   storage  |  |
- *                       |     storage    :                |   > DMEM_SIZE
+ *                       |     storage    :                |  |
  *                       +----------------+----------------+  |
- *        DMEM_STRTOK -> |     strtok() internal state     | /
+ *        DMEM_STRTOK -> |     strtok() internal state     |   > DMEM_SIZE
+ *                       +---------------------------------+  |
+ *        DMEM_TUPLE  -> |       tuple assembly area       |  |
+ *                       +---------------------------------+  |
+ *   DMEM_TUPLE_DFLT  -> |       default empty tuple       | /
  *                       +---------------------------------+
  */
 
@@ -88,17 +92,23 @@ typedef struct dt_dctx {
 		 P2ROUNDUP((dtp)->dt_options[DTRACEOPT_STRSIZE] + 1, 8))
 #define DMEM_STRTOK_SZ(dtp) \
 		(sizeof(uint64_t) + (dtp)->dt_options[DTRACEOPT_STRSIZE] + 1)
+#define DMEM_TUPLE_SZ(dtp) \
+		((dtp)->dt_maxtuplesize)
 
 /*
- * Macro to determine the offset from mem to the strtok internal state.
+ * Macros to determine the offset of the components of dctx->mem.
  */
 #define DMEM_STRTOK(dtp) \
 		MAX(DMEM_STACK_SZ(dtp), DMEM_TSTR_SZ(dtp))
+#define DMEM_TUPLE(dtp) \
+		(DMEM_STRTOK(dtp) + DMEM_STRTOK_SZ(dtp))
+#define DMEM_TUPLE_DFLT(dtp) \
+		(DMEM_TUPLE(dtp) + DMEM_TUPLE_SZ(dtp))
 
 /*
  * Macro to determine the total size of the mem area.
  */
-#define DMEM_SIZE(dtp)	(DMEM_STRTOK(dtp) + DMEM_STRTOK_SZ(dtp))
+#define DMEM_SIZE(dtp)	(DMEM_TUPLE_DFLT(dtp) + DMEM_TUPLE_SZ(dtp))
 
 /*
  * The stack layout for BPF programs that are generated as trampolines for
diff --git a/libdtrace/dt_dlibs.c b/libdtrace/dt_dlibs.c
index ebed0509..329a951a 100644
--- a/libdtrace/dt_dlibs.c
+++ b/libdtrace/dt_dlibs.c
@@ -67,6 +67,7 @@ static const dt_ident_t		dt_bpf_symbols[] = {
 	DT_BPF_SYMBOL(specs, DT_IDENT_PTR),
 	DT_BPF_SYMBOL(state, DT_IDENT_PTR),
 	DT_BPF_SYMBOL(strtab, DT_IDENT_PTR),
+	DT_BPF_SYMBOL(tuples, DT_IDENT_PTR),
 
 	/* BPF internal identifiers */
 	DT_BPF_SYMBOL_ID(EPID, DT_IDENT_SCALAR, DT_CONST_EPID),
@@ -79,6 +80,7 @@ static const dt_ident_t		dt_bpf_symbols[] = {
 	DT_BPF_SYMBOL_ID(BOOTTM, DT_IDENT_SCALAR, DT_CONST_BOOTTM),
 	DT_BPF_SYMBOL_ID(NSPEC, DT_IDENT_SCALAR, DT_CONST_NSPEC),
 	DT_BPF_SYMBOL_ID(NCPUS, DT_IDENT_SCALAR, DT_CONST_NCPUS),
+	DT_BPF_SYMBOL_ID(TUPSZ, DT_IDENT_SCALAR, DT_CONST_TUPSZ),
 
 	/* End-of-list marker */
 	{ NULL, }
diff --git a/libdtrace/dt_ident.c b/libdtrace/dt_ident.c
index 6d8a8efa..098f1fcb 100644
--- a/libdtrace/dt_ident.c
+++ b/libdtrace/dt_ident.c
@@ -1016,13 +1016,14 @@ dt_ident_set_storage(dt_ident_t *idp, uint_t alignment, uint_t size)
 		idp->di_offset = (dhp->dh_nextoff + (alignment - 1)) &
 				 ~(alignment - 1);
 		dhp->dh_nextoff = idp->di_offset + size;
-	} else {
+	} else
 		idp->di_offset = 0;
-		if (size > dtp->dt_maxdvarsize)
-			dtp->dt_maxdvarsize = size;
-	}
 
 	idp->di_size = size;
+
+	if ((idp->di_kind == DT_IDENT_ARRAY ||
+	     (idp->di_flags & DT_IDFLG_TLS)) && (size > dtp->dt_maxdvarsize))
+		dtp->dt_maxdvarsize = size;
 }
 
 void
diff --git a/libdtrace/dt_impl.h b/libdtrace/dt_impl.h
index f738ebeb..747815ac 100644
--- a/libdtrace/dt_impl.h
+++ b/libdtrace/dt_impl.h
@@ -293,6 +293,7 @@ struct dtrace_hdl {
 	uint_t dt_strlen;	/* global string table (runtime) size */
 	uint_t dt_maxreclen;	/* largest record size across programs */
 	uint_t dt_maxdvarsize;	/* largest dynamic variable across programs */
+	uint_t dt_maxtuplesize;	/* largest tuple across programs */
 	uint_t dt_maxlvaralloc;	/* largest lvar alloc across pcbs */
 	dt_tstring_t *dt_tstrings; /* temporary string slots */
 	dt_list_t dt_modlist;	/* linked list of dt_module_t's */
diff --git a/test/unittest/assocs/err.limited_space.d b/test/unittest/assocs/err.limited_space.d
new file mode 100644
index 00000000..024dd42a
--- /dev/null
+++ b/test/unittest/assocs/err.limited_space.d
@@ -0,0 +1,34 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2022, 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 - no drops yet */
+
+/*
+ * ASSERTION: Trying to store more associative array elements than we have
+ *	      space for will trigger a dynamic variable drop.
+ *
+ * SECTION: Variables/Thread-Local Variables
+ */
+
+#pragma D option dynvarsize=15
+
+BEGIN
+{
+	a[1] = 1;
+	a[2] = 2;
+	a[3] = 3;
+	a[4] = 4;
+}
+
+BEGIN
+{
+	exit(0);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/assocs/err.limited_space.r b/test/unittest/assocs/err.limited_space.r
new file mode 100644
index 00000000..41a7c38a
--- /dev/null
+++ b/test/unittest/assocs/err.limited_space.r
@@ -0,0 +1,6 @@
+                   FUNCTION:NAME
+                          :BEGIN 
+
+-- @@stderr --
+dtrace: script 'test/unittest/variables/tvar/err.limited_space.d' matched 3 probes
+dtrace: 1 dynamic variable drop
diff --git a/test/unittest/assocs/err.tupoflow.d b/test/unittest/assocs/err.tupoflow.d
index 25e0ddcd..5ca1753b 100644
--- a/test/unittest/assocs/err.tupoflow.d
+++ b/test/unittest/assocs/err.tupoflow.d
@@ -1,10 +1,9 @@
 /*
  * Oracle Linux DTrace.
- * Copyright (c) 2006, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2006, 2022, 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:
diff --git a/test/unittest/assocs/err.tupoflow.r b/test/unittest/assocs/err.tupoflow.r
index 4421bae1..ba73a4d2 100644
--- a/test/unittest/assocs/err.tupoflow.r
+++ b/test/unittest/assocs/err.tupoflow.r
@@ -1,2 +1,2 @@
 -- @@stderr --
-dtrace: failed to compile script test/unittest/assocs/err.tupoflow.d: Insufficient registers to generate code
+dtrace: failed to compile script test/unittest/assocs/err.tupoflow.d: Insufficient tuple registers to generate code
diff --git a/test/unittest/assocs/tst.clean-tuple.d b/test/unittest/assocs/tst.clean-tuple.d
new file mode 100644
index 00000000..a5b20204
--- /dev/null
+++ b/test/unittest/assocs/tst.clean-tuple.d
@@ -0,0 +1,29 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2022, 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: Tuple assembly does not contain garbage from previous uses
+ *
+ * SECTION: Variables/Associative Arrays
+ */
+
+#pragma D option quiet
+
+BEGIN {
+	x["foo"] = 1234;
+	x["long key her"] = 56789;
+
+	trace(x["foo"]);
+	trace(x["long key her"]);
+
+	exit(x["foo"] != 1234 || x["long key her"] != 56789);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/assocs/tst.cpyarray.d b/test/unittest/assocs/tst.cpyarray.d
index 9e4871bc..367428c6 100644
--- a/test/unittest/assocs/tst.cpyarray.d
+++ b/test/unittest/assocs/tst.cpyarray.d
@@ -1,10 +1,9 @@
 /*
  * Oracle Linux DTrace.
- * Copyright (c) 2006, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2006, 2022, 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:
diff --git a/test/unittest/assocs/tst.gvar-postdec.d b/test/unittest/assocs/tst.gvar-postdec.d
new file mode 100644
index 00000000..17ed5edb
--- /dev/null
+++ b/test/unittest/assocs/tst.gvar-postdec.d
@@ -0,0 +1,22 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2022, 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: Post-decrement should work for global associative arrays.
+ *
+ * SECTION: Variables/Associative Arrays
+ */
+
+#pragma D option quiet
+
+BEGIN
+{
+	old = a["a"] = 42;
+	val = a["a"]--;
+
+	exit(val != old || a["a"] != old - 1);
+}
diff --git a/test/unittest/assocs/tst.gvar-postinc.d b/test/unittest/assocs/tst.gvar-postinc.d
new file mode 100644
index 00000000..69fb1047
--- /dev/null
+++ b/test/unittest/assocs/tst.gvar-postinc.d
@@ -0,0 +1,22 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2022, 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: Post-increment should work for global associative arrays.
+ *
+ * SECTION: Variables/Associative Arrays
+ */
+
+#pragma D option quiet
+
+BEGIN
+{
+	old = a["a"] = 42;
+	val = a["a"]++;
+
+	exit(val != old || a["a"] != old + 1);
+}
diff --git a/test/unittest/assocs/tst.gvar-predec.d b/test/unittest/assocs/tst.gvar-predec.d
new file mode 100644
index 00000000..36b6917d
--- /dev/null
+++ b/test/unittest/assocs/tst.gvar-predec.d
@@ -0,0 +1,22 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2022, 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: Pre-decrement should work for global associative arrays.
+ *
+ * SECTION: Variables/Associative Arrays
+ */
+
+#pragma D option quiet
+
+BEGIN
+{
+	old = a["a"] = 42;
+	val = --a["a"];
+
+	exit(val != old - 1);
+}
diff --git a/test/unittest/assocs/tst.gvar-preinc.d b/test/unittest/assocs/tst.gvar-preinc.d
new file mode 100644
index 00000000..ad0d8b9d
--- /dev/null
+++ b/test/unittest/assocs/tst.gvar-preinc.d
@@ -0,0 +1,22 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2022, 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: Pre-increment should work for global associative arrays.
+ *
+ * SECTION: Variables/Associative Arrays
+ */
+
+#pragma D option quiet
+
+BEGIN
+{
+	old = a["a"] = 42;
+	val = ++a["a"];
+
+	exit(val != old + 1);
+}
diff --git a/test/unittest/assocs/tst.init-str.d b/test/unittest/assocs/tst.init-str.d
new file mode 100644
index 00000000..2894fd68
--- /dev/null
+++ b/test/unittest/assocs/tst.init-str.d
@@ -0,0 +1,29 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2022, 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: Default value of unassigned elements in asssociative string
+ *	      arrays is 0.  This will cause an 'invalid address 0x0' fault
+ *	      upon dereferencing.
+ *
+ * SECTION: Variables/Associative Arrays
+ */
+
+#pragma D option quiet
+
+string s[int];
+
+BEGIN
+{
+	trace(s[1]);
+	exit(1);
+}
+
+ERROR
+{
+	exit(arg4 != 1 || arg5 != 0);
+}
diff --git a/test/unittest/assocs/tst.init.d b/test/unittest/assocs/tst.init.d
new file mode 100644
index 00000000..567898b6
--- /dev/null
+++ b/test/unittest/assocs/tst.init.d
@@ -0,0 +1,21 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2022, 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: Default value of unassigned elements in asssociative arrays is 0
+ *
+ * SECTION: Variables/Associative Arrays
+ */
+
+#pragma D option quiet
+
+BEGIN
+{
+	a["a"] = 42;
+
+	exit(a["b"] != 0);
+}
diff --git a/test/unittest/assocs/tst.misc.d b/test/unittest/assocs/tst.misc.d
index eec35719..ed5498a5 100644
--- a/test/unittest/assocs/tst.misc.d
+++ b/test/unittest/assocs/tst.misc.d
@@ -1,10 +1,9 @@
 /*
  * Oracle Linux DTrace.
- * Copyright (c) 2006, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2006, 2022, 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:
diff --git a/test/unittest/assocs/tst.orthogonality.d b/test/unittest/assocs/tst.orthogonality.d
index 0ae50d08..b87211ea 100644
--- a/test/unittest/assocs/tst.orthogonality.d
+++ b/test/unittest/assocs/tst.orthogonality.d
@@ -1,10 +1,9 @@
 /*
  * Oracle Linux DTrace.
- * Copyright (c) 2006, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2006, 2022, 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 */
 
 /*
  * This test confirms the orthogonality of associative arrays and thread-local
diff --git a/test/unittest/assocs/tst.store_zero_deletes.d b/test/unittest/assocs/tst.store_zero_deletes.d
new file mode 100644
index 00000000..2db9c6c0
--- /dev/null
+++ b/test/unittest/assocs/tst.store_zero_deletes.d
@@ -0,0 +1,35 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2022, 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: Storing 0 into an associate array element removes it from
+ *	      storage, making room for another element.
+ *
+ * SECTION: Variables/Thread-Local Variables
+ */
+
+#pragma D option quiet
+#pragma D option dynvarsize=15
+
+BEGIN
+{
+	a[1] = 1;
+	a[2] = 2;
+	a[3] = 3;
+	a[1] = 0;
+	a[4] = 4;
+	trace(a[1]);
+	trace(a[2]);
+	trace(a[3]);
+	trace(a[4]);
+	exit(0);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/assocs/tst.store_zero_deletes.r b/test/unittest/assocs/tst.store_zero_deletes.r
new file mode 100644
index 00000000..0f6f3a7d
--- /dev/null
+++ b/test/unittest/assocs/tst.store_zero_deletes.r
@@ -0,0 +1 @@
+0234
diff --git a/test/unittest/assocs/tst.tvar-postdec.d b/test/unittest/assocs/tst.tvar-postdec.d
new file mode 100644
index 00000000..a8f6bf7f
--- /dev/null
+++ b/test/unittest/assocs/tst.tvar-postdec.d
@@ -0,0 +1,22 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2022, 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: Post-decrement should work for TLS associative arrays.
+ *
+ * SECTION: Variables/Associative Arrays
+ */
+
+#pragma D option quiet
+
+BEGIN
+{
+	old = self->a["a"] = 42;
+	val = self->a["a"]--;
+
+	exit(val != old || self->a["a"] != old - 1);
+}
diff --git a/test/unittest/assocs/tst.tvar-postinc.d b/test/unittest/assocs/tst.tvar-postinc.d
new file mode 100644
index 00000000..e9a50763
--- /dev/null
+++ b/test/unittest/assocs/tst.tvar-postinc.d
@@ -0,0 +1,22 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2022, 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: Post-increment should work for TLS associative arrays.
+ *
+ * SECTION: Variables/Associative Arrays
+ */
+
+#pragma D option quiet
+
+BEGIN
+{
+	old = self->a["a"] = 42;
+	val = self->a["a"]++;
+
+	exit(val != old || self->a["a"] != old + 1);
+}
diff --git a/test/unittest/assocs/tst.tvar-predec.d b/test/unittest/assocs/tst.tvar-predec.d
new file mode 100644
index 00000000..8b85b990
--- /dev/null
+++ b/test/unittest/assocs/tst.tvar-predec.d
@@ -0,0 +1,22 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2022, 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: Pre-decrement should work for TLS associative arrays.
+ *
+ * SECTION: Variables/Associative Arrays
+ */
+
+#pragma D option quiet
+
+BEGIN
+{
+	old = self->a["a"] = 42;
+	val = --self->a["a"];
+
+	exit(val != old - 1);
+}
diff --git a/test/unittest/assocs/tst.tvar-preinc.d b/test/unittest/assocs/tst.tvar-preinc.d
new file mode 100644
index 00000000..ddea5609
--- /dev/null
+++ b/test/unittest/assocs/tst.tvar-preinc.d
@@ -0,0 +1,22 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2022, 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: Pre-increment should work for TLS associative arrays.
+ *
+ * SECTION: Variables/Associative Arrays
+ */
+
+#pragma D option quiet
+
+BEGIN
+{
+	old = self->a["a"] = 42;
+	val = ++self->a["a"];
+
+	exit(val != old + 1);
+}
-- 
2.34.1




More information about the DTrace-devel mailing list