[RFC PATCH v2 4/7] fib: add DIR8-16-8 algorithm

Vladimir Medvedkin vladimir.medvedkin at intel.com
Wed Oct 14 20:39:18 CEST 2026


Add an IPv4 dataplane algorithm splitting the address in three strides
of 8, 16 and 8 bits: a 256 entry root table resolves the first stride,
then 65536 entry groups (tbl16) and 256 entry groups (tbl8) allocated
from pools sized on FIB creation resolve the next ones. A lookup needs
one to three memory accesses.

Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin at intel.com>
---
 app/test-fib/main.c                    |  86 +++-
 app/test/test_fib.c                    | 101 +++-
 app/test/test_fib_perf.c               |  65 ++-
 doc/guides/prog_guide/fib_lib.rst      |  26 ++
 doc/guides/rel_notes/release_26_11.rst |   5 +
 lib/fib/dir8_16_8.c                    | 614 +++++++++++++++++++++++++
 lib/fib/dir8_16_8.h                    | 143 ++++++
 lib/fib/dir8_16_8_avx512.c             | 142 ++++++
 lib/fib/dir8_16_8_avx512.h             |  16 +
 lib/fib/meson.build                    |   4 +-
 lib/fib/rte_fib.c                      |  24 +-
 lib/fib/rte_fib.h                      |  17 +-
 12 files changed, 1204 insertions(+), 39 deletions(-)
 create mode 100644 lib/fib/dir8_16_8.c
 create mode 100644 lib/fib/dir8_16_8.h
 create mode 100644 lib/fib/dir8_16_8_avx512.c
 create mode 100644 lib/fib/dir8_16_8_avx512.h

diff --git a/app/test-fib/main.c b/app/test-fib/main.c
index 0bc8dd2aa4..be1ba20277 100644
--- a/app/test-fib/main.c
+++ b/app/test-fib/main.c
@@ -39,6 +39,8 @@
 #define	DEF_LOOKUP_IPS_NUM	0x100000
 #define BURST_SZ		64
 #define DEFAULT_LPM_TBL8	100000U
+/* One second level (16 bit stride) group per /8 covers the whole space. */
+#define DIR8_16_8_MAX_TBL16	256U
 
 #define CMP_FLAG		(1 << 0)
 #define CMP_ALL_FLAG		(1 << 1)
@@ -46,9 +48,12 @@
 #define FIB_RIB_TYPE		(1 << 3)
 #define FIB_V4_DIR_TYPE		(1 << 4)
 #define FIB_V6_TRIE_TYPE	(1 << 4)
-#define FIB_TYPE_MASK		(FIB_RIB_TYPE|FIB_V4_DIR_TYPE|FIB_V6_TRIE_TYPE)
+#define FIB_V4_DIR_8_16_8_TYPE	(1 << 5)
+#define FIB_TYPE_MASK		(FIB_RIB_TYPE|FIB_V4_DIR_TYPE|FIB_V6_TRIE_TYPE| \
+				FIB_V4_DIR_8_16_8_TYPE)
 #define SHUFFLE_FLAG		(1 << 7)
 #define DRY_RUN_FLAG		(1 << 8)
+#define ENT_SZ_FLAG		(1 << 9)
 
 static char *distrib_string;
 static char line[LINE_MAX];
@@ -147,6 +152,8 @@ get_fib_type(void)
 	} else {
 		if ((config.flags & FIB_TYPE_MASK) == FIB_V4_DIR_TYPE)
 			return RTE_FIB_DIR24_8;
+		if ((config.flags & FIB_TYPE_MASK) == FIB_V4_DIR_8_16_8_TYPE)
+			return RTE_FIB_DIR8_16_8;
 		if ((config.flags & FIB_TYPE_MASK) == FIB_RIB_TYPE)
 			return RTE_FIB_DUMMY;
 	}
@@ -619,18 +626,20 @@ print_usage(void)
 		"[-b <fib algorithm>]\n\tavailable options for ipv4\n"
 		"\t\trib - RIB based FIB\n"
 		"\t\tdir - DIR24_8 based FIB\n"
+		"\t\tdir8_16_8 - DIR8_16_8 based FIB (8 byte next hop)\n"
 		"\tavailable options for ipv6:\n"
 		"\t\trib - RIB based FIB\n"
 		"\t\ttrie - TRIE based FIB\n"
 		"defaults are: dir for ipv4 and trie for ipv6\n"
 		"[-e <entry size (valid only for dir and trie fib types): "
 		"1/2/4/8 (default 4)>]\n"
-		"[-g <number of tbl8's for dir24_8 or trie FIBs>]\n"
+		"[-g <number of tbl8's for dir24_8, dir8_16_8 or trie FIBs>]\n"
 		"[-w <path to the file to dump routing table>]\n"
 		"[-u <path to the file to dump ip's for lookup>]\n"
 		"[-v <type of lookup function:"
 		"\ts1, s2, s3 (3 types of scalar), v (vector) -"
 		" for DIR24_8 based FIB\n"
+		"\ts, v - for DIR8_16_8 based FIB\n"
 		"\ts, v - for TRIE based ipv6 FIB>]\n",
 		config.prgname);
 }
@@ -665,6 +674,22 @@ check_config(void)
 		printf("-e 1 is valid only for ipv4\n");
 		return -1;
 	}
+
+	if ((config.flags & FIB_V4_DIR_8_16_8_TYPE) &&
+			(config.flags & IPV6_FLAG)) {
+		printf("dir8_16_8 fib type is only valid for ipv4\n");
+		return -1;
+	}
+	/* The dir8_16_8 fib type only supports 8 byte next hops. */
+	if ((config.flags & FIB_TYPE_MASK) == FIB_V4_DIR_8_16_8_TYPE) {
+		if ((config.flags & ENT_SZ_FLAG) && config.ent_sz != 8) {
+			printf("-e %u is not valid for the dir8_16_8 fib type, "
+				"only 8 byte next hops are supported\n",
+				config.ent_sz);
+			return -1;
+		}
+		config.ent_sz = 8;
+	}
 	return 0;
 }
 
@@ -744,6 +769,9 @@ parse_opts(int argc, char **argv)
 			} else if (strcmp(optarg, "dir") == 0) {
 				config.flags &= ~FIB_TYPE_MASK;
 				config.flags |= FIB_V4_DIR_TYPE;
+			} else if (strcmp(optarg, "dir8_16_8") == 0) {
+				config.flags &= ~FIB_TYPE_MASK;
+				config.flags |= FIB_V4_DIR_8_16_8_TYPE;
 			} else if (strcmp(optarg, "trie") == 0) {
 				config.flags &= ~FIB_TYPE_MASK;
 				config.flags |= FIB_V6_TRIE_TYPE;
@@ -757,6 +785,7 @@ parse_opts(int argc, char **argv)
 				print_usage();
 				rte_exit(-EINVAL, "Invalid option -e\n");
 			}
+			config.flags |= ENT_SZ_FLAG;
 			break;
 		case 'g':
 			errno = 0;
@@ -817,6 +846,35 @@ print_depth_err(void)
 		"-d 0:0 option or remove /0 prefix from routes file\n");
 }
 
+static int
+select_lookup_fn_v4(struct rte_fib *fib, enum rte_fib_type type)
+{
+	enum rte_fib_lookup_type lookup_type;
+
+	if (type == RTE_FIB_DIR8_16_8) {
+		if (config.lookup_fn == 1)
+			lookup_type = RTE_FIB_LOOKUP_DIR8_16_8_SCALAR;
+		else if (config.lookup_fn == 2)
+			lookup_type = RTE_FIB_LOOKUP_DIR8_16_8_VECTOR_AVX512;
+		else
+			return -EINVAL;
+	} else if (type == RTE_FIB_DIR24_8) {
+		if (config.lookup_fn == 1)
+			lookup_type = RTE_FIB_LOOKUP_DIR24_8_SCALAR_MACRO;
+		else if (config.lookup_fn == 2)
+			lookup_type = RTE_FIB_LOOKUP_DIR24_8_VECTOR_AVX512;
+		else if (config.lookup_fn == 3)
+			lookup_type = RTE_FIB_LOOKUP_DIR24_8_SCALAR_INLINE;
+		else if (config.lookup_fn == 4)
+			lookup_type = RTE_FIB_LOOKUP_DIR24_8_SCALAR_UNI;
+		else
+			return -EINVAL;
+	} else {
+		return -EINVAL;
+	}
+	return rte_fib_select_lookup(fib, lookup_type);
+}
+
 static int
 run_v4(void)
 {
@@ -853,6 +911,15 @@ run_v4(void)
 		conf.dir24_8.nh_sz = rte_ctz32(config.ent_sz);
 		conf.dir24_8.num_tbl8 = RTE_MIN(config.tbl8,
 			get_max_nh(conf.dir24_8.nh_sz));
+	} else if (conf.type == RTE_FIB_DIR8_16_8) {
+		/*
+		 * DIR8_16_8 only supports 8 byte next hops. The first stride
+		 * (8 bits) needs at most one second level group per /8, so 256
+		 * groups always suffice. The third level (last 8 bits) groups
+		 * are sized like the DIR24_8 tbl8s.
+		 */
+		conf.dir8_16_8.num_tbl16 = DIR8_16_8_MAX_TBL16;
+		conf.dir8_16_8.num_tbl8 = config.tbl8;
 	}
 
 	fib = rte_fib_create("test", -1, &conf);
