compiler, runtime: Initial Support For Calling Controls

Support calling (invoking) a Control. There is still plenty more to
do here, but we are off to a good start.

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
This commit is contained in:
Will Hawkins
2026-04-30 18:39:52 -04:00
parent b934089fe7
commit ed976c7855
5 changed files with 524 additions and 28 deletions
+322
View File
@@ -0,0 +1,322 @@
// p4rse, Copyright 2026, Will Hawkins
//
// This file is part of p4rse.
//
// This file is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import Common
import Foundation
import Macros
import Runtime
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
import P4Lang
@testable import P4Compiler
@Test func test_simple_control_declaration() async throws {
let simple_parser_declaration = """
control simple() {
action a() {
}
table t {
key = {
true: exact;
}
}
apply {
}
};
"""
let x = { (tipe: P4Type) -> Bool in
switch tipe.dataType() {
case let c as Control: c.name == "simple"
default: false
}
}
let program = try! #UseOkResult(Program.Compile(simple_parser_declaration))
#expect(program.InstancesWithTypes(x).count == 1)
}
@Test func test_simple_control_declaration2() async throws {
let simple_parser_declaration = """
struct Testing {
};
control simple() {
action a() {
}
table t {
key = {
true: exact;
}
}
apply {
}
};
control complex() {
action b() {
}
table t {
key = {
true: exact;
}
}
apply {
}
};
"""
let filter = { (tipe: P4Type) -> Bool in
switch tipe.dataType() {
case let c as Control: c.name == "simple" || c.name == "complex"
default: false
}
}
let program = try! #UseOkResult(Program.Compile(simple_parser_declaration))
#expect(program.InstancesWithTypes(filter).count == 2)
}
@Test func test_simple_control_declaration_with_actions() async throws {
let simple_parser_declaration = """
control simple() {
action a() {
}
table t {
key = {
true: exact;
}
actions = {
a;
}
}
apply {
}
};
"""
let x = { (tipe: P4Type) -> Bool in
switch tipe.dataType() {
case let c as Control: c.name == "simple"
default: false
}
}
let program = try! #UseOkResult(Program.Compile(simple_parser_declaration))
#expect(program.InstancesWithTypes(x).count == 1)
}
@Test func test_simple_control_declaration_with_misnamed_actions() async throws {
let simple_parser_declaration = """
control simple() {
action a() {
}
table t {
key = {
true: exact;
}
actions = {
b;
}
}
apply {
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage: "{54, 63}: Error(s) parsing property list: {91, 26}: Error(s) parsing table actions: Cannot find b in lexical scope."
),
Program.Compile(simple_parser_declaration))
)
}
@Test func test_simple_control_declaration_with_misnamed_actions2() async throws {
let simple_parser_declaration = """
control simple() {
action a() {
}
table t {
key = {
true: exact;
}
actions = {
a;
b;
}
}
apply {
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage: "{54, 72}: Error(s) parsing property list: {91, 35}: Error(s) parsing table actions: Cannot find b in lexical scope."
),
Program.Compile(simple_parser_declaration))
)
}
@Test func test_simple_control_declaration_with_mistyped_actions() async throws {
let simple_parser_declaration = """
bool a() {
return true;
};
control simple() {
table t {
key = {
true: exact;
}
actions = {
a;
}
}
apply {
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage: "{64, 63}: Error(s) parsing property list: {101, 26}: Error(s) parsing table actions: {101, 26}: a does not name an action"
),
Program.Compile(simple_parser_declaration))
)
}
@Test func test_simple_control_declaration_with_parameters() async throws {
let simple_parser_declaration = """
control simple(bool x, bool y) {
action a() {
}
table t {
key = {
x: exact;
y: exact;
}
}
apply {
}
};
"""
#expect(#RequireOkResult(Program.Compile(simple_parser_declaration)))
}
@Test func test_simple_control_declaration_with_multiple_tables() async throws {
let simple_parser_declaration = """
control simple(bool x, bool y, bool a, bool b) {
action a() {
}
table t {
key = {
x: exact;
y: exact;
}
}
table u {
key = {
a: exact;
b: exact;
}
}
apply {
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage: "{0, 215}: More than one table in control declaration"
),
Program.Compile(simple_parser_declaration))
)
}
@Test func test_simple_control_declaration_with_action_using_parameter() async throws {
let simple_parser_declaration = """
control simple(bool x, bool y) {
action a(int z) {
z = 5;
}
table t {
key = {
x: exact;
y: exact;
}
}
apply {
}
};
"""
#expect(#RequireOkResult(Program.Compile(simple_parser_declaration)))
}
@Test func test_simple_control_declaration_with_action_using_parameter_wrong_type() async throws {
let simple_parser_declaration = """
control simple(bool x, bool y) {
action a(int z) {
z = false;
}
table t {
key = {
x: exact;
y: exact;
}
}
apply {
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage:
"{57, 10}: Failed to parse a statement element: {57, 1}: Cannot assign value with type Boolean to identifier z with type Int"
),
Program.Compile(simple_parser_declaration))
)
}
@Test func test_simple_control_declaration_with_element_after_apply() async throws {
let simple_parser_declaration = """
control simple(bool x, bool y) {
action a(int z) {
z = false;
}
table t {
key = {
x: exact;
y: exact;
}
}
apply {
}
table x {
key = {
x: exact;
y: exact;
}
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage:
"Could not compile the P4 program"
),
Program.Compile(simple_parser_declaration))
)
}
+384
View File
@@ -0,0 +1,384 @@
// p4rse, Copyright 2026, Will Hawkins
//
// This file is part of p4rse.
//
// This file is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import Common
import Foundation
import Macros
import P4Lang
import P4Runtime
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Compiler
@Test func test_control_single_key() async throws {
let simple_parser_declaration = """
control simple(inout int result, bool x) {
action a() {
result = 5;
}
action b() {
result = 7;
}
table t {
key = {
x: exact;
}
actions = {
a;
b;
}
}
apply {
}
};
"""
let program = try! #UseOkResult(Program.Compile(simple_parser_declaration))
// Pull the control out of the compiled program.
let controls = program.InstancesWithTypes() { (tipe: P4Type) -> Bool in
switch tipe.dataType() {
case let c as Control: c.name == "simple"
default: false
}
}
var control = ((controls[0].dataType() as P4DataType) as! Control)
// Add entries to the table.
control = control.updateTable(
addEntry: (
P4Value(P4BooleanValue(withValue: true)),
TypedIdentifier(name: "a", withType: P4Type(Action()))
)
).updateTable(
addEntry: (
P4Value(P4BooleanValue(withValue: false)),
TypedIdentifier(name: "b", withType: P4Type(Action()))
))
// Set a variable in the global scope for the inout first parameter.
var global_values = VarValueScopes().enter()
global_values = global_values.declare(
identifier: Identifier(name: "result_arg"),
withValue: P4Value(
P4IntValue(withValue: 0),
P4Type(P4Int())))
let runtime = try #UseOkResult(
P4Runtime.Runtime<P4TableHitMissValue, Control>.create(control: control, withGlobalValues: global_values))
let (hit_miss, updated_execution) = try #UseOkResult(runtime.run(
withArguments: ArgumentList([
Argument(TypedIdentifier(name: "result_arg", withType: P4Type(P4Int())), atIndex: 0),
Argument(P4Value(P4BooleanValue(withValue: true)), atIndex: 1),
])))
// We expect there to be a hit.
#expect(hit_miss == P4TableHitMissValue.Hit)
// And that the proper action was invoked.
let result_arg = try #UseOkResult(updated_execution.scopes.lookup(identifier: Identifier(name: "result_arg")))
#expect(result_arg.eq(P4Value(P4IntValue(withValue: 5))))
}
@Test func test_control_single_key_false() async throws {
let simple_parser_declaration = """
control simple(inout int result, bool x) {
action a() {
result = 5;
}
action b() {
result = 7;
}
table t {
key = {
x: exact;
}
actions = {
a;
b;
}
}
apply {
}
};
"""
let program = try! #UseOkResult(Program.Compile(simple_parser_declaration))
// Pull the control out of the compiled program.
let controls = program.InstancesWithTypes() { (tipe: P4Type) -> Bool in
switch tipe.dataType() {
case let c as Control: c.name == "simple"
default: false
}
}
var control = ((controls[0].dataType() as P4DataType) as! Control)
// Add entries to the table.
control = control.updateTable(
addEntry: (
P4Value(P4BooleanValue(withValue: true)),
TypedIdentifier(name: "a", withType: P4Type(Action()))
)
).updateTable(
addEntry: (
P4Value(P4BooleanValue(withValue: false)),
TypedIdentifier(name: "b", withType: P4Type(Action()))
))
// Set a variable in the global scope for the inout first parameter.
var global_values = VarValueScopes().enter()
global_values = global_values.declare(
identifier: Identifier(name: "result_arg"),
withValue: P4Value(
P4IntValue(withValue: 0),
P4Type(P4Int())))
let runtime = try #UseOkResult(
P4Runtime.Runtime<P4TableHitMissValue, Control>.create(control: control, withGlobalValues: global_values))
let (hit_miss, updated_execution) = try #UseOkResult(runtime.run(
withArguments: ArgumentList([
Argument(TypedIdentifier(name: "result_arg", withType: P4Type(P4Int())), atIndex: 0),
Argument(P4Value(P4BooleanValue(withValue: false)), atIndex: 1),
])))
// We expect there to be a hit.
#expect(hit_miss == P4TableHitMissValue.Hit)
// And that the proper action was invoked.
let result_arg = try #UseOkResult(updated_execution.scopes.lookup(identifier: Identifier(name: "result_arg")))
#expect(result_arg.eq(P4Value(P4IntValue(withValue: 7))))
}
@Test func test_control_single_integer_key_hit() async throws {
let simple_parser_declaration = """
control simple(inout int result, int x) {
action a() {
result = 5;
}
action b() {
result = 7;
}
table t {
key = {
x: exact;
}
actions = {
a;
b;
}
}
apply {
}
};
"""
let program = try! #UseOkResult(Program.Compile(simple_parser_declaration))
// Pull the control out of the compiled program.
let controls = program.InstancesWithTypes() { (tipe: P4Type) -> Bool in
switch tipe.dataType() {
case let c as Control: c.name == "simple"
default: false
}
}
var control = ((controls[0].dataType() as P4DataType) as! Control)
// Add entries to the table.
control = control.updateTable(
addEntry: (
P4Value(P4IntValue(withValue: 5)),
TypedIdentifier(name: "a", withType: P4Type(Action()))
)
).updateTable(
addEntry: (
P4Value(P4IntValue(withValue: 2)),
TypedIdentifier(name: "b", withType: P4Type(Action()))
))
// Set a variable in the global scope for the inout first parameter.
var global_values = VarValueScopes().enter()
global_values = global_values.declare(
identifier: Identifier(name: "result_arg"),
withValue: P4Value(
P4IntValue(withValue: 0),
P4Type(P4Int())))
let runtime = try #UseOkResult(
P4Runtime.Runtime<P4TableHitMissValue, Control>.create(control: control, withGlobalValues: global_values))
let (hit_miss, updated_execution) = try #UseOkResult(runtime.run(
withArguments: ArgumentList([
Argument(TypedIdentifier(name: "result_arg", withType: P4Type(P4Int())), atIndex: 0),
Argument(P4Value(P4IntValue(withValue: 5)), atIndex: 1),
])))
// We expect there to be a hit.
#expect(hit_miss == P4TableHitMissValue.Hit)
let result_arg = try #UseOkResult(updated_execution.scopes.lookup(identifier: Identifier(name: "result_arg")))
#expect(result_arg.eq(P4Value(P4IntValue(withValue: 5))))
}
@Test func test_control_single_integer_key_miss() async throws {
let simple_parser_declaration = """
control simple(inout int result, int x) {
action a() {
result = 5;
}
action b() {
result = 7;
}
table t {
key = {
x: exact;
}
actions = {
a;
b;
}
}
apply {
}
};
"""
let program = try! #UseOkResult(Program.Compile(simple_parser_declaration))
// Pull the control out of the compiled program.
let controls = program.InstancesWithTypes() { (tipe: P4Type) -> Bool in
switch tipe.dataType() {
case let c as Control: c.name == "simple"
default: false
}
}
var control = ((controls[0].dataType() as P4DataType) as! Control)
// Add entries to the table.
control = control.updateTable(
addEntry: (
P4Value(P4IntValue(withValue: 1)),
TypedIdentifier(name: "a", withType: P4Type(Action()))
)
).updateTable(
addEntry: (
P4Value(P4IntValue(withValue: 2)),
TypedIdentifier(name: "b", withType: P4Type(Action()))
))
// Set a variable in the global scope for the inout first parameter.
var global_values = VarValueScopes().enter()
global_values = global_values.declare(
identifier: Identifier(name: "result_arg"),
withValue: P4Value(
P4IntValue(withValue: 0),
P4Type(P4Int())))
let runtime = try #UseOkResult(
P4Runtime.Runtime<P4TableHitMissValue, Control>.create(control: control, withGlobalValues: global_values))
let (hit_miss, updated_execution) = try #UseOkResult(runtime.run(
withArguments: ArgumentList([
Argument(TypedIdentifier(name: "result_arg", withType: P4Type(P4Int())), atIndex: 0),
Argument(P4Value(P4IntValue(withValue: 3)), atIndex: 1),
])))
// We expect there to be a hit.
#expect(hit_miss == P4TableHitMissValue.Miss)
let result_arg = try #UseOkResult(updated_execution.scopes.lookup(identifier: Identifier(name: "result_arg")))
#expect(result_arg.eq(P4Value(P4IntValue(withValue: 0))))
}
@Test func test_control_multiple_keys() async throws {
let simple_parser_declaration = """
control simple(inout int result, bool x, int f) {
action a() {
result = 5;
}
action b() {
result = 7;
}
table t {
key = {
x: exact;
f: exact;
}
actions = {
a;
b;
}
}
apply {
}
};
"""
let program = try! #UseOkResult(Program.Compile(simple_parser_declaration))
// Pull the control out of the compiled program.
let controls = program.InstancesWithTypes() { (tipe: P4Type) -> Bool in
switch tipe.dataType() {
case let c as Control: c.name == "simple"
default: false
}
}
var control = ((controls[0].dataType() as P4DataType) as! Control)
// Add entries to the table.
control = control.updateTable(
addEntry: (
P4Value(P4BooleanValue(withValue: true)),
TypedIdentifier(name: "a", withType: P4Type(Action()))
)
).updateTable(
addEntry: (
P4Value(P4IntValue(withValue: 5)),
TypedIdentifier(name: "b", withType: P4Type(Action()))
))
// Set a variable in the global scope for the inout first parameter.
var global_values = VarValueScopes().enter()
global_values = global_values.declare(
identifier: Identifier(name: "result_arg"),
withValue: P4Value(
P4IntValue(withValue: 0),
P4Type(P4Int())))
let runtime = try #UseOkResult(
P4Runtime.Runtime<P4TableHitMissValue, Control>.create(control: control, withGlobalValues: global_values))
let (hit_miss, updated_execution) = try #UseOkResult(runtime.run(
withArguments: ArgumentList([
Argument(TypedIdentifier(name: "result_arg", withType: P4Type(P4Int())), atIndex: 0),
Argument(P4Value(P4BooleanValue(withValue: false)), atIndex: 1),
Argument(P4Value(P4IntValue(withValue: 5)), atIndex: 2),
])))
// We expect there to be a hit.
#expect(hit_miss == P4TableHitMissValue.Hit)
// And that the proper action was invoked.
let result_arg = try #UseOkResult(updated_execution.scopes.lookup(identifier: Identifier(name: "result_arg")))
#expect(result_arg.eq(P4Value(P4IntValue(withValue: 7))))
}