Launch options & multi inputs for single-source RS
Bug: 23535985
Also renamed rsParallelFor to rsForEach.
Added checks for number of allocations to rsForEach matching kernel function
expectation.
Added slang tests.
Removed code from RSContext for remembering the rs_allocation AST subtree.
Change-Id: Ibc22bd5e9585a4471b15920ef60fe1fe2312de49
diff --git a/slang_backend.cpp b/slang_backend.cpp
index 738c848..87fadbd 100644
--- a/slang_backend.cpp
+++ b/slang_backend.cpp
@@ -383,17 +383,6 @@
}
bool Backend::HandleTopLevelDecl(clang::DeclGroupRef D) {
- // Find and remember the TypeDecl for rs_allocation so we can use it
- // later during the compilation
- for (clang::DeclGroupRef::iterator I = D.begin(), E = D.end();
- I != E; I++) {
- clang::TypeDecl* TD = llvm::dyn_cast<clang::TypeDecl>(*I);
- if (TD && TD->getName().equals("rs_allocation")) {
- mContext->addAllocationType(TD);
- break;
- }
- }
-
// Disallow user-defined functions with prefix "rs"
if (!mAllowRSPrefix) {
// Iterate all function declarations in the program.
@@ -432,18 +421,20 @@
AnnotateFunction(FD);
}
- if (RSExportForEach::isRSForEachFunc(getTargetAPI(), FD)) {
- // Log kernels by their names, and assign them slot numbers.
- if (getTargetAPI() == SLANG_DEVELOPMENT_TARGET_API &&
- !Slang::IsLocInRSHeaderFile(FD->getLocation(), mSourceMgr)) {
- mContext->addForEach(FD);
+ if (getTargetAPI() == SLANG_DEVELOPMENT_TARGET_API) {
+ if (FD && FD->hasBody() &&
+ RSExportForEach::isRSForEachFunc(getTargetAPI(), FD)) {
+ // Log kernels by their names, and assign them slot numbers.
+ if (!Slang::IsLocInRSHeaderFile(FD->getLocation(), mSourceMgr)) {
+ mContext->addForEach(FD);
+ }
+ } else {
+ // Look for any kernel launch calls and translate them into using the
+ // internal API.
+ // TODO: Simply ignores kernel launch inside a kernel for now.
+ // Needs more rigorous and comprehensive checks.
+ LowerRSForEachCall(FD);
}
- } else {
- // Look for any kernel launch calls and translate them into using the
- // internal API.
- // TODO: Simply ignores kernel launch inside a kernel for now.
- // Needs more rigorous and comprehensive checks.
- LowerRSForEachCall(FD);
}
}
diff --git a/slang_rs_context.cpp b/slang_rs_context.cpp
index bc674a0..5f1878c 100644
--- a/slang_rs_context.cpp
+++ b/slang_rs_context.cpp
@@ -212,10 +212,6 @@
return (ET != nullptr);
}
-void RSContext::addAllocationType(const clang::TypeDecl* TD) {
- mAllocationType = mCtx.getTypeDeclType(TD);
-}
-
bool RSContext::processExports() {
bool valid = true;
diff --git a/slang_rs_context.h b/slang_rs_context.h
index 8298a67..702c9c9 100644
--- a/slang_rs_context.h
+++ b/slang_rs_context.h
@@ -166,11 +166,6 @@
inline const std::string &getRSPackageName() const { return mRSPackageName; }
- void addAllocationType(const clang::TypeDecl* TD);
- inline const clang::QualType& getAllocationType() const {
- return mAllocationType;
- }
-
bool addForEach(const clang::FunctionDecl* FD);
bool processExports();
inline void newExportable(RSExportable *E) {
diff --git a/slang_rs_export_foreach.cpp b/slang_rs_export_foreach.cpp
index 8baac34..b602c88 100644
--- a/slang_rs_export_foreach.cpp
+++ b/slang_rs_export_foreach.cpp
@@ -629,4 +629,16 @@
return false;
}
+unsigned RSExportForEach::getNumInputs(unsigned int targetAPI,
+ const clang::FunctionDecl *FD) {
+ unsigned numInputs = 0;
+ for (const clang::ParmVarDecl* param : FD->params()) {
+ if (lookupSpecialParameter(param->getName()) < 0) {
+ numInputs++;
+ }
+ }
+
+ return numInputs;
+}
+
} // namespace slang
diff --git a/slang_rs_export_foreach.h b/slang_rs_export_foreach.h
index 033e9ed..67b3a5c 100644
--- a/slang_rs_export_foreach.h
+++ b/slang_rs_export_foreach.h
@@ -162,6 +162,9 @@
static bool isRSForEachFunc(unsigned int targetAPI,
const clang::FunctionDecl *FD);
+
+ static unsigned getNumInputs(unsigned int targetAPI,
+ const clang::FunctionDecl *FD);
}; // RSExportForEach
} // namespace slang
diff --git a/slang_rs_foreach_lowering.cpp b/slang_rs_foreach_lowering.cpp
index ab32c43..dffc072 100644
--- a/slang_rs_foreach_lowering.cpp
+++ b/slang_rs_foreach_lowering.cpp
@@ -25,9 +25,10 @@
namespace {
-const char KERNEL_LAUNCH_FUNCTION_NAME[] = "rsParallelFor";
+const char KERNEL_LAUNCH_FUNCTION_NAME[] = "rsForEach";
+const char KERNEL_LAUNCH_FUNCTION_NAME_WITH_OPTIONS[] = "rsForEachWithOptions";
const char INTERNAL_LAUNCH_FUNCTION_NAME[] =
- "_Z17rsForEachInternali13rs_allocationS_";
+ "_Z17rsForEachInternaliP14rs_script_calliiz";
} // anonymous namespace
@@ -69,19 +70,17 @@
return nullptr;
}
- // TODO: Verify the launch has the expected number of input allocations
-
return FD;
}
-// Checks if the call expression is a legal rsParallelFor call by looking for the
+// Checks if the call expression is a legal rsForEach call by looking for the
// following pattern in the AST. On success, returns the first argument that is
// a FunctionDecl of a kernel function.
//
// CallExpr 'void'
// |
// |-ImplicitCastExpr 'void (*)(void *, ...)' <FunctionToPointerDecay>
-// | `-DeclRefExpr 'void (void *, ...)' 'rsParallelFor' 'void (void *, ...)'
+// | `-DeclRefExpr 'void (void *, ...)' 'rsForEach' 'void (void *, ...)'
// |
// |-ImplicitCastExpr 'void *' <BitCast>
// | `-ImplicitCastExpr 'int (*)(int)' <FunctionToPointerDecay>
@@ -93,7 +92,7 @@
// `-ImplicitCastExpr 'rs_allocation':'rs_allocation' <LValueToRValue>
// `-DeclRefExpr 'rs_allocation':'rs_allocation' lvalue ParmVar 'out' 'rs_allocation':'rs_allocation'
const clang::FunctionDecl* RSForEachLowering::matchKernelLaunchCall(
- clang::CallExpr* CE) {
+ clang::CallExpr* CE, int* slot, bool* hasOptions) {
const clang::Decl* D = CE->getCalleeDecl();
const clang::FunctionDecl* FD = clang::dyn_cast<clang::FunctionDecl>(D);
@@ -103,15 +102,30 @@
const clang::StringRef& funcName = FD->getName();
- if (!funcName.equals(KERNEL_LAUNCH_FUNCTION_NAME)) {
+ if (funcName.equals(KERNEL_LAUNCH_FUNCTION_NAME)) {
+ *hasOptions = false;
+ } else if (funcName.equals(KERNEL_LAUNCH_FUNCTION_NAME_WITH_OPTIONS)) {
+ *hasOptions = true;
+ } else {
return nullptr;
}
- const clang::FunctionDecl* kernel = matchFunctionDesignator(CE->getArg(0));
+ clang::Expr* arg0 = CE->getArg(0);
+ const clang::FunctionDecl* kernel = matchFunctionDesignator(arg0);
- if (kernel == nullptr ||
- CE->getNumArgs() < 3) { // TODO: Make argument check more accurate
- mCtxt->ReportError(CE->getExprLoc(), "Invalid kernel launch call.");
+ if (kernel == nullptr) {
+ mCtxt->ReportError(arg0->getExprLoc(),
+ "Invalid kernel launch call. "
+ "Expects a function designator for the first argument.");
+ return nullptr;
+ }
+
+ // Verifies that kernel is indeed a "kernel" function.
+ *slot = mCtxt->getForEachSlotNumber(kernel);
+ if (*slot == -1) {
+ mCtxt->ReportError(CE->getExprLoc(), "%0 applied to non kernel function %1")
+ << funcName << kernel->getName();
+ return nullptr;
}
return kernel;
@@ -119,7 +133,6 @@
// Create an AST node for the declaration of rsForEachInternal
clang::FunctionDecl* RSForEachLowering::CreateForEachInternalFunctionDecl() {
- const clang::QualType& AllocTy = mCtxt->getAllocationType();
clang::DeclContext* DC = mASTCtxt.getTranslationUnitDecl();
clang::SourceLocation Loc;
@@ -128,19 +141,26 @@
clang::DeclarationName N(&II);
clang::FunctionProtoType::ExtProtoInfo EPI;
+ EPI.Variadic = true; // varargs
clang::QualType T = mASTCtxt.getFunctionType(
- mASTCtxt.VoidTy, // Return type
- {mASTCtxt.IntTy, AllocTy, AllocTy}, // Argument types
+ mASTCtxt.VoidTy, // Return type
+ // Argument types:
+ { mASTCtxt.IntTy, // int slot
+ mASTCtxt.VoidPtrTy, // rs_script_call_t* launch_options
+ mASTCtxt.IntTy, // int numOutput
+ mASTCtxt.IntTy // int numInputs
+ },
EPI);
clang::FunctionDecl* FD = clang::FunctionDecl::Create(
mASTCtxt, DC, Loc, Loc, N, T, nullptr, clang::SC_Extern);
+
return FD;
}
// Create an expression like the following that references the rsForEachInternal to
-// replace the callee in the original call expression that references rsParallelFor.
+// replace the callee in the original call expression that references rsForEach.
//
// ImplicitCastExpr 'void (*)(int, rs_allocation, rs_allocation)' <FunctionToPointerDecay>
// `-DeclRefExpr 'void' Function '_Z17rsForEachInternali13rs_allocationS_' 'void (int, rs_allocation, rs_allocation)'
@@ -161,28 +181,87 @@
}
// This visit method checks (via pattern matching) if the call expression is to
-// rsParallelFor, and the arguments satisfy the restrictions on the
-// rsParallelFor API. If so, replace the call with a rsForEachInternal call
+// rsForEach, and the arguments satisfy the restrictions on the
+// rsForEach API. If so, replace the call with a rsForEachInternal call
// with the first argument replaced by the slot number of the kernel function
// referenced in the original first argument.
//
// See comments to the helper methods defined above for details.
void RSForEachLowering::VisitCallExpr(clang::CallExpr* CE) {
- const clang::FunctionDecl* kernel = matchKernelLaunchCall(CE);
+ int slot;
+ bool hasOptions;
+ const clang::FunctionDecl* kernel = matchKernelLaunchCall(CE, &slot, &hasOptions);
if (kernel == nullptr) {
return;
}
+ slangAssert(slot >= 0);
+
+ const unsigned numArgsOrig = CE->getNumArgs();
+
+ clang::QualType resultType = kernel->getReturnType().getCanonicalType();
+ int numOutput = resultType->isVoidType() ? 0 : 1;
+
+ unsigned numInputs = RSExportForEach::getNumInputs(mCtxt->getTargetAPI(), kernel);
+
+ // Verifies that rsForEach takes the right number of input and output allocations.
+ // TODO: Check input/output allocation types match kernel function expectation.
+ const unsigned expectedNumAllocations = numArgsOrig - (hasOptions ? 2 : 1);
+ if (numInputs + numOutput != expectedNumAllocations) {
+ mCtxt->ReportError(
+ CE->getExprLoc(),
+ "Number of input and output allocations unexpected for kernel function %0")
+ << kernel->getName();
+ return;
+ }
+
clang::Expr* calleeNew = CreateCalleeExprForInternalForEach();
CE->setCallee(calleeNew);
- const int slot = mCtxt->getForEachSlotNumber(kernel);
- const llvm::APInt APIntSlot(mASTCtxt.getTypeSize(mASTCtxt.IntTy), slot);
+ const clang::CanQualType IntTy = mASTCtxt.IntTy;
+ const unsigned IntTySize = mASTCtxt.getTypeSize(IntTy);
+ const llvm::APInt APIntSlot(IntTySize, slot);
const clang::Expr* arg0 = CE->getArg(0);
const clang::SourceLocation Loc(arg0->getLocStart());
clang::Expr* IntSlotNum =
- clang::IntegerLiteral::Create(mASTCtxt, APIntSlot, mASTCtxt.IntTy, Loc);
+ clang::IntegerLiteral::Create(mASTCtxt, APIntSlot, IntTy, Loc);
CE->setArg(0, IntSlotNum);
+
+ const unsigned numAdditionalArgs =
+ hasOptions ?
+ 2 : // numOutputs and numInputs
+ 3; // launchOptions, numOutputs and numInputs
+
+ const unsigned numArgs = numArgsOrig + numAdditionalArgs;
+
+ // Makes extra room in the argument list for new arguments to add at position
+ // 1, 2, and 3. The last (numInputs + numOutput) arguments, i.e., the input
+ // and output allocations, are moved up numAdditionalArgs (2 or 3) positions
+ // in the argument list.
+ CE->setNumArgs(mASTCtxt, numArgs);
+ for (unsigned i = numArgs - 1; i >= 4; i--) {
+ CE->setArg(i, CE->getArg(i - numAdditionalArgs));
+ }
+
+ // Sets the new arguments for NULL launch option (if the user does not set one),
+ // the number of outputs, and the number of inputs.
+
+ if (!hasOptions) {
+ const llvm::APInt APIntZero(IntTySize, 0);
+ clang::Expr* IntNull =
+ clang::IntegerLiteral::Create(mASTCtxt, APIntZero, IntTy, Loc);
+ CE->setArg(1, IntNull);
+ }
+
+ const llvm::APInt APIntNumOutput(IntTySize, numOutput);
+ clang::Expr* IntNumOutput =
+ clang::IntegerLiteral::Create(mASTCtxt, APIntNumOutput, IntTy, Loc);
+ CE->setArg(2, IntNumOutput);
+
+ const llvm::APInt APIntNumInputs(IntTySize, numInputs);
+ clang::Expr* IntNumInputs =
+ clang::IntegerLiteral::Create(mASTCtxt, APIntNumInputs, IntTy, Loc);
+ CE->setArg(3, IntNumInputs);
}
void RSForEachLowering::VisitStmt(clang::Stmt* S) {
diff --git a/slang_rs_foreach_lowering.h b/slang_rs_foreach_lowering.h
index 6cb0e62..2e30733 100644
--- a/slang_rs_foreach_lowering.h
+++ b/slang_rs_foreach_lowering.h
@@ -42,7 +42,9 @@
clang::ASTContext& mASTCtxt;
const clang::FunctionDecl* matchFunctionDesignator(clang::Expr* expr);
- const clang::FunctionDecl* matchKernelLaunchCall(clang::CallExpr* CE);
+ const clang::FunctionDecl* matchKernelLaunchCall(clang::CallExpr* CE,
+ int* slot,
+ bool* hasOptions);
clang::FunctionDecl* CreateForEachInternalFunctionDecl();
clang::Expr* CreateCalleeExprForInternalForEach();
}; // RSForEachLowering
diff --git a/tests/F_foreach_forward_reference/foreach_forward_reference.rs b/tests/F_foreach_forward_reference/foreach_forward_reference.rs
new file mode 100644
index 0000000..904f6e9
--- /dev/null
+++ b/tests/F_foreach_forward_reference/foreach_forward_reference.rs
@@ -0,0 +1,13 @@
+// -target-api 0
+#pragma version(1)
+#pragma rs java_package_name(com.example.foo)
+
+void testStart(rs_allocation in, rs_allocation out) {
+ rsForEach(goo, in, out);
+}
+
+int RS_KERNEL goo(int a) {
+ return a;
+}
+
+
diff --git a/tests/F_foreach_forward_reference/stderr.txt.expect b/tests/F_foreach_forward_reference/stderr.txt.expect
new file mode 100644
index 0000000..8e214b1
--- /dev/null
+++ b/tests/F_foreach_forward_reference/stderr.txt.expect
@@ -0,0 +1,2 @@
+foreach_forward_reference.rs:6:13: error: use of undeclared identifier 'goo'; did you mean 'goto'?
+foreach_forward_reference.rs:6:13: error: expected expression
diff --git a/tests/F_foreach_forward_reference/stdout.txt.expect b/tests/F_foreach_forward_reference/stdout.txt.expect
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/F_foreach_forward_reference/stdout.txt.expect
diff --git a/tests/F_foreach_non_kernel/foreach_non_kernel.rs b/tests/F_foreach_non_kernel/foreach_non_kernel.rs
new file mode 100644
index 0000000..6800e64
--- /dev/null
+++ b/tests/F_foreach_non_kernel/foreach_non_kernel.rs
@@ -0,0 +1,13 @@
+// -target-api 0
+#pragma version(1)
+#pragma rs java_package_name(com.example.foo)
+
+int foo(int a) {
+ return a;
+}
+
+void testStart(rs_allocation in, rs_allocation out) {
+ rsForEach(foo, in, out);
+}
+
+
diff --git a/tests/F_foreach_non_kernel/stderr.txt.expect b/tests/F_foreach_non_kernel/stderr.txt.expect
new file mode 100644
index 0000000..6faada1
--- /dev/null
+++ b/tests/F_foreach_non_kernel/stderr.txt.expect
@@ -0,0 +1 @@
+foreach_non_kernel.rs:10:3: error: rsForEach applied to non kernel function foo
diff --git a/tests/F_foreach_non_kernel/stdout.txt.expect b/tests/F_foreach_non_kernel/stdout.txt.expect
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/F_foreach_non_kernel/stdout.txt.expect
diff --git a/tests/F_foreach_unexpected_allocs/foreach_unexpected_allocs.rs b/tests/F_foreach_unexpected_allocs/foreach_unexpected_allocs.rs
new file mode 100644
index 0000000..c77b820
--- /dev/null
+++ b/tests/F_foreach_unexpected_allocs/foreach_unexpected_allocs.rs
@@ -0,0 +1,12 @@
+// -target-api 0
+#pragma version(1)
+#pragma rs java_package_name(com.example.foo)
+
+int RS_KERNEL foo(int a) {
+ return a;
+}
+
+void testStart(rs_allocation in, rs_allocation out) {
+ rsForEach(foo, in);
+ rsForEach(foo, in, in, out);
+}
diff --git a/tests/F_foreach_unexpected_allocs/stderr.txt.expect b/tests/F_foreach_unexpected_allocs/stderr.txt.expect
new file mode 100644
index 0000000..0f5b389
--- /dev/null
+++ b/tests/F_foreach_unexpected_allocs/stderr.txt.expect
@@ -0,0 +1,2 @@
+foreach_unexpected_allocs.rs:10:3: error: Number of input and output allocations unexpected for kernel function foo
+foreach_unexpected_allocs.rs:11:3: error: Number of input and output allocations unexpected for kernel function foo
diff --git a/tests/F_foreach_unexpected_allocs/stdout.txt.expect b/tests/F_foreach_unexpected_allocs/stdout.txt.expect
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/F_foreach_unexpected_allocs/stdout.txt.expect
diff --git a/tests/F_foreach_unexpected_kernel_arg/foreach_unexpected_kernel_arg.rs b/tests/F_foreach_unexpected_kernel_arg/foreach_unexpected_kernel_arg.rs
new file mode 100644
index 0000000..338d7f2
--- /dev/null
+++ b/tests/F_foreach_unexpected_kernel_arg/foreach_unexpected_kernel_arg.rs
@@ -0,0 +1,17 @@
+// -target-api 0
+#pragma version(1)
+#pragma rs java_package_name(com.example.foo)
+
+int RS_KERNEL foo(int a) {
+ return a;
+}
+
+void testStart(rs_allocation in, rs_allocation out) {
+ int (*fp)(int) = &foo;
+ rsForEach(fp, in, out);
+ rsForEach(&foo, in, out);
+ rsForEachWithOptions(fp, NULL, in, out);
+ rsForEachWithOptions(&foo, NULL, in, out);
+}
+
+
diff --git a/tests/F_foreach_unexpected_kernel_arg/stderr.txt.expect b/tests/F_foreach_unexpected_kernel_arg/stderr.txt.expect
new file mode 100644
index 0000000..880117e
--- /dev/null
+++ b/tests/F_foreach_unexpected_kernel_arg/stderr.txt.expect
@@ -0,0 +1,4 @@
+foreach_unexpected_kernel_arg.rs:11:13: error: Invalid kernel launch call. Expects a function designator for the first argument.
+foreach_unexpected_kernel_arg.rs:12:13: error: Invalid kernel launch call. Expects a function designator for the first argument.
+foreach_unexpected_kernel_arg.rs:13:24: error: Invalid kernel launch call. Expects a function designator for the first argument.
+foreach_unexpected_kernel_arg.rs:14:24: error: Invalid kernel launch call. Expects a function designator for the first argument.
diff --git a/tests/F_foreach_unexpected_kernel_arg/stdout.txt.expect b/tests/F_foreach_unexpected_kernel_arg/stdout.txt.expect
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/F_foreach_unexpected_kernel_arg/stdout.txt.expect
diff --git a/tests/P_foreach/foreach.rs b/tests/P_foreach/foreach.rs
new file mode 100644
index 0000000..a87aab9
--- /dev/null
+++ b/tests/P_foreach/foreach.rs
@@ -0,0 +1,11 @@
+// -target-api 0
+#pragma version(1)
+#pragma rs java_package_name(com.example.foo)
+
+int RS_KERNEL foo(int a) {
+ return a;
+}
+
+void testStart(rs_allocation in, rs_allocation out) {
+ rsForEach(foo, in, out);
+}
diff --git a/tests/P_foreach/stderr.txt.expect b/tests/P_foreach/stderr.txt.expect
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/P_foreach/stderr.txt.expect
diff --git a/tests/P_foreach/stdout.txt.expect b/tests/P_foreach/stdout.txt.expect
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/P_foreach/stdout.txt.expect
diff --git a/tests/P_foreach_multi/foreach_multi.rs b/tests/P_foreach_multi/foreach_multi.rs
new file mode 100644
index 0000000..e8a597e
--- /dev/null
+++ b/tests/P_foreach_multi/foreach_multi.rs
@@ -0,0 +1,11 @@
+// -target-api 0
+#pragma version(1)
+#pragma rs java_package_name(com.example.foo)
+
+int RS_KERNEL foo(int a, int b) {
+ return a + b;
+}
+
+void testStart(rs_allocation in, rs_allocation out) {
+ rsForEach(foo, in, in, out);
+}
diff --git a/tests/P_foreach_multi/stderr.txt.expect b/tests/P_foreach_multi/stderr.txt.expect
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/P_foreach_multi/stderr.txt.expect
diff --git a/tests/P_foreach_multi/stdout.txt.expect b/tests/P_foreach_multi/stdout.txt.expect
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/P_foreach_multi/stdout.txt.expect
diff --git a/tests/P_foreach_options/foreach_options.rs b/tests/P_foreach_options/foreach_options.rs
new file mode 100644
index 0000000..cb15b73
--- /dev/null
+++ b/tests/P_foreach_options/foreach_options.rs
@@ -0,0 +1,14 @@
+// -target-api 0
+#pragma version(1)
+#pragma rs java_package_name(com.example.foo)
+
+int RS_KERNEL foo(int a) {
+ return a;
+}
+
+void testStart(rs_allocation in, rs_allocation out) {
+ rs_script_call_t opts = {0};
+ opts.xStart=0;
+ opts.xEnd = 100;
+ rsForEach(foo, in, out);
+}
diff --git a/tests/P_foreach_options/stderr.txt.expect b/tests/P_foreach_options/stderr.txt.expect
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/P_foreach_options/stderr.txt.expect
diff --git a/tests/P_foreach_options/stdout.txt.expect b/tests/P_foreach_options/stdout.txt.expect
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/P_foreach_options/stdout.txt.expect