[EmitC] Fix Windows builds (#18546)

`_alloca(0)` returns NULL on the Windows runner, so we make sure to pad
stack allocations to at least one byte.

Fixes #18428

---------

Signed-off-by: Simon Camphausen <simon.camphausen@iml.fraunhofer.de>
diff --git a/build_tools/cmake/ctest_all.sh b/build_tools/cmake/ctest_all.sh
index 176e9a3..cdb930c 100755
--- a/build_tools/cmake/ctest_all.sh
+++ b/build_tools/cmake/ctest_all.sh
@@ -124,10 +124,6 @@
     # TODO(#11070): Fix argument/result signature mismatch
     "iree/tests/e2e/tosa_ops/check_vmvx_local-sync_microkernels_fully_connected.mlir"
     "iree/tests/e2e/tosa_ops/check_vmvx_local-sync_microkernels_matmul.mlir"
-    # TODO(#18428): Fix these tests failing on GitHub-hosted Windows runners
-    "iree/tests/e2e/stablehlo_models/mnist_fake_weights_llvm_cpu_static_c_test"
-    "iree/tests/e2e/stablehlo_models/simple_mul_llvm_cpu_static_c_test"
-    "iree/samples/static_library/static_library_demo_c_test"
   )
 elif [[ "${OSTYPE}" =~ ^darwin ]]; then
   excluded_tests+=(
diff --git a/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/ConvertVMToEmitC.cpp b/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/ConvertVMToEmitC.cpp
index 24fb391..1967a4c 100644
--- a/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/ConvertVMToEmitC.cpp
+++ b/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/ConvertVMToEmitC.cpp
@@ -1965,6 +1965,11 @@
   }
 
 private:
+  struct MaybeZeroValue {
+    Value value;
+    bool isZero;
+  };
+
   LogicalResult createVariadicImportShims(IREE::VM::ImportOp &importOp,
                                           OpBuilder &builder) const {
     SetVector<const void *> arities;
@@ -2080,23 +2085,18 @@
 
       builder.setInsertionPointToStart(block);
 
-      auto argumentSize = buildSizeExpression(
+      MaybeZeroValue argumentSize = buildSizeExpression(
           flattenInputTypes(importOp, segmentSizes, builder), builder, loc);
-      auto resultSize =
+      MaybeZeroValue resultSize =
           buildSizeExpression(importOp.getResultTypes(), builder, loc);
 
-      if (failed(argumentSize) || failed(resultSize)) {
-        return importOp.emitError()
-               << "Failed to build size expressions for call struct";
-      }
-
       const int importArgIndex = 1;
       const BlockArgument importArg = newFuncOp.getArgument(importArgIndex);
       auto importArgLValue = emitc_builders::asLValue(builder, loc, importArg);
       failIfImportUnresolved(builder, loc, importArgLValue);
 
-      auto call = buildIreeVmFunctionCallStruct(
-          importArg, argumentSize.value(), resultSize.value(), builder, loc);
+      auto call = buildIreeVmFunctionCallStruct(importArg, argumentSize,
+                                                resultSize, builder, loc);
 
       if (failed(call)) {
         return importOp.emitError() << "failed to create call struct";
@@ -2158,8 +2158,8 @@
     return {result};
   }
 
-  FailureOr<Value> buildSizeExpression(ArrayRef<Type> types, OpBuilder &builder,
-                                       Location loc) const {
+  MaybeZeroValue buildSizeExpression(ArrayRef<Type> types, OpBuilder &builder,
+                                     Location loc) const {
     auto ctx = builder.getContext();
 
     Type hostSizeType = emitc::OpaqueType::get(ctx, "iree_host_size_t");
@@ -2170,7 +2170,7 @@
                            /*resultType=*/hostSizeType,
                            /*value=*/emitc::OpaqueAttr::get(ctx, "0"))
                        .getResult();
-
+    bool isZero = true;
     for (Type type : types) {
       Type valueType = typeConverter.convertTypeAsNonPointer(type);
       Value size =
@@ -2182,14 +2182,15 @@
                        /*type=*/hostSizeType,
                        /*operands=*/ArrayRef<Value>{result, size})
                    .getResult();
+      isZero = false;
     }
 
-    return {result};
+    return MaybeZeroValue{result, isZero};
   }
 
   FailureOr<TypedValue<emitc::LValueType>>
-  buildIreeVmFunctionCallStruct(Value import, Value argumentSize,
-                                Value resultSize, OpBuilder &builder,
+  buildIreeVmFunctionCallStruct(Value import, MaybeZeroValue argumentSize,
+                                MaybeZeroValue resultSize, OpBuilder &builder,
                                 Location loc) const {
     auto ctx = builder.getContext();
 
@@ -2212,9 +2213,10 @@
     return {call};
   }
 
-  Value allocateByteSpan(TypedValue<emitc::LValueType> call, Value size,
-                         StringRef memberName, OpBuilder &builder,
-                         Location loc) const {
+  Value allocateByteSpan(TypedValue<emitc::LValueType> call,
+                         MaybeZeroValue size, StringRef memberName,
+                         OpBuilder &builder, Location loc) const {
+
     auto ctx = builder.getContext();
 
     // byteSpan = call.<memberName>;
@@ -2226,6 +2228,22 @@
                             memberName, call)
                         .getResult();
 
+    // alloca_(0) returns NULL in some configurations on Windows. Make sure to
+    // allocate at least one byte to get a valid pointer.
+    Value allocaSize;
+    if (size.isZero) {
+      Type hostSizeType = emitc::OpaqueType::get(ctx, "iree_host_size_t");
+
+      allocaSize = builder
+                       .create<emitc::ConstantOp>(
+                           /*location=*/loc,
+                           /*resultType=*/hostSizeType,
+                           /*value=*/emitc::OpaqueAttr::get(ctx, "1"))
+                       .getResult();
+    } else {
+      allocaSize = size.value;
+    }
+
     // void *byteSpan_data_void = iree_alloca(size);
     auto byteSpanDataVoid =
         builder
@@ -2234,7 +2252,7 @@
                 /*type=*/
                 emitc::PointerType::get(emitc::OpaqueType::get(ctx, "void")),
                 /*callee=*/"iree_alloca",
-                /*operands=*/ArrayRef<Value>{size})
+                /*operands=*/ArrayRef<Value>{allocaSize})
             .getResult(0);
 
     // uint8_t *byteSpan_data = (uint8_t*)byteSpan_data_void;
@@ -2250,7 +2268,7 @@
     emitc_builders::structMemberAssign(builder, loc,
                                        /*memberName=*/"data_length",
                                        /*operand=*/byteSpan,
-                                       /*value=*/size);
+                                       /*value=*/size.value);
 
     // byteSpan.data = byteSpan_data
     emitc_builders::structMemberAssign(builder, loc,
@@ -2259,7 +2277,7 @@
                                        /*value=*/byteSpanData);
 
     // memset(byteSpanData, 0, SIZE);
