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  }