Skip to contentSkip to conversation

BPF JIT for PPC64

RFC PATCH In review Matt Evans 4 participants 7 messages Jun 24, 2011 – Jul 11, 2011

Subsystems (5)
lore
Share & tools
lore

Matt Evans

author
#0 lore raw

RFC PATCH 1/1 BPF JIT for PPC64

 arch/powerpc/Kconfig                  |    1 +
 arch/powerpc/Makefile                 |    3 +-
 arch/powerpc/include/asm/ppc-opcode.h |   40 ++
 arch/powerpc/net/Makefile             |    4 +
 arch/powerpc/net/bpf_jit.S            |  138 +++++++
 arch/powerpc/net/bpf_jit.h            |  226 +++++++++++
 arch/powerpc/net/bpf_jit_comp.c       |  697 +++++++++++++++++++++++++++++++++
 7 files changed, 1108 insertions(+), 1 deletions(-)
arch/powerpc/Kconfig +1 -0
index 2729c66..39860fc 100644
@@ -134,6 +134,7 @@ config PPC
 	select GENERIC_IRQ_SHOW_LEVEL
 	select HAVE_RCU_TABLE_FREE if SMP
 	select HAVE_SYSCALL_TRACEPOINTS
+	select HAVE_BPF_JIT if PPC64
 
 config EARLY_PRINTK
 	bool
arch/powerpc/Makefile +2 -1
index b7212b6..b94740f 100644
@@ -154,7 +154,8 @@ core-y				+= arch/powerpc/kernel/ \
 				   arch/powerpc/lib/ \
 				   arch/powerpc/sysdev/ \
 				   arch/powerpc/platforms/ \
-				   arch/powerpc/math-emu/
+				   arch/powerpc/math-emu/ \
+				   arch/powerpc/net/
 core-$(CONFIG_XMON)		+= arch/powerpc/xmon/
 core-$(CONFIG_KVM) 		+= arch/powerpc/kvm/
 
arch/powerpc/include/asm/ppc-opcode.h +40 -0
index e472659..e980faa 100644
@@ -71,6 +71,42 @@
 #define PPC_INST_ERATSX			0x7c000126
 #define PPC_INST_ERATSX_DOT		0x7c000127
 
+/* Misc instructions for BPF compiler */
+#define PPC_INST_LD			0xe8000000
+#define PPC_INST_LHZ			0xa0000000
+#define PPC_INST_LWZ			0x80000000
+#define PPC_INST_STD			0xf8000000
+#define PPC_INST_STDU			0xf8000001
+#define PPC_INST_MFLR			0x7c0802a6
+#define PPC_INST_MTLR			0x7c0803a6
+#define PPC_INST_CMPWI			0x2c000000
+#define PPC_INST_CMPDI			0x2c200000
+#define PPC_INST_CMPLW			0x7c000040
+#define PPC_INST_CMPLWI			0x28000000
+#define PPC_INST_ADDI			0x38000000
+#define PPC_INST_ADDIS			0x3c000000
+#define PPC_INST_ADD			0x7c000214
+#define PPC_INST_SUB			0x7c000050
+#define PPC_INST_BLR			0x4e800020
+#define PPC_INST_BLRL			0x4e800021
+#define PPC_INST_MULLW			0x7c0001d6
+#define PPC_INST_MULHWU			0x7c000016
+#define PPC_INST_MULLI			0x1c000000
+#define PPC_INST_DIVWU			0x7c0003d6
+#define PPC_INST_RLWINM			0x54000000
+#define PPC_INST_RLDICR			0x78000004
+#define PPC_INST_SLW			0x7c000030
+#define PPC_INST_SRW			0x7c000430
+#define PPC_INST_AND			0x7c000038
+#define PPC_INST_ANDDOT			0x7c000039
+#define PPC_INST_OR			0x7c000378
+#define PPC_INST_ANDI			0x70000000
+#define PPC_INST_ORI			0x60000000
+#define PPC_INST_ORIS			0x64000000
+#define PPC_INST_NEG			0x7c0000d0
+#define PPC_INST_BRANCH			0x48000000
+#define PPC_INST_BRANCH_COND		0x40800000
+
 /* macros to insert fields into opcodes */
 #define __PPC_RA(a)	(((a) & 0x1f) << 16)
 #define __PPC_RB(b)	(((b) & 0x1f) << 11)
