• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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