| # Copyright (c) Barefoot Networks, Inc. |
| # Licensed under the Apache License, Version 2.0 (the "License") |
| |
| from p4_hlir.hlir import parse_call, p4_field, p4_parse_value_set, \ |
| P4_DEFAULT, p4_parse_state, p4_table, \ |
| p4_conditional_node, p4_parser_exception, \ |
| p4_header_instance, P4_NEXT |
| import ebpfProgram |
| import ebpfStructType |
| import ebpfInstance |
| import programSerializer |
| from compilationException import * |
| |
| |
| class EbpfParser(object): |
| def __init__(self, hlirParser): # hlirParser is a P4 parser |
| self.parser = hlirParser |
| self.name = hlirParser.name |
| |
| def serialize(self, serializer, program): |
| assert isinstance(serializer, programSerializer.ProgramSerializer) |
| assert isinstance(program, ebpfProgram.EbpfProgram) |
| |
| serializer.emitIndent() |
| serializer.appendFormat("{0}: ", self.name) |
| serializer.blockStart() |
| for op in self.parser.call_sequence: |
| self.serializeOperation(serializer, op, program) |
| |
| self.serializeBranch(serializer, self.parser.branch_on, |
| self.parser.branch_to, program) |
| |
| serializer.blockEnd(True) |
| |
| def serializeSelect(self, selectVarName, serializer, branch_on, program): |
| # selectVarName - name of temp variable to use for the select expression |
| assert isinstance(selectVarName, str) |
| assert isinstance(serializer, programSerializer.ProgramSerializer) |
| assert isinstance(program, ebpfProgram.EbpfProgram) |
| |
| totalWidth = 0 |
| switchValue = "" |
| for e in branch_on: |
| if isinstance(e, p4_field): |
| instance = e.instance |
| assert isinstance(instance, p4_header_instance) |
| index = "" |
| |
| if ebpfProgram.EbpfProgram.isArrayElementInstance(instance): |
| ebpfStack = program.getStackInstance(instance.base_name) |
| assert isinstance(ebpfStack, ebpfInstance.EbpfHeaderStack) |
| |
| if isinstance(instance.index, int): |
| index = "[" + str(instance.index) + "]" |
| elif instance.index is P4_NEXT: |
| index = "[" + ebpfStack.indexVar + "]" |
| else: |
| raise CompilationException(True, |
| "Unexpected index for array {0}", instance.index) |
| basetype = ebpfStack.basetype |
| name = ebpfStack.name |
| else: |
| ebpfHeader = program.getInstance(instance.name) |
| assert isinstance(ebpfHeader, ebpfInstance.EbpfHeader) |
| basetype = ebpfHeader.type |
| name = ebpfHeader.name |
| |
| ebpfField = basetype.getField(e.name) |
| assert isinstance(ebpfField, ebpfStructType.EbpfField) |
| |
| totalWidth += ebpfField.widthInBits() |
| fieldReference = (program.headerStructName + "." + name + |
| index + "." + ebpfField.name) |
| |
| if switchValue == "": |
| switchValue = fieldReference |
| else: |
| switchValue = ("(" + switchValue + " << " + |
| str(ebpfField.widthInBits()) + ")") |
| switchValue = switchValue + " | " + fieldReference |
| elif isinstance(e, tuple): |
| switchValue = self.currentReferenceAsString(e, program) |
| else: |
| raise CompilationException( |
| True, "Unexpected element in match {0}", e) |
| |
| if totalWidth > 32: |
| raise NotSupportedException("{0}: Matching on {1}-bit value", |
| branch_on, totalWidth) |
| serializer.emitIndent() |
| serializer.appendFormat("{0}32 {1} = {2};", |
| program.config.uprefix, |
| selectVarName, switchValue) |
| serializer.newline() |
| |
| def generatePacketLoad(self, startBit, width, alignment, program): |
| # Generates an expression that does a load_*, shift and mask |
| # to load 'width' bits starting at startBit from the current |
| # packet offset. |
| # alignment is an integer <= 8 that holds the current alignment |
| # of of the packet offset. |
| assert width > 0 |
| assert alignment < 8 |
| assert isinstance(startBit, int) |
| assert isinstance(width, int) |
| assert isinstance(alignment, int) |
| |
| firstBitIndex = startBit + alignment |
| lastBitIndex = startBit + width + alignment - 1 |
| firstWordIndex = firstBitIndex / 8 |
| lastWordIndex = lastBitIndex / 8 |
| |
| wordsToRead = lastWordIndex - firstWordIndex + 1 |
| if wordsToRead == 1: |
| load = "load_byte" |
| loadSize = 8 |
| elif wordsToRead == 2: |
| load = "load_half" |
| loadSize = 16 |
| elif wordsToRead <= 4: |
| load = "load_word" |
| loadSize = 32 |
| elif wordsToRead <= 8: |
| load = "load_dword" |
| loadSize = 64 |
| else: |
| raise CompilationException(True, "Attempt to load more than 1 word") |
| |
| readtype = program.config.uprefix + str(loadSize) |
| loadInstruction = "{0}({1}, ({2} + {3}) / 8)".format( |
| load, program.packetName, program.offsetVariableName, startBit) |
| shift = loadSize - alignment - width |
| load = "(({0}) >> ({1}))".format(loadInstruction, shift) |
| if width != loadSize: |
| mask = " & EBPF_MASK({0}, {1})".format(readtype, width) |
| else: |
| mask = "" |
| return load + mask |
| |
| def currentReferenceAsString(self, tpl, program): |
| # a string describing an expression of the form current(position, width) |
| # The assumption is that at this point the packet cursor is ALWAYS |
| # byte aligned. This should be true because headers are supposed |
| # to have sizes an integral number of bytes. |
| assert isinstance(tpl, tuple) |
| if len(tpl) != 2: |
| raise CompilationException( |
| True, "{0} Expected a tuple with 2 elements", tpl) |
| |
| minIndex = tpl[0] |
| totalWidth = tpl[1] |
| result = self.generatePacketLoad( |
| minIndex, totalWidth, 0, program) # alignment is 0 |
| return result |
| |
| def serializeCases(self, selectVarName, serializer, branch_to, program): |
| assert isinstance(selectVarName, str) |
| assert isinstance(serializer, programSerializer.ProgramSerializer) |
| assert isinstance(program, ebpfProgram.EbpfProgram) |
| |
| branches = 0 |
| seenDefault = False |
| for e in branch_to.keys(): |
| serializer.emitIndent() |
| value = branch_to[e] |
| |
| if isinstance(e, int): |
| serializer.appendFormat("if ({0} == {1})", selectVarName, e) |
| elif isinstance(e, tuple): |
| serializer.appendFormat( |
| "if (({0} & {1}) == {2})", selectVarName, e[0], e[1]) |
| elif isinstance(e, p4_parse_value_set): |
| raise NotSupportedException("{0}: Parser value sets", e) |
| elif e is P4_DEFAULT: |
| seenDefault = True |
| if branches > 0: |
| serializer.append("else") |
| else: |
| raise CompilationException( |
| True, "Unexpected element in match case {0}", e) |
| |
| branches += 1 |
| serializer.newline() |
| serializer.increaseIndent() |
| serializer.emitIndent() |
| |
| label = program.getLabel(value) |
| |
| if isinstance(value, p4_parse_state): |
| serializer.appendFormat("goto {0};", label) |
| elif isinstance(value, p4_table): |
| serializer.appendFormat("goto {0};", label) |
| elif isinstance(value, p4_conditional_node): |
| serializer.appendFormat("goto {0};", label) |
| elif isinstance(value, p4_parser_exception): |
| raise CompilationException(True, "Not yet implemented") |
| else: |
| raise CompilationException( |
| True, "Unexpected element in match case {0}", value) |
| |
| serializer.decreaseIndent() |
| serializer.newline() |
| |
| # Must create default if it is missing |
| if not seenDefault: |
| serializer.emitIndent() |
| serializer.appendFormat( |
| "{0} = p4_pe_unhandled_select;", program.errorName) |
| serializer.newline() |
| serializer.emitIndent() |
| serializer.appendFormat("default: goto end;") |
| serializer.newline() |
| |
| def serializeBranch(self, serializer, branch_on, branch_to, program): |
| assert isinstance(serializer, programSerializer.ProgramSerializer) |
| assert isinstance(program, ebpfProgram.EbpfProgram) |
| |
| if branch_on == []: |
| dest = branch_to.values()[0] |
| serializer.emitIndent() |
| name = program.getLabel(dest) |
| serializer.appendFormat("goto {0};", name) |
| serializer.newline() |
| elif isinstance(branch_on, list): |
| tmpvar = program.generateNewName("tmp") |
| self.serializeSelect(tmpvar, serializer, branch_on, program) |
| self.serializeCases(tmpvar, serializer, branch_to, program) |
| else: |
| raise CompilationException( |
| True, "Unexpected branch_on {0}", branch_on) |
| |
| def serializeOperation(self, serializer, op, program): |
| assert isinstance(serializer, programSerializer.ProgramSerializer) |
| assert isinstance(program, ebpfProgram.EbpfProgram) |
| |
| operation = op[0] |
| if operation is parse_call.extract: |
| self.serializeExtract(serializer, op[1], program) |
| elif operation is parse_call.set: |
| self.serializeMetadataSet(serializer, op[1], op[2], program) |
| else: |
| raise CompilationException( |
| True, "Unexpected operation in parser {0}", op) |
| |
| def serializeFieldExtract(self, serializer, headerInstanceName, |
| index, field, alignment, program): |
| assert isinstance(index, str) |
| assert isinstance(headerInstanceName, str) |
| assert isinstance(field, ebpfStructType.EbpfField) |
| assert isinstance(serializer, programSerializer.ProgramSerializer) |
| assert isinstance(alignment, int) |
| assert isinstance(program, ebpfProgram.EbpfProgram) |
| |
| fieldToExtractTo = headerInstanceName + index + "." + field.name |
| |
| serializer.emitIndent() |
| width = field.widthInBits() |
| if field.name == "valid": |
| serializer.appendFormat( |
| "{0}.{1} = 1;", program.headerStructName, fieldToExtractTo) |
| serializer.newline() |
| return |
| |
| serializer.appendFormat("if ({0}->len < BYTES({1} + {2})) ", |
| program.packetName, |
| program.offsetVariableName, width) |
| serializer.blockStart() |
| serializer.emitIndent() |
| serializer.appendFormat("{0} = p4_pe_header_too_short;", |
| program.errorName) |
| serializer.newline() |
| serializer.emitIndent() |
| serializer.appendLine("goto end;") |
| # TODO: jump to correct exception handler |
| serializer.blockEnd(True) |
| |
| if width <= 32: |
| serializer.emitIndent() |
| load = self.generatePacketLoad(0, width, alignment, program) |
| |
| serializer.appendFormat("{0}.{1} = {2};", |
| program.headerStructName, |
| fieldToExtractTo, load) |
| serializer.newline() |
| else: |
| # Destination is bigger than 4 bytes and |
| # represented as a byte array. |
| if alignment == 0: |
| shift = 0 |
| else: |
| shift = 8 - alignment |
| |
| assert shift >= 0 |
| if shift == 0: |
| method = "load_byte" |
| else: |
| method = "load_half" |
| b = (width + 7) / 8 |
| for i in range(0, b): |
| serializer.emitIndent() |
| serializer.appendFormat("{0}.{1}[{2}] = ({3}8)", |
| program.headerStructName, |
| fieldToExtractTo, i, |
| program.config.uprefix) |
| serializer.appendFormat("(({0}({1}, ({2} / 8) + {3}) >> {4})", |
| method, program.packetName, |
| program.offsetVariableName, i, shift) |
| if (i == b - 1) and (width % 8 != 0): |
| serializer.appendFormat(" & EBPF_MASK({0}8, {1})", |
| program.config.uprefix, width % 8) |
| serializer.append(")") |
| serializer.endOfStatement(True) |
| |
| serializer.emitIndent() |
| serializer.appendFormat("{0} += {1};", |
| program.offsetVariableName, width) |
| serializer.newline() |
| |
| def serializeExtract(self, serializer, headerInstance, program): |
| assert isinstance(serializer, programSerializer.ProgramSerializer) |
| assert isinstance(headerInstance, p4_header_instance) |
| assert isinstance(program, ebpfProgram.EbpfProgram) |
| |
| if ebpfProgram.EbpfProgram.isArrayElementInstance(headerInstance): |
| ebpfStack = program.getStackInstance(headerInstance.base_name) |
| assert isinstance(ebpfStack, ebpfInstance.EbpfHeaderStack) |
| |
| # write bounds check |
| serializer.emitIndent() |
| serializer.appendFormat("if ({0} >= {1}) ", |
| ebpfStack.indexVar, ebpfStack.arraySize) |
| serializer.blockStart() |
| serializer.emitIndent() |
| serializer.appendFormat("{0} = p4_pe_index_out_of_bounds;", |
| program.errorName) |
| serializer.newline() |
| serializer.emitIndent() |
| serializer.appendLine("goto end;") |
| serializer.blockEnd(True) |
| |
| if isinstance(headerInstance.index, int): |
| index = "[" + str(headerInstance.index) + "]" |
| elif headerInstance.index is P4_NEXT: |
| index = "[" + ebpfStack.indexVar + "]" |
| else: |
| raise CompilationException( |
| True, "Unexpected index for array {0}", |
| headerInstance.index) |
| basetype = ebpfStack.basetype |
| else: |
| ebpfHeader = program.getHeaderInstance(headerInstance.name) |
| basetype = ebpfHeader.type |
| index = "" |
| |
| # extract all fields |
| alignment = 0 |
| for field in basetype.fields: |
| assert isinstance(field, ebpfStructType.EbpfField) |
| |
| self.serializeFieldExtract(serializer, headerInstance.base_name, |
| index, field, alignment, program) |
| alignment += field.widthInBits() |
| alignment = alignment % 8 |
| |
| if ebpfProgram.EbpfProgram.isArrayElementInstance(headerInstance): |
| # increment stack index |
| ebpfStack = program.getStackInstance(headerInstance.base_name) |
| assert isinstance(ebpfStack, ebpfInstance.EbpfHeaderStack) |
| |
| # write bounds check |
| serializer.emitIndent() |
| serializer.appendFormat("{0}++;", ebpfStack.indexVar) |
| serializer.newline() |
| |
| def serializeMetadataSet(self, serializer, field, value, program): |
| assert isinstance(serializer, programSerializer.ProgramSerializer) |
| assert isinstance(program, ebpfProgram.EbpfProgram) |
| assert isinstance(field, p4_field) |
| |
| dest = program.getInstance(field.instance.name) |
| assert isinstance(dest, ebpfInstance.SimpleInstance) |
| destType = dest.type |
| assert isinstance(destType, ebpfStructType.EbpfStructType) |
| destField = destType.getField(field.name) |
| |
| if destField.widthInBits() > 32: |
| useMemcpy = True |
| bytesToCopy = destField.widthInBits() / 8 |
| if destField.widthInBits() % 8 != 0: |
| raise CompilationException( |
| True, |
| "{0}: Not implemented: wide field w. sz not multiple of 8", |
| field) |
| else: |
| useMemcpy = False |
| bytesToCopy = None # not needed, but compiler is confused |
| |
| serializer.emitIndent() |
| destination = "{0}.{1}.{2}".format( |
| program.metadataStructName, dest.name, destField.name) |
| if isinstance(value, int): |
| source = str(value) |
| if useMemcpy: |
| raise CompilationException( |
| True, |
| "{0}: Not implemented: copying from wide constant", |
| value) |
| elif isinstance(value, tuple): |
| source = self.currentReferenceAsString(value, program) |
| elif isinstance(value, p4_field): |
| source = program.getInstance(value.instance.name) |
| if isinstance(source, ebpfInstance.EbpfMetadata): |
| sourceStruct = program.metadataStructName |
| else: |
| sourceStruct = program.headerStructName |
| source = "{0}.{1}.{2}".format(sourceStruct, source.name, value.name) |
| else: |
| raise CompilationException( |
| True, "Unexpected type for parse_call.set {0}", value) |
| |
| if useMemcpy: |
| serializer.appendFormat("memcpy(&{0}, &{1}, {2})", |
| destination, source, bytesToCopy) |
| else: |
| serializer.appendFormat("{0} = {1}", destination, source) |
| |
| serializer.endOfStatement(True) |