@@ -83,6 +119,10 @@
 #define __PPC_T_TLB(t)	(((t) & 0x3) << 21)
 #define __PPC_WC(w)	(((w) & 0x3) << 21)
 #define __PPC_WS(w)	(((w) & 0x1f) << 11)
+#define __PPC_SH(s)	__PPC_WS(s)
+#define __PPC_MB(s)	(((s) & 0x1f) << 6)
+#define __PPC_ME(s)	(((s) & 0x1f) << 1)
+#define __PPC_BI(s)	(((s) & 0x1f) << 16)
 
 /*
  * Only use the larx hint bit on 64bit CPUs. e500v1/v2 based CPUs will treat a
arch/powerpc/net/Makefile new file +4 -0
new file mode 100644
index 0000000..90568c3
@@ -0,0 +1,4 @@
+#
+# Arch-specific network modules
+#
+obj-$(CONFIG_BPF_JIT) += bpf_jit.o bpf_jit_comp.o
arch/powerpc/net/bpf_jit.S new file +138 -0

138 lines not on this page · raw message

arch/powerpc/net/bpf_jit.h new file +226 -0

226 lines not on this page · raw message

arch/powerpc/net/bpf_jit_comp.c new file +697 -0

697 lines not on this page · raw message

#1 lore raw

On Fri, 2011-06-24 at 16:02 +1000, Matt Evans wrote:
[...]

arch/powerpc/net/bpf_jit_comp.c line 167

Quoted 9 lines
arch/powerpc/net/bpf_jit_comp.c new file +9 -0
+		case BPF_S_ALU_ADD_K: /* A += K; */
+			if (!K)
+				break;
+			if (K < 32768)
+				PPC_ADDI(r_A, r_A, K);
+			else
+				PPC_ADDI(r_A, r_A, IMM_L(K));
+				PPC_ADDIS(r_A, r_A, IMM_HA(K));
+			break;

Missing braces.

arch/powerpc/net/bpf_jit_comp.c line 180

Quoted 13 lines
arch/powerpc/net/bpf_jit_comp.c new file +13 -0
+		case BPF_S_ALU_SUB_X: /* A -= X; */
+			ctx->seen |= SEEN_XREG;
+			PPC_SUB(r_A, r_A, r_X);
+			break;
+		case BPF_S_ALU_SUB_K: /* A -= K */
+			if (!K)
+				break;
+			if (K < 32768)
+				PPC_ADDI(r_A, r_A, -K);
+			else
+				PPC_ADDI(r_A, r_A, IMM_L(-K));
+				PPC_ADDIS(r_A, r_A, IMM_HA(-K));
+			break;

[...]

Here as well.

Ben.

Signature · 3 lines
Ben Hutchings, Senior Software Engineer, Solarflare
Not speaking for my employer; that's the marketing department's job.
They asked us to note that Solarflare product names are trademarked.
author
#2 lore raw
Quoted 29 lines · Ben Hutchings

On Fri, 2011-06-24 at 16:02 +1000, Matt Evans wrote:
[...]

arch/powerpc/net/bpf_jit_comp.c line 167

Quoted 9 lines
arch/powerpc/net/bpf_jit_comp.c new file +9 -0
+		case BPF_S_ALU_ADD_K: /* A += K; */
+			if (!K)
+				break;
+			if (K < 32768)
+				PPC_ADDI(r_A, r_A, K);
+			else
+				PPC_ADDI(r_A, r_A, IMM_L(K));
+				PPC_ADDIS(r_A, r_A, IMM_HA(K));
+			break;

Missing braces.

