Skip to content

Commit

Permalink
Add utilities for running modifications inside nested IR nodes (p4lan…
Browse files Browse the repository at this point in the history
…g#4940)

* Add ir-traversal

Signed-off-by: Vladimír Štill <[email protected]>

* Docs for ir-traversal

Signed-off-by: Vladimír Štill <[email protected]>

* Further docs tweaks

Signed-off-by: Vladimír Štill <[email protected]>

* Code tweaks

Signed-off-by: Vladimír Štill <[email protected]>

* Fix formatting

Signed-off-by: Vladimír Štill <[email protected]>

* Add tests, few fixes in impl

Signed-off-by: Vladimír Štill <[email protected]>

* Fix a typo & make lint happy

Signed-off-by: Vladimír Štill <[email protected]>

* Format

Signed-off-by: Vladimir Still <[email protected]>

* Put the namespace Detail into a separate file

Signed-off-by: Vladimír Štill <[email protected]>

* Exemplify IR::Traversal on few places in testgen

Signed-off-by: Vladimír Štill <[email protected]>

* Try using quote includes

Signed-off-by: Vladimír Štill <[email protected]>

---------

Signed-off-by: Vladimír Štill <[email protected]>
Signed-off-by: Vladimir Still <[email protected]>
  • Loading branch information
vlstill authored Oct 10, 2024
1 parent a1bb06b commit 6eaa655
Show file tree
Hide file tree
Showing 8 changed files with 373 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "ir/dump.h"
#include "ir/id.h"
#include "ir/indexed_vector.h"
#include "ir/ir-traversal.h"
#include "ir/irutils.h"
#include "lib/cstring.h"
#include "lib/exceptions.h"
Expand Down Expand Up @@ -171,10 +172,10 @@ bool AbstractStepper::stepToStructSubexpr(
return stepToSubexpr(
nonValueComponent, result, state,
[nonValueComponentIdx, rebuildCmd, subexpr](const Continuation::Parameter *v) {
auto *result = subexpr->clone();
auto *namedClone = result->components[nonValueComponentIdx]->clone();
namedClone->expression = v->param;
result->components[nonValueComponentIdx] = namedClone;
auto *result = IR::Traversal::apply(subexpr, &IR::StructExpression::components,
IR::Traversal::Index(nonValueComponentIdx),
&IR::NamedExpression::expression,
IR::Traversal::Assign(v->param));
return rebuildCmd(result);
});
}
Expand Down
28 changes: 14 additions & 14 deletions backends/p4tools/modules/testgen/core/small_step/expr_stepper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "backends/p4tools/common/lib/trace_event_types.h"
#include "backends/p4tools/common/lib/variables.h"
#include "ir/declaration.h"
#include "ir/ir-traversal.h"
#include "ir/irutils.h"
#include "ir/node.h"
#include "ir/solver.h"
Expand Down Expand Up @@ -137,23 +138,22 @@ bool ExprStepper::resolveMethodCallArguments(const IR::MethodCallExpression *cal
// the argument is not yet symbolic, try to resolve it.
return stepToSubexpr(
argExpr, result, state, [call, idx, param](const Continuation::Parameter *v) {
// TODO: It seems expensive to copy the function every time we resolve an argument.
// TODO: It seems expensive to copy the function call every time we resolve an
// argument.
// We should do this all at once. But how?
// This is the same problem as in stepToListSubexpr
// Thankfully, most method calls have less than 10 arguments.
auto *clonedCall = call->clone();
auto *arguments = clonedCall->arguments->clone();
auto *arg = arguments->at(idx)->clone();
const IR::Expression *computedExpr = v->param;
// A parameter with direction InOut might be read and also written to.
// We capture this ambiguity with an InOutReference.
if (param->direction == IR::Direction::InOut) {
auto stateVar = ToolsVariables::convertReference(arg->expression);
computedExpr = new IR::InOutReference(stateVar, computedExpr);
}
arg->expression = computedExpr;
(*arguments)[idx] = arg;
clonedCall->arguments = arguments;
auto *clonedCall = IR::Traversal::apply(
call, &IR::MethodCallExpression::arguments, IR::Traversal::Index(idx),
&IR::Argument::expression, [&](IR::Expression *expr) -> const IR::Expression * {
// A parameter with direction InOut might be read and also written to.
// We capture this ambiguity with an InOutReference.
if (param->direction == IR::Direction::InOut) {
auto stateVar = ToolsVariables::convertReference(expr);
return new IR::InOutReference(stateVar, v->param);
}
return v->param;
});
return Continuation::Return(clonedCall);
});
}
Expand Down
25 changes: 13 additions & 12 deletions backends/p4tools/modules/testgen/core/small_step/extern_stepper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "backends/p4tools/common/lib/trace_event_types.h"
#include "backends/p4tools/common/lib/variables.h"
#include "ir/id.h"
#include "ir/ir-traversal.h"
#include "ir/ir.h"
#include "ir/irutils.h"
#include "ir/vector.h"
Expand All @@ -32,6 +33,14 @@

namespace P4::P4Tools::P4Testgen {

/// Replace argument of given index in a method call with given value.
const IR::MethodCallExpression *replaceCallArg(const IR::MethodCallExpression *call, size_t argIdx,
const IR::Expression *replacement) {
return IR::Traversal::apply(call, &IR::MethodCallExpression::arguments,
IR::Traversal::Index(argIdx), &IR::Argument::expression,
IR::Traversal::Assign(replacement));
}

std::vector<std::pair<IR::StateVariable, const IR::Expression *>> ExprStepper::setFields(
ExecutionState &nextState, const std::vector<IR::StateVariable> &flatFields,
int varBitFieldSize) {
Expand Down Expand Up @@ -392,12 +401,8 @@ const ExprStepper::ExternMethodImpls<ExprStepper> ExprStepper::CORE_EXTERN_METHO
if (!SymbolicEnv::isSymbolicValue(advanceExpr)) {
stepToSubexpr(advanceExpr, stepper.result, stepper.state,
[&externInfo](const Continuation::Parameter *v) {
auto *clonedCall = externInfo.originalCall.clone();
auto *arguments = clonedCall->arguments->clone();
auto *arg = arguments->at(0)->clone();
arg->expression = v->param;
(*arguments)[0] = arg;
clonedCall->arguments = arguments;
const auto *clonedCall =
replaceCallArg(&externInfo.originalCall, 0, v->param);
return Continuation::Return(clonedCall);
});
return;
Expand Down Expand Up @@ -541,12 +546,8 @@ const ExprStepper::ExternMethodImpls<ExprStepper> ExprStepper::CORE_EXTERN_METHO
if (!SymbolicEnv::isSymbolicValue(varbitExtractExpr)) {
stepToSubexpr(varbitExtractExpr, stepper.result, stepper.state,
[&externInfo](const Continuation::Parameter *v) {
auto *clonedCall = externInfo.originalCall.clone();
auto *arguments = clonedCall->arguments->clone();
auto *arg = arguments->at(1)->clone();
arg->expression = v->param;
(*arguments)[1] = arg;
clonedCall->arguments = arguments;
const auto *clonedCall =
replaceCallArg(&externInfo.originalCall, 1, v->param);
return Continuation::Return(clonedCall);
});
return;
Expand Down
2 changes: 2 additions & 0 deletions ir/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ set (IR_HDRS
ir-inline.h
ir-tree-macros.h
ir.h
ir-traversal.h
ir-traversal-internal.h
irutils.h
json_generator.h
json_loader.h
Expand Down
108 changes: 108 additions & 0 deletions ir/ir-traversal-internal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
Copyright 2024-present Intel Corporation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* \file
* \brief IR traversing utilities, internal implementation details, not to be used directly.
* \date 2024
* \author Vladimír Štill
*/

#ifndef IR_IR_TRAVERSAL_INTERNAL_H_
#define IR_IR_TRAVERSAL_INTERNAL_H_

#include "lib/exceptions.h"
#include "lib/rtti_utils.h"

#ifndef IR_IR_TRAVERSAL_INTERNAL_ENABLE
#error "This file must not be used directly"
#endif

namespace P4::IR::Traversal::Detail {

/// @brief Internal, don't use directly.
/// The class exists so that the functions can be mutually recursive.
struct Traverse {
template <typename Obj, typename T>
static const Obj *modify(const Obj *, Assign<const T *> asgn) {
static_assert(!std::is_const_v<Obj>, "Cannot modify constant object");
return asgn.value;
}

template <typename Obj, typename T>
static const Obj *modify(Obj *, Assign<const T *> asgn) {
static_assert(!std::is_const_v<Obj>, "Cannot modify constant object");
return asgn.value;
}

template <typename Obj, typename T>
static Obj *modify(Obj *obj, Assign<T> &&asgn) {
static_assert(!std::is_const_v<Obj>, "Cannot modify constant object");
*obj = std::move(asgn.value);
return obj;
}

template <typename Obj, typename T>
static Obj *modify(Obj *obj, const Assign<T> &asgn) {
static_assert(!std::is_const_v<Obj>, "Cannot modify constant object");
*obj = asgn.value;
return obj;
}

template <typename Obj, typename Fn>
static decltype(auto) modify(Obj *obj, Fn fn) {
static_assert(!std::is_const_v<Obj>, "Cannot modify constant object");
return fn(obj);
}

template <typename Obj, typename... Selectors>
static Obj *modify(Obj *obj, Index idx, Selectors &&...selectors) {
static_assert(!std::is_const_v<Obj>, "Cannot modify constant object");
BUG_CHECK(obj->size() > idx.value, "Index %1% out of bounds of %2%", idx.value, obj);
modifyRef((*obj)[idx.value], std::forward<Selectors>(selectors)...);
return obj;
}

template <typename Obj, typename To, typename... Selectors>
static To *modify(Obj *obj, RTTI::Detail::ToType<To> cast, Selectors &&...selectors) {
static_assert(!std::is_const_v<Obj>, "Cannot modify constant object");
auto *casted = cast(obj);
BUG_CHECK(casted, "Cast of %1% failed", obj);
return modify(casted, std::forward<Selectors>(selectors)...);
}

template <typename Obj, typename T, typename Sub, typename... Selectors>
static Obj *modify(Obj *obj, Sub T::*member, Selectors &&...selectors) {
static_assert(!std::is_const_v<Obj>, "Cannot modify constant object");
modifyRef(obj->*member, std::forward<Selectors>(selectors)...);
return obj;
}

template <typename T, typename... Selectors>
static void modifyRef(T &ref, Selectors &&...selectors) {
if constexpr (std::is_pointer_v<T>) {
ref = modify(ref->clone(), std::forward<Selectors>(selectors)...);
} else {
auto *res = modify(&ref, std::forward<Selectors>(selectors)...);
if (&ref != res) {
ref = *res;
}
}
}
};

} // namespace P4::IR::Traversal::Detail

#endif // IR_IR_TRAVERSAL_INTERNAL_H_
135 changes: 135 additions & 0 deletions ir/ir-traversal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
Copyright 2024-present Intel Corporation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* \file
* \brief IR traversing utilities. This is mainly useful for modifying a sub-component of an IR
* node.
* \date 2024
* \author Vladimír Štill
*/

#ifndef IR_IR_TRAVERSAL_H_
#define IR_IR_TRAVERSAL_H_
#define IR_IR_TRAVERSAL_INTERNAL_ENABLE

#include <cstddef>

namespace P4::IR::Traversal {

/// @brief A selector used at the end of selector chain to assign to the current sub-object.
/// e.g. `modify(obj, &IR::AssignmentStatement::left, Assign(var))` will set the LHS of assignment.
/// @tparam T the parameter is usually derived by the C++ compiler.
template <typename T>
struct Assign {
explicit Assign(const T &value) : value(value) {}
T value;
};

/// @brief Select an index of a integer-indexed sub-object. %This is useful e.g. to select first
/// parameter of a method call and in similar cases involving @ref IR::Vector or @ref
/// IR::IndexedVector.
///
/// @code
/// modify(mce, &IR::MethodCallExpression::arguments, Index(0), &IR::Argument::expression,
/// Assign(val))
/// @endcode
/// This snippet assigns `val` into the first argument of a method call.
struct Index {
explicit Index(size_t value) : value(value) {}
size_t value;
};

} // namespace P4::IR::Traversal

// Put the internals out of the main file to increase its readability.
#include "ir-traversal-internal.h"

namespace P4::IR::Traversal {

/// @brief Given an object @p obj and a series of selector (ending with a modifier), modify the @p
/// obj's sub object at the selected path. %This is useful for deeper modification of objects that
/// is not based on object types (in that case please use visitors) but on object structure (e.g.
/// member paths of C++ objects).
/// @param obj An object to modify.
/// @param ...selectors A series of selectors ending either with Assign, or with a modifier
/// function (typically a lambda).
/// @return Modified object. This will usually be the same object as @p obj (unless the only
/// selector is Assign with a pointer).
///
/// @section sec_selectors Path Selectors
///
/// Selection of the sub-objects is perfomed mainly using member pointers, but other selectors are
/// also possible.
///
/// - Object member pointer (e.g. `&IR::Expression::type`, `&IR::AssignmentStatement::left`) --
/// these are used to select members of object. Note that since the IR sometimes uses inline
/// members and sometimes uses pointer members these are transparently handled the same -- this
/// behaves as-if at any point the current object was a pointer to value that is to be processed
/// by the following selectors.
/// - @ref `IR::Traversal::Index` -- used for indexing e.g. @ref `IR::Vector` or @ref
/// `IR::IndexedVector`.
/// - @ref `RTTI::to<T>` -- used to cast to a more specific type. This is necessary to access
/// members of the more specific type (e.g. if you know that a RHS of `IR::AssignmentStatement` is
/// `IR::Lss`, you can use `RTTI::to<IR::Lss>` and then access members of `IR::Lss`. Note that the
/// cast is not applied to anything -- we are passing a cast object.
///
/// @section sec_modifiers Modifiers
///
/// Modifier is a last component of the chain, it modifies the selected object.
/// - @ref `Traversal::Assign` -- a simple modifier that assigns/replaces the currently selected
/// value. If the argument is a pointer, the value is replaced, if the argument is non-pointer,
/// the selected value is assigned.
/// - freeform modifier object -- usually a lambda. This object will receive pointer to non-const
/// value of the currently selected object and can modify it in any way (e.g. increment value).
///
/// For example, given an extern call, you can modify the type of the first argument by adding a
/// cast to it like this:
/// @code
/// modify(call, &IR::MethodCallStatement::methodCall, &IR::MethodCallExpression::arguments,
/// Index(0), &IR::Argument::expression, [&](const IR::Expression *expr)
/// {
/// return new IR::Cast(expr->srcInfo, idxType, expr);
/// });
/// @endcode
///
/// Similarly, you can set the type of the first argument in the type of the extern like this:
/// @code
/// modify(call, &IR::MethodCallStatement::methodCall, &IR::MethodCallExpression::method,
/// &IR::Expression::type, RTTI::to<IR::Type_Method>, &IR::Type_Method::parameters,
/// &IR::ParameterList::parameters, Index(0), &IR::Parameter::type, Assign(idxType));
/// @endcode
///
/// Any time a cast fails or an index is out of range the modification triggers a `BUG`.
///
/// Please note that any modification is applied only to the selected path, that is, if the same
/// node occurs multiple times in the IR DAG, and one of them is "focused" by the selector and
/// modified, the other instance is unchanged (because the "focused" instance is cloned).
template <typename Obj, typename... Selectors>
Obj *modify(Obj *obj, Selectors &&...selectors) {
return Detail::Traverse::modify(obj, std::forward<Selectors>(selectors)...);
}

/// @brief Similar to modify, but accepts constant argument which is cloned. Therefore, the result
/// is a different object then @p obj. @sa `Traversal::modify`.
template <typename Obj, typename... Selectors>
Obj *apply(const Obj *obj, Selectors &&...selectors) {
return Detail::Traverse::modify(obj->clone(), std::forward<Selectors>(selectors)...);
}

} // namespace P4::IR::Traversal

#undef IR_IR_TRAVERSAL_INTERNAL_ENABLE
#endif // IR_IR_TRAVERSAL_H_
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ set (GTEST_UNITTEST_SOURCES
gtest/hvec_map.cpp
gtest/hvec_set.cpp
gtest/indexed_vector.cpp
gtest/ir-traversal-test.cpp
gtest/json_test.cpp
gtest/map.cpp
gtest/midend_def_use.cpp
Expand Down
Loading

0 comments on commit 6eaa655

Please sign in to comment.