@@ -862,20 +929,7 @@ run_v4(void)
 	}
 
 	if (config.lookup_fn != 0) {
-		if (config.lookup_fn == 1)
-			ret = rte_fib_select_lookup(fib,
-				RTE_FIB_LOOKUP_DIR24_8_SCALAR_MACRO);
-		else if (config.lookup_fn == 2)
-			ret = rte_fib_select_lookup(fib,
-				RTE_FIB_LOOKUP_DIR24_8_VECTOR_AVX512);
-		else if (config.lookup_fn == 3)
-			ret = rte_fib_select_lookup(fib,
-				RTE_FIB_LOOKUP_DIR24_8_SCALAR_INLINE);
-		else if (config.lookup_fn == 4)
-			ret = rte_fib_select_lookup(fib,
-				RTE_FIB_LOOKUP_DIR24_8_SCALAR_UNI);
-		else
-			ret = -EINVAL;
+		ret = select_lookup_fn_v4(fib, conf.type);
 		if (ret != 0) {
 			printf("Can not init lookup function\n");
 			return ret;
diff --git a/app/test/test_fib.c b/app/test/test_fib.c
index bd73399d56..bb8021c818 100644
--- a/app/test/test_fib.c
+++ b/app/test/test_fib.c
@@ -6,9 +6,12 @@
 #include <stdio.h>
 #include <stdint.h>
 #include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
 
 #include <rte_ip.h>
 #include <rte_log.h>
+#include <rte_byteorder.h>
 #include <rte_fib.h>
 #include <rte_malloc.h>
 
@@ -65,7 +68,7 @@ test_create_invalid(void)
 		"Call succeeded with invalid parameters\n");
 	config.max_routes = MAX_ROUTES;
 
-	config.type = RTE_FIB_DIR24_8 + 1;
+	config.type = RTE_FIB_DIR8_16_8 + 1;
 	fib = rte_fib_create(__func__, SOCKET_ID_ANY, &config);
 	RTE_TEST_ASSERT(fib == NULL,
 		"Call succeeded with invalid parameters\n");
@@ -84,6 +87,19 @@ test_create_invalid(void)
 	RTE_TEST_ASSERT(fib == NULL,
 		"Call succeeded with invalid parameters\n");
 
+	config.type = RTE_FIB_DIR8_16_8;
+	config.dir8_16_8.num_tbl16 = MAX_TBL8;
+	config.dir8_16_8.num_tbl8 = 0;
+	fib = rte_fib_create(__func__, SOCKET_ID_ANY, &config);
+	RTE_TEST_ASSERT(fib == NULL,
+		"Call succeeded with invalid parameters\n");
+
+	config.dir8_16_8.num_tbl16 = 0;
+	config.dir8_16_8.num_tbl8 = MAX_TBL8;
+	fib = rte_fib_create(__func__, SOCKET_ID_ANY, &config);
+	RTE_TEST_ASSERT(fib == NULL,
+		"Call succeeded with invalid parameters\n");
+
 	return TEST_SUCCESS;
 }
 
@@ -376,6 +392,23 @@ test_lookup(void)
 		"Check_fib fails for DIR24_8_8B type\n");
 	rte_fib_free(fib);
 
+	config.type = RTE_FIB_DIR8_16_8;
+	config.dir8_16_8.num_tbl16 = 16;
+	config.dir8_16_8.num_tbl8 = MAX_TBL8;
+	fib = rte_fib_create(__func__, SOCKET_ID_ANY, &config);
+	RTE_TEST_ASSERT(fib != NULL, "Failed to create FIB\n");
+	ret = check_fib(fib);
+	RTE_TEST_ASSERT(ret == TEST_SUCCESS,
+		"Check_fib fails for DIR8_16_8 type\n");
+	/* The vector lookup is only available with a 512 bit SIMD width. */
+	if (rte_fib_select_lookup(fib,
+			RTE_FIB_LOOKUP_DIR8_16_8_VECTOR_AVX512) == 0) {
+		ret = check_fib(fib);
+		RTE_TEST_ASSERT(ret == TEST_SUCCESS,
+			"Check_fib fails for DIR8_16_8 type, AVX512 lookup\n");
+	}
+	rte_fib_free(fib);
+
 	return TEST_SUCCESS;
 }
 
@@ -588,6 +621,71 @@ test_fib_rcu_sync_rw(void)
 	return status == 0 ? TEST_SUCCESS : TEST_FAILED;
 }
 