-    emitc_builders::memset(builder, loc, byteSpanData, 0, size);
+    emitc_builders::memset(builder, loc, byteSpanData, 0, allocaSize);
 
     return byteSpan;
   }
diff --git a/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/test/control_flow_ops.mlir b/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/test/control_flow_ops.mlir
index 3beba24..ad01b90 100644
--- a/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/test/control_flow_ops.mlir
+++ b/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/test/control_flow_ops.mlir
@@ -398,23 +398,27 @@
 
   // Allocate space for the arguments.
   // CHECK-NEXT: %[[ARGBYTESPAN_MEMBER:.+]] = "emitc.member"(%[[ARGSTRUCT]]) <{member = "arguments"}> : (!emitc.lvalue<!emitc.opaque<"iree_vm_function_call_t">>) -> !emitc.lvalue<!emitc.opaque<"iree_byte_span_t">>
-  // CHECK-NEXT: %[[ARGBYTESPANDATAVOID:.+]] = emitc.call_opaque "iree_alloca"(%[[ARGSIZE]]) : (!emitc.opaque<"iree_host_size_t">) -> !emitc.ptr<!emitc.opaque<"void">>
+  // alloca_(0) can return NULL on Windows. So we always allocate at least one byte
+  // CHECK-NEXT: %[[ARGALLOCASIZE:.+]] = "emitc.constant"() <{value = #emitc.opaque<"1">}> : () -> !emitc.opaque<"iree_host_size_t">
+  // CHECK-NEXT: %[[ARGBYTESPANDATAVOID:.+]] = emitc.call_opaque "iree_alloca"(%[[ARGALLOCASIZE]]) : (!emitc.opaque<"iree_host_size_t">) -> !emitc.ptr<!emitc.opaque<"void">>
   // CHECK-NEXT: %[[ARGBYTESPANDATA:.+]] = emitc.cast %[[ARGBYTESPANDATAVOID]] : !emitc.ptr<!emitc.opaque<"void">> to !emitc.ptr<ui8>
   // CHECK-NEXT: %[[ARGSDATALENGTH:.+]] = "emitc.member"(%[[ARGBYTESPAN_MEMBER]]) <{member = "data_length"}> : (!emitc.lvalue<!emitc.opaque<"iree_byte_span_t">>) -> !emitc.lvalue<!emitc.opaque<"iree_host_size_t">>
   // CHECK-NEXT: emitc.assign %[[ARGSIZE]] : !emitc.opaque<"iree_host_size_t"> to %[[ARGSDATALENGTH]] : <!emitc.opaque<"iree_host_size_t">>
   // CHECK-NEXT: %[[ARGSDATA:.+]] = "emitc.member"(%[[ARGBYTESPAN_MEMBER]]) <{member = "data"}> : (!emitc.lvalue<!emitc.opaque<"iree_byte_span_t">>) -> !emitc.lvalue<!emitc.ptr<ui8>>
   // CHECK-NEXT: emitc.assign %[[ARGBYTESPANDATA]] : !emitc.ptr<ui8> to %[[ARGSDATA]] : <!emitc.ptr<ui8>>
