Skip to content
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
86 changes: 86 additions & 0 deletions backends/bmv2/common/action.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,25 @@ void ActionConverter::convertActionBody(const IR::Vector<IR::StatOrDecl> *body,
(jumpInfo->offsetToJumpParams)[curOffset] = parameters;
primitive2->emplace_non_null("source_info"_cs, s->sourceInfoJsonObj());
continue;
} else if (s->is<IR::BreakStatement>()) {
BUG_CHECK(jumpInfo->labelIdBreak >= 0, "%1%: break statement outside of a loop", s);
unsigned int curOffset = result->size();
auto parameters = new Util::JsonArray();
auto primitive = mkPrimitive("_jump"_cs, result);
(jumpInfo->offsetToTargetLabelId)[curOffset] = jumpInfo->labelIdBreak;
(jumpInfo->offsetToJumpParams)[curOffset] = parameters;
primitive->emplace_non_null("source_info"_cs, s->sourceInfoJsonObj());
continue;
} else if (s->is<IR::ContinueStatement>()) {
BUG_CHECK(jumpInfo->labelIdContinue >= 0, "%1%: continue statement outside of a loop",
s);
unsigned int curOffset = result->size();
auto parameters = new Util::JsonArray();
auto primitive = mkPrimitive("_jump"_cs, result);
(jumpInfo->offsetToTargetLabelId)[curOffset] = jumpInfo->labelIdContinue;
(jumpInfo->offsetToJumpParams)[curOffset] = parameters;
primitive->emplace_non_null("source_info"_cs, s->sourceInfoJsonObj());
continue;
} else if (s->is<IR::AssignmentStatement>()) {
const IR::Expression *l, *r;
auto assign = s->to<IR::AssignmentStatement>();
Expand Down Expand Up @@ -267,6 +286,73 @@ void ActionConverter::convertActionBody(const IR::Vector<IR::StatOrDecl> *body,
}
(jumpInfo->labelIdToJumpOffset)[labelIdEndOfIf] = result->size();
continue;
} else if (s->is<IR::ForStatement>()) {
auto forStmt = s->to<IR::ForStatement>();

// Allocate labels for the loop structure:
// labelCondition: where the condition check starts
// labelUpdate: where continue jumps to (update section)
// labelEndOfLoop: where to jump after the loop (or on break)
int labelIdCondition = jumpInfo->numLabels;
jumpInfo->numLabels += 1;
int labelIdUpdate = jumpInfo->numLabels;
jumpInfo->numLabels += 1;
int labelIdEndOfLoop = jumpInfo->numLabels;
jumpInfo->numLabels += 1;

// Emit init statements
convertActionBody(&forStmt->init, result, inConditional, jumpInfo);

// Record label for condition check
(jumpInfo->labelIdToJumpOffset)[labelIdCondition] = result->size();

// Emit condition check - _jump_if_zero(cond, labelEndOfLoop)
{
unsigned int curOffset = result->size();
auto parameters = new Util::JsonArray();
auto primitive = mkPrimitive("_jump_if_zero"_cs, result);
auto cond = ctxt->conv->convert(forStmt->condition, true, true, true);
parameters->append(cond);
(jumpInfo->offsetToTargetLabelId)[curOffset] = labelIdEndOfLoop;
(jumpInfo->offsetToJumpParams)[curOffset] = parameters;
primitive->emplace_non_null("source_info"_cs, s->sourceInfoJsonObj());
}

// Emit loop body with break/continue context.
// Save and set the innermost loop labels so that
// BreakStatement/ContinueStatement handlers know where to jump.
int savedBreak = jumpInfo->labelIdBreak;
int savedContinue = jumpInfo->labelIdContinue;
jumpInfo->labelIdBreak = labelIdEndOfLoop;
jumpInfo->labelIdContinue = labelIdUpdate;

auto bodyVec = new IR::IndexedVector<IR::StatOrDecl>();
bodyVec->push_back(forStmt->body);
convertActionBody(bodyVec, result, true, jumpInfo);

// Restore previous loop context (for nested loops)
jumpInfo->labelIdBreak = savedBreak;
jumpInfo->labelIdContinue = savedContinue;

// Record label for update section (continue target)
(jumpInfo->labelIdToJumpOffset)[labelIdUpdate] = result->size();

// Emit update statements
convertActionBody(&forStmt->updates, result, true, jumpInfo);

// Unconditional jump back to condition check
{
unsigned int curOffset = result->size();
auto parameters = new Util::JsonArray();
auto primitive = mkPrimitive("_jump"_cs, result);
(jumpInfo->offsetToTargetLabelId)[curOffset] = labelIdCondition;
(jumpInfo->offsetToJumpParams)[curOffset] = parameters;
primitive->emplace_non_null("source_info"_cs, s->sourceInfoJsonObj());
}

// Record label for end of loop (break target)
(jumpInfo->labelIdToJumpOffset)[labelIdEndOfLoop] = result->size();
continue;
}
::P4::error(ErrorType::ERR_UNSUPPORTED, "%1% not yet supported on this target", s);
}
Expand Down
6 changes: 6 additions & 0 deletions backends/bmv2/common/action.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ struct JumpLabelInfo {
// 'exit' statement within a conditionally executed part of an
// action body, to jump to the end of the action.
int labelIdEndOfAction;
// Label ID for the end of the innermost enclosing loop (break target).
// -1 means we are not inside any loop.
int labelIdBreak = -1;
// Label ID for the update section of the innermost enclosing loop (continue target).
// -1 means we are not inside any loop.
int labelIdContinue = -1;
// Let F be the set of offsets in the action that contain a
// primitive "_jump" or _jump_if_zero". For each offset f in F,
// offsetToTargetLabelId[f] is the label ID to which the primitive
Expand Down
6 changes: 1 addition & 5 deletions backends/bmv2/common/check_unsupported.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,8 @@ limitations under the License.
namespace P4::BMV2 {

class CheckUnsupported : public Inspector {
bool preorder(const IR::ForStatement *fs) override {
error(ErrorType::ERR_UNSUPPORTED, "%sBMV2 does not support loops", fs->srcInfo);
return false;
}
bool preorder(const IR::ForInStatement *fs) override {
error(ErrorType::ERR_UNSUPPORTED, "%sBMV2 does not support loops", fs->srcInfo);
error(ErrorType::ERR_UNSUPPORTED, "%sBMV2 does not support for-in loops", fs->srcInfo);
return false;
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ p4tools_add_xfail_reason(
p4tools_add_xfail_reason(
"testgen-p4c-bmv2-metadata"
"Unhandled node type in Bmv2V1ModelCmdStepper: ForStatement"
forloop-bmv2.p4
issue4739.p4
loop-3-clause-tricky2.p4
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ p4tools_add_xfail_reason(
p4tools_add_xfail_reason(
"testgen-p4c-bmv2-ptf"
"Unhandled node type in Bmv2V1ModelCmdStepper: ForStatement"
forloop-bmv2.p4
issue4739.p4
loop-3-clause-tricky2.p4
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ p4tools_add_xfail_reason(
p4tools_add_xfail_reason(
"testgen-p4c-bmv2-protobuf-ir"
"Unhandled node type in Bmv2V1ModelCmdStepper: ForStatement"
forloop-bmv2.p4
issue4739.p4
loop-3-clause-tricky2.p4
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ p4tools_add_xfail_reason(
issue907-bmv2.p4
)

p4tools_add_xfail_reason(
"testgen-p4c-bmv2-protobuf"
"Unhandled node type in Bmv2V1ModelCmdStepper: ForStatement"
forloop-bmv2.p4
issue4739.p4
loop-3-clause-tricky2.p4
)

p4tools_add_xfail_reason(
"testgen-p4c-bmv2-protobuf"
"Could not find type for"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ p4tools_add_xfail_reason(
p4tools_add_xfail_reason(
"testgen-p4c-bmv2-stf"
"Unhandled node type in Bmv2V1ModelCmdStepper: ForStatement"
forloop-bmv2.p4
issue4739.p4
loop-3-clause-tricky2.p4
)
Expand Down
103 changes: 103 additions & 0 deletions testdata/p4_16_samples/forloop-bmv2.p4
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
Test: ForStatement support in BMv2 action bodies.

Exercises simple for-loops, nested for-loops, and loops with
early return/exit inside actions compiled to BMv2 JSON using
_jump / _jump_if_zero primitives.
*/

#include <core.p4>
#include <v1model.p4>

header hdr_t {
bit<8> count;
bit<32> sum;
bit<32> product;
}

struct Headers {
hdr_t h;
}

struct Meta {}

parser p(packet_in b, out Headers h, inout Meta m, inout standard_metadata_t sm) {
state start {
b.extract(h.h);
transition accept;
}
}

control vrfy(inout Headers h, inout Meta m) { apply {} }
control update(inout Headers h, inout Meta m) { apply {} }
control egress(inout Headers h, inout Meta m, inout standard_metadata_t sm) { apply {} }

control deparser(packet_out b, in Headers h) {
apply { b.emit(h.h); }
}

control ingress(inout Headers h, inout Meta m, inout standard_metadata_t sm) {

// Simple for-loop: sum integers 1..count
action sum_loop() {
h.h.sum = 0;
for (bit<8> i = 1; i <= h.h.count; i = i + 1) {
h.h.sum = h.h.sum + (bit<32>)i;
}
}

// Nested for-loops: compute count * count
action nested_loop() {
h.h.product = 0;
for (bit<8> i = 0; i < h.h.count; i = i + 1) {
for (bit<8> j = 0; j < h.h.count; j = j + 1) {
h.h.product = h.h.product + 1;
}
}
}

// For-loop with conditional inside
action conditional_loop() {
h.h.sum = 0;
for (bit<8> i = 0; i < 10; i = i + 1) {
if (i < 5) {
h.h.sum = h.h.sum + 1;
}
}
}

// For-loop with break: count up, break when i == 5
action break_loop() {
h.h.sum = 0;
for (bit<8> i = 0; i < h.h.count; i = i + 1) {
if (i == 5) {
break;
}
h.h.sum = h.h.sum + 1;
}
}

// For-loop with continue: skip even values, sum odd values
action continue_loop() {
h.h.sum = 0;
for (bit<8> i = 0; i < 10; i = i + 1) {
if ((bit<8>)(i & 1) == 0) {
continue;
}
h.h.sum = h.h.sum + 1;
}
}

table t {
key = { h.h.count : exact; }
actions = { sum_loop; nested_loop; conditional_loop; break_loop; continue_loop; }
const default_action = sum_loop;
}

apply {
t.apply();
sm.egress_spec = 0;
}
}

V1Switch(p(), vrfy(), ingress(), egress(), update(), deparser()) main;
39 changes: 39 additions & 0 deletions testdata/p4_16_samples/forloop-bmv2.stf
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Scenario 1: Default Action: Simple for Loop (sum_loop)
# Input packet: count=5 (0x05), sum=0, product=0
packet 0 050000000000000000
# Expected output: sum=15 (0x0f)
expect 0 050000000f00000000

# Scenario 2: Nested for Loop (nested_loop)
add t h.h.count:0x04 nested_loop()
# Input packet: count=4 (0x04)
packet 0 040000000000000000
# Expected output: product=16 (0x10)
expect 0 040000000000000010

# Scenario 3: Conditional for Loop (conditional_loop)
add t h.h.count:0x0a conditional_loop()
# Input packet: count=10 (0x0a)
packet 0 0a0000000000000000
# Expected output: sum=5 (0x05)
expect 0 0a0000000500000000

# Scenario 4: Zero Iterations (Default Action sum_loop)
# Input packet: count=0 (0x00)
packet 0 000000000000000000
# Expected output: sum=0
expect 0 000000000000000000

# Scenario 5: Break Loop (break_loop)
# Loop from 0..9, break when i==5 → sum increments for i=0,1,2,3,4 → sum=5
add t h.h.count:0x06 break_loop()
packet 0 060000000000000000
# Expected output: sum=5 (0x05)
expect 0 060000000500000000

# Scenario 6: Continue Loop (continue_loop)
# Loop from 0..9, skip even i (continue), sum++ for odd i → 1,3,5,7,9 → sum=5
add t h.h.count:0x07 continue_loop()
packet 0 070000000000000000
# Expected output: sum=5 (0x05)
expect 0 070000000500000000
Loading
Loading