gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/bpf/optimizer_test.go (about) 1 // Copyright 2023 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package bpf 16 17 import ( 18 "slices" 19 "strings" 20 "testing" 21 ) 22 23 func prettyInstructions(insns []Instruction) string { 24 if len(insns) == 0 { 25 return "[no instructions]" 26 } 27 if len(insns) == 1 { 28 return insns[0].String() 29 } 30 var sb strings.Builder 31 sb.WriteString("{\n") 32 for _, ins := range insns { 33 sb.WriteString(" ") 34 sb.WriteString(ins.String()) 35 sb.WriteRune('\n') 36 } 37 sb.WriteRune('}') 38 return sb.String() 39 } 40 41 func TestOptimize(t *testing.T) { 42 for _, test := range []struct { 43 name string 44 optimizers []optimizerFunc // If unset, use all optimizers 45 insns []Instruction 46 want []Instruction 47 }{ 48 { 49 name: "trivial program", 50 insns: []Instruction{ 51 Stmt(Ret|K, 0), 52 }, 53 want: []Instruction{ 54 Stmt(Ret|K, 0), 55 }, 56 }, 57 { 58 name: "conditional jump", 59 optimizers: []optimizerFunc{optimizeConditionalJumps}, 60 insns: []Instruction{ 61 Stmt(Ld|Imm|W, 42), 62 Jump(Jmp|Jeq|K, 42, 0, 1), 63 Jump(Jmp|Ja, 2, 0, 0), 64 Jump(Jmp|Ja, 0, 0, 0), 65 Stmt(Ld|Imm|W, 37), 66 Stmt(Ret|K, 0), 67 }, 68 want: []Instruction{ 69 Stmt(Ld|Imm|W, 42), 70 Jump(Jmp|Jeq|K, 42, 3, 2), 71 Jump(Jmp|Ja, 2, 0, 0), 72 Jump(Jmp|Ja, 0, 0, 0), 73 Stmt(Ld|Imm|W, 37), 74 Stmt(Ret|K, 0), 75 }, 76 }, 77 { 78 name: "same final target jump", 79 optimizers: []optimizerFunc{ 80 optimizeConditionalJumps, 81 optimizeSameTargetConditionalJumps, 82 }, 83 insns: []Instruction{ 84 Stmt(Ld|Imm|W, 42), 85 Jump(Jmp|Jeq|K, 42, 0, 1), 86 Jump(Jmp|Ja, 1, 0, 0), 87 Jump(Jmp|Ja, 0, 0, 0), 88 Stmt(Ld|Imm|W, 37), 89 Stmt(Ret|K, 0), 90 }, 91 want: []Instruction{ 92 Stmt(Ld|Imm|W, 42), 93 Jump(Jmp|Ja, 2, 0, 0), 94 Jump(Jmp|Ja, 1, 0, 0), 95 Jump(Jmp|Ja, 0, 0, 0), 96 Stmt(Ld|Imm|W, 37), 97 Stmt(Ret|K, 0), 98 }, 99 }, 100 { 101 name: "dead code removed", 102 optimizers: []optimizerFunc{ 103 optimizeConditionalJumps, 104 optimizeSameTargetConditionalJumps, 105 removeDeadCode, 106 }, 107 insns: []Instruction{ 108 Stmt(Ld|Imm|W, 42), 109 Jump(Jmp|Jeq|K, 42, 0, 1), 110 Jump(Jmp|Ja, 1, 0, 0), 111 Jump(Jmp|Ja, 0, 0, 0), 112 Stmt(Ld|Imm|W, 37), 113 Stmt(Ret|K, 0), 114 }, 115 want: []Instruction{ 116 Stmt(Ld|Imm|W, 42), 117 Jump(Jmp|Ja, 0, 0, 0), 118 Stmt(Ld|Imm|W, 37), 119 Stmt(Ret|K, 0), 120 }, 121 }, 122 { 123 name: "zero-instructions jumps removed", 124 optimizers: []optimizerFunc{ 125 optimizeConditionalJumps, 126 optimizeSameTargetConditionalJumps, 127 removeZeroInstructionJumps, 128 removeDeadCode, 129 }, 130 insns: []Instruction{ 131 Stmt(Ld|Imm|W, 42), 132 Jump(Jmp|Jeq|K, 42, 0, 1), 133 Jump(Jmp|Ja, 1, 0, 0), 134 Jump(Jmp|Ja, 0, 0, 0), 135 Stmt(Ld|Imm|W, 37), 136 Stmt(Ret|K, 0), 137 }, 138 want: []Instruction{ 139 Stmt(Ld|Imm|W, 42), 140 Stmt(Ld|Imm|W, 37), 141 Stmt(Ret|K, 0), 142 }, 143 }, 144 { 145 name: "redundant loads removed", 146 optimizers: []optimizerFunc{ 147 removeRedundantLoads, 148 }, 149 insns: []Instruction{ 150 Stmt(Ld|Imm|W, 42), // Not removed. 151 Jump(Jmp|Jeq|K, 42, 0, 0), 152 Jump(Jmp|Jgt|K, 42, 1, 0), 153 Stmt(Ld|Imm|W, 42), // Removed as it is equal in all cases. 154 Jump(Jmp|Jeq|K, 42, 1, 0), 155 Stmt(Ld|Imm|W, 43), 156 Stmt(Ld|Imm|W, 43), // Not removed as the jump above may skip over previous instruction. 157 Stmt(Ld|Imm|W, 43), // Removed. 158 Stmt(Ld|Imm|W, 44), // Not removed. 159 Stmt(Ld|Imm|W, 44), // Removed. 160 Stmt(Ld|Abs|W, 0), // Not removed. 161 Stmt(Ld|Abs|W, 0), // Removed. 162 Stmt(Ld|Abs|W, 0), // Removed. 163 Stmt(Ld|Abs|W, 4), // Not removed. 164 Stmt(Ld|Abs|W, 0), // Not removed. 165 Stmt(Ld|Abs|W, 11), // Not removed. 166 Jump(Jmp|Jeq|K, 42, 1, 0), // "True" branch will be culled. 167 Stmt(Ld|Abs|W, 11), // Removed as there is only one way to get here and it already loads 11. 168 Stmt(Ld|Abs|W, 11), // There are two branches to get here but the first gets culled, so it is still removed. 169 Stmt(Ld|Abs|W, 11), // Removed. 170 Stmt(Ret|K, 0), 171 }, 172 want: []Instruction{ 173 Stmt(Ld|Imm|W, 42), 174 Jump(Jmp|Jeq|K, 42, 0, 0), 175 Jump(Jmp|Jgt|K, 42, 0, 0), 176 Jump(Jmp|Jeq|K, 42, 1, 0), 177 Stmt(Ld|Imm|W, 43), 178 Stmt(Ld|Imm|W, 43), 179 Stmt(Ld|Imm|W, 44), 180 Stmt(Ld|Abs|W, 0), 181 Stmt(Ld|Abs|W, 4), 182 Stmt(Ld|Abs|W, 0), 183 Stmt(Ld|Abs|W, 11), 184 Jump(Jmp|Jeq|K, 42, 0, 0), 185 Stmt(Ret|K, 0), 186 }, 187 }, 188 { 189 name: "jumps to return", 190 optimizers: []optimizerFunc{ 191 optimizeJumpsToReturn, 192 }, 193 insns: []Instruction{ 194 Stmt(Ld|Imm|W, 42), 195 Jump(Jmp|Jeq|K, 42, 0, 1), 196 Jump(Jmp|Ja, 1, 0, 0), 197 Jump(Jmp|Ja, 2, 0, 0), 198 Stmt(Ld|Imm|W, 37), 199 Stmt(Ret|K, 0), 200 Stmt(Ret|K, 1), 201 }, 202 want: []Instruction{ 203 Stmt(Ld|Imm|W, 42), 204 Jump(Jmp|Jeq|K, 42, 0, 1), 205 Jump(Jmp|Ja, 1, 0, 0), 206 Stmt(Ret|K, 1), 207 Stmt(Ld|Imm|W, 37), 208 Stmt(Ret|K, 0), 209 Stmt(Ret|K, 1), 210 }, 211 }, 212 { 213 name: "jumps to smallest set of return", 214 optimizers: []optimizerFunc{ 215 removeDeadCode, 216 optimizeJumpsToSmallestSetOfReturns, 217 }, 218 insns: []Instruction{ 219 Stmt(Ld|Imm|W, 42), 220 Jump(Jmp|Jeq|K, 42, 0, 1), 221 Stmt(Ret|K, 7), 222 Jump(Jmp|Jeq|K, 43, 0, 1), 223 Stmt(Ret|K, 7), 224 Jump(Jmp|Jeq|K, 44, 0, 1), 225 Stmt(Ret|K, 7), 226 Jump(Jmp|Jeq|K, 45, 0, 1), 227 Stmt(Ret|K, 7), 228 Jump(Jmp|Jeq|K, 46, 0, 1), 229 Stmt(Ret|K, 7), 230 Jump(Jmp|Jeq|K, 47, 0, 1), 231 Stmt(Ret|K, 7), 232 Stmt(Ret|K, 3), 233 }, 234 want: []Instruction{ 235 Stmt(Ld|Imm|W, 42), 236 Jump(Jmp|Jeq|K, 42, 5, 0), 237 Jump(Jmp|Jeq|K, 43, 4, 0), 238 Jump(Jmp|Jeq|K, 44, 3, 0), 239 Jump(Jmp|Jeq|K, 45, 2, 0), 240 Jump(Jmp|Jeq|K, 46, 1, 0), 241 Jump(Jmp|Jeq|K, 47, 0, 1), 242 Stmt(Ret|K, 7), 243 Stmt(Ret|K, 3), 244 }, 245 }, 246 { 247 name: "jumps to smallest set of return but keep fallthrough return statements", 248 optimizers: []optimizerFunc{ 249 removeDeadCode, 250 optimizeJumpsToSmallestSetOfReturns, 251 }, 252 insns: []Instruction{ 253 Stmt(Ld|Imm|W, 42), 254 Jump(Jmp|Jeq|K, 42, 0, 1), 255 Jump(Jmp|Jeq|K, 42, 1, 2), 256 Stmt(Ld|Imm|W, 43), 257 Stmt(Ret|K, 7), 258 Jump(Jmp|Jeq|K, 43, 0, 1), 259 Stmt(Ret|K, 7), 260 Jump(Jmp|Jeq|K, 44, 0, 1), 261 Stmt(Ret|K, 7), 262 Stmt(Ret|K, 3), 263 }, 264 want: []Instruction{ 265 Stmt(Ld|Imm|W, 42), 266 Jump(Jmp|Jeq|K, 42, 0, 1), 267 Jump(Jmp|Jeq|K, 42, 1, 2), 268 Stmt(Ld|Imm|W, 43), 269 Stmt(Ret|K, 7), 270 Jump(Jmp|Jeq|K, 43, 1, 0), 271 Jump(Jmp|Jeq|K, 44, 0, 1), 272 Stmt(Ret|K, 7), 273 Stmt(Ret|K, 3), 274 }, 275 }, 276 { 277 name: "all optimizations", 278 insns: []Instruction{ 279 Stmt(Ld|Imm|W, 42), 280 Jump(Jmp|Jeq|K, 42, 0, 1), 281 Jump(Jmp|Ja, 1, 0, 0), 282 Jump(Jmp|Ja, 2, 0, 0), 283 Stmt(Ld|Imm|W, 37), 284 Stmt(Ret|K, 0), 285 Stmt(Ret|K, 1), 286 }, 287 want: []Instruction{ 288 Stmt(Ld|Imm|W, 42), 289 Jump(Jmp|Jeq|K, 42, 0, 2), 290 Stmt(Ld|Imm|W, 37), 291 Stmt(Ret|K, 0), 292 Stmt(Ret|K, 1), 293 }, 294 }, 295 } { 296 t.Run(test.name, func(t *testing.T) { 297 optimizedInsns := make([]Instruction, len(test.insns)) 298 copy(optimizedInsns, test.insns) 299 if len(test.optimizers) > 0 { 300 optimizedInsns = optimize(optimizedInsns, test.optimizers) 301 } else { 302 optimizedInsns = Optimize(optimizedInsns) 303 } 304 if !slices.Equal(optimizedInsns, test.want) { 305 t.Errorf("got optimized instructions:\n%v\nwant:\n%v\n", prettyInstructions(optimizedInsns), prettyInstructions(test.want)) 306 } 307 }) 308 } 309 }