Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for parser value_set to eBPF backend #3235

Merged
merged 4 commits into from
Apr 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion backends/ebpf/ebpfParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,20 @@ bool StateTranslationVisitor::preorder(const IR::SelectExpression* expression) {
builder->appendFormat("%s = ", selectValue);
visit(expression->select);
builder->endOfStatement(true);

// Init value_sets
for (auto e : expression->selectCases) {
if (e->keyset->is<IR::PathExpression>()) {
cstring pvsName = e->keyset->to<IR::PathExpression>()->path->name.name;
cstring pvsKeyVarName = state->parser->program->refMap->newName(pvsName + "_key");
auto pvs = state->parser->getValueSet(pvsName);
if (pvs != nullptr)
pvs->emitKeyInitializer(builder, expression, pvsKeyVarName);
else
::error(ErrorType::ERR_UNKNOWN, "%1%: expected a value_set instance", e->keyset);
}
}

for (auto e : expression->selectCases)
visit(e);

Expand All @@ -176,7 +190,14 @@ bool StateTranslationVisitor::preorder(const IR::SelectExpression* expression) {

bool StateTranslationVisitor::preorder(const IR::SelectCase* selectCase) {
builder->emitIndent();
if (auto mask = selectCase->keyset->to<IR::Mask>()) {
if (auto pe = selectCase->keyset->to<IR::PathExpression>()) {
builder->append("if (");
cstring pvsName = pe->path->name.name;
auto pvs = state->parser->getValueSet(pvsName);
if (pvs)
pvs->emitLookup(builder);
builder->append(" != NULL)");
} else if (auto mask = selectCase->keyset->to<IR::Mask>()) {
builder->appendFormat("if ((%s", selectValue);
builder->append(" & ");
visit(mask->right);
Expand Down Expand Up @@ -530,6 +551,8 @@ void EBPFParser::emitDeclaration(CodeBuilder* builder, const IR::Declaration* de
BUG_CHECK(vd->initializer == nullptr,
"%1%: declarations with initializers not supported", decl);
return;
} else if (decl->is<IR::P4ValueSet>()) {
return;
}
BUG("%1%: not yet handled", decl);
}
Expand Down Expand Up @@ -586,9 +609,31 @@ bool EBPFParser::build() {
if (ht == nullptr)
return false;
headerType = EBPFTypeFactory::instance->create(ht);

for (auto decl : parserBlock->container->parserLocals) {
if (decl->is<IR::P4ValueSet>()) {
cstring extName = EBPFObject::externalName(decl);
auto pvs = new EBPFValueSet(program, decl->to<IR::P4ValueSet>(),
extName, visitor);
valueSets.emplace(decl->name.name, pvs);
}
}

return true;
}

void EBPFParser::emitTypes(CodeBuilder* builder) {
for (auto pvs : valueSets) {
pvs.second->emitTypes(builder);
}
}

void EBPFParser::emitValueSetInstances(CodeBuilder* builder) {
for (auto pvs : valueSets) {
pvs.second->emitInstance(builder);
}
}

void EBPFParser::emitRejectState(CodeBuilder* builder) {
builder->emitIndent();
builder->appendFormat("return %s;", builder->target->abortReturnCode().c_str());
Expand Down
11 changes: 9 additions & 2 deletions backends/ebpf/ebpfParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ limitations under the License.
#include "ir/ir.h"
#include "ebpfObject.h"
#include "ebpfProgram.h"
#include "ebpfTable.h"
#include "frontends/p4/methodInstance.h"

namespace EBPF {
Expand Down Expand Up @@ -86,15 +87,21 @@ class EBPFParser : public EBPFObject {

StateTranslationVisitor* visitor;

std::map<cstring, EBPFValueSet*> valueSets;

explicit EBPFParser(const EBPFProgram* program, const IR::ParserBlock* block,
const P4::TypeMap* typeMap);
virtual void emitDeclaration(CodeBuilder* builder, const IR::Declaration* decl);
void emit(CodeBuilder* builder);
virtual bool build();

virtual void emitTypes(CodeBuilder* builder) { (void) builder; }
virtual void emitValueSetInstances(CodeBuilder* builder) { (void) builder; }
virtual void emitTypes(CodeBuilder* builder);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this works with the standard ebpf backend too. Thank you.

virtual void emitValueSetInstances(CodeBuilder* builder);
virtual void emitRejectState(CodeBuilder* builder);

EBPFValueSet* getValueSet(cstring name) const {
return ::get(valueSets, name);
}
};

} // namespace EBPF
Expand Down
2 changes: 2 additions & 0 deletions backends/ebpf/ebpfProgram.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ void EBPFProgram::emitC(CodeBuilder* builder, cstring header) {
emitPreamble(builder);
builder->append("REGISTER_START()\n");
control->emitTableInstances(builder);
parser->emitValueSetInstances(builder);
builder->append("REGISTER_END()\n");
builder->newline();
builder->emitIndent();
Expand Down Expand Up @@ -138,6 +139,7 @@ void EBPFProgram::emitH(CodeBuilder* builder, cstring) {
builder->newline();
emitTypes(builder);
control->emitTableTypes(builder);
parser->emitTypes(builder);
builder->appendLine("#if CONTROL_PLANE");
builder->appendLine("static void init_tables() ");
builder->blockStart();
Expand Down
142 changes: 142 additions & 0 deletions backends/ebpf/ebpfTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -872,4 +872,146 @@ void EBPFCounterTable::emitTypes(CodeBuilder* builder) {
builder->endOfStatement(true);
}

////////////////////////////////////////////////////////////////

EBPFValueSet::EBPFValueSet(const EBPFProgram* program, const IR::P4ValueSet* p4vs,
cstring instanceName, CodeGenInspector* codeGen)
: EBPFTableBase(program, instanceName, codeGen), size(0), pvs(p4vs) {
CHECK_NULL(pvs);
valueTypeName = "u32"; // map value is not used, so its type can be anything

// validate size
if (pvs->size->is<IR::Constant>()) {
auto sc = pvs->size->to<IR::Constant>();
if (sc->fitsUint())
size = sc->asUnsigned();
if (size == 0)
::error(ErrorType::ERR_OVERLIMIT,
"Size must be a positive value less than 2^32, got %1% entries", pvs->size);
} else {
::error(ErrorType::ERR_UNSUPPORTED_ON_TARGET,
"Size of value_set must be know at compilation time: %1%", pvs->size);
}

// validate type
auto elemType = program->typeMap->getTypeType(pvs->elementType, true);
if (elemType->is<IR::Type_Bits>() || elemType->is<IR::Type_Tuple>()) {
// no restrictions
} else if (elemType->is<IR::Type_Struct>()) {
keyTypeName = elemType->to<IR::Type_Struct>()->name.name;
} else if (auto h = elemType->to<IR::Type_Header>()) {
keyTypeName = h->name.name;

::warning("Header type may contain additional shadow data: %1%", pvs->elementType);
::warning("Header defined here: %1%", h);
} else {
::error(ErrorType::ERR_UNSUPPORTED,
"Unsupported type with value_set: %1%", pvs->elementType);
}

keyTypeName = "struct " + keyTypeName;
}

void EBPFValueSet::emitTypes(CodeBuilder* builder) {
auto elemType = program->typeMap->getTypeType(pvs->elementType, true);

if (auto tsl = elemType->to<IR::Type_StructLike>()) {
for (auto field : tsl->fields) {
fieldNames.emplace_back(std::make_pair(field->name.name, field->type));
}
// Do not re-declare this type
return;
}

builder->emitIndent();
builder->appendFormat("%s ", keyTypeName.c_str());
builder->blockStart();

auto fieldEmitter = [builder](const IR::Type* type, cstring name){
auto etype = EBPFTypeFactory::instance->create(type);
builder->emitIndent();
etype->declare(builder, name, false);
builder->endOfStatement(true);
};

if (auto tb = elemType->to<IR::Type_Bits>()) {
cstring name = "field0";
fieldEmitter(tb, name);
fieldNames.emplace_back(std::make_pair(name, tb));
} else if (auto tuple = elemType->to<IR::Type_Tuple>()) {
int i = 0;
for (auto field : tuple->components) {
cstring name = Util::printf_format("field%d", i++);
fieldEmitter(field, name);
fieldNames.emplace_back(std::make_pair(name, field));
}
} else {
BUG("Type for value_set not implemented %1%", pvs->elementType);
}

builder->blockEnd(false);
builder->endOfStatement(true);
}

void EBPFValueSet::emitInstance(CodeBuilder* builder) {
builder->target->emitTableDecl(builder, instanceName, TableKind::TableHash,
keyTypeName, valueTypeName, size);
}

void EBPFValueSet::emitKeyInitializer(CodeBuilder* builder,
const IR::SelectExpression* expression,
cstring varName) {
if (fieldNames.size() != expression->select->components.size()) {
::error(ErrorType::ERR_EXPECTED,
"Fields number of value_set do not match number of arguments: %1%", expression);
return;
}
keyVarName = varName;
builder->emitIndent();
builder->appendFormat("%s %s = {0}", keyTypeName.c_str(), keyVarName.c_str());
builder->endOfStatement(true);

for (unsigned int i = 0; i < fieldNames.size(); i++) {
bool useMemcpy = true;
if (fieldNames.at(i).second->is<IR::Type_Bits>()) {
if (fieldNames.at(i).second->to<IR::Type_Bits>()->width_bits() <= 64)
useMemcpy = false;
}
builder->emitIndent();

auto keyExpr = expression->select->components.at(i);
if (useMemcpy) {
if (keyExpr->is<IR::Mask>()) {
::error(ErrorType::ERR_UNSUPPORTED_ON_TARGET,
"%1%: mask not supported for fields larger than 64 bits within value_set",
keyExpr);
continue;
}

cstring dst = Util::printf_format("%s.%s", keyVarName.c_str(),
fieldNames.at(i).first.c_str());
builder->appendFormat("__builtin_memcpy(&%s, &(", dst.c_str());
codeGen->visit(keyExpr);
builder->appendFormat("), sizeof(%s))", dst.c_str());
} else {
builder->appendFormat("%s.%s = ", keyVarName.c_str(), fieldNames.at(i).first.c_str());
if (auto mask = keyExpr->to<IR::Mask>()) {
builder->append("((");
codeGen->visit(mask->left);
builder->append(") & (");
codeGen->visit(mask->right);
builder->append("))");
} else {
codeGen->visit(keyExpr);
}
}

builder->endOfStatement(true);
}
}

void EBPFValueSet::emitLookup(CodeBuilder* builder) {
builder->target->emitTableLookup(builder, instanceName, keyVarName, "");
}

} // namespace EBPF
18 changes: 18 additions & 0 deletions backends/ebpf/ebpfTable.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,24 @@ class EBPFCounterTable : public EBPFTableBase {
virtual void emitMethodInvocation(CodeBuilder* builder, const P4::ExternMethod* method);
};

class EBPFValueSet : public EBPFTableBase {
protected:
size_t size;
const IR::P4ValueSet* pvs;
std::vector<std::pair<cstring, const IR::Type*>> fieldNames;
cstring keyVarName;

public:
EBPFValueSet(const EBPFProgram* program, const IR::P4ValueSet* p4vs,
cstring instanceName, CodeGenInspector* codeGen);

void emitTypes(CodeBuilder* builder);
void emitInstance(CodeBuilder* builder);
void emitKeyInitializer(CodeBuilder* builder, const IR::SelectExpression* expression,
cstring varName);
void emitLookup(CodeBuilder* builder);
};

} // namespace EBPF

#endif /* _BACKENDS_EBPF_EBPFTABLE_H_ */
12 changes: 11 additions & 1 deletion backends/ebpf/psa/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,15 @@ $ psabpf-ctl meter update pipe "$PIPELINE" DemoIngress_meter index 0 132000:1000

`psabpf-ctl` accepts PIR and CIR values in bytes/s units or packets/s. PBS and CBS in bytes or packets.

### value_set

[value_set](https://p4.org/p4-spec/docs/P4-16-v1.2.2.html#sec-value-set) is a P4 lang construct allowing to determine next
parser state based on runtime values. The P4-eBPF compiler generates additional hash map for each `ValueSet` instance. In
select case expression each `select()` on `ValueSet` is translated into a lookup into the BPF hash map to check if an entry
for a given key exists. A value of the BPF map is ignored.

**Note:** As of April 2022, support for value_set in `psabpf-ctl` CLI/API is not implemented yet. As a workaround you can
use the `bpftool` command.

# Getting started

Expand Down Expand Up @@ -393,6 +402,7 @@ with some NICs. So far, we have verified the correct behavior with Intel 82599ES
- `psa_idle_timeout` is not supported yet.
- DirectCounter and DirectMeter externs are not supported for P4 tables with implementation (ActionProfile).
- The `xdp2tc=head` mode works only for packets larger than 34 bytes (the size of Ethernet and IPv4 header).
- `value_set` only supports the exact match type and can only match on a single field in the `select()` expression.

# Roadmap

Expand All @@ -405,7 +415,7 @@ The TC subsystem enables implementation of the full PSA specification, contrary
contribute the XDP-based version of P4-eBPF that is not fully spec-compliant, but provides higher throughput.
- **XDP2TC mode.** the generated eBPF programs use `bpf_xdp_adjust_meta()` to transfer original EtherType from XDP to TC. This mode
may not be supported by some NIC drivers. We will add two other modes that can be used alternatively.
- **ValueSet support.** The parser generated by P4-eBPF does not support ValueSet. We plan to contribute ValueSet implementation for eBPF.
- **ValueSet support.** We plan to extend implementation to support other match kinds and multiple fields in the `select()` expression.
- **All PSA externs.** We plan to contribute implementation of [all PSA externs defined by the PSA specification](https://p4.org/p4-spec/docs/PSA.html#sec-psa-externs).
- **Ternary matching.** The PSA implementation for eBPF backend currently supports `exact` and `lpm` only. We will add support for `ternary` match kind.

Expand Down
8 changes: 8 additions & 0 deletions backends/ebpf/psa/ebpfPsaGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,14 @@ bool ConvertToEBPFParserPSA::preorder(const IR::ParserBlock *prsr) {
return true;
}

bool ConvertToEBPFParserPSA::preorder(const IR::P4ValueSet* pvs) {
cstring extName = EBPFObject::externalName(pvs);
auto instance = new EBPFValueSet(program, pvs, extName, parser->visitor);
parser->valueSets.emplace(pvs->name.name, instance);

return false;
}

// =====================EBPFControl=============================
bool ConvertToEBPFControlPSA::preorder(const IR::ControlBlock *ctrl) {
control = new EBPFControlPSA(program,
Expand Down
3 changes: 1 addition & 2 deletions backends/ebpf/psa/ebpfPsaGen.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,8 @@ class ConvertToEBPFParserPSA : public Inspector {
parser(nullptr), options(options) {}

bool preorder(const IR::ParserBlock *prsr) override;
bool preorder(const IR::P4ValueSet* pvs) override;
EBPF::EBPFParser* getEBPFParser() { return parser; }

void findValueSets(const IR::ParserBlock *prsr);
};

class ConvertToEBPFControlPSA : public Inspector {
Expand Down
25 changes: 1 addition & 24 deletions backends/ebpf/psa/ebpfPsaParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,30 +31,6 @@ bool PsaStateTranslationVisitor::preorder(const IR::Expression* expression) {
return CodeGenInspector::preorder(expression);
}

bool PsaStateTranslationVisitor::preorder(const IR::Mask *mask) {
if (currentSelectExpression == nullptr) {
::error(ErrorType::ERR_UNSUPPORTED,
"%1%: Masks outside of select expression are not supported.",
mask);
return false;
}

BUG_CHECK(currentSelectExpression->select->components.size() == 1,
"%1%: tuple not eliminated in select",
currentSelectExpression->select);

builder->append("(");
visit(currentSelectExpression->select->components.at(0));
builder->append(" & ");
visit(mask->right);
builder->append(") == (");
visit(mask->left);
builder->append(" & ");
visit(mask->right);
builder->append(")");
return false;
}

void PsaStateTranslationVisitor::processFunction(const P4::ExternFunction* function) {
if (function->method->name.name == "verify") {
compileVerify(function->expr);
Expand Down Expand Up @@ -111,6 +87,7 @@ void PsaStateTranslationVisitor::compileVerify(const IR::MethodCallExpression *
builder->blockEnd(true);
}

// =====================EBPFPsaParser=============================
EBPFPsaParser::EBPFPsaParser(const EBPFProgram* program, const IR::ParserBlock* block,
const P4::TypeMap* typeMap) : EBPFParser(program, block, typeMap) {
visitor = new PsaStateTranslationVisitor(program->refMap, program->typeMap, this);
Expand Down
Loading