diff --git a/include/linux/netfilter/nf_tables.h b/include/linux/netfilter/nf_tables.h
index a236cc3..b76401b 100644
--- a/include/linux/netfilter/nf_tables.h
+++ b/include/linux/netfilter/nf_tables.h
@@ -9,7 +9,40 @@ enum nft_registers {
 	NFT_REG_2,
 	NFT_REG_3,
 	NFT_REG_4,
-	__NFT_REG_MAX
+	__NFT_REG_MAX,
+	/* 64 bits addressing */
+	NFT_REG_5 = __NFT_REG_MAX,
+	NFT_REG_6,
+	NFT_REG_7,
+	NFT_REG_8,
+	NFT_REG_9,
+	NFT_REG_10,
+	NFT_REG_11,
+	NFT_REG_12,
+	NFT_REG_13,
+	NFT_REG_14,
+	/* 32 bits addressing */
+	NFT_REG_15,
+	NFT_REG_16,
+	NFT_REG_17,
+	NFT_REG_18,
+	NFT_REG_19,
+	NFT_REG_20,
+	NFT_REG_21,
+	NFT_REG_22,
+	NFT_REG_23,
+	NFT_REG_24,
+	NFT_REG_25,
+	NFT_REG_26,
+	NFT_REG_27,
+	NFT_REG_28,
+	NFT_REG_29,
+	NFT_REG_30,
+	NFT_REG_31,
+	NFT_REG_32,
+	NFT_REG_33,
+	NFT_REG_34,
+	NFT_REG_ADDR_MAX,
 };
 #define NFT_REG_MAX	(__NFT_REG_MAX - 1)
 
diff --git a/include/utils.h b/include/utils.h
index 854986f..38ef7cc 100644
--- a/include/utils.h
+++ b/include/utils.h
@@ -62,6 +62,8 @@
 	(void) (&_max1 == &_max2);		\
 	_max1 > _max2 ? _max1 : _max2; })
 
+#define ALIGN(len, x) ( ((len)+(x)-1) & ~((x)-1) )
+
 extern void memory_allocation_error(void) __noreturn;
 
 extern void xfree(const void *ptr);
diff --git a/src/evaluate.c b/src/evaluate.c
index 94fee64..fbc4490 100644
--- a/src/evaluate.c
+++ b/src/evaluate.c
@@ -585,11 +585,13 @@ static int list_member_evaluate(struct eval_ctx *ctx, struct expr **expr)
 	return err;
 }
 
+#define ALIGN(len, x) ( ((len)+(x)-1) & ~((x)-1) )
+
 static int expr_evaluate_concat(struct eval_ctx *ctx, struct expr **expr)
 {
 	const struct datatype *dtype = ctx->ectx.dtype, *tmp;
 	unsigned int type = dtype ? dtype->type : 0;
-	int off = dtype ? dtype->size: 0;
+	int off = dtype ? dtype->size: 0, len = 0;
 	unsigned int flags = EXPR_F_CONSTANT | EXPR_F_SINGLETON;
 	struct expr *i, *next;
 	unsigned int n;
@@ -606,6 +608,8 @@ static int expr_evaluate_concat(struct eval_ctx *ctx, struct expr **expr)
 
 		if (list_member_evaluate(ctx, &i) < 0)
 			return -1;
+
+		len += ALIGN(i->dtype->size, 32);
 		flags &= i->flags;
 
 		n++;
@@ -613,6 +617,7 @@ static int expr_evaluate_concat(struct eval_ctx *ctx, struct expr **expr)
 
 	(*expr)->flags |= flags;
 	(*expr)->dtype = concat_type_alloc(*expr);
+	(*expr)->len = len;
 
 	if (off > 0)
 		return expr_error(ctx, *expr,
diff --git a/src/netlink.c b/src/netlink.c
index a62c357..d3b369b 100644
--- a/src/netlink.c
+++ b/src/netlink.c
@@ -151,8 +151,19 @@ static struct nft_set_elem *alloc_nft_setelem(const struct expr *expr)
 		netlink_gen_data(expr, &nld);
 		nft_set_elem_attr_set(nlse, NFT_SET_ELEM_ATTR_KEY,
 				      &nld.value, nld.len);
-	} else {
-		assert(expr->ops->type == EXPR_MAPPING);
+	} else if (expr->ops->type == EXPR_CONCAT) {
+		struct expr *i;
+		struct nft_data_linearize nld2 = {};
+
+		list_for_each_entry(i, &expr->expressions, list) {
+			struct nft_data_linearize nld = {};
+			netlink_gen_data(i, &nld);
+			nld2.len += ALIGN(nld.len, 4);
+			memcpy(&nld2.value[div_round_up(nld2.len, 4) - 1], nld.value, nld.len);
+		}
+		nft_set_elem_attr_set(nlse, NFT_SET_ELEM_ATTR_KEY,
+				      &nld2.value, nld2.len);
+	} else if (expr->ops->type == EXPR_MAPPING) {
 		netlink_gen_data(expr->left, &nld);
 		nft_set_elem_attr_set(nlse, NFT_SET_ELEM_ATTR_KEY,
 				      &nld.value, nld.len);
diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c
index da8be20..23d9c61 100644
--- a/src/netlink_linearize.c
+++ b/src/netlink_linearize.c
@@ -19,21 +19,130 @@
 #include <gmputil.h>
 #include <utils.h>
 
+enum reg_mode {
+	REG128 = 0,
+	REG64,
+	REG32,
+};
+
 struct netlink_linearize_ctx {
 	struct nft_rule		*nlr;
+	enum reg_mode		mode;
 	unsigned int		reg_low;
+	unsigned int		reg_low_64;
+	unsigned int		reg_low_32;
+};
+
+static LIST_HEAD(delta_list);
+
+struct delta {
+	struct list_head	list;
+	int			reg_low;
+	int			reg_low_64;
+	int			reg_low_32;
+};
+
+static int reg_to_offset[NFT_REG_ADDR_MAX] = {
+        /* 128-bits addressing */
+        [NFT_REG_VERDICT]       = 0,
+        [NFT_REG_1]             = 4,
+        [NFT_REG_2]             = 8,
+        [NFT_REG_3]             = 12,
+        [NFT_REG_4]             = 16,
+        /* 64-bits addressing */
+        [NFT_REG_5]             = 0, /* NFT_REG_VERDICT */
+        [NFT_REG_6]             = 2, /* NFT_REG_VERDICT */
+        [NFT_REG_7]             = 4,
+        [NFT_REG_8]             = 6,
+        [NFT_REG_9]             = 8,
+        [NFT_REG_10]            = 10,
+        [NFT_REG_11]            = 12,
+        [NFT_REG_12]            = 14,
+        [NFT_REG_13]            = 16,
+        [NFT_REG_14]            = 18,
+        /* 32-bits addressing */
+        [NFT_REG_15]            = 0, /* NFT_REG_VERDICT */
+        [NFT_REG_16]            = 1, /* NFT_REG_VERDICT */
+        [NFT_REG_17]            = 2, /* NFT_REG_VERDICT */
+        [NFT_REG_18]            = 3, /* NFT_REG_VERDICT */
+        [NFT_REG_19]            = 4,
+        [NFT_REG_20]            = 5,
+        [NFT_REG_21]            = 6,
+        [NFT_REG_22]            = 7,
+        [NFT_REG_23]            = 8,
+        [NFT_REG_24]            = 9,
+        [NFT_REG_25]            = 10,
+        [NFT_REG_26]            = 11,
+        [NFT_REG_27]            = 12,
+        [NFT_REG_28]            = 13,
+        [NFT_REG_29]            = 14,
+        [NFT_REG_30]            = 15,
+        [NFT_REG_31]            = 16,
+        [NFT_REG_32]            = 17,
+        [NFT_REG_33]            = 18,
+        [NFT_REG_34]            = 19,
 };
 
 static enum nft_registers get_register(struct netlink_linearize_ctx *ctx)
 {
-	if (ctx->reg_low > NFT_REG_MAX)
-		BUG("register reg_low %u invalid\n", ctx->reg_low);
-	return ctx->reg_low++;
+	struct delta *delta = calloc(1, sizeof(struct delta));
+
+	switch (ctx->mode) {
+	case REG128:
+		ctx->reg_low_32 += 4;
+		delta->reg_low_32 = 4;
+		ctx->reg_low_64 = 2;
+		delta->reg_low_64 += 2;
+		delta->reg_low = 1;
+
+		list_add_tail(&delta->list, &delta_list);
+		return ctx->reg_low++;
+	case REG64:
+		if (reg_to_offset[ctx->reg_low_64] % 2 == 0) {
+			ctx->reg_low++;
+			delta->reg_low = 1;
+			ctx->reg_low_32 += 2;
+			delta->reg_low_32 = 2;
+		}
+		delta->reg_low_64 = 1;
+
+		list_add_tail(&delta->list, &delta_list);
+		return ctx->reg_low_64++;
+	case REG32:
+		if (reg_to_offset[ctx->reg_low_32] % 4 == 0) {
+			ctx->reg_low++;
+			delta->reg_low = 1;
+		}
+		if (reg_to_offset[ctx->reg_low_32] % 2 == 0) {
+			ctx->reg_low_64++;
+			delta->reg_low_64 = 1;
+		}
+		delta->reg_low_32 = 1;
+
+		list_add_tail(&delta->list, &delta_list);
+
+		return ctx->reg_low_32++;
+	}
+	return 0; /* Shouldn't happen */
 }
 
 static void release_register(struct netlink_linearize_ctx *ctx)
 {
-	ctx->reg_low--;
+	struct delta *delta;
+
+	if (delta_list.prev == &delta_list) {
+		printf("BUG: too many register releases\n");
+		return;
+	}
+
+	delta = list_entry(delta_list.prev, struct delta, list);
+	list_del(&delta->list);
+
+	ctx->reg_low_32 -= delta->reg_low_32;
+	ctx->reg_low_64 -= delta->reg_low_64;
+	ctx->reg_low -= delta->reg_low;
+
+	xfree(delta);
 }
 
 static void netlink_gen_expr(struct netlink_linearize_ctx *ctx,
@@ -46,8 +155,11 @@ static void netlink_gen_concat(struct netlink_linearize_ctx *ctx,
 {
 	const struct expr *i;
 
-	list_for_each_entry(i, &expr->expressions, list)
+	list_for_each_entry(i, &expr->expressions, list) {
 		netlink_gen_expr(ctx, i, dreg);
+		dreg = get_register(ctx);
+	}
+	release_register(ctx);
 }
 
 static void netlink_gen_payload(struct netlink_linearize_ctx *ctx,
@@ -117,6 +229,10 @@ static void netlink_gen_map(struct netlink_linearize_ctx *ctx,
 
 	assert(expr->mappings->ops->type == EXPR_SET_REF);
 
+	/* Enter 32 bits mode to squash data into registers */
+	if (expr->map->left->ops->type == EXPR_CONCAT)
+		ctx->mode = REG32;
+
 	if (dreg == NFT_REG_VERDICT)
 		sreg = get_register(ctx);
 	else
@@ -133,6 +249,9 @@ static void netlink_gen_map(struct netlink_linearize_ctx *ctx,
 	if (dreg == NFT_REG_VERDICT)
 		release_register(ctx);
 
+	if (expr->map->left->ops->type == EXPR_CONCAT)
+		ctx->mode = REG128;
+
 	nft_rule_add_expr(ctx->nlr, nle);
 }
 
@@ -146,6 +265,10 @@ static void netlink_gen_lookup(struct netlink_linearize_ctx *ctx,
 	assert(expr->right->ops->type == EXPR_SET_REF);
 	assert(dreg == NFT_REG_VERDICT);
 
+	/* Enter 32 bits mode to squash data into registers */
+	if (expr->left->ops->type == EXPR_CONCAT)
+		ctx->mode = REG32;
+
 	sreg = get_register(ctx);
 	netlink_gen_expr(ctx, expr->left, sreg);
 
@@ -155,6 +278,11 @@ static void netlink_gen_lookup(struct netlink_linearize_ctx *ctx,
 			      expr->right->set->handle.set);
 
 	release_register(ctx);
+
+	/* Back to 128 bits mode */
+	if (expr->left->ops->type == EXPR_CONCAT)
+		ctx->mode = REG128;
+
 	nft_rule_add_expr(ctx->nlr, nle);
 }
 
@@ -666,6 +794,8 @@ int netlink_linearize_rule(struct netlink_ctx *ctx, struct nft_rule *nlr,
 
 	memset(&lctx, 0, sizeof(lctx));
 	lctx.reg_low = NFT_REG_1;
+	lctx.reg_low_64 = NFT_REG_7;
+	lctx.reg_low_32 = NFT_REG_19;
 	lctx.nlr = nlr;
 
 	list_for_each_entry(stmt, &rule->stmts, list)
