Commit Diff


commit - ba140128df35d91f4de66435f4e72fa777fbb885
commit + 9da70207aa5c8a2e8dca78ce34a425b18e4711bc
blob - 146027757b6bbc2aefe3b08a03bfe6ff2145a4b0
blob + 77b39af248b692bb60cef3e9acefbf257765e7fd
--- perf/tuple.cc
+++ perf/tuple.cc
@@ -276,7 +276,10 @@ tuple_access_members(benchmark::State& state)
 			i = 0;
 		}
 		struct tuple *t = tuples[i++];
-		benchmark::DoNotOptimize(bool(t->is_dirty));
+		// Previously tuple had is_dirty bit field, which was later
+		// replaced with tuple flags. So to avoid changing test
+		// semantics we check now if tuple has corresponding flag.
+		benchmark::DoNotOptimize(tuple_has_flag(t, TUPLE_IS_DIRTY));
 		benchmark::DoNotOptimize(uint16_t(t->format_id));
 	}
 	total_count += i;
blob - 499cdf43ffd579517f5c7dc75627fe5e5dcb3f76
blob + f1356a29d17a8885da7b9df65d82986ef730b530
--- src/box/memtx_tx.c
+++ src/box/memtx_tx.c
@@ -764,7 +764,7 @@ memtx_tx_story_new(struct space *space, struct tuple *
 		   bool tuple_is_referenced_to_pk)
 {
 	txm.must_do_gc_steps += TX_MANAGER_GC_STEPS_SIZE;
-	assert(!tuple->is_dirty);
+	assert(!tuple_has_flag(tuple, TUPLE_IS_DIRTY));
 	uint32_t index_count = space->index_count;
 	assert(index_count < BOX_INDEX_MAX);
 	struct mempool *pool = &txm.memtx_tx_story_pool[index_count];
@@ -780,7 +780,7 @@ memtx_tx_story_new(struct space *space, struct tuple *
 		(const struct memtx_story **) &story;
 	struct memtx_story **empty = NULL;
 	mh_history_put(txm.history, put_story, &empty, 0);
-	tuple->is_dirty = true;
+	tuple_set_flag(tuple, TUPLE_IS_DIRTY);
 	tuple_ref(tuple);
 	story->status = MEMTX_TX_STORY_USED;
 	struct memtx_tx_stats *stats = &txm.story_stats[story->status];
@@ -835,7 +835,7 @@ memtx_tx_story_delete(struct memtx_story *story)
 	assert(pos != mh_end(txm.history));
 	mh_history_del(txm.history, pos, 0);
 
-	story->tuple->is_dirty = false;
+	tuple_clear_flag(story->tuple, TUPLE_IS_DIRTY);
 	tuple_unref(story->tuple);
 
 #ifndef NDEBUG
@@ -856,7 +856,7 @@ memtx_tx_story_delete(struct memtx_story *story)
 static struct memtx_story *
 memtx_tx_story_get(struct tuple *tuple)
 {
-	assert(tuple->is_dirty);
+	assert(tuple_has_flag(tuple, TUPLE_IS_DIRTY));
 
 	mh_int_t pos = mh_history_find(txm.history, tuple, 0);
 	assert(pos != mh_end(txm.history));
@@ -1527,7 +1527,8 @@ check_dup_clean(struct txn_stmt *stmt, struct tuple *n
 		enum dup_replace_mode mode,
 		struct memtx_tx_conflict **collected_conflicts)
 {
-	assert(replaced[0] == NULL || !replaced[0]->is_dirty);
+	assert(replaced[0] == NULL ||
+	       !tuple_has_flag(replaced[0], TUPLE_IS_DIRTY));
 	struct space *space = stmt->space;
 	struct txn *txn = stmt->txn;
 
@@ -1553,7 +1554,7 @@ check_dup_clean(struct txn_stmt *stmt, struct tuple *n
 				   collected_conflicts);
 			continue;
 		}
-		if (!replaced[i]->is_dirty) {
+		if (!tuple_has_flag(replaced[i], TUPLE_IS_DIRTY)) {
 			/* Check like there's no mvcc. */
 			if (memtx_tx_check_dup(new_tuple, replaced[0],
 					       replaced[i], DUP_INSERT,
@@ -1601,7 +1602,8 @@ check_dup_dirty(struct txn_stmt *stmt, struct tuple *n
 		enum dup_replace_mode mode,
 		struct memtx_tx_conflict **collected_conflicts)
 {
-	assert(replaced[0] != NULL && replaced[0]->is_dirty);
+	assert(replaced[0] != NULL &&
+	       tuple_has_flag(replaced[0], TUPLE_IS_DIRTY));
 	struct space *space = stmt->space;
 	struct txn *txn = stmt->txn;
 
@@ -1631,7 +1633,7 @@ check_dup_dirty(struct txn_stmt *stmt, struct tuple *n
 				   collected_conflicts);
 			continue;
 		}
-		if (!replaced[i]->is_dirty) {
+		if (!tuple_has_flag(replaced[i], TUPLE_IS_DIRTY)) {
 			/*
 			 * Non-null clean tuple cannot be NULL or
 			 * visible_replaced since visible_replaced is dirty.
@@ -1679,7 +1681,7 @@ check_dup_common(struct txn_stmt *stmt, struct tuple *
 		 struct memtx_tx_conflict **collected_conflicts)
 {
 	struct tuple *replaced = directly_replaced[0];
-	if (replaced == NULL || !replaced->is_dirty)
+	if (replaced == NULL || !tuple_has_flag(replaced, TUPLE_IS_DIRTY))
 		return check_dup_clean(stmt, new_tuple, directly_replaced,
 				       old_tuple, mode,
 				       collected_conflicts);
@@ -1714,12 +1716,12 @@ memtx_tx_handle_gap_write(struct txn *txn, struct spac
 		    memtx_tx_cause_conflict(txn, fsc_item->txn) != 0)
 			return -1;
 	}
-	if (successor != NULL && !successor->is_dirty)
+	if (successor != NULL && !tuple_has_flag(successor, TUPLE_IS_DIRTY))
 		return 0; /* no gap records */
 
 	struct rlist *list = &index->nearby_gaps;
 	if (successor != NULL) {
-		assert(successor->is_dirty);
+		assert(tuple_has_flag(successor, TUPLE_IS_DIRTY));
 		struct memtx_story *succ_story = memtx_tx_story_get(successor);
 		assert(ind < succ_story->index_count);
 		list = &succ_story->link[ind].nearby_gaps;
@@ -1851,7 +1853,7 @@ memtx_tx_history_add_insert_stmt(struct txn_stmt *stmt
 		goto fail;
 	memtx_tx_story_link_added_by(add_story, stmt);
 
-	if (replaced != NULL && !replaced->is_dirty) {
+	if (replaced != NULL && !tuple_has_flag(replaced, TUPLE_IS_DIRTY)) {
 		/*
 		 * Note that despite the tuple is not in pk,
 		 * it is referenced to it, so we pass true as the last argument.
@@ -1891,7 +1893,7 @@ memtx_tx_history_add_insert_stmt(struct txn_stmt *stmt
 				goto fail;
 			continue;
 		}
-		assert(directly_replaced[i]->is_dirty);
+		assert(tuple_has_flag(directly_replaced[i], TUPLE_IS_DIRTY));
 		struct memtx_story *secondary_replaced =
 			memtx_tx_story_get(directly_replaced[i]);
 		memtx_tx_story_link_top_light(add_story, secondary_replaced, i);
@@ -1899,7 +1901,7 @@ memtx_tx_history_add_insert_stmt(struct txn_stmt *stmt
 	}
 
 	if (old_tuple != NULL) {
-		assert(old_tuple->is_dirty);
+		assert(tuple_has_flag(old_tuple, TUPLE_IS_DIRTY));
 
 		struct memtx_story *del_story = NULL;
 		if (old_tuple == replaced)
@@ -1978,7 +1980,7 @@ memtx_tx_history_add_delete_stmt(struct txn_stmt *stmt
 	struct space *space = stmt->space;
 	struct memtx_story *del_story;
 
-	if (old_tuple->is_dirty) {
+	if (tuple_has_flag(old_tuple, TUPLE_IS_DIRTY)) {
 		del_story = memtx_tx_story_get(old_tuple);
 	} else {
 		assert(stmt->txn != NULL);
@@ -2015,7 +2017,7 @@ memtx_tx_history_add_stmt(struct txn_stmt *stmt, struc
 	assert(stmt != NULL);
 	assert(stmt->space != NULL);
 	assert(new_tuple != NULL || old_tuple != NULL);
-	assert(new_tuple == NULL || !new_tuple->is_dirty);
+	assert(new_tuple == NULL || !tuple_has_flag(new_tuple, TUPLE_IS_DIRTY));
 
 	if (new_tuple != NULL)
 		return memtx_tx_history_add_insert_stmt(stmt, old_tuple,
@@ -2315,7 +2317,7 @@ memtx_tx_tuple_clarify_impl(struct txn *txn, struct sp
 			    struct tuple *tuple, struct index *index,
 			    uint32_t mk_index, bool is_prepared_ok)
 {
-	assert(tuple->is_dirty);
+	assert(tuple_has_flag(tuple, TUPLE_IS_DIRTY));
 	struct memtx_story *story = memtx_tx_story_get(tuple);
 	bool own_change = false;
 	struct tuple *result = NULL;
@@ -2577,7 +2579,7 @@ memtx_tx_track_read(struct txn *txn, struct space *spa
 	if (space->def->opts.is_ephemeral)
 		return 0;
 
-	if (tuple->is_dirty) {
+	if (tuple_has_flag(tuple, TUPLE_IS_DIRTY)) {
 		struct memtx_story *story = memtx_tx_story_get(tuple);
 		return memtx_tx_track_read_story(txn, space, story, UINT64_MAX);
 	} else {
@@ -2789,7 +2791,7 @@ memtx_tx_track_gap_slow(struct txn *txn, struct space 
 
 	if (successor != NULL) {
 		struct memtx_story *story;
-		if (successor->is_dirty) {
+		if (tuple_has_flag(successor, TUPLE_IS_DIRTY)) {
 			story = memtx_tx_story_get(successor);
 		} else {
 			/*
blob - dc06fcc9972e865e597437f704c9efb5e4b1275d
blob + 1ca14ebc7b1f2929e9a94f2ed306c311075aa6e8
--- src/box/memtx_tx.h
+++ src/box/memtx_tx.h
@@ -491,7 +491,7 @@ memtx_tx_tuple_clarify(struct txn *txn, struct space *
 {
 	if (!memtx_tx_manager_use_mvcc_engine)
 		return tuple;
-	if (!tuple->is_dirty) {
+	if (!tuple_has_flag(tuple, TUPLE_IS_DIRTY)) {
 		memtx_tx_track_read(txn, space, tuple);
 		return tuple;
 	}
blob - 60e008a5b1133b4e33d73638dca50d86c804d28e
blob + 6c2e89ebc0c78aa6600bb32e7e61f7c5412f93db
--- src/box/tuple.c
+++ src/box/tuple.c
@@ -148,7 +148,7 @@ runtime_tuple_delete(struct tuple_format *format, stru
 	assert(format->vtab.tuple_delete == tuple_format_runtime_vtab.tuple_delete);
 	say_debug("%s(%p)", __func__, tuple);
 	assert(tuple->local_refs == 0);
-	assert(!tuple->has_uploaded_refs);
+	assert(!tuple_has_flag(tuple, TUPLE_HAS_UPLOADED_REFS));
 	size_t total = tuple_size(tuple);
 	tuple_format_unref(format);
 	smfree(&runtime_alloc, tuple, total);
@@ -219,7 +219,7 @@ enum { TUPLE_UPLOAD_REFS = TUPLE_LOCAL_REF_MAX / 2 + 1
 static uint32_t
 tuple_ref_get_uploaded_refs(struct tuple *tuple)
 {
-	if (!tuple->has_uploaded_refs)
+	if (!tuple_has_flag(tuple, TUPLE_HAS_UPLOADED_REFS))
 		return 0;
 	mh_int_t pos = mh_tuple_uploaded_refs_find(tuple_uploaded_refs, tuple,
 						   0);
@@ -246,7 +246,7 @@ tuple_ref_set_uploaded_refs(struct tuple *tuple, uint3
 static void
 tuple_ref_drop_uploaded_refs(struct tuple *tuple)
 {
-	assert(tuple->has_uploaded_refs);
+	assert(tuple_has_flag(tuple, TUPLE_HAS_UPLOADED_REFS));
 	mh_int_t pos = mh_tuple_uploaded_refs_find(tuple_uploaded_refs, tuple,
 						   0);
 	assert(pos != mh_end(tuple_uploaded_refs));
@@ -259,7 +259,7 @@ tuple_upload_refs(struct tuple *tuple)
 	assert(tuple->local_refs == TUPLE_LOCAL_REF_MAX);
 	uint32_t refs = tuple_ref_get_uploaded_refs(tuple);
 	tuple_ref_set_uploaded_refs(tuple, refs + TUPLE_UPLOAD_REFS);
-	tuple->has_uploaded_refs = true;
+	tuple_set_flag(tuple, TUPLE_HAS_UPLOADED_REFS);
 	tuple->local_refs -= TUPLE_UPLOAD_REFS;
 }
 
@@ -267,11 +267,11 @@ void
 tuple_acquire_refs(struct tuple *tuple)
 {
 	assert(tuple->local_refs == 0);
-	assert(tuple->has_uploaded_refs);
+	assert(tuple_has_flag(tuple, TUPLE_HAS_UPLOADED_REFS));
 	uint32_t refs = tuple_ref_get_uploaded_refs(tuple);
 	if (refs == TUPLE_UPLOAD_REFS) {
 		tuple_ref_drop_uploaded_refs(tuple);
-		tuple->has_uploaded_refs = false;
+		tuple_clear_flag(tuple, TUPLE_HAS_UPLOADED_REFS);
 	} else {
 		tuple_ref_set_uploaded_refs(tuple, refs - TUPLE_UPLOAD_REFS);
 	}
blob - 1bedac77dad1f70ee3893cba9e8436f5cd113af0
blob + 4c5c99de0f64753068e8cbc940190ad3303ad406
--- src/box/tuple.h
+++ src/box/tuple.h
@@ -295,6 +295,26 @@ int
 box_tuple_validate(box_tuple_t *tuple, box_tuple_format_t *format);
 
 /** \endcond public */
+
+/**
+ * Tuple flags. Each value is a position of corresponding bit in
+ * tuple->flags member.
+ */
+enum tuple_flag {
+	/**
+	 * Flag that shows that the tuple has more references in separate
+	 * external storage, see @sa tuple_upload_refs and tuple_acquire_refs.
+	 * For private use only.
+	 */
+	TUPLE_HAS_UPLOADED_REFS = 0,
+	/**
+	 * The tuple (if it's found in index for example) could be invisible
+	 * for current transactions. The flag means that the tuple must
+	 * be clarified by transaction engine.
+	 */
+	TUPLE_IS_DIRTY = 1,
+	tuple_flag_MAX,
+};
 
 /**
  * An atom of Tarantool storage. Represents MsgPack Array.
@@ -319,18 +339,8 @@ struct PACKED tuple
 	 * tuple_ref_init, tuple_ref, tuple_unref instead.
 	 */
 	uint8_t local_refs;
-	/**
-	 * Flag that shows that the tuple has more references in separate
-	 * external storage, see @sa tuple_upload_refs and tuple_acquire_refs.
-	 * For private use only.
-	 */
-	bool has_uploaded_refs : 1;
-	/**
-	 * The tuple (if it's found in index for example) could be invisible
-	 * for current transactions. The flag means that the tuple must
-	 * be clarified by transaction engine.
-	 */
-	bool is_dirty : 1;
+	/** Tuple flags (e.g. see TUPLE_HAS_UPLOADED_REFS). */
+	uint8_t flags;
 	/** Format identifier. */
 	uint16_t format_id;
 	/**
@@ -358,7 +368,35 @@ struct PACKED tuple
 };
 
 static_assert(sizeof(struct tuple) == 10, "Just to be sure");
+
+static_assert(DIV_ROUND_UP(tuple_flag_MAX, 8) <=
+	      sizeof(((struct tuple *)0)->flags),
+	      "enum tuple_flag doesn't fit into tuple->flags");
 
+/** Set flag of the tuple. */
+static inline void
+tuple_set_flag(struct tuple *tuple, enum tuple_flag flag)
+{
+	assert(flag < tuple_flag_MAX);
+	tuple->flags |= (1 << flag);
+}
+
+/** Test if tuple has a flag. */
+static inline bool
+tuple_has_flag(struct tuple *tuple, enum tuple_flag flag)
+{
+	assert(flag < tuple_flag_MAX);
+	return (tuple->flags & (1 << flag)) != 0;
+}
+
+/** Clears tuple flag. */
+static inline void
+tuple_clear_flag(struct tuple *tuple, enum tuple_flag flag)
+{
+	assert(flag < tuple_flag_MAX);
+	tuple->flags &= ~(1 << flag);
+}
+
 enum {
 	/** Maximal value of tuple::local_refs. */
 	TUPLE_LOCAL_REF_MAX = UINT8_MAX,
@@ -421,8 +459,7 @@ tuple_create(struct tuple *tuple, uint8_t refs, uint16
 {
 	assert(data_offset <= INT16_MAX);
 	tuple->local_refs = refs;
-	tuple->has_uploaded_refs = false;
-	tuple->is_dirty = false;
+	tuple->flags = 0;
 	tuple->format_id = format_id;
 	if (make_compact) {
 		assert(tuple_can_be_compact(data_offset, bsize));
@@ -448,7 +485,7 @@ static inline void
 tuple_ref_init(struct tuple *tuple, uint8_t refs)
 {
 	tuple->local_refs = refs;
-	tuple->has_uploaded_refs = false;
+	tuple_clear_flag(tuple, TUPLE_HAS_UPLOADED_REFS);
 }
 
 /**
@@ -645,8 +682,7 @@ tuple_delete(struct tuple *tuple)
 {
 	say_debug("%s(%p)", __func__, tuple);
 	assert(tuple->local_refs == 0);
-	assert(!tuple->has_uploaded_refs);
-	assert(!tuple->is_dirty);
+	assert(tuple->flags == 0);
 	struct tuple_format *format = tuple_format(tuple);
 	format->vtab.tuple_delete(format, tuple);
 }
@@ -1299,7 +1335,7 @@ tuple_unref(struct tuple *tuple)
 {
 	assert(tuple->local_refs >= 1);
 	if (--tuple->local_refs == 0) {
-		if (unlikely(tuple->has_uploaded_refs))
+		if (unlikely(tuple_has_flag(tuple, TUPLE_HAS_UPLOADED_REFS)))
 			tuple_acquire_refs(tuple);
 		else
 			tuple_delete(tuple);