1# Copyright (c) Barefoot Networks, Inc. 2# Licensed under the Apache License, Version 2.0 (the "License") 3 4from p4_hlir.hlir import p4_action, p4_field 5from p4_hlir.hlir import p4_signature_ref, p4_header_instance 6import ebpfProgram 7from programSerializer import ProgramSerializer 8from compilationException import * 9import ebpfScalarType 10import ebpfCounter 11import ebpfType 12import ebpfInstance 13 14 15class EbpfActionData(object): 16 def __init__(self, name, argtype): 17 self.name = name 18 self.argtype = argtype 19 20 21class EbpfActionBase(object): 22 def __init__(self, p4action): 23 self.name = p4action.name 24 self.hliraction = p4action 25 self.builtin = False 26 self.arguments = [] 27 28 def serializeArgumentsAsStruct(self, serializer): 29 serializer.emitIndent() 30 serializer.appendFormat("/* no arguments for {0} */", self.name) 31 serializer.newline() 32 33 def serializeBody(self, serializer, valueName, program): 34 serializer.emitIndent() 35 serializer.appendFormat("/* no body for {0} */", self.name) 36 serializer.newline() 37 38 def __str__(self): 39 return "EbpfAction({0})".format(self.name) 40 41 42class EbpfAction(EbpfActionBase): 43 unsupported = [ 44 # The following cannot be done in EBPF 45 "add_header", "remove_header", "execute_meter", 46 "clone_ingress_pkt_to_egress", 47 "clone_egress_pkt_to_egress", "generate_digest", "resubmit", 48 "modify_field_with_hash_based_offset", "truncate", "push", "pop", 49 # The following could be done, but are not yet implemented 50 # The situation with copy_header is complicated, 51 # because we don't do checksums 52 "copy_header", "count", 53 "register_read", "register_write"] 54 55 # noinspection PyUnresolvedReferences 56 def __init__(self, p4action, program): 57 super(EbpfAction, self).__init__(p4action) 58 assert isinstance(p4action, p4_action) 59 assert isinstance(program, ebpfProgram.EbpfProgram) 60 61 self.builtin = False 62 self.invalid = False # a leaf action which is never 63 # called from a table can be invalid. 64 65 for i in range(0, len(p4action.signature)): 66 param = p4action.signature[i] 67 width = p4action.signature_widths[i] 68 if width is None: 69 self.invalid = True 70 return 71 argtype = ebpfScalarType.EbpfScalarType(p4action, width, 72 False, program.config) 73 actionData = EbpfActionData(param, argtype) 74 self.arguments.append(actionData) 75 76 def serializeArgumentsAsStruct(self, serializer): 77 if self.invalid: 78 raise CompilationException(True, 79 "{0} Attempting to generate code for an invalid action", 80 self.hliraction) 81 82 # Build a struct containing all action arguments. 83 serializer.emitIndent() 84 serializer.append("struct ") 85 serializer.blockStart() 86 assert isinstance(serializer, ProgramSerializer) 87 for arg in self.arguments: 88 assert isinstance(arg, EbpfActionData) 89 serializer.emitIndent() 90 argtype = arg.argtype 91 assert isinstance(argtype, ebpfType.EbpfType) 92 argtype.declare(serializer, arg.name, False) 93 serializer.endOfStatement(True) 94 serializer.blockEnd(False) 95 serializer.space() 96 serializer.append(self.name) 97 serializer.endOfStatement(True) 98 99 def serializeBody(self, serializer, dataContainer, program): 100 if self.invalid: 101 raise CompilationException(True, 102 "{0} Attempting to generate code for an invalid action", 103 self.hliraction) 104 105 # TODO: generate PARALLEL implementation 106 # dataContainer is a string containing the variable name 107 # containing the action data 108 assert isinstance(serializer, ProgramSerializer) 109 assert isinstance(program, ebpfProgram.EbpfProgram) 110 assert isinstance(dataContainer, str) 111 callee_list = self.hliraction.flat_call_sequence 112 for e in callee_list: 113 action = e[0] 114 assert isinstance(action, p4_action) 115 arguments = e[1] 116 assert isinstance(arguments, list) 117 self.serializeCallee(self, action, arguments, serializer, 118 dataContainer, program) 119 120 def checkSize(self, call, args, program): 121 size = None 122 for a in args: 123 if a is None: 124 continue 125 if size is None: 126 size = a 127 elif a != size: 128 program.emitWarning( 129 "{0}: Arguments do not have the same size {1} and {2}", 130 call, size, a) 131 return size 132 133 @staticmethod 134 def translateActionToOperator(actionName): 135 if actionName == "add" or actionName == "add_to_field": 136 return "+" 137 elif actionName == "bit_and": 138 return "&" 139 elif actionName == "bit_or": 140 return "|" 141 elif actionName == "bit_xor": 142 return "^" 143 elif actionName == "subtract" or actionName == "subtract_from_field": 144 return "-" 145 else: 146 raise CompilationException(True, 147 "Unexpected primitive action {0}", 148 actionName) 149 150 def serializeCount(self, caller, arguments, serializer, 151 dataContainer, program): 152 assert isinstance(serializer, ProgramSerializer) 153 assert isinstance(program, ebpfProgram.EbpfProgram) 154 assert isinstance(arguments, list) 155 assert len(arguments) == 2 156 157 counter = arguments[0] 158 index = ArgInfo(arguments[1], caller, dataContainer, program) 159 ctr = program.getCounter(counter.name) 160 assert isinstance(ctr, ebpfCounter.EbpfCounter) 161 serializer.emitIndent() 162 serializer.blockStart() 163 164 # This is actually incorrect, since the key is not always an u32. 165 # This code is currently disabled 166 key = program.reservedPrefix + "index" 167 serializer.emitIndent() 168 serializer.appendFormat("u32 {0} = {1};", key, index.asString) 169 serializer.newline() 170 171 ctr.serializeCode(key, serializer, program) 172 173 serializer.blockEnd(True) 174 175 def serializeCallee(self, caller, callee, arguments, 176 serializer, dataContainer, program): 177 if self.invalid: 178 raise CompilationException( 179 True, 180 "{0} Attempting to generate code for an invalid action", 181 self.hliraction) 182 183 assert isinstance(serializer, ProgramSerializer) 184 assert isinstance(program, ebpfProgram.EbpfProgram) 185 assert isinstance(callee, p4_action) 186 assert isinstance(arguments, list) 187 188 if callee.name in EbpfAction.unsupported: 189 raise NotSupportedException("{0}", callee) 190 191 # This is not yet ready 192 #if callee.name == "count": 193 # self.serializeCount(caller, arguments, 194 # serializer, dataContainer, program) 195 # return 196 197 serializer.emitIndent() 198 args = self.transformArguments(arguments, caller, 199 dataContainer, program) 200 if callee.name == "modify_field": 201 dst = args[0] 202 src = args[1] 203 204 size = self.checkSize(callee, 205 [a.widthInBits() for a in args], 206 program) 207 if size is None: 208 raise CompilationException( 209 True, "Cannot infer width for arguments {0}", 210 callee) 211 elif size <= 32: 212 serializer.appendFormat("{0} = {1};", 213 dst.asString, 214 src.asString) 215 else: 216 if not dst.isLvalue: 217 raise NotSupportedException( 218 "Constants wider than 32-bit: {0}({1})", 219 dst.caller, dst.asString) 220 if not src.isLvalue: 221 raise NotSupportedException( 222 "Constants wider than 32-bit: {0}({1})", 223 src.caller, src.asString) 224 serializer.appendFormat("memcpy(&{0}, &{1}, {2});", 225 dst.asString, 226 src.asString, 227 size / 8) 228 elif (callee.name == "add" or 229 callee.name == "bit_and" or 230 callee.name == "bit_or" or 231 callee.name == "bit_xor" or 232 callee.name == "subtract"): 233 size = self.checkSize(callee, 234 [a.widthInBits() for a in args], 235 program) 236 if size is None: 237 raise CompilationException( 238 True, 239 "Cannot infer width for arguments {0}", 240 callee) 241 if size > 32: 242 raise NotSupportedException("{0}: Arithmetic on {1}-bits", 243 callee, size) 244 op = EbpfAction.translateActionToOperator(callee.name) 245 serializer.appendFormat("{0} = {1} {2} {3};", 246 args[0].asString, 247 args[1].asString, 248 op, 249 args[2].asString) 250 elif (callee.name == "add_to_field" or 251 callee.name == "subtract_from_field"): 252 size = self.checkSize(callee, 253 [a.widthInBits() for a in args], 254 program) 255 if size is None: 256 raise CompilationException( 257 True, "Cannot infer width for arguments {0}", callee) 258 if size > 32: 259 raise NotSupportedException( 260 "{0}: Arithmetic on {1}-bits", callee, size) 261 262 op = EbpfAction.translateActionToOperator(callee.name) 263 serializer.appendFormat("{0} = {0} {1} {2};", 264 args[0].asString, 265 op, 266 args[1].asString) 267 elif callee.name == "no_op": 268 serializer.append("/* noop */") 269 elif callee.name == "drop": 270 serializer.appendFormat("{0} = 1;", program.dropBit) 271 elif callee.name == "push" or callee.name == "pop": 272 raise CompilationException( 273 True, "{0} push/pop not yet implemented", callee) 274 else: 275 raise CompilationException( 276 True, "Unexpected primitive action {0}", callee) 277 serializer.newline() 278 279 def transformArguments(self, arguments, caller, dataContainer, program): 280 result = [] 281 for a in arguments: 282 t = ArgInfo(a, caller, dataContainer, program) 283 result.append(t) 284 return result 285 286 287class BuiltinAction(EbpfActionBase): 288 def __init__(self, p4action): 289 super(BuiltinAction, self).__init__(p4action) 290 self.builtin = True 291 292 def serializeBody(self, serializer, valueName, program): 293 # This is ugly; there should be a better way 294 if self.name == "drop": 295 serializer.emitIndent() 296 serializer.appendFormat("{0} = 1;", program.dropBit) 297 serializer.newline() 298 else: 299 serializer.emitIndent() 300 serializer.appendFormat("/* no body for {0} */", self.name) 301 serializer.newline() 302 303 304class ArgInfo(object): 305 # noinspection PyUnresolvedReferences 306 # Represents an argument passed to an action 307 def __init__(self, argument, caller, dataContainer, program): 308 self.width = None 309 self.asString = None 310 self.isLvalue = True 311 self.caller = caller 312 313 assert isinstance(program, ebpfProgram.EbpfProgram) 314 assert isinstance(caller, EbpfAction) 315 316 if isinstance(argument, int): 317 self.asString = str(argument) 318 self.isLvalue = False 319 # size is unknown 320 elif isinstance(argument, p4_field): 321 if ebpfProgram.EbpfProgram.isArrayElementInstance( 322 argument.instance): 323 if isinstance(argument.instance.index, int): 324 index = "[" + str(argument.instance.index) + "]" 325 else: 326 raise CompilationException( 327 True, 328 "Unexpected index for array {0}", 329 argument.instance.index) 330 stackInstance = program.getStackInstance( 331 argument.instance.base_name) 332 assert isinstance(stackInstance, ebpfInstance.EbpfHeaderStack) 333 fieldtype = stackInstance.basetype.getField(argument.name) 334 self.width = fieldtype.widthInBits() 335 self.asString = "{0}.{1}{3}.{2}".format( 336 program.headerStructName, 337 stackInstance.name, argument.name, index) 338 else: 339 instance = program.getInstance(argument.instance.base_name) 340 if isinstance(instance, ebpfInstance.EbpfHeader): 341 parent = program.headerStructName 342 else: 343 parent = program.metadataStructName 344 fieldtype = instance.type.getField(argument.name) 345 self.width = fieldtype.widthInBits() 346 self.asString = "{0}.{1}.{2}".format( 347 parent, instance.name, argument.name) 348 elif isinstance(argument, p4_signature_ref): 349 refarg = caller.arguments[argument.idx] 350 self.asString = "{0}->u.{1}.{2}".format( 351 dataContainer, caller.name, refarg.name) 352 self.width = caller.arguments[argument.idx].argtype.widthInBits() 353 elif isinstance(argument, p4_header_instance): 354 # This could be a header array element 355 # Unfortunately for push and pop, the user mean the whole array, 356 # but the representation contains just the first element here. 357 # This looks like a bug in the HLIR. 358 if ebpfProgram.EbpfProgram.isArrayElementInstance(argument): 359 if isinstance(argument.index, int): 360 index = "[" + str(argument.index) + "]" 361 else: 362 raise CompilationException( 363 True, 364 "Unexpected index for array {0}", argument.index) 365 stackInstance = program.getStackInstance(argument.base_name) 366 assert isinstance(stackInstance, ebpfInstance.EbpfHeaderStack) 367 fieldtype = stackInstance.basetype 368 self.width = fieldtype.widthInBits() 369 self.asString = "{0}.{1}{2}".format( 370 program.headerStructName, stackInstance.name, index) 371 else: 372 instance = program.getInstance(argument.name) 373 instancetype = instance.type 374 self.width = instancetype.widthInBits() 375 self.asString = "{0}.{1}".format( 376 program.headerStructName, argument.name) 377 else: 378 raise CompilationException( 379 True, "Unexpected action argument {0}", argument) 380 381 def widthInBits(self): 382 return self.width 383