-  // CHECK-NEXT: emitc.call_opaque "memset"(%[[ARGBYTESPANDATA]], %[[ARGSIZE]]) {args = [0 : index, 0 : ui32, 1 : index]}
+  // CHECK-NEXT: emitc.call_opaque "memset"(%[[ARGBYTESPANDATA]], %[[ARGALLOCASIZE]]) {args = [0 : index, 0 : ui32, 1 : index]}
 
   // Allocate space for the result.
   // CHECK-NEXT: %[[RESBYTESPAN_MEMBER:.+]] = "emitc.member"(%[[ARGSTRUCT]]) <{member = "results"}> : (!emitc.lvalue<!emitc.opaque<"iree_vm_function_call_t">>) -> !emitc.lvalue<!emitc.opaque<"iree_byte_span_t">>
-  // CHECK-NEXT: %[[RESBYTESPANDATAVOID:.+]] = emitc.call_opaque "iree_alloca"(%[[RESULTSIZE]]) : (!emitc.opaque<"iree_host_size_t">) -> !emitc.ptr<!emitc.opaque<"void">>
+  // alloca_(0) can return NULL on Windows. So we always allocate at least one byte
+  // CHECK-NEXT: %[[RESALLOCASIZE:.+]] = "emitc.constant"() <{value = #emitc.opaque<"1">}> : () -> !emitc.opaque<"iree_host_size_t">
+  // CHECK-NEXT: %[[RESBYTESPANDATAVOID:.+]] = emitc.call_opaque "iree_alloca"(%[[RESALLOCASIZE]]) : (!emitc.opaque<"iree_host_size_t">) -> !emitc.ptr<!emitc.opaque<"void">>
   // CHECK-NEXT: %[[RESBYTESPANDATA:.+]] = emitc.cast %[[RESBYTESPANDATAVOID]] : !emitc.ptr<!emitc.opaque<"void">> to !emitc.ptr<ui8>
   // CHECK-NEXT: %[[RESSDATALENGTH:.+]] = "emitc.member"(%[[RESBYTESPAN_MEMBER]]) <{member = "data_length"}> : (!emitc.lvalue<!emitc.opaque<"iree_byte_span_t">>) -> !emitc.lvalue<!emitc.opaque<"iree_host_size_t">>
   // CHECK-NEXT: emitc.assign %[[RESULTSIZE]] : !emitc.opaque<"iree_host_size_t"> to %[[RESSDATALENGTH]] : <!emitc.opaque<"iree_host_size_t">>
   // CHECK-NEXT: %[[RESSDATA:.+]] = "emitc.member"(%[[RESBYTESPAN_MEMBER]]) <{member = "data"}> : (!emitc.lvalue<!emitc.opaque<"iree_byte_span_t">>) -> !emitc.lvalue<!emitc.ptr<ui8>>
   // CHECK-NEXT: emitc.assign %[[RESBYTESPANDATA]] : !emitc.ptr<ui8> to %[[RESSDATA]] : <!emitc.ptr<ui8>>
-  // CHECK-NEXT: emitc.call_opaque "memset"(%[[RESBYTESPANDATA]], %[[RESULTSIZE]]) {args = [0 : index, 0 : ui32, 1 : index]}
+  // CHECK-NEXT: emitc.call_opaque "memset"(%[[RESBYTESPANDATA]], %[[RESALLOCASIZE]]) {args = [0 : index, 0 : ui32, 1 : index]}
 
   // Check that we don't pack anything into the argument struct.
   // CHECK-NOT: "emitc.member"(%{{.+}}) <{member = "arguments"}>