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