Quoted 13 lines
arch/powerpc/net/bpf_jit_comp.c new file +13 -0
+		case BPF_S_ALU_SUB_X: /* A -= X; */
+			ctx->seen |= SEEN_XREG;
+			PPC_SUB(r_A, r_A, r_X);
+			break;
+		case BPF_S_ALU_SUB_K: /* A -= K */
+			if (!K)
+				break;
+			if (K < 32768)
+				PPC_ADDI(r_A, r_A, -K);
+			else
+				PPC_ADDI(r_A, r_A, IMM_L(-K));
+				PPC_ADDIS(r_A, r_A, IMM_HA(-K));
+			break;

[...]

Quoted 1 line

Here as well.

Thanks, Ben -- oops! :) Really, just the ADDISes need to be conditional, too.

Cheers,

Matt

#3 lore raw
In reply to Matt Evans

Review

Quoted 9 lines · Matt Evans
(quoted diff) +5 -0
+	stdu	r1, -128(r1);					\
 
+	addi	r5, r1, 128+BPF_PPC_STACK_BASIC+(2*8);		\
 
+	addi	r1, r1, 128;					\
 
+					PPC_STD(r_M + i, 1, -128 + (8*i));
 
+					PPC_LD(r_M + i, 1, -128 + (8*i));

s/128/BPF_PPC_STACK_SAVE/?

Andreas.

Signature · 3 lines
Andreas Schwab, [email protected]
GPG Key fingerprint = 58CA 54C7 6D53 942B 1756  01D3 44D5 214B 8276 4ED5
"And now for something completely different."
#4 lore raw
Quoted 6 lines · Andreas Schwab
Quoted 3 lines · Matt Evans
arch/powerpc/net/bpf_jit.S new file +2 -0
+	stdu	r1, -128(r1);					\
 
+	addi	r5, r1, 128+BPF_PPC_STACK_BASIC+(2*8);		\
Quoted 8 lines

Review

Quoted 5 lines
(quoted diff) +3 -0
+	addi	r1, r1, 128;					\
 
+					PPC_STD(r_M + i, 1, -128 + (8*i));
 
+					PPC_LD(r_M + i, 1, -128 + (8*i));

s/128/BPF_PPC_STACK_SAVE/?

I am not sure using registers to hold MEM[] is a win if MEM[idx] is used once in the filter

# tcpdump "tcp[20]+tcp[21]=0" -d
(000) ldh      [12]
(001) jeq      #0x800           jt 2	jf 15
(002) ldb      [23]
(003) jeq      #0x6             jt 4	jf 15
(004) ldh      [20]
(005) jset     #0x1fff          jt 15	jf 6
(006) ldxb     4*([14]&0xf)
(007) ldb      [x + 34]
(008) st       M[1]
(009) ldb      [x + 35]
(010) tax      
(011) ld       M[1]
(012) add      x
(013) jeq      #0x0             jt 14	jf 15
(014) ret      #65535
(015) ret      #0

In this sample, we use M[1] once ( one store, one load)

So saving previous register content on stack in prologue, and restoring
it in epilogue actually slow down the code, and adds two instructions in filter asm code.

This also makes epilogue code not easy (not possible as a matter of fact) to unwind in helper function

In x86_64 implementation, I chose bpf_error be able to force an exception, not returning to JIT code but directly to bpf_func() caller

bpf_error:

# force a return 0 from jit handler
        xor             %eax,%eax
        mov             -8(%rbp),%rbx
        leaveq
        ret

_______________________________________________
Linuxppc-dev mailing list
[email protected]
https://lists.ozlabs.org/listinfo/linuxppc-dev

author
#5 lore raw

Hi Eric,

Quoted 37 lines · Eric Dumazet
Quoted 6 lines · Andreas Schwab
Quoted 3 lines · Matt Evans
arch/powerpc/net/bpf_jit.S new file +2 -0
+	stdu	r1, -128(r1);					\
 
+	addi	r5, r1, 128+BPF_PPC_STACK_BASIC+(2*8);		\
Quoted 8 lines

Review

Quoted 5 lines
(quoted diff) +3 -0
+	addi	r1, r1, 128;					\
 
+					PPC_STD(r_M + i, 1, -128 + (8*i));
 
+					PPC_LD(r_M + i, 1, -128 + (8*i));

s/128/BPF_PPC_STACK_SAVE/?

I am not sure using registers to hold MEM[] is a win if MEM[idx] is used once in the filter

# tcpdump "tcp[20]+tcp[21]=0" -d
(000) ldh      [12]
(001) jeq      #0x800           jt 2	jf 15
(002) ldb      [23]
(003) jeq      #0x6             jt 4	jf 15
(004) ldh      [20]
(005) jset     #0x1fff          jt 15	jf 6
(006) ldxb     4*([14]&0xf)
(007) ldb      [x + 34]
(008) st       M[1]
(009) ldb      [x + 35]
(010) tax      
(011) ld       M[1]
(012) add      x
(013) jeq      #0x0             jt 14	jf 15
(014) ret      #65535
(015) ret      #0
Quoted 5 lines

In this sample, we use M[1] once ( one store, one load)

So saving previous register content on stack in prologue, and restoring it in epilogue actually slow down the code, and adds two instructions in filter asm code.

The x86 version generates all accesses of M[x] as a load or store to the stackframe, so your example would result in one store + one load to/from the stack. More than one access would result in N stores/loads. By having M[] live in r16-31 on PPC, there is a one-off cost of saving/restoring the (non-volatile) register to the stack plus a per-use cost of a register-register move (MR, which is pretty cheap compared to loads/stores!).

You are correct in that, for a single store/load of M[1] above, I'll generate a STD, MR, MR, LD, but this extra cost of the two MRs is pretty small. With the current implementation the gains seen when accessing M[x] /more/ than once will IMHO more than justify this. (For several M[x] accesses, x86 would have several mem ops, PPC would have several reg-reg moves and one load+store.)

An obvious alternative would be to use some of the three free volatile registers first (in the hope that "most" filters won't use >3 M[] locations), with a simple register allocator. This would save the (non-volatile reg) spill/fill to stack, too. In the interest of simplicity I didn't want to do that on a first pass.

Quoted 6 lines

This also makes epilogue code not easy (not possible as a matter of fact) to unwind in helper function

In x86_64 implementation, I chose bpf_error be able to force an exception, not returning to JIT code but directly to bpf_func() caller

Quoted 6 lines

bpf_error:

# force a return 0 from jit handler
        xor             %eax,%eax
        mov             -8(%rbp),%rbx
        leaveq
        ret

Yep, if I use non-volatile regs a return isn't just a simple stack "pop". Currently, I've an extra branch in the return path to hit the common epilogue. This could be optimised such that the out of line error path jumps directly to the common epilogue to return (rather than back to the callsite, checking a flag and /then/ to the epilogue) to speed up the non-error case. However, it's just a question of getting to the (existing) epilogue code to clean up; it doesn't need to be unwound in the helper function. I don't think this issue is a strong argument against having M[] exist in registers, though.

From the current position, I think going in the direction of using volatile regs (without backup/restore cost) is better than going in the direction of making all M[] references stack accesses. Do you think it's bearable to continue as it is and then perform that optimisation later?

Also, thanks for reading/commenting on the patch!

Cheers,

Matt

author
#6 lore raw
In reply to Andreas Schwab
Quoted 6 lines · Andreas Schwab
Quoted 3 lines · Matt Evans
arch/powerpc/net/bpf_jit.S new file +2 -0
+	stdu	r1, -128(r1);					\
 
+	addi	r5, r1, 128+BPF_PPC_STACK_BASIC+(2*8);		\
Quoted 7 lines

Review

Quoted 5 lines
(quoted diff) +3 -0
+	addi	r1, r1, 128;					\
 
+					PPC_STD(r_M + i, 1, -128 + (8*i));
 
+					PPC_LD(r_M + i, 1, -128 + (8*i));

s/128/BPF_PPC_STACK_SAVE/?

Actually, that's a different 128, but that nicely illustrates that I should've #defined something more recognisable :-) The second set, with -128, is actually in the save area for non-volatile regs, whereas the first is just a stackframe size.

Cheers,

Matt