+#define TEST_NUM_TBL16	16
+#define TEST_NUM_TBL8	64
+#define DEF_NH		77
+
+/*
+ * Network order lookups with every lookup implementation of DIR8_16_8, with a
+ * burst of two vector groups and a leftover.
+ */
+static int32_t
+test_lookup_network_order(void)
+{
+	const enum rte_fib_lookup_type types[] = {
+		RTE_FIB_LOOKUP_DIR8_16_8_SCALAR,
+		RTE_FIB_LOOKUP_DIR8_16_8_VECTOR_AVX512,
+	};
+	const uint32_t probes[] = {
+		RTE_IPV4(10, 2, 3, 4),	/* /8 */
+		RTE_IPV4(10, 1, 1, 5),	/* /24 */
+		RTE_IPV4(10, 1, 1, 7),	/* /32 */
+		RTE_IPV4(8, 8, 8, 8),	/* miss */
+	};
+	const uint64_t probe_nhs[] = { 100, 300, 400, DEF_NH };
+	struct rte_fib_conf cfg = { 0 };
+	struct rte_fib *fib;
+	uint32_t ips[17];
+	uint64_t nhs[17];
+	unsigned int i, t;
+
+	cfg.type = RTE_FIB_DIR8_16_8;
+	cfg.max_routes = 100;
+	cfg.default_nh = DEF_NH;
+	cfg.flags = RTE_FIB_F_LOOKUP_NETWORK_ORDER;
+	cfg.dir8_16_8.num_tbl16 = TEST_NUM_TBL16;
+	cfg.dir8_16_8.num_tbl8 = TEST_NUM_TBL8;
+
+	fib = rte_fib_create("fib_be", SOCKET_ID_ANY, &cfg);
+	RTE_TEST_ASSERT(fib != NULL, "Failed to create FIB\n");
+
+	RTE_TEST_ASSERT(rte_fib_add(fib, RTE_IPV4(10, 0, 0, 0), 8, 100) == 0,
+		"add /8\n");
+	RTE_TEST_ASSERT(rte_fib_add(fib, RTE_IPV4(10, 1, 1, 0), 24, 300) == 0,
+		"add /24\n");
+	RTE_TEST_ASSERT(rte_fib_add(fib, RTE_IPV4(10, 1, 1, 7), 32, 400) == 0,
+		"add /32\n");
+	for (i = 0; i < RTE_DIM(ips); i++)
+		ips[i] = rte_cpu_to_be_32(probes[i % RTE_DIM(probes)]);
+
+	for (t = 0; t < RTE_DIM(types); t++) {
+		/* The vector lookup needs a 512 bit SIMD width. */
+		if (rte_fib_select_lookup(fib, types[t]) != 0)
+			continue;
+
+		memset(nhs, 0, sizeof(nhs));
+		RTE_TEST_ASSERT(rte_fib_lookup_bulk(fib, ips, nhs,
+			RTE_DIM(ips)) == 0, "lookup failed\n");
+		for (i = 0; i < RTE_DIM(ips); i++)
+			RTE_TEST_ASSERT(nhs[i] == probe_nhs[i % RTE_DIM(probes)],
+				"lookup type %u: ip %u got %"PRIu64"\n",
+				types[t], i, nhs[i]);
+	}
+	rte_fib_free(fib);
+
+	return TEST_SUCCESS;
+}
+
 static struct unit_test_suite fib_fast_tests = {
 	.suite_name = "fib autotest",
 	.setup = NULL,
@@ -598,6 +696,7 @@ static struct unit_test_suite fib_fast_tests = {
 	TEST_CASE(test_add_del_invalid),
 	TEST_CASE(test_get_invalid),
 	TEST_CASE(test_lookup),
+	TEST_CASE(test_lookup_network_order),
 	TEST_CASE(test_invalid_rcu),
 	TEST_CASE(test_fib_rcu_sync_rw),
 	TEST_CASES_END()
diff --git a/app/test/test_fib_perf.c b/app/test/test_fib_perf.c
index 1b2ad04854..0967644316 100644
--- a/app/test/test_fib_perf.c
+++ b/app/test/test_fib_perf.c
@@ -7,6 +7,7 @@
 #include <stdint.h>
 #include <stdlib.h>
 #include <math.h>
+#include <inttypes.h>
 
 #include <rte_cycles.h>
 #include <rte_random.h>
@@ -317,31 +318,16 @@ print_route_distribution(const struct route_rule *table, uint32_t n)
 }
 
 static int
-test_fib_perf(void)
+measure_fib_perf(const char *name, struct rte_fib_conf *config)
 {
 	struct rte_fib *fib = NULL;
-	struct rte_fib_conf config = { 0 };
-
-	config.max_routes = 2000000;
-	config.rib_ext_sz = 0;
-	config.type = RTE_FIB_DIR24_8;
-	config.default_nh = 0;
-	config.dir24_8.nh_sz = RTE_FIB_DIR24_8_4B;
-	config.dir24_8.num_tbl8 = 65535;
 	uint64_t begin, total_time;
 	unsigned int i, j;
 	uint32_t next_hop_add = 0xAA;
 	int status = 0;
 	int64_t count = 0;
 
-	generate_large_route_rule_table();
-
-	printf("No. routes = %u\n", (unsigned int) NUM_ROUTE_ENTRIES);
-
-	print_route_distribution(large_route_table,
-		(uint32_t) NUM_ROUTE_ENTRIES);
-
-	fib = rte_fib_create(__func__, SOCKET_ID_ANY, &config);
+	fib = rte_fib_create(name, SOCKET_ID_ANY, config);
 	TEST_FIB_ASSERT(fib != NULL);
 
 	/* Measure add. */
@@ -407,4 +393,49 @@ test_fib_perf(void)
 	return 0;
 }
 
+static int
+test_fib_perf(void)
+{
+	struct rte_fib_conf config = { 0 };
+
+	config.max_routes = 2000000;
+	config.rib_ext_sz = 0;
+	config.type = RTE_FIB_DIR24_8;
+	config.default_nh = 0;
+	config.dir24_8.nh_sz = RTE_FIB_DIR24_8_4B;
+	config.dir24_8.num_tbl8 = 65535;
+
+	generate_large_route_rule_table();
+
+	printf("No. routes = %u\n", (unsigned int) NUM_ROUTE_ENTRIES);
+
+	print_route_distribution(large_route_table,
+		(uint32_t) NUM_ROUTE_ENTRIES);
+
+	return measure_fib_perf("fib_perf_dir24_8", &config);
+}
+
+static int
+test_fib_perf_dir8_16_8(void)
+{
+	struct rte_fib_conf config = { 0 };
+
+	config.max_routes = 2000000;
+	config.rib_ext_sz = 0;
+	config.type = RTE_FIB_DIR8_16_8;
+	config.default_nh = 0;
+	config.dir8_16_8.num_tbl16 = 256;
+	config.dir8_16_8.num_tbl8 = 1 << 16;
+
+	generate_large_route_rule_table();
+
+	printf("No. routes = %u\n", (unsigned int) NUM_ROUTE_ENTRIES);
+
+	print_route_distribution(large_route_table,
+		(uint32_t) NUM_ROUTE_ENTRIES);
+
+	return measure_fib_perf("fib_perf_dir8_16_8", &config);
+}
+
 REGISTER_PERF_TEST(fib_perf_autotest, test_fib_perf);
+REGISTER_PERF_TEST(fib_perf_dir8_16_8_autotest, test_fib_perf_dir8_16_8);
diff --git a/doc/guides/prog_guide/fib_lib.rst b/doc/guides/prog_guide/fib_lib.rst
index 41ab5764b6..c20759faaf 100644
--- a/doc/guides/prog_guide/fib_lib.rst
+++ b/doc/guides/prog_guide/fib_lib.rst
@@ -127,6 +127,32 @@ An entry in a tbl8 contains the following fields:
 * 1 bit indicating if the lookup should proceed inside the tbl8.
 
 
+DIR-8-16-8
+~~~~~~~~~~
+
+This algorithm is a three level variant of DIR-24-8: the IPv4 address is split
+in strides of 8, 16 and 8 bits. The first level is a 256 entry root table, the
+second level is made of 65536 entry groups (``tbl16``) and the third level of
+256 entry groups (``tbl8``), both allocated from pools sized on FIB creation.
+A lookup needs one to three memory accesses. Only 8 byte next hop IDs are
+supported.
+
+This algorithm will be used if the ``RTE_FIB_DIR8_16_8`` type is configured as the
+dataplane algorithm on FIB creation.
+
+The main FIB configuration struct stores the dataplane parameters inside ``dir8_16_8``
+within the ``rte_fib_conf`` and it consists of:
+
+* ``num_tbl16``: The number of second level groups, at most one per /8 is ever
+  needed.
+
+* ``num_tbl8``: The number of third level groups.
+
+Compared to DIR-24-8, the root table is small (2 KB), which makes it suitable
+when many tables must be kept, at the price of an extra memory access for
+prefixes longer than 8 bits.
+
+
 Use cases
 ---------
 
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 03b4feae0f..04781a2761 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -206,6 +206,11 @@ New Features
   when using ``RTE_UNCORE_PM_ENV_AUTO_DETECT``,
   instead of defaulting only to the Intel uncore driver.
 
+* **Added new FIB dataplane algorithms.**
+
+  * Added the ``RTE_FIB_DIR8_16_8`` IPv4 algorithm, a three level variant of
+    DIR-24-8 with a small root table, with scalar and AVX512 lookup functions.
+
 
 Removed Items
 -------------
diff --git a/lib/fib/dir8_16_8.c b/lib/fib/dir8_16_8.c
new file mode 100644
index 0000000000..7207cd4748
--- /dev/null
+++ b/lib/fib/dir8_16_8.c
@@ -0,0 +1,614 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Intel Corporation
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+
+#include <rte_debug.h>
+#include <rte_malloc.h>
+#include <rte_errno.h>
+#include <rte_vect.h>
+#include <rte_cpuflags.h>
+
+#include <rte_rib.h>
+#include <rte_fib.h>
+#include "dir8_16_8.h"
+#include "fib_log.h"
+
+#ifdef CC_AVX512_SUPPORT
+#include "dir8_16_8_avx512.h"
+#endif
+
+#define DIR8_16_8_NAMESIZE	64
+
+/*
+ * The IPv4 address is split in three strides handled by three table levels:
+ *   level 0: bits [31..24], 8 bits,  fixed 256 entry root table (dp->tbl0)
+ *   level 1: bits [23..8],  16 bits, dynamically allocated 65536 entry groups
+ *   level 2: bits [7..0],   8 bits,  dynamically allocated 256 entry groups
+ */
+
+struct dir8_16_8_level {
+	uint32_t	num_ent;	/**< Number of entries per group. */
+	uint8_t		shift;		/**< IP bit offset of this level. */
+	uint8_t		mask_bits;	/**< Number of IP bits at this level. */
+};
+
+static const struct dir8_16_8_level dir8_16_8_levels[] = {
+	{ DIR8_16_8_TBL0_NUM_ENT,      24, 8  },
+	{ DIR8_16_8_TBL16_GRP_NUM_ENT,  8, 16 },
+	{ DIR8_16_8_TBL8_GRP_NUM_ENT,   0, 8  },
+};
+
+#define DIR8_16_8_NUM_LEVELS	RTE_DIM(dir8_16_8_levels)
+
+static inline uint32_t
+dir8_16_8_level_idx(uint32_t ip, int level)
+{
+	const struct dir8_16_8_level *l = &dir8_16_8_levels[level];
+
+	return (ip >> l->shift) & ((1U << l->mask_bits) - 1);
+}
+
+struct dir8_16_8_rcu_dq_entry {
+	uint32_t	tbl_idx;
+	uint32_t	level;
+};
+
+static rte_fib_lookup_fn_t
+get_scalar_fn(bool be_addr)
+{
+	return be_addr ? dir8_16_8_lookup_bulk_be : dir8_16_8_lookup_bulk;
+}
+
+static rte_fib_lookup_fn_t
+get_vector_fn(bool be_addr)
+{
+#ifdef CC_AVX512_SUPPORT
+	if (rte_cpu_get_flag_enabled(RTE_CPUFLAG_AVX512F) <= 0 ||
+			rte_cpu_get_flag_enabled(RTE_CPUFLAG_AVX512DQ) <= 0 ||
+			rte_vect_get_max_simd_bitwidth() < RTE_VECT_SIMD_512)
+		return NULL;
+
+	if (be_addr && rte_cpu_get_flag_enabled(RTE_CPUFLAG_AVX512BW) <= 0)
+		return NULL;
+
+	return be_addr ? rte_dir8_16_8_vec_lookup_bulk_be :
+		rte_dir8_16_8_vec_lookup_bulk;
+#else
+	RTE_SET_USED(be_addr);
+	return NULL;
+#endif
+}
+
+rte_fib_lookup_fn_t
+dir8_16_8_get_lookup_fn(void *p, enum rte_fib_lookup_type type, bool be_addr)
+{
+	struct dir8_16_8_tbl *dp = p;
+	rte_fib_lookup_fn_t fn;
+
+	if (dp == NULL)
+		return NULL;
+
+	switch (type) {
+	case RTE_FIB_LOOKUP_DIR8_16_8_SCALAR:
+		return get_scalar_fn(be_addr);
+	case RTE_FIB_LOOKUP_DIR8_16_8_VECTOR_AVX512:
+		return get_vector_fn(be_addr);
+	case RTE_FIB_LOOKUP_DEFAULT:
+		fn = get_vector_fn(be_addr);
+		return (fn != NULL) ? fn : get_scalar_fn(be_addr);
+	default:
+		return NULL;
+	}
+}
+
+static inline uint64_t *
+get_tbl_by_level(struct dir8_16_8_tbl *dp, int level)
+{
+	switch (level) {
+	case 1:
+		return dp->tbl16;
+	case 2:
+		return dp->tbl8;
+	default:
+		return dp->tbl0;
+	}
+}
+
+static inline int32_t
+tbl_pool_get(struct dir8_16_8_tbl *dp, int level)
+{
+	uint32_t *pool = (level == 1) ? dp->tbl16_pool : dp->tbl8_pool;
+	uint32_t *pos = (level == 1) ? &dp->tbl16_pool_pos : &dp->tbl8_pool_pos;
+	uint32_t num = (level == 1) ? dp->number_tbl16s : dp->number_tbl8s;
+
+	if (*pos == num)
+		return -ENOSPC;
+
+	return pool[(*pos)++];
+}
+
+static inline void
+tbl_pool_put(struct dir8_16_8_tbl *dp, int level, uint32_t idx)
+{
+	uint32_t *pool = (level == 1) ? dp->tbl16_pool : dp->tbl8_pool;
+	uint32_t *pos = (level == 1) ? &dp->tbl16_pool_pos : &dp->tbl8_pool_pos;
+
+	pool[--(*pos)] = idx;
+}
+
+static inline uint64_t *
+group_ptr(struct dir8_16_8_tbl *dp, int level, uint32_t grp_idx)
+{
+	uint64_t *tbl = get_tbl_by_level(dp, level);
+
+	return &tbl[(uint64_t)grp_idx * dir8_16_8_levels[level].num_ent];
+}
+
+static int
+tbl_group_alloc(struct dir8_16_8_tbl *dp, int level, uint64_t nh)
+{
+	uint32_t i, ent_num = dir8_16_8_levels[level].num_ent;
+	uint64_t *grp;
+	int idx;
+
+	idx = tbl_pool_get(dp, level);
+
+	/* If there are no free groups try to reclaim one. */
+	if (unlikely(idx == -ENOSPC && dp->dq)) {
+		/*
+		 * Deferred queue contains tbl8 mixed with tbl16 groups.
+		 * Reclaim until required group is reclaimed
+		 */
+		unsigned int freed = 0;
+		int ret;
+		do {
+			ret = rte_rcu_qsbr_dq_reclaim(dp->dq, 1, &freed, NULL, NULL);
+			idx = tbl_pool_get(dp, level);
+		} while (idx < 0 && ret == 0 && freed != 0);
+	}
+
+	if (idx < 0)
+		return idx;
+
+	grp = group_ptr(dp, level, idx);
+	for (i = 0; i < ent_num; i++)
+		grp[i] = nh & ~(uint64_t)DIR8_16_8_EXT_ENT;
+
+	if (level == 1)
+		dp->cur_tbl16s++;
+	else
+		dp->cur_tbl8s++;
+
+	return idx;
+}
+
+static void
+tbl_group_cleanup_and_free(struct dir8_16_8_tbl *dp, int level, uint32_t idx)
+{
+	uint64_t *grp = group_ptr(dp, level, idx);
+
+	memset(grp, 0, dir8_16_8_levels[level].num_ent * sizeof(uint64_t));
+	tbl_pool_put(dp, level, idx);
+	if (level == 1)
+		dp->cur_tbl16s--;
+	else
+		dp->cur_tbl8s--;
+}
+
+static void
+__rcu_qsbr_free_resource(void *p, void *data, unsigned int n __rte_unused)
+{
+	struct dir8_16_8_tbl *dp = p;
+	struct dir8_16_8_rcu_dq_entry *e = data;
+
+	tbl_group_cleanup_and_free(dp, e->level, e->tbl_idx);
+}
+
+/*
+ * If every entry of a group holds the same final nexthop, collapse the group:
+ * write that nexthop into the parent entry and free the group.
+ */
+static void
+tbl_group_recycle(struct dir8_16_8_tbl *dp, int level, uint64_t *par_ent,
+	uint32_t grp_idx)
+{
+	struct dir8_16_8_rcu_dq_entry dq_entry;
+	uint64_t *grp = group_ptr(dp, level, grp_idx);
+	uint64_t nh = grp[0];
+	uint32_t i;
+
+	if (dir8_16_8_is_entry_extended(nh))
+		return;
+	for (i = 1; i < dir8_16_8_levels[level].num_ent; i++) {
+		if (nh != grp[i])
+			return;
+	}
+
+	*par_ent = nh & ~DIR8_16_8_EXT_ENT;
+
+	if (dp->v == NULL) {
+		tbl_group_cleanup_and_free(dp, level, grp_idx);
+	} else if (dp->rcu_mode == RTE_FIB_QSBR_MODE_SYNC) {
+		rte_rcu_qsbr_synchronize(dp->v, RTE_QSBR_THRID_INVALID);
+		tbl_group_cleanup_and_free(dp, level, grp_idx);
+	} else { /* RTE_FIB_QSBR_MODE_DQ */
+		dq_entry.tbl_idx = grp_idx;
+		dq_entry.level = level;
+		if (rte_rcu_qsbr_dq_enqueue(dp->dq, &dq_entry))
+			FIB_LOG(ERR, "Failed to push QSBR FIFO");
+	}
+}
+
+/*
+ * Make sure the entry "ent" at "level" points to a child group, allocating a
+ * new group (initialized with the entry's current nexthop) if necessary.
+ * On success returns the child group index, otherwise a negative errno.
+ */
+static int
+tbl_get_child(struct dir8_16_8_tbl *dp, int level, uint64_t *ent)
+{
+	int idx;
+
+	if (dir8_16_8_is_entry_extended(*ent))
+		return *ent >> 1;
+
+	idx = tbl_group_alloc(dp, level + 1, *ent);
+	if (idx < 0)
+		return idx;
+	*ent = ((uint64_t)idx << 1) | DIR8_16_8_EXT_ENT;
+
+	return idx;
+}
+
+static int
+install_to_fib(struct dir8_16_8_tbl *dp, int level, uint64_t base,
+	uint64_t ledge, uint64_t redge, uint64_t next_hop)
+{
+	const struct dir8_16_8_level *l = &dir8_16_8_levels[level];
+	uint64_t *tbl = get_tbl_by_level(dp, level);
+	uint64_t step = 1ULL << l->shift;
+	uint64_t lalign, ralign;
+	uint32_t lidx, ridx, idx;
+	int child, ret;
+
+	if (level == (int)DIR8_16_8_NUM_LEVELS - 1) {
+		/* Leaf level: every entry is a single address, plain fill. */
+		lidx = dir8_16_8_level_idx((uint32_t)ledge, level);
+		ridx = lidx + (uint32_t)(redge - ledge);
+		for (idx = lidx; idx < ridx; idx++)
+			tbl[base + idx] = (next_hop << 1);
+
+		return 0;
+	}
+
+	lalign = RTE_ALIGN_CEIL(ledge, step);
+	ralign = RTE_ALIGN_FLOOR(redge, step);
+
+	/* Left partial sub-range that does not start on a child boundary. */
+	if (ledge < lalign) {
+		uint64_t sub_right_edge = RTE_MIN(lalign, redge);
+
+		lidx = dir8_16_8_level_idx((uint32_t)ledge, level);
+		child = tbl_get_child(dp, level, &tbl[base + lidx]);
+		if (child < 0)
+			return child;
+
+		ret = install_to_fib(dp, level + 1,
+			(uint64_t)child * dir8_16_8_levels[level + 1].num_ent,
+			ledge, sub_right_edge, next_hop);
+		if (ret != 0)
+			return ret;
+
+		tbl_group_recycle(dp, level + 1, &tbl[base + lidx], child);
+		ledge = sub_right_edge;
+	}
+
+	/* Whole child sub-ranges filled directly at this level. */
+	if (ledge < ralign) {
+		lidx = dir8_16_8_level_idx((uint32_t)ledge, level);
+		ridx = lidx + (uint32_t)((ralign - ledge) >> l->shift);
+		for (idx = lidx; idx < ridx; idx++)
+			tbl[base + idx] = (next_hop << 1);
+
+		ledge = ralign;
+	}
+
+	/* Right partial sub-range that does not end on a child boundary. */
+	if (ledge < redge) {
+		lidx = dir8_16_8_level_idx((uint32_t)ledge, level);
+		child = tbl_get_child(dp, level, &tbl[base + lidx]);
+		if (child < 0)
+			return child;
+
+		ret = install_to_fib(dp, level + 1,
+			(uint64_t)child * dir8_16_8_levels[level + 1].num_ent,
+			ledge, redge, next_hop);
+		if (ret != 0)
+			return ret;
+
+		tbl_group_recycle(dp, level + 1, &tbl[base + lidx], child);
+	}
+
+	return 0;
+}
+
+static int
+modify_fib(struct dir8_16_8_tbl *dp, struct rte_rib *rib, uint32_t ip,
+	uint8_t depth, uint64_t next_hop)
+{
+	struct rte_rib_node *tmp = NULL;
+	uint64_t ledge, redge;
+	uint32_t tmp_ip;
+	int ret;
+	uint8_t tmp_depth;
+
+	ledge = ip;
+	do {
+		tmp = rte_rib_get_nxt(rib, ip, depth, tmp,
+			RTE_RIB_GET_NXT_COVER);
+		if (tmp != NULL) {
+			rte_rib_get_depth(tmp, &tmp_depth);
+			if (tmp_depth == depth)
+				continue;
+
+			rte_rib_get_ip(tmp, &tmp_ip);
+			redge = tmp_ip & rte_rib_depth_to_mask(tmp_depth);
+			if (ledge == redge) {
+				ledge = redge + (1ULL << (32 - tmp_depth));
+				continue;
+			}
+
+			ret = install_to_fib(dp, 0, 0, ledge, redge, next_hop);
+			if (ret != 0)
+				return ret;
+
+			ledge = redge + (1ULL << (32 - tmp_depth));
+			/*
+			 * we got to the end of address space
+			 * and wrapped around
+			 */
+			if (ledge >= (1ULL << 32))
+				break;
+		} else {
+			redge = (uint64_t)ip + (1ULL << (32 - depth));
+			if (ledge == redge && ledge != 0)
+				break;
+
+			ret = install_to_fib(dp, 0, 0, ledge, redge, next_hop);
+			if (ret != 0)
+				return ret;
+		}
+	} while (tmp);
+
+	return 0;
+}
+
+int
+dir8_16_8_modify(struct rte_fib *fib, uint32_t ip, uint8_t depth,
+	uint64_t next_hop, int op)
+{
+	struct dir8_16_8_tbl *dp;
+	struct rte_rib *rib;
+	struct rte_rib_node *node;
+	struct rte_rib_node *parent;
+	int ret = 0;
+	uint64_t par_nh, node_nh;
+
+	if ((fib == NULL) || (depth > RTE_FIB_MAXDEPTH))
+		return -EINVAL;
+
+	dp = rte_fib_get_dp(fib);
+	rib = rte_fib_get_rib(fib);
+	RTE_ASSERT((dp != NULL) && (rib != NULL));
+
+	if (next_hop > dir8_16_8_get_max_nh())
+		return -EINVAL;
+
+	ip &= rte_rib_depth_to_mask(depth);
+
+	node = rte_rib_lookup_exact(rib, ip, depth);
+	switch (op) {
+	case RTE_FIB_ADD:
+		if (node != NULL) {
+			rte_rib_get_nh(node, &node_nh);
+			if (node_nh == next_hop)
+				return 0;
+
+			ret = modify_fib(dp, rib, ip, depth, next_hop);
+			if (ret == 0)
+				rte_rib_set_nh(node, next_hop);
+
+			return ret;
+		}
+		node = rte_rib_insert(rib, ip, depth);
+		if (node == NULL)
+			return -rte_errno;
+
+		rte_rib_set_nh(node, next_hop);
+		parent = rte_rib_lookup_parent(node);
+		if (parent != NULL) {
+			rte_rib_get_nh(parent, &par_nh);
+			if (par_nh == next_hop)
+				return 0;
+		}
+		ret = modify_fib(dp, rib, ip, depth, next_hop);
+		if (ret != 0) {
+			rte_rib_remove(rib, ip, depth);
+			return ret;
+		}
+
+		return 0;
+	case RTE_FIB_DEL:
+		if (node == NULL)
+			return -ENOENT;
+
+		parent = rte_rib_lookup_parent(node);
+		if (parent != NULL) {
+			rte_rib_get_nh(parent, &par_nh);
+			rte_rib_get_nh(node, &node_nh);
+			if (par_nh != node_nh)
+				ret = modify_fib(dp, rib, ip, depth, par_nh);
+		} else
+			ret = modify_fib(dp, rib, ip, depth, dp->def_nh);
+		if (ret == 0)
+			rte_rib_remove(rib, ip, depth);
+		return ret;
+	default:
+		break;
+	}
+
+	return -EINVAL;
+}
+
+void *
+dir8_16_8_create(const char *name, int socket_id, struct rte_fib_conf *fib_conf)
+{
+	char mem_name[DIR8_16_8_NAMESIZE];
+	struct dir8_16_8_tbl *dp;
+	uint64_t def_nh;
+	uint32_t num_tbl16, num_tbl8;
+	uint32_t i;
+	uint64_t tbl16_sz, tbl8_sz;
+
+	if ((name == NULL) || (fib_conf == NULL) || (fib_conf->dir8_16_8.num_tbl16 == 0) ||
+			(fib_conf->dir8_16_8.num_tbl8 == 0) ||
+			(fib_conf->default_nh > dir8_16_8_get_max_nh())) {
+		rte_errno = EINVAL;
+		return NULL;
+	}
+
+	def_nh = fib_conf->default_nh;
+	num_tbl16 = fib_conf->dir8_16_8.num_tbl16;
+	num_tbl8 = fib_conf->dir8_16_8.num_tbl8;
+
+	dp = rte_zmalloc_socket(name, sizeof(struct dir8_16_8_tbl) +
+		DIR8_16_8_TBL0_NUM_ENT * sizeof(uint64_t), RTE_CACHE_LINE_SIZE, socket_id);
+	if (dp == NULL) {
+		rte_errno = ENOMEM;
+		return NULL;
+	}
+
+	/* Init first level table with the default nexthop. */
+	for (i = 0; i < DIR8_16_8_TBL0_NUM_ENT; i++)
+		dp->tbl0[i] = def_nh << 1;
+
+	snprintf(mem_name, sizeof(mem_name), "TBL16_%p", dp);
+	tbl16_sz = (uint64_t)DIR8_16_8_TBL16_GRP_NUM_ENT * sizeof(uint64_t) * num_tbl16;
+	dp->tbl16 = rte_zmalloc_socket(mem_name, tbl16_sz, RTE_CACHE_LINE_SIZE, socket_id);
+	if (dp->tbl16 == NULL) {
+		rte_errno = ENOMEM;
+		goto free_dp;
+	}
+
+	snprintf(mem_name, sizeof(mem_name), "TBL8_%p", dp);
+	tbl8_sz = (uint64_t)DIR8_16_8_TBL8_GRP_NUM_ENT * sizeof(uint64_t) * num_tbl8;
+	dp->tbl8 = rte_zmalloc_socket(mem_name, tbl8_sz, RTE_CACHE_LINE_SIZE, socket_id);
+	if (dp->tbl8 == NULL) {
+		rte_errno = ENOMEM;
+		goto free_tbl16;
+	}
+
+	snprintf(mem_name, sizeof(mem_name), "TBL16_pool_%p", dp);
+	dp->tbl16_pool = rte_zmalloc_socket(mem_name, sizeof(uint32_t) * num_tbl16,
+		RTE_CACHE_LINE_SIZE, socket_id);
+	if (dp->tbl16_pool == NULL) {
+		rte_errno = ENOMEM;
+		goto free_tbl8;
+	}
+	for (i = 0; i < num_tbl16; i++)
+		dp->tbl16_pool[i] = i;
+
+	snprintf(mem_name, sizeof(mem_name), "TBL8_pool_%p", dp);
+	dp->tbl8_pool = rte_zmalloc_socket(mem_name, sizeof(uint32_t) * num_tbl8,
+		RTE_CACHE_LINE_SIZE, socket_id);
+	if (dp->tbl8_pool == NULL) {
+		rte_errno = ENOMEM;
+		goto free_tbl16_pool;
+	}
+
+	for (i = 0; i < num_tbl8; i++)
+		dp->tbl8_pool[i] = i;
+
+	dp->def_nh = def_nh;
+	dp->number_tbl16s = num_tbl16;
+	dp->number_tbl8s = num_tbl8;
+
+	return dp;
+
+free_tbl16_pool:
+	rte_free(dp->tbl16_pool);
+free_tbl8:
+	rte_free(dp->tbl8);
+free_tbl16:
+	rte_free(dp->tbl16);
+free_dp:
+	rte_free(dp);
+	return NULL;
+}
+
+void
+dir8_16_8_free(void *p)
+{
+	struct dir8_16_8_tbl *dp = (struct dir8_16_8_tbl *)p;
+
+	if (dp == NULL)
+		return;
+
+	rte_rcu_qsbr_dq_delete(dp->dq);
+	rte_free(dp->tbl8_pool);
+	rte_free(dp->tbl16_pool);
+	rte_free(dp->tbl8);
+	rte_free(dp->tbl16);
+	rte_free(dp);
+}
+
+int
+dir8_16_8_rcu_qsbr_add(struct dir8_16_8_tbl *dp, struct rte_fib_rcu_config *cfg,
+	const char *name)
+{
+	struct rte_rcu_qsbr_dq_parameters params = {0};
+	char rcu_dq_name[RTE_RCU_QSBR_DQ_NAMESIZE];
+
+	if (dp == NULL || cfg == NULL)
+		return -EINVAL;
+
+	if (dp->v != NULL)
+		return -EEXIST;
+
+	if (cfg->mode == RTE_FIB_QSBR_MODE_SYNC) {
+		/* No other things to do. */
+	} else if (cfg->mode == RTE_FIB_QSBR_MODE_DQ) {
+		/* Init QSBR defer queue. */
+		snprintf(rcu_dq_name, sizeof(rcu_dq_name),
+				"FIB_RCU_%s", name);
+		params.name = rcu_dq_name;
+		params.size = cfg->dq_size;
+		if (params.size == 0)
+			params.size = RTE_FIB_RCU_DQ_RECLAIM_SZ;
+
+		params.trigger_reclaim_limit = cfg->reclaim_thd;
+		params.max_reclaim_size = cfg->reclaim_max;
+		if (params.max_reclaim_size == 0)
+			params.max_reclaim_size = RTE_FIB_RCU_DQ_RECLAIM_MAX;
+
+		params.esize = sizeof(struct dir8_16_8_rcu_dq_entry);
+		params.free_fn = __rcu_qsbr_free_resource;
+		params.p = dp;
+		params.v = cfg->v;
+		dp->dq = rte_rcu_qsbr_dq_create(&params);
+		if (dp->dq == NULL) {
+			FIB_LOG(ERR, "LPM defer queue creation failed");
+			return -rte_errno;
+		}
+	} else {
+		return -EINVAL;
+	}
+
+	dp->rcu_mode = cfg->mode;
+	dp->v = cfg->v;
+
+	return 0;
+}
diff --git a/lib/fib/dir8_16_8.h b/lib/fib/dir8_16_8.h
new file mode 100644
index 0000000000..31ca5162de
--- /dev/null
+++ b/lib/fib/dir8_16_8.h
@@ -0,0 +1,143 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Intel Corporation
+ */
+
+#ifndef _DIR8_16_8_H_
+#define _DIR8_16_8_H_
+
+#include <stdalign.h>
+#include <stdbool.h>
+
+#include <rte_byteorder.h>
+#include <rte_prefetch.h>
+#include <rte_branch_prediction.h>
+#include <rte_rcu_qsbr.h>
+
+/**
+ * @file
+ * DIR8_16_8 algorithm
+ *
+ * Three level variant of the DIR24_8 algorithm.
+ * The 32 bit IPv4 address is split in three strides of 8, 16 and 8 bits.
+ * Only a single nexthop size of 8 bytes is supported.
+ */
+
+/** Number of entries in the first level table (top 8 bits). */
+#define DIR8_16_8_TBL0_NUM_ENT		256U
+/** Number of entries in a second level group (middle 16 bits). */
+#define DIR8_16_8_TBL16_GRP_NUM_ENT	(1U << 16)
+/** Number of entries in a third level group (last 8 bits). */
+#define DIR8_16_8_TBL8_GRP_NUM_ENT	256U
+/** Flag set in an entry pointing to the next level table. */
+#define DIR8_16_8_EXT_ENT		1
+
+struct dir8_16_8_tbl {
+	uint32_t	number_tbl16s;	/**< Total number of tbl16 groups. */
+	uint32_t	cur_tbl16s;	/**< Current number of tbl16 groups. */
+	uint32_t	number_tbl8s;	/**< Total number of tbl8 groups. */
+	uint32_t	cur_tbl8s;	/**< Current number of tbl8 groups. */
+	uint64_t	def_nh;		/**< Default next hop. */
+	uint64_t	*tbl16;		/**< Second level table. */
+	uint64_t	*tbl8;		/**< Third level table. */
+	uint32_t	*tbl16_pool;	/**< Stack of free tbl16 group idxes. */
+	uint32_t	*tbl8_pool;	/**< Stack of free tbl8 group idxes. */
+	uint32_t	tbl16_pool_pos;	/**< Allocation pos in tbl16 pool. */
+	uint32_t	tbl8_pool_pos;	/**< Allocation pos in tbl8 pool. */
+	/* RCU config. */
+	enum rte_fib_qsbr_mode rcu_mode;/* Blocking, defer queue. */
+	struct rte_rcu_qsbr *v;		/* RCU QSBR variable. */
+	struct rte_rcu_qsbr_dq *dq;	/* RCU QSBR defer queue. */
+	/* First level table (top 8 bits). */
+	alignas(RTE_CACHE_LINE_SIZE) uint64_t	tbl0[];
+};
+
+static inline uint32_t
+dir8_16_8_get_tbl0_idx(uint32_t ip)
+{
+	return ip >> 24;
+}
+
+static inline size_t
+dir8_16_8_get_tbl16_idx(uint64_t ent, uint32_t ip)
+{
+	return (ent >> 1) * DIR8_16_8_TBL16_GRP_NUM_ENT +
+		((ip >> 8) & 0xffff);
+}
+
+static inline size_t
+dir8_16_8_get_tbl8_idx(uint64_t ent, uint32_t ip)
+{
+	return (ent >> 1) * DIR8_16_8_TBL8_GRP_NUM_ENT + (uint8_t)ip;
+}
+
+static inline uint64_t
+dir8_16_8_get_max_nh(void)
+{
+	return (UINT64_MAX >> 1);
+}
+
+static inline int
+dir8_16_8_is_entry_extended(uint64_t ent)
+{
+	return (ent & DIR8_16_8_EXT_ENT) == DIR8_16_8_EXT_ENT;
+}
+
+static inline void
+dir8_16_8_lookup_bulk(void *p, const uint32_t *ips,
+	uint64_t *next_hops, const unsigned int n)
+{
+	struct dir8_16_8_tbl *dp = (struct dir8_16_8_tbl *)p;
+	uint64_t tmp;
+	uint32_t i;
+
+	for (i = 0; i < n; i++) {
+		tmp = dp->tbl0[dir8_16_8_get_tbl0_idx(ips[i])];
+		/* Prefixes are usually longer than /8. */
+		if (dir8_16_8_is_entry_extended(tmp))
+			tmp = dp->tbl16[dir8_16_8_get_tbl16_idx(tmp, ips[i])];
+		if (unlikely(dir8_16_8_is_entry_extended(tmp)))
+			tmp = dp->tbl8[dir8_16_8_get_tbl8_idx(tmp, ips[i])];
+		next_hops[i] = tmp >> 1;
+	}
+}
+
+#define DIR8_16_8_BSWAP_MAX_LENGTH	64
+
+static inline void
+dir8_16_8_lookup_bulk_be(void *p, const uint32_t *ips,
+	uint64_t *next_hops, const unsigned int n)
+{
+#if RTE_BYTE_ORDER == RTE_BIG_ENDIAN
+	dir8_16_8_lookup_bulk(p, ips, next_hops, n);
+#else
+	uint32_t le_ips[DIR8_16_8_BSWAP_MAX_LENGTH];
+	unsigned int i;
+
+	for (i = 0; i < n; i += DIR8_16_8_BSWAP_MAX_LENGTH) {
+		unsigned int j;
+		for (j = 0; j < DIR8_16_8_BSWAP_MAX_LENGTH && i + j < n; j++)
+			le_ips[j] = rte_be_to_cpu_32(ips[i + j]);
+
+		dir8_16_8_lookup_bulk(p, le_ips, next_hops + i, j);
+	}
+#endif
+}
+
+void *
+dir8_16_8_create(const char *name, int socket_id, struct rte_fib_conf *conf);
+
+void
+dir8_16_8_free(void *p);
+
+rte_fib_lookup_fn_t
+dir8_16_8_get_lookup_fn(void *p, enum rte_fib_lookup_type type, bool be_addr);
+
+int
+dir8_16_8_modify(struct rte_fib *fib, uint32_t ip, uint8_t depth,
+	uint64_t next_hop, int op);
+
+int
+dir8_16_8_rcu_qsbr_add(struct dir8_16_8_tbl *dp, struct rte_fib_rcu_config *cfg,
+	const char *name);
+
+#endif /* _DIR8_16_8_H_ */
diff --git a/lib/fib/dir8_16_8_avx512.c b/lib/fib/dir8_16_8_avx512.c
new file mode 100644
index 0000000000..c147451252
--- /dev/null
+++ b/lib/fib/dir8_16_8_avx512.c
@@ -0,0 +1,142 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Intel Corporation
+ */
+
+#include <rte_vect.h>
+#include <rte_fib.h>
+
+#include "dir8_16_8.h"
+#include "dir8_16_8_avx512.h"
+
+static inline __m256i
+dir8_16_8_vec_load_ips(const uint32_t *ips, bool be_addr)
+{
+	const __m256i bswap32 = _mm256_set_epi8(
+		12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3,
+		12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3
+	);
+	__m256i ip_vec = _mm256_loadu_si256((const void *)ips);
+
+	return be_addr ? _mm256_shuffle_epi8(ip_vec, bswap32) : ip_vec;
+}
+
+static inline __m256i
+dir8_16_8_vec_get_tbl0_idx(__m256i ip_vec)
+{
+	return _mm256_srli_epi32(ip_vec, 24);
+}
+
+/* Second lookup stage input for 8 IPs, filled by the first stage. */
+struct dir8_16_8_vec_stage {
+	/* Address of the entry to read for every IP. */
+	const uint64_t *ent[8];
+	/*
+	 * Copy of the tbl0 entries. A not extended entry is read from here,
+	 * reading it again from tbl0 could see a concurrent update.
+	 */
+	uint64_t tbl0_ent[8];
+};
+
+/*
+ * First lookup stage of 8 IPs: read their tbl0 entries and prefetch the
+ * tbl16 entries the extended ones point to. Scalar loads are used, as on
+ * some CPUs a gather costs much more than the 8 loads it replaces.
+ */
+static inline void
+dir8_16_8_vec_prefetch_x8(const struct dir8_16_8_tbl *dp, const uint32_t *ips, bool be_addr,
+	struct dir8_16_8_vec_stage *st)
+{
+	const __m256i ip_vec = dir8_16_8_vec_load_ips(ips, be_addr);
+	const rte_ymm_t idx0 = { .y = dir8_16_8_vec_get_tbl0_idx(ip_vec) };
+	const rte_ymm_t ip = { .y = ip_vec };
+	const uint64_t *tbl0 = dp->tbl0;
+	const uint64_t *tbl16 = dp->tbl16;
+	uint64_t ent;
+	uint32_t i;
+
+	for (i = 0; i < 8; i++) {
+		ent = tbl0[idx0.u32[i]];
+		st->tbl0_ent[i] = ent;
+		st->ent[i] = &st->tbl0_ent[i];
+		if (dir8_16_8_is_entry_extended(ent)) {
+			st->ent[i] = &tbl16[dir8_16_8_get_tbl16_idx(ent, ip.u32[i])];
+			rte_prefetch0(st->ent[i]);
+		}
+	}
+}
+
+/* Second lookup stage of 8 IPs: read the entries found by the first stage. */
+static inline void
+dir8_16_8_vec_lookup_x8(const struct dir8_16_8_tbl *dp, const struct dir8_16_8_vec_stage *st,
+	const uint32_t *ips, bool be_addr, uint64_t *next_hops)
+{
+	const uint64_t * const *ent = st->ent;
+	const __m512i lsb = _mm512_set1_epi64(1);
+	__m512i ip64, res, idxes;
+	__mmask8 msk_ext;
+
+	res = _mm512_set_epi64(*ent[7], *ent[6], *ent[5], *ent[4],
+			*ent[3], *ent[2], *ent[1], *ent[0]);
+	ip64 = _mm512_cvtepu32_epi64(dir8_16_8_vec_load_ips(ips, be_addr));
+
+	msk_ext = _mm512_test_epi64_mask(res, lsb);
+	if (msk_ext != 0) {
+		idxes = _mm512_slli_epi64(_mm512_srli_epi64(res, 1), 8);
+		idxes = _mm512_add_epi64(idxes, _mm512_and_epi64(ip64, _mm512_set1_epi64(0xff)));
+		res = _mm512_mask_i64gather_epi64(res, msk_ext, idxes, (const void *)dp->tbl8, 8);
+	}
+
+	_mm512_storeu_si512(next_hops, _mm512_srli_epi64(res, 1));
+}
+
+static inline void
+vec_lookup_bulk(void *p, const uint32_t *ips, uint64_t *next_hops, unsigned int n, bool be_addr)
+{
+	const struct dir8_16_8_tbl *dp = p;
+	struct dir8_16_8_vec_stage stage[3];
+	struct dir8_16_8_vec_stage *st0 = &stage[0];
+	struct dir8_16_8_vec_stage *st1 = &stage[1];
+	struct dir8_16_8_vec_stage *st2 = &stage[2];
+	struct dir8_16_8_vec_stage *tmp;
+	unsigned int i = 0;
+
+	if (n >= 16) {
+		dir8_16_8_vec_prefetch_x8(dp, ips, be_addr, st0);
+		dir8_16_8_vec_prefetch_x8(dp, &ips[8], be_addr, st1);
+		for (; i + 24 <= n; i += 8) {
+			dir8_16_8_vec_prefetch_x8(dp, &ips[i + 16], be_addr, st2);
+			dir8_16_8_vec_lookup_x8(dp, st0, &ips[i], be_addr, &next_hops[i]);
+			tmp = st0;
+			st0 = st1;
+			st1 = st2;
+			st2 = tmp;
+		}
+		dir8_16_8_vec_lookup_x8(dp, st0, &ips[i], be_addr, &next_hops[i]);
+		dir8_16_8_vec_lookup_x8(dp, st1, &ips[i + 8], be_addr, &next_hops[i + 8]);
+		i += 16;
+	} else if (n >= 8) {
+		dir8_16_8_vec_prefetch_x8(dp, ips, be_addr, st0);
+		dir8_16_8_vec_lookup_x8(dp, st0, ips, be_addr, next_hops);
+		i = 8;
+	}
+
+	/* Scalar lookup of the last n % 8 IPs. */
+	if (be_addr)
+		dir8_16_8_lookup_bulk_be(p, &ips[i], &next_hops[i], n - i);
+	else
+		dir8_16_8_lookup_bulk(p, &ips[i], &next_hops[i], n - i);
+}
+
+void
+rte_dir8_16_8_vec_lookup_bulk(void *p, const uint32_t *ips,
+	uint64_t *next_hops, const unsigned int n)
+{
+	vec_lookup_bulk(p, ips, next_hops, n, false);
+}
+
+void
+rte_dir8_16_8_vec_lookup_bulk_be(void *p, const uint32_t *ips,
+	uint64_t *next_hops, const unsigned int n)
+{
+	vec_lookup_bulk(p, ips, next_hops, n, true);
+}
diff --git a/lib/fib/dir8_16_8_avx512.h b/lib/fib/dir8_16_8_avx512.h
new file mode 100644
index 0000000000..d468481029
--- /dev/null
+++ b/lib/fib/dir8_16_8_avx512.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Intel Corporation
+ */
+
+#ifndef _DIR8_16_8_AVX512_H_
+#define _DIR8_16_8_AVX512_H_
+
+void
+rte_dir8_16_8_vec_lookup_bulk(void *p, const uint32_t *ips,
+	uint64_t *next_hops, const unsigned int n);
+
+void
+rte_dir8_16_8_vec_lookup_bulk_be(void *p, const uint32_t *ips,
+	uint64_t *next_hops, const unsigned int n);
+
+#endif /* _DIR8_16_8_AVX512_H_ */
diff --git a/lib/fib/meson.build b/lib/fib/meson.build
index 573fc50ff1..f24f679039 100644
--- a/lib/fib/meson.build
+++ b/lib/fib/meson.build
@@ -2,14 +2,14 @@
 # Copyright(c) 2018 Vladimir Medvedkin <medvedkinv at gmail.com>
 # Copyright(c) 2019 Intel Corporation
 
-sources = files('rte_fib.c', 'rte_fib6.c', 'dir24_8.c', 'trie.c')
+sources = files('rte_fib.c', 'rte_fib6.c', 'dir24_8.c', 'dir8_16_8.c', 'trie.c')
 headers = files('rte_fib.h', 'rte_fib6.h')
 deps += ['rib']
 deps += ['rcu']
 deps += ['net']
 
 if dpdk_conf.has('RTE_ARCH_X86_64')
-    sources_avx512 += files('dir24_8_avx512.c', 'trie_avx512.c')
+    sources_avx512 += files('dir24_8_avx512.c', 'dir8_16_8_avx512.c', 'trie_avx512.c')
 elif dpdk_conf.has('RTE_ARCH_RISCV')
     sources += files('dir24_8_rvv.c')
 endif
diff --git a/lib/fib/rte_fib.c b/lib/fib/rte_fib.c
index 184210f380..fcc28899cb 100644
--- a/lib/fib/rte_fib.c
+++ b/lib/fib/rte_fib.c
@@ -18,6 +18,7 @@
 #include <rte_fib.h>
 
 #include "dir24_8.h"
+#include "dir8_16_8.h"
 #include "fib_log.h"
 
 RTE_LOG_REGISTER_DEFAULT(fib_logtype, INFO);
@@ -112,6 +113,14 @@ init_dataplane(struct rte_fib *fib, __rte_unused int socket_id,
 			RTE_FIB_LOOKUP_DEFAULT, !!(fib->flags & RTE_FIB_F_LOOKUP_NETWORK_ORDER));
 		fib->modify = dir24_8_modify;
 		return 0;
+	case RTE_FIB_DIR8_16_8:
+		fib->dp = dir8_16_8_create(dp_name, socket_id, conf);
+		if (fib->dp == NULL)
+			return -rte_errno;
+		fib->lookup = dir8_16_8_get_lookup_fn(fib->dp,
+			RTE_FIB_LOOKUP_DEFAULT, !!(fib->flags & RTE_FIB_F_LOOKUP_NETWORK_ORDER));
+		fib->modify = dir8_16_8_modify;
+		return 0;
 	default:
 		return -EINVAL;
 	}
@@ -165,7 +174,7 @@ rte_fib_create(const char *name, int socket_id, struct rte_fib_conf *conf)
 	/* Check user arguments. */
 	if ((name == NULL) || (conf == NULL) ||	(conf->max_routes < 0) ||
 			(conf->flags & ~RTE_FIB_ALLOWED_FLAGS) ||
-			(conf->type > RTE_FIB_DIR24_8)) {
+			(conf->type > RTE_FIB_DIR8_16_8)) {
 		rte_errno = EINVAL;
 		return NULL;
 	}
@@ -281,6 +290,10 @@ free_dataplane(struct rte_fib *fib)
 		return;
 	case RTE_FIB_DIR24_8:
 		dir24_8_free(fib->dp);
+		return;
+	case RTE_FIB_DIR8_16_8:
+		dir8_16_8_free(fib->dp);
+		return;
 	default:
 		return;
 	}
@@ -345,6 +358,13 @@ rte_fib_select_lookup(struct rte_fib *fib,
 			return -EINVAL;
 		fib->lookup = fn;
 		return 0;
+	case RTE_FIB_DIR8_16_8:
+		fn = dir8_16_8_get_lookup_fn(fib->dp, type,
+			!!(fib->flags & RTE_FIB_F_LOOKUP_NETWORK_ORDER));
+		if (fn == NULL)
+			return -EINVAL;
+		fib->lookup = fn;
+		return 0;
 	default:
 		return -EINVAL;
 	}
@@ -360,6 +380,8 @@ rte_fib_rcu_qsbr_add(struct rte_fib *fib, struct rte_fib_rcu_config *cfg)
 	switch (fib->type) {
 	case RTE_FIB_DIR24_8:
 		return dir24_8_rcu_qsbr_add(fib->dp, cfg, fib->name);
+	case RTE_FIB_DIR8_16_8:
+		return dir8_16_8_rcu_qsbr_add(fib->dp, cfg, fib->name);
 	default:
 		return -ENOTSUP;
 	}
diff --git a/lib/fib/rte_fib.h b/lib/fib/rte_fib.h
index b16a653535..68ee6f0652 100644
--- a/lib/fib/rte_fib.h
+++ b/lib/fib/rte_fib.h
@@ -49,7 +49,8 @@ enum rte_fib_qsbr_mode {
 /** Type of FIB struct */
 enum rte_fib_type {
 	RTE_FIB_DUMMY,		/**< RIB tree based FIB */
-	RTE_FIB_DIR24_8		/**< DIR24_8 based FIB */
+	RTE_FIB_DIR24_8,	/**< DIR24_8 based FIB */
+	RTE_FIB_DIR8_16_8	/**< DIR8_16_8 based FIB */
 };
 
 /** Modify FIB function */
@@ -87,8 +88,14 @@ enum rte_fib_lookup_type {
 	/**<
 	 * Unified lookup function for all next hop sizes
 	 */
-	RTE_FIB_LOOKUP_DIR24_8_VECTOR_AVX512
+	RTE_FIB_LOOKUP_DIR24_8_VECTOR_AVX512,
 	/**< Vector implementation using AVX512 */
+	RTE_FIB_LOOKUP_DIR8_16_8_SCALAR,
+	/**<
+	 * Scalar implementation for the DIR8_16_8 algorithm
+	 */
+	RTE_FIB_LOOKUP_DIR8_16_8_VECTOR_AVX512
+	/**< Vector (AVX512) lookup for the DIR8_16_8 algorithm */
 };
 
 /** If set, fib lookup is expecting IPv4 address in network byte order */
@@ -108,6 +115,12 @@ struct rte_fib_conf {
 			enum rte_fib_dir24_8_nh_sz nh_sz;
 			uint32_t	num_tbl8;
 		} dir24_8;
+		struct {
+			/** Number of second level (16 bit stride) groups. */
+			uint32_t	num_tbl16;
+			/** Number of third level (8 bit stride) groups. */
+			uint32_t	num_tbl8;
+		} dir8_16_8;
 	};
 	unsigned int flags; /**< Optional feature flags from RTE_FIB_F_* */
 };
-- 
2.43.0



More information about the dev mailing list