From f391d3171fc46922e4f40e18013fc140e132f4e2 Mon Sep 17 00:00:00 2001 From: Mouse Date: Wed, 29 May 2024 18:27:04 +0000 Subject: [PATCH] Enable variable-width extraction for the TC backend Enable variable-width extraction for the TC backend However note that it still doesn't add varbit support for the deparser --- backends/ebpf/ebpfParser.cpp | 196 +++++++++++++++++++++++++++++++---- backends/ebpf/ebpfParser.h | 7 +- backends/ebpf/ebpfType.cpp | 10 +- backends/ebpf/ebpfType.h | 5 +- backends/tc/ebpfCodeGen.cpp | 137 +++++++++++++++++++++++- backends/tc/ebpfCodeGen.h | 6 +- 6 files changed, 329 insertions(+), 32 deletions(-) diff --git a/backends/ebpf/ebpfParser.cpp b/backends/ebpf/ebpfParser.cpp index 34297002c34..742c5126e4d 100644 --- a/backends/ebpf/ebpfParser.cpp +++ b/backends/ebpf/ebpfParser.cpp @@ -274,15 +274,25 @@ bool StateTranslationVisitor::preorder(const IR::SelectCase *selectCase) { return false; } -void StateTranslationVisitor::compileExtractField(const IR::Expression *expr, - const IR::StructField *field, - unsigned hdrOffsetBits, EBPFType *type) { +unsigned int StateTranslationVisitor::compileExtractField(const IR::Expression *expr, + const IR::StructField *field, + unsigned hdrOffsetBits, EBPFType *type, + const char *sizecode) { unsigned alignment = hdrOffsetBits % 8; unsigned widthToExtract = type->as().widthInBits(); auto program = state->parser->program; cstring msgStr; cstring fieldName = field->name.name; + builder->appendFormat("/* EBPF::StateTranslationVisitor::compileExtractField: field %s", + fieldName); + if (type->is()) { + builder->appendFormat(" (%s scalar)", + type->as().isvariable ? "variable" : "fixed"); + } + if (sizecode) builder->appendFormat("\n%s", sizecode); + builder->appendFormat("*/"); + msgStr = Util::printf_format("Parser: extracting field %s", fieldName); builder->target->emitTraceMessage(builder, msgStr.c_str()); @@ -399,15 +409,127 @@ void StateTranslationVisitor::compileExtractField(const IR::Expression *expr, msgStr = Util::printf_format("Parser: extracted %s (%u bits)", fieldName, widthToExtract); builder->target->emitTraceMessage(builder, msgStr.c_str()); } + return (0); } -void StateTranslationVisitor::compileExtract(const IR::Expression *destination) { +/* + * There has GOT to be a better way to do this, but, given the + * interfaces the rest of the code exports, I don't see anything much + * better. Maybe I've just missed something. + * + * My thinking is that ->toString returns a P4(ish) decompilation of + * the Expression, so we can't use that. The only thing I know of + * that generates C code is to visit() it. But that outputs to a + * SourceCodeBuilder. I tried using a CodeBuilder, but this->builder + * is typed as an EBPF::CodeBuilder, so we can't swap a + * Util::SourceCodeBuilder in instead. So we create a relatively + * transient EBPF::CodeBuilder and output to that (by swapping it into + * this->builder for the duration). This means we need a Target to + * pass when creating the EBPF::CodeBuilder, and Target is a virtual + * base class, demanding implementations for a bunch of methods we + * have no use for. Annoying, but nothing worse; we just provide + * dummies that do nothing. + * + * The returned string is on the heap, allocated with strdup(). It + * would probably be better to make this a std::string or some such, + * but I'm not good enough at C++ to get that right. + * + * Fortunately, this->builder is a pointer, or I'd have to worry about + * copies and moves and try to get _that_ right. But pointers are + * lightweight and totally copyable without issue. + */ +char *StateTranslationVisitor::visit_to_string(const IR::Expression *expr) { + class StringTarget : public Target { + public: + StringTarget(cstring n) : Target(n) {} +#define VB1(a) \ + { (void)a; } +#define VB2(a, b) \ + { \ + (void)a; \ + (void)b; \ + } +#define VB3(a, b, c) \ + { \ + (void)a; \ + (void)b; \ + (void)c; \ + } +#define VB4(a, b, c, d) \ + { \ + (void)a; \ + (void)b; \ + (void)c; \ + (void)d; \ + } +#define VB6(a, b, c, d, e, f) \ + { \ + (void)a; \ + (void)b; \ + (void)c; \ + (void)d; \ + (void)e; \ + (void)f; \ + } +#define SB0() \ + { return (""); } +#define SB1(a) \ + { \ + (void)a; \ + return (""); \ + } + virtual void emitLicense(Util::SourceCodeBuilder *b, cstring l) const + VB2(b, l) virtual void emitCodeSection(Util::SourceCodeBuilder *b, cstring n) const + VB2(b, n) virtual void emitIncludes(Util::SourceCodeBuilder *b) const + VB1(b) virtual void emitResizeBuffer(Util::SourceCodeBuilder *b, cstring bf, + cstring o) const + VB3(b, bf, o) virtual void emitTableLookup(Util::SourceCodeBuilder *b, cstring n, + cstring k, cstring v) const + VB4(b, n, k, v) virtual void emitTableUpdate(Util::SourceCodeBuilder *b, cstring n, + cstring k, cstring v) const + VB4(b, n, k, v) virtual void emitUserTableUpdate(Util::SourceCodeBuilder *b, cstring n, + cstring k, cstring v) const + VB4(b, n, k, v) virtual void emitTableDecl(Util::SourceCodeBuilder *b, cstring tn, + TableKind k, cstring kt, cstring vt, + unsigned int sz) const + VB6(b, tn, k, kt, vt, sz) virtual void emitMain(Util::SourceCodeBuilder *b, cstring fn, + cstring an) const + VB3(b, fn, an) virtual cstring dataOffset(cstring b) const SB1(b) virtual cstring + dataEnd(cstring b) const SB1(b) virtual cstring dataLength(cstring b) const + SB1(b) virtual cstring forwardReturnCode() const SB0() virtual cstring + dropReturnCode() const SB0() virtual cstring abortReturnCode() const + SB0() virtual cstring sysMapPath() const SB0() virtual cstring + packetDescriptorType() const SB0() +#undef VB1 +#undef VB2 +#undef VB3 +#undef VB4 +#undef VB6 +#undef SB0 +#undef SB1 + }; + StringTarget t = StringTarget("stringizer"); + auto save = builder; + builder = new EBPF::CodeBuilder(&t); + visit(expr); + auto s = builder->toString(); + delete builder; + builder = save; + return (strdup(s.c_str())); +} + +void StateTranslationVisitor::compileExtract(const IR::Expression *dest, + const IR::Expression *varsize) { + builder->appendFormat("//compileExtract\n"); + builder->appendFormat("// compileExtract: dest = %s\n", dest->toString()); + builder->appendFormat("// compileExtract: varsize = %s\n", + varsize ? varsize->toString() : "nil"); cstring msgStr; - auto type = state->parser->typeMap->getType(destination); + auto type = state->parser->typeMap->getType(dest); auto ht = type->to(); if (ht == nullptr) { ::error(ErrorType::ERR_UNSUPPORTED_ON_TARGET, "Cannot extract to a non-struct type %1%", - destination); + dest); return; } @@ -415,7 +537,7 @@ void StateTranslationVisitor::compileExtract(const IR::Expression *destination) unsigned width = ht->width_bits(); if ((width % 8) != 0) { ::error(ErrorType::ERR_UNSUPPORTED_ON_TARGET, - "Header %1% size %2% is not a multiple of 8 bits.", destination, width); + "Header %1% size %2% is not a multiple of 8 bits.", dest, width); return; } @@ -465,28 +587,58 @@ void StateTranslationVisitor::compileExtract(const IR::Expression *destination) builder->newline(); builder->blockEnd(true); - msgStr = Util::printf_format("Parser: extracting header %s", destination->toString()); + msgStr = Util::printf_format("Parser: extracting header %s", dest->toString()); builder->target->emitTraceMessage(builder, msgStr.c_str()); builder->newline(); unsigned hdrOffsetBits = 0; + /* + * Some of the tests in this loop appear to be can't-happens. For + * example, when varsize is not nil, there appears to be code + * elsewhere which (a) requires at least one varbit field in the + * header struct and (b) forbids multiple varbit fields in a header, + * so we will have exactly one varbit field. I'm leaving the tests + * in both for the cases which aren't can't-happens and for the sake + * of firewalling in case code elsewhere changes such that the + * can't-happens actually can happen. + */ + bool had_varbit; + had_varbit = false; for (auto f : ht->fields) { auto ftype = state->parser->typeMap->getType(f); + char *sizecode = 0; + if (ftype->is()) { + if (varsize == nullptr) { + ::error( + ErrorType::ERR_INVALID, + "Extract to a header with a varbit<> member requires two-argument extract()"); + return; + } else if (had_varbit) { + ::error(ErrorType::ERR_INVALID, + "Two-argument extract() target must not have multiple varbit<> members"); + return; + } else { + sizecode = visit_to_string(varsize); + builder->appendFormat("/* compileExtract varbit size = %s */", sizecode); + builder->newline(); + had_varbit = true; + } + } auto etype = EBPFTypeFactory::instance->create(ftype); auto et = etype->to(); if (et == nullptr) { ::error(ErrorType::ERR_UNSUPPORTED_ON_TARGET, - "Only headers with fixed widths supported %1%", f); + "Headers must use defined-widths types: %1%", f); return; } - compileExtractField(destination, f, hdrOffsetBits, etype); - hdrOffsetBits += et->widthInBits(); + unsigned int advance = compileExtractField(dest, f, hdrOffsetBits, etype, sizecode); + hdrOffsetBits += advance ? advance : et->widthInBits(); } builder->newline(); if (ht->is()) { builder->emitIndent(); - visit(destination); + visit(dest); builder->appendLine(".ebpf_valid = 1;"); } @@ -495,7 +647,7 @@ void StateTranslationVisitor::compileExtract(const IR::Expression *destination) builder->appendFormat("%s += BYTES(%u);", program->headerStartVar.c_str(), width); builder->newline(); - msgStr = Util::printf_format("Parser: extracted %s", destination->toString()); + msgStr = Util::printf_format("Parser: extracted %s", dest->toString()); builder->target->emitTraceMessage(builder, msgStr.c_str()); builder->newline(); @@ -516,12 +668,20 @@ void StateTranslationVisitor::processMethod(const P4::ExternMethod *method) { auto decl = method->object; if (decl == state->parser->packet) { if (method->method->name.name == p4lib.packetIn.extract.name) { - if (expression->arguments->size() != 1) { - ::error(ErrorType::ERR_UNSUPPORTED_ON_TARGET, - "Variable-sized header fields not yet supported %1%", expression); - return; + switch (expression->arguments->size()) { + case 1: + compileExtract(expression->arguments->at(0)->expression); + break; + case 2: + compileExtract(expression->arguments->at(0)->expression, + expression->arguments->at(1)->expression); + break; + default: + // Can other size() values occur? + ::error(ErrorType::ERR_UNEXPECTED, "extract() with unexpected arg count %1%", + expression->arguments->size()); + break; } - compileExtract(expression->arguments->at(0)->expression); return; } else if (method->method->name.name == p4lib.packetIn.length.name) { builder->append(state->parser->program->lengthVar); diff --git a/backends/ebpf/ebpfParser.h b/backends/ebpf/ebpfParser.h index 5e9e1a982fa..8699a498cb3 100644 --- a/backends/ebpf/ebpfParser.h +++ b/backends/ebpf/ebpfParser.h @@ -36,9 +36,9 @@ class StateTranslationVisitor : public CodeGenInspector { P4::P4CoreLibrary &p4lib; const EBPFParserState *state; - virtual void compileExtractField(const IR::Expression *expr, const IR::StructField *field, - unsigned hdrOffsetBits, EBPFType *type); - virtual void compileExtract(const IR::Expression *destination); + virtual unsigned int compileExtractField(const IR::Expression *, const IR::StructField *, + unsigned int, EBPFType *, const char *); + virtual void compileExtract(const IR::Expression *dest, const IR::Expression *varsize = 0); virtual void compileLookahead(const IR::Expression *destination); void compileAdvance(const P4::ExternMethod *ext); void compileVerify(const IR::MethodCallExpression *expression); @@ -62,6 +62,7 @@ class StateTranslationVisitor : public CodeGenInspector { return false; } bool preorder(const IR::AssignmentStatement *stat) override; + char *visit_to_string(const IR::Expression *); }; class EBPFParserState : public EBPFObject { diff --git a/backends/ebpf/ebpfType.cpp b/backends/ebpf/ebpfType.cpp index 9814f46d7c2..c02de91cd68 100644 --- a/backends/ebpf/ebpfType.cpp +++ b/backends/ebpf/ebpfType.cpp @@ -128,16 +128,20 @@ void EBPFScalarType::emit(CodeBuilder *builder) { } void EBPFScalarType::declare(CodeBuilder *builder, cstring id, bool asPointer) { - if (EBPFScalarType::generatesScalar(width)) { + if (isvariable) { + builder->appendFormat("struct { u8 data[%d]; u16 curwidth; } %s", (width + 7) >> 3, + id.c_str()); + } else if (EBPFScalarType::generatesScalar(width)) { emit(builder); if (asPointer) builder->append("*"); builder->spc(); builder->append(id); } else { - if (asPointer) + if (asPointer) { builder->appendFormat("u8* %s", id.c_str()); - else + } else { builder->appendFormat("u8 %s[%d]", id.c_str(), bytesRequired()); + } } } diff --git a/backends/ebpf/ebpfType.h b/backends/ebpf/ebpfType.h index 703738b59ad..84cf41049ac 100644 --- a/backends/ebpf/ebpfType.h +++ b/backends/ebpf/ebpfType.h @@ -107,10 +107,11 @@ class EBPFScalarType : public EBPFType, public IHasWidth { public: const unsigned width; const bool isSigned; + const bool isvariable; explicit EBPFScalarType(const IR::Type_Bits *bits) - : EBPFType(bits), width(bits->size), isSigned(bits->isSigned) {} + : EBPFType(bits), width(bits->size), isSigned(bits->isSigned), isvariable(false) {} explicit EBPFScalarType(const IR::Type_Varbits *bits) - : EBPFType(bits), width(bits->size), isSigned(false) {} + : EBPFType(bits), width(bits->size), isSigned(false), isvariable(true) {} unsigned bytesRequired() const { return ROUNDUP(width, 8); } unsigned alignment() const; void emit(CodeBuilder *builder) override; diff --git a/backends/tc/ebpfCodeGen.cpp b/backends/tc/ebpfCodeGen.cpp index 1a62b310a75..f65f6b0b69e 100644 --- a/backends/tc/ebpfCodeGen.cpp +++ b/backends/tc/ebpfCodeGen.cpp @@ -16,6 +16,8 @@ and limitations under the License. #include "ebpfCodeGen.h" +#include + namespace TC { // =====================PNAEbpfGenerator============================= @@ -591,19 +593,139 @@ void EBPFPnaParser::emitRejectState(EBPF::CodeBuilder *builder) { builder->endOfStatement(true); } +/* + * The API to this function (inherited from compileExtractField's) is a + * bit broken. Our caller expects us to return the number of bits + * parsed out of the packet. But, for varbit fields, we can't know + * that until run time. Perhaps fortunately, nothing depends on more + * than the low three bits of this value, and we check that both size + * and location are octet-aligned, so returning 0 here is suitable. + * + * We do generate code to advance ebpf_packetOffsetInBits (the offset + * at runtime) by the correct (runtime) amount. + */ +unsigned int PnaStateTranslationVisitor::compileExtractVarbits(const IR::Expression *expr, + const IR::StructField *field, + unsigned bitoff, + EBPF::EBPFType *type, + const char *sizecode) { + static const char *const wvar = "ebpf_varbits_width"; + static const char *const ovar = "ebpf_varbits_offset"; + int maxwidth; + int w; + auto bytetype = EBPF::EBPFTypeFactory::instance->create(IR::Type_Bits::get(8)); + cstring fieldName = field->name.name; + auto program = state->parser->program; + + maxwidth = type->to()->widthInBits(); + // XXX Is this an error condition? + // if (maxwidth & 7) ... + // I'm going to assume not and let the runtime test handle it. + maxwidth >>= 3; + // Do we need to handle the case where bitoff isn't a multiple of 8? + // It would greatly complicate much other code. + if (bitoff & 7) { + ::error(ErrorType::ERR_UNSUPPORTED_ON_TARGET, + "Variable-size extract must be on a byte boundary"); + return (0); + } + builder->emitIndent(); + builder->blockStart(); + builder->newline(); + builder->emitIndent(); + builder->appendFormat("u32 %s = %s;", wvar, sizecode); + builder->newline(); + builder->emitIndent(); + builder->appendFormat("u32 %s;", ovar); + builder->newline(); + builder->emitIndent(); + builder->appendFormat("// Can't handle this case yet"); + builder->newline(); + builder->emitIndent(); + builder->appendFormat("if (%s & 7) ", wvar); + builder->blockStart(); + builder->emitIndent(); + // XXX Is ParserInvalidArgument the best error code here? + builder->appendFormat("ebpf_errorCode = ParserInvalidArgument;"); + builder->newline(); + builder->emitIndent(); + builder->appendFormat("goto reject;"); + builder->newline(); + builder->blockEnd(true); + builder->emitIndent(); + builder->appendFormat("%s >>= 3;", wvar); + builder->newline(); + /* + * We can't loop in BPF code; I don't know about EBPF, but it's + * relatively simple to work around here. We generate a string of + * assignments, wrapped in a switch to enter the string at the + * correct place at runtime (vaguely reminiscent of Duff's device). + */ + builder->emitIndent(); + builder->appendFormat("%s = BYTES(%s) + %s;\n", ovar, program->offsetVar.c_str(), wvar); + builder->newline(); + builder->emitIndent(); + builder->appendFormat("switch (%s)", wvar); + builder->newline(); + builder->emitIndent(); + builder->blockStart(); + for (w = maxwidth - 1; w >= 0; w--) { + builder->emitIndent(); + builder->appendFormat("case %d: ", w + 1); + builder->emitIndent(); + visit(expr); + builder->appendFormat(".%s.data[%d] = (", fieldName.c_str(), w); + bytetype->emit(builder); + builder->appendFormat(")load_byte(%s,--%s);", program->packetStartVar.c_str(), ovar); + builder->newline(); + } + builder->blockEnd(true); + builder->emitIndent(); + visit(expr); + builder->appendFormat(".%s.curwidth = %s;", fieldName.c_str(), wvar); + builder->newline(); + builder->emitIndent(); + builder->appendFormat("%s += %s << 3;", program->offsetVar.c_str(), wvar); + builder->newline(); + builder->blockEnd(true); + // See function header comment for why 0. + return (0); +} + // This code is similar to compileExtractField function in PsaStateTranslationVisitor. // Handled TC "macaddr" annotation. -void PnaStateTranslationVisitor::compileExtractField(const IR::Expression *expr, - const IR::StructField *field, - unsigned hdrOffsetBits, EBPF::EBPFType *type) { +unsigned int PnaStateTranslationVisitor::compileExtractField(const IR::Expression *expr, + const IR::StructField *field, + unsigned hdrOffsetBits, + EBPF::EBPFType *type, + const char *sizecode) { unsigned alignment = hdrOffsetBits % 8; auto width = type->to(); - if (width == nullptr) return; + if (width == nullptr) return (0); unsigned widthToExtract = width->widthInBits(); auto program = state->parser->program; cstring msgStr; cstring fieldName = field->name.name; + if (type->is() && type->as().isvariable) { + if (!sizecode) assert(!"Impossible extract of varbits field with no size code"); + } else { + if (sizecode) assert(!"Impossible extract of fixed-width field with size code"); + } + builder->appendFormat("/* TC::PnaStateTranslationVisitor::compileExtractField: field %s", + fieldName); + if (type->is()) { + builder->appendFormat(" (%s scalar)", + type->as().isvariable ? "variable" : "fixed"); + } + if (sizecode) builder->appendFormat(", sizecode = %s", sizecode); + builder->appendFormat(" */"); + builder->newline(); + + if (sizecode) { + return (compileExtractVarbits(expr, field, hdrOffsetBits, type, sizecode)); + } + bool noEndiannessConversion = false; auto annolist = field->getAnnotations()->annotations; for (auto anno : annolist) { @@ -753,6 +875,7 @@ void PnaStateTranslationVisitor::compileExtractField(const IR::Expression *expr, } builder->newline(); + return (0); } void PnaStateTranslationVisitor::compileLookahead(const IR::Expression *destination) { @@ -1786,6 +1909,12 @@ void ControlBodyTranslatorPNA::processApply(const P4::ApplyMethod *method) { builder->appendFormat("struct %s %s", table->keyTypeName.c_str(), keyname.c_str()); builder->endOfStatement(true); builder->emitIndent(); + builder->appendFormat("// XXX eBPF gets upset at memset(); let's"); + builder->newline(); + builder->emitIndent(); + builder->appendFormat("// hope __builtin_memset() does what we want"); + builder->newline(); + builder->emitIndent(); builder->appendFormat("__builtin_memset(&%s, 0, sizeof(%s))", keyname.c_str(), keyname.c_str()); builder->endOfStatement(true); diff --git a/backends/tc/ebpfCodeGen.h b/backends/tc/ebpfCodeGen.h index e0975115a91..993b15e8544 100644 --- a/backends/tc/ebpfCodeGen.h +++ b/backends/tc/ebpfCodeGen.h @@ -124,8 +124,10 @@ class PnaStateTranslationVisitor : public EBPF::PsaStateTranslationVisitor { : EBPF::PsaStateTranslationVisitor(refMap, typeMap, prsr) {} protected: - void compileExtractField(const IR::Expression *expr, const IR::StructField *field, - unsigned hdrOffsetBits, EBPF::EBPFType *type) override; + unsigned int compileExtractField(const IR::Expression *, const IR::StructField *, unsigned int, + EBPF::EBPFType *, const char *) override; + unsigned int compileExtractVarbits(const IR::Expression *, const IR::StructField *, + unsigned int, EBPF::EBPFType *, const char *); void compileLookahead(const IR::Expression *destination) override; };