# Copyright (c) Barefoot Networks, Inc. # Licensed under the Apache License, Version 2.0 (the "License") from p4_hlir.hlir import p4_action, p4_field from p4_hlir.hlir import p4_signature_ref, p4_header_instance import ebpfProgram from programSerializer import ProgramSerializer from compilationException import * import ebpfScalarType import ebpfCounter import ebpfType import ebpfInstance class EbpfActionData(object): def __init__(self, name, argtype): self.name = name self.argtype = argtype class EbpfActionBase(object): def __init__(self, p4action): self.name = p4action.name self.hliraction = p4action self.builtin = False self.arguments = [] def serializeArgumentsAsStruct(self, serializer): serializer.emitIndent() serializer.appendFormat("/* no arguments for {0} */", self.name) serializer.newline() def serializeBody(self, serializer, valueName, program): serializer.emitIndent() serializer.appendFormat("/* no body for {0} */", self.name) serializer.newline() def __str__(self): return "EbpfAction({0})".format(self.name) class EbpfAction(EbpfActionBase): unsupported = [ # The following cannot be done in EBPF "add_header", "remove_header", "execute_meter", "clone_ingress_pkt_to_egress", "clone_egress_pkt_to_egress", "generate_digest", "resubmit", "modify_field_with_hash_based_offset", "truncate", "push", "pop", # The following could be done, but are not yet implemented # The situation with copy_header is complicated, # because we don't do checksums "copy_header", "count", "register_read", "register_write"] # noinspection PyUnresolvedReferences def __init__(self, p4action, program): super(EbpfAction, self).__init__(p4action) assert isinstance(p4action, p4_action) assert isinstance(program, ebpfProgram.EbpfProgram) self.builtin = False self.invalid = False # a leaf action which is never # called from a table can be invalid. for i in range(0, len(p4action.signature)): param = p4action.signature[i] width = p4action.signature_widths[i] if width is None: self.invalid = True return argtype = ebpfScalarType.EbpfScalarType(p4action, width, False, program.config) actionData = EbpfActionData(param, argtype) self.arguments.append(actionData) def serializeArgumentsAsStruct(self, serializer): if self.invalid: raise CompilationException(True, "{0} Attempting to generate code for an invalid action", self.hliraction) # Build a struct containing all action arguments. serializer.emitIndent() serializer.append("struct ") serializer.blockStart() assert isinstance(serializer, ProgramSerializer) for arg in self.arguments: assert isinstance(arg, EbpfActionData) serializer.emitIndent() argtype = arg.argtype assert isinstance(argtype, ebpfType.EbpfType) argtype.declare(serializer, arg.name, False) serializer.endOfStatement(True) serializer.blockEnd(False) serializer.space() serializer.append(self.name) serializer.endOfStatement(True) def serializeBody(self, serializer, dataContainer, program): if self.invalid: raise CompilationException(True, "{0} Attempting to generate code for an invalid action", self.hliraction) # TODO: generate PARALLEL implementation # dataContainer is a string containing the variable name # containing the action data assert isinstance(serializer, ProgramSerializer) assert isinstance(program, ebpfProgram.EbpfProgram) assert isinstance(dataContainer, str) callee_list = self.hliraction.flat_call_sequence for e in callee_list: action = e[0] assert isinstance(action, p4_action) arguments = e[1] assert isinstance(arguments, list) self.serializeCallee(self, action, arguments, serializer, dataContainer, program) def checkSize(self, call, args, program): size = None for a in args: if a is None: continue if size is None: size = a elif a != size: program.emitWarning( "{0}: Arguments do not have the same size {1} and {2}", call, size, a) return size @staticmethod def translateActionToOperator(actionName): if actionName == "add" or actionName == "add_to_field": return "+" elif actionName == "bit_and": return "&" elif actionName == "bit_or": return "|" elif actionName == "bit_xor": return "^" elif actionName == "subtract" or actionName == "subtract_from_field": return "-" else: raise CompilationException(True, "Unexpected primitive action {0}", actionName) def serializeCount(self, caller, arguments, serializer, dataContainer, program): assert isinstance(serializer, ProgramSerializer) assert isinstance(program, ebpfProgram.EbpfProgram) assert isinstance(arguments, list) assert len(arguments) == 2 counter = arguments[0] index = ArgInfo(arguments[1], caller, dataContainer, program) ctr = program.getCounter(counter.name) assert isinstance(ctr, ebpfCounter.EbpfCounter) serializer.emitIndent() serializer.blockStart() # This is actually incorrect, since the key is not always an u32. # This code is currently disabled key = program.reservedPrefix + "index" serializer.emitIndent() serializer.appendFormat("u32 {0} = {1};", key, index.asString) serializer.newline() ctr.serializeCode(key, serializer, program) serializer.blockEnd(True) def serializeCallee(self, caller, callee, arguments, serializer, dataContainer, program): if self.invalid: raise CompilationException( True, "{0} Attempting to generate code for an invalid action", self.hliraction) assert isinstance(serializer, ProgramSerializer) assert isinstance(program, ebpfProgram.EbpfProgram) assert isinstance(callee, p4_action) assert isinstance(arguments, list) if callee.name in EbpfAction.unsupported: raise NotSupportedException("{0}", callee) # This is not yet ready #if callee.name == "count": # self.serializeCount(caller, arguments, # serializer, dataContainer, program) # return serializer.emitIndent() args = self.transformArguments(arguments, caller, dataContainer, program) if callee.name == "modify_field": dst = args[0] src = args[1] size = self.checkSize(callee, [a.widthInBits() for a in args], program) if size is None: raise CompilationException( True, "Cannot infer width for arguments {0}", callee) elif size <= 32: serializer.appendFormat("{0} = {1};", dst.asString, src.asString) else: if not dst.isLvalue: raise NotSupportedException( "Constants wider than 32-bit: {0}({1})", dst.caller, dst.asString) if not src.isLvalue: raise NotSupportedException( "Constants wider than 32-bit: {0}({1})", src.caller, src.asString) serializer.appendFormat("memcpy(&{0}, &{1}, {2});", dst.asString, src.asString, size / 8) elif (callee.name == "add" or callee.name == "bit_and" or callee.name == "bit_or" or callee.name == "bit_xor" or callee.name == "subtract"): size = self.checkSize(callee, [a.widthInBits() for a in args], program) if size is None: raise CompilationException( True, "Cannot infer width for arguments {0}", callee) if size > 32: raise NotSupportedException("{0}: Arithmetic on {1}-bits", callee, size) op = EbpfAction.translateActionToOperator(callee.name) serializer.appendFormat("{0} = {1} {2} {3};", args[0].asString, args[1].asString, op, args[2].asString) elif (callee.name == "add_to_field" or callee.name == "subtract_from_field"): size = self.checkSize(callee, [a.widthInBits() for a in args], program) if size is None: raise CompilationException( True, "Cannot infer width for arguments {0}", callee) if size > 32: raise NotSupportedException( "{0}: Arithmetic on {1}-bits", callee, size) op = EbpfAction.translateActionToOperator(callee.name) serializer.appendFormat("{0} = {0} {1} {2};", args[0].asString, op, args[1].asString) elif callee.name == "no_op": serializer.append("/* noop */") elif callee.name == "drop": serializer.appendFormat("{0} = 1;", program.dropBit) elif callee.name == "push" or callee.name == "pop": raise CompilationException( True, "{0} push/pop not yet implemented", callee) else: raise CompilationException( True, "Unexpected primitive action {0}", callee) serializer.newline() def transformArguments(self, arguments, caller, dataContainer, program): result = [] for a in arguments: t = ArgInfo(a, caller, dataContainer, program) result.append(t) return result class BuiltinAction(EbpfActionBase): def __init__(self, p4action): super(BuiltinAction, self).__init__(p4action) self.builtin = True def serializeBody(self, serializer, valueName, program): # This is ugly; there should be a better way if self.name == "drop": serializer.emitIndent() serializer.appendFormat("{0} = 1;", program.dropBit) serializer.newline() else: serializer.emitIndent() serializer.appendFormat("/* no body for {0} */", self.name) serializer.newline() class ArgInfo(object): # noinspection PyUnresolvedReferences # Represents an argument passed to an action def __init__(self, argument, caller, dataContainer, program): self.width = None self.asString = None self.isLvalue = True self.caller = caller assert isinstance(program, ebpfProgram.EbpfProgram) assert isinstance(caller, EbpfAction) if isinstance(argument, int): self.asString = str(argument) self.isLvalue = False # size is unknown elif isinstance(argument, p4_field): if ebpfProgram.EbpfProgram.isArrayElementInstance( argument.instance): if isinstance(argument.instance.index, int): index = "[" + str(argument.instance.index) + "]" else: raise CompilationException( True, "Unexpected index for array {0}", argument.instance.index) stackInstance = program.getStackInstance( argument.instance.base_name) assert isinstance(stackInstance, ebpfInstance.EbpfHeaderStack) fieldtype = stackInstance.basetype.getField(argument.name) self.width = fieldtype.widthInBits() self.asString = "{0}.{1}{3}.{2}".format( program.headerStructName, stackInstance.name, argument.name, index) else: instance = program.getInstance(argument.instance.base_name) if isinstance(instance, ebpfInstance.EbpfHeader): parent = program.headerStructName else: parent = program.metadataStructName fieldtype = instance.type.getField(argument.name) self.width = fieldtype.widthInBits() self.asString = "{0}.{1}.{2}".format( parent, instance.name, argument.name) elif isinstance(argument, p4_signature_ref): refarg = caller.arguments[argument.idx] self.asString = "{0}->u.{1}.{2}".format( dataContainer, caller.name, refarg.name) self.width = caller.arguments[argument.idx].argtype.widthInBits() elif isinstance(argument, p4_header_instance): # This could be a header array element # Unfortunately for push and pop, the user mean the whole array, # but the representation contains just the first element here. # This looks like a bug in the HLIR. if ebpfProgram.EbpfProgram.isArrayElementInstance(argument): if isinstance(argument.index, int): index = "[" + str(argument.index) + "]" else: raise CompilationException( True, "Unexpected index for array {0}", argument.index) stackInstance = program.getStackInstance(argument.base_name) assert isinstance(stackInstance, ebpfInstance.EbpfHeaderStack) fieldtype = stackInstance.basetype self.width = fieldtype.widthInBits() self.asString = "{0}.{1}{2}".format( program.headerStructName, stackInstance.name, index) else: instance = program.getInstance(argument.name) instancetype = instance.type self.width = instancetype.widthInBits() self.asString = "{0}.{1}".format( program.headerStructName, argument.name) else: raise CompilationException( True, "Unexpected action argument {0}", argument) def widthInBits(self): return self.width