Compare commits

...
Author SHA1 Message Date
Alessandro de Oliveira Faria (A.K.A.CABELO)andGitHub 6657ded4fa vendor : update subprocess.h (#27409) 2026-08-23 10:38:29 +03:00
Aman KarkiandGitHub 29ea9412a6 cuda : add POOL_1D support (#27573)
* cuda : add POOL_1D support

* fix: add missing trailing newline for editorconfig compliance
2026-08-23 10:37:32 +03:00
Xuan-Son NguyenandGitHub 70adb1b4ce common: json.h: fix clang lto (#27575) 2026-08-23 01:11:10 +02:00
3f545becce vulkan : added the PAD_REFLECT_1D operation (#26586)
* vulkan : added PAD_REFLECT_1D operation

Implemented the GGML_OP_PAD_REFLECT_1D operation for the Vulkan backend

Changes:
- pad_reflect_1d.comp: implemented the GLSL compute shader with reflection logic
- vulkan-shaders-gen.cpp: register the shader for SPIR-V compilation
- ggml-vulkan.cpp: pushed constants struct, pipeline creation,
  supports_op, dispatch function, compute switch and debug validation

Tested the PAD_REFLECT_1D on Intel Iris Xe (Vulkan 1.4, Mesa 25.2.8):

Correctness:
  PAD_REFLECT_1D(type=f32,ne_a=[512,34,2,1],pad_0=10,pad_1=9) = Pass
  PAD_REFLECT_1D(type=f32,ne_a=[3000,384,4,1],pad_0=10,pad_1=9) = Pass
  2/2 tests passed
 - All test are passed

Performance:
  ne_a=[512,34,2,1] -> 5.38 us/run, 24.55 GB/s
  ne_a=[3000,80,1,1] -> 30.09 us/run, 59.62 GB/s
  ne_a=[3000,384,4,1] -> 158.31 us/run, 54.39 GB/s

* Update ggml/src/ggml-vulkan/vulkan-shaders/pad_reflect_1d.comp

Co-authored-by: Jeff Bolz <jbolz@nvidia.com>

---------

Co-authored-by: Jeff Bolz <jbolz@nvidia.com>
2026-08-22 14:42:20 -05:00
10 changed files with 553 additions and 43 deletions
+22 -26
View File
@@ -78,19 +78,21 @@ common_json_value::common_json_value(const common_json & val) :
common_json_value::common_json_value(common_json && val) :
type(VAL_JSON), val_json(std::make_shared<common_json>(std::move(val))) {}
// the ctors and get<T>() below are explicit specializations, giving strong symbols
// an explicit instantiation is a weak symbol, dropped by some LTO builds (clang-cl)
template <typename T>
common_json_value::common_json_value(const std::set<T> & vals) : type(VAL_JSON) {
static std::shared_ptr<common_json> set_json(const std::set<T> & vals) {
common_json out = common_json::array();
for (const auto & val : vals) {
out.push_back(val);
}
val_json = std::make_shared<common_json>(std::move(out));
return std::make_shared<common_json>(std::move(out));
}
// a set value is usable only for the types below
#define COMMON_JSON_SET(...) template common_json_value::common_json_value(const std::set<__VA_ARGS__> &);
#define COMMON_JSON_SET(...) template <> common_json_value::common_json_value(const std::set<__VA_ARGS__> & vals) : type(VAL_JSON), val_json(set_json(vals)) {}
COMMON_JSON_SET(int)
COMMON_JSON_SET(std::string)
@@ -98,56 +100,45 @@ COMMON_JSON_SET(std::string)
#undef COMMON_JSON_SET
template <typename T>
common_json_value::common_json_value(const std::map<std::string, T> & vals) : type(VAL_JSON) {
static std::shared_ptr<common_json> map_json(const T & vals) {
common_json out = common_json::object();
for (const auto & val : vals) {
out.set({ val.first, val.second });
}
val_json = std::make_shared<common_json>(std::move(out));
return std::make_shared<common_json>(std::move(out));
}
// a map value is usable only for the types below
#define COMMON_JSON_MAP(...) template common_json_value::common_json_value(const std::map<std::string, __VA_ARGS__> &);
#define COMMON_JSON_MAP(...) template <> common_json_value::common_json_value(const std::map<std::string, __VA_ARGS__> & vals) : type(VAL_JSON), val_json(map_json(vals)) {}
COMMON_JSON_MAP(bool)
COMMON_JSON_MAP(std::string)
#undef COMMON_JSON_MAP
template <typename T>
common_json_value::common_json_value(const std::unordered_map<std::string, T> & vals) : type(VAL_JSON) {
common_json out = common_json::object();
for (const auto & val : vals) {
out.set({ val.first, val.second });
}
val_json = std::make_shared<common_json>(std::move(out));
}
// an unordered map value is usable only for the types below
#define COMMON_JSON_UMAP(...) template common_json_value::common_json_value(const std::unordered_map<std::string, __VA_ARGS__> &);
#define COMMON_JSON_UMAP(...) template <> common_json_value::common_json_value(const std::unordered_map<std::string, __VA_ARGS__> & vals) : type(VAL_JSON), val_json(map_json(vals)) {}
COMMON_JSON_UMAP(size_t)
#undef COMMON_JSON_UMAP
template <typename T>
common_json_value::common_json_value(const std::vector<T> & vals) : type(VAL_JSON) {
static std::shared_ptr<common_json> vec_json(const std::vector<T> & vals) {
common_json out = common_json::array();
for (const auto & val : vals) {
out.push_back(val);
}
val_json = std::make_shared<common_json>(std::move(out));
return std::make_shared<common_json>(std::move(out));
}
// a vector value is usable only for the types below
// note: std::vector<bool> is not here, its proxy reference does not convert
#define COMMON_JSON_VEC(...) template common_json_value::common_json_value(const std::vector<__VA_ARGS__> &);
#define COMMON_JSON_VEC(...) template <> common_json_value::common_json_value(const std::vector<__VA_ARGS__> & vals) : type(VAL_JSON), val_json(vec_json(vals)) {}
COMMON_JSON_VEC(int)
COMMON_JSON_VEC(unsigned char)
@@ -404,10 +395,6 @@ common_json::items_view common_json::items() const {
return items_view(const_cast<common_json *>(this), size());
}
template <typename T> T common_json::get() const {
return guard([&] { return as_json(this).get<T>(); });
}
// the backing library cannot build a common_json, so this one is just a copy
template <> common_json common_json::get<common_json>() const {
return *this;
@@ -415,7 +402,7 @@ template <> common_json common_json::get<common_json>() const {
// get<T>() is usable only for the types below
#define COMMON_JSON_GET(...) template __VA_ARGS__ common_json::get<__VA_ARGS__>() const;
#define COMMON_JSON_GET(...) template <> __VA_ARGS__ common_json::get<__VA_ARGS__>() const { return guard([&] { return as_json(this).get<__VA_ARGS__>(); }); }
COMMON_JSON_GET(bool)
COMMON_JSON_GET(int)
@@ -435,3 +422,12 @@ COMMON_JSON_GET(std::vector<size_t>)
COMMON_JSON_GET(std::unordered_map<std::string, size_t>)
#undef COMMON_JSON_GET
// must stay below the get<std::string> specialization
common_json::operator std::string() const {
return get<std::string>();
}
std::string common_json::value(const std::string & key, const char * def) const {
return contains(key) ? at(key).get<std::string>() : std::string(def);
}
+2 -4
View File
@@ -221,16 +221,14 @@ class common_json {
// implicit get<T>() for plain values, so they can be assigned to their C++ type directly
// note: kept to this short list on purpose, a wider one makes j["key"] ambiguous
// note: a numeric one would make "str = json;" ambiguous, a number converts to char too
operator std::string() const { return get<std::string>(); }
operator std::string() const;
template <typename T>
T value(const std::string & key, T def) const {
return contains(key) ? at(key).get<T>() : def;
}
std::string value(const std::string & key, const char * def) const {
return contains(key) ? at(key).get<std::string>() : std::string(def);
}
std::string value(const std::string & key, const char * def) const;
// a JSON default needs no get<T>(), it is already the right type
common_json value(const std::string & key, const common_json & def) const {
+5
View File
@@ -38,6 +38,7 @@
#include "ggml-cuda/out-prod.cuh"
#include "ggml-cuda/pad.cuh"
#include "ggml-cuda/pool2d.cuh"
#include "ggml-cuda/pool1d.cuh"
#include "ggml-cuda/quantize.cuh"
#include "ggml-cuda/rope.cuh"
#include "ggml-cuda/roll.cuh"
@@ -2326,6 +2327,9 @@ static bool ggml_cuda_compute_forward(ggml_backend_cuda_context & ctx, struct gg
case GGML_OP_POOL_2D:
ggml_cuda_op_pool2d(ctx, dst);
break;
case GGML_OP_POOL_1D:
ggml_cuda_op_pool1d(ctx, dst);
break;
case GGML_OP_SUM:
ggml_cuda_op_sum(ctx, dst);
break;
@@ -5245,6 +5249,7 @@ static bool ggml_backend_cuda_device_supports_op(ggml_backend_dev_t dev, const g
case GGML_OP_CONV_2D_DW:
return op->src[0]->type == GGML_TYPE_F32;
case GGML_OP_CONV_TRANSPOSE_2D:
case GGML_OP_POOL_1D:
case GGML_OP_POOL_2D:
return true;
case GGML_OP_ACC:
+85
View File
@@ -0,0 +1,85 @@
#include "pool1d.cuh"
static __global__ void pool1d_nchw_kernel(
const int iw, const int ow,
const int kw, const int sw, const int pw,
const int parallel_elements,
const float * src, float * dst, const enum ggml_op_pool op) {
const int idx = threadIdx.x + blockIdx.x * blockDim.x;
if (idx >= parallel_elements) {
return;
}
const int nc = idx / ow;
const int cur_ow = idx % ow;
const float * i_ptr = src + nc * iw;
float * o_ptr = dst + nc * ow;
const int start = cur_ow * sw - pw;
const int b = max(0, start);
const int e = min(iw, start + kw);
float res;
switch (op) {
case GGML_OP_POOL_AVG: res = 0.0f; break;
case GGML_OP_POOL_MAX: res = -FLT_MAX; break;
default: return;
}
int count = 0;
for (int i = b; i < e; i++) {
#if __CUDA_ARCH__ >= 350
float cur = __ldg(i_ptr + i);
#else
float cur = i_ptr[i];
#endif
switch (op) {
case GGML_OP_POOL_AVG: res += cur; break;
case GGML_OP_POOL_MAX: res = max(res, cur); break;
default: break;
}
count++;
}
if (op == GGML_OP_POOL_AVG) {
res = (count > 0) ? (res / count) : 0.0f;
}
o_ptr[cur_ow] = res;
}
static void pool1d_nchw_kernel_f32_f32_cuda(
const int iw, const int ow,
const int kw, const int sw, const int pw,
const int parallel_elements,
const float * src, float * dst, const enum ggml_op_pool op,
cudaStream_t stream) {
const int num_blocks = (parallel_elements + CUDA_POOL1D_BLOCK_SIZE - 1) / CUDA_POOL1D_BLOCK_SIZE;
dim3 block_nums(num_blocks);
pool1d_nchw_kernel<<<block_nums, CUDA_POOL1D_BLOCK_SIZE, 0, stream>>>(iw, ow, kw, sw, pw, parallel_elements, src, dst, op);
}
void ggml_cuda_op_pool1d(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
const ggml_tensor * src0 = dst->src[0];
const float * src0_d = (const float *)src0->data;
float * dst_d = (float *)dst->data;
cudaStream_t stream = ctx.stream();
GGML_ASSERT(src0->type == GGML_TYPE_F32);
GGML_ASSERT( dst->type == GGML_TYPE_F32);
const int32_t * opts = (const int32_t *)dst->op_params;
enum ggml_op_pool op = static_cast<ggml_op_pool>(opts[0]);
const int k0 = opts[1];
const int s0 = opts[2];
const int p0 = opts[3];
const int64_t IW = src0->ne[0];
const int64_t OW = dst->ne[0];
const int64_t nr = ggml_nrows(src0);
const int parallel_elements = (int)(nr * OW);
pool1d_nchw_kernel_f32_f32_cuda(IW, OW, k0, s0, p0, parallel_elements, src0_d, dst_d, op, stream);
}
+5
View File
@@ -0,0 +1,5 @@
#include "common.cuh"
#define CUDA_POOL1D_BLOCK_SIZE 256
void ggml_cuda_op_pool1d(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
+26
View File
@@ -955,6 +955,7 @@ struct vk_device_struct {
vk_pipeline pipeline_diag[2];
vk_pipeline pipeline_clamp[2];
vk_pipeline pipeline_pad_f32;
vk_pipeline pipeline_pad_reflect_1d_f32;
vk_pipeline pipeline_roll_f32;
vk_pipeline pipeline_repeat_i32, pipeline_repeat_back_f32;
vk_pipeline pipeline_repeat_i16;
@@ -5630,6 +5631,7 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
ggml_vk_create_pipeline(device, device->pipeline_diag[1], "diag_f16", diag_f16_len, diag_f16_data, "main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_pad_f32, "pad_f32", pad_f32_len, pad_f32_data, "main", 2, sizeof(vk_op_pad_push_constants), {512, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_pad_reflect_1d_f32, "pad_reflect_1d_f32", pad_reflect_1d_f32_len, pad_reflect_1d_f32_data, "main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_roll_f32, "roll_f32", roll_f32_len, roll_f32_data, "main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1);
@@ -11336,6 +11338,11 @@ static vk_pipeline ggml_vk_op_get_pipeline(ggml_backend_vk_context * ctx, const
return ctx->device->pipeline_pad_f32;
}
return nullptr;
case GGML_OP_PAD_REFLECT_1D:
if (src0->type == GGML_TYPE_F32 && dst->type == GGML_TYPE_F32) {
return ctx->device->pipeline_pad_reflect_1d_f32;
}
return nullptr;
case GGML_OP_ROLL:
if (src0->type == GGML_TYPE_F32 && dst->type == GGML_TYPE_F32) {
return ctx->device->pipeline_roll_f32;
@@ -12239,6 +12246,7 @@ static void ggml_vk_op_f32(ggml_backend_vk_context * ctx, vk_context& subctx, co
case GGML_OP_CLAMP:
case GGML_OP_LEAKY_RELU:
case GGML_OP_PAD:
case GGML_OP_PAD_REFLECT_1D:
case GGML_OP_ROLL:
case GGML_OP_REPEAT:
case GGML_OP_REPEAT_BACK:
@@ -13111,6 +13119,17 @@ static void ggml_vk_pad(ggml_backend_vk_context * ctx, vk_context& subctx, const
ggml_vk_op_f32(ctx, subctx, src0, nullptr, nullptr, nullptr, dst, GGML_OP_PAD, std::move(p));
}
static void ggml_vk_pad_reflect_1d(ggml_backend_vk_context * ctx, vk_context& subctx, const ggml_tensor * src0, ggml_tensor * dst) {
const uint32_t p0 = (uint32_t)dst->op_params[0];
const uint32_t p1 = (uint32_t)dst->op_params[1];
vk_op_unary_push_constants p = vk_op_unary_push_constants_init(src0, dst, ggml_nelements(dst));
memcpy(&p.param1, &p0, sizeof(float));
memcpy(&p.param2, &p1, sizeof(float));
ggml_vk_op_f32(ctx, subctx, src0, nullptr, nullptr, nullptr, dst, GGML_OP_PAD_REFLECT_1D, std::move(p));
}
static void ggml_vk_roll(ggml_backend_vk_context * ctx, vk_context& subctx, const ggml_tensor * src0, ggml_tensor * dst) {
const int32_t s0 = ggml_get_op_params_i32(dst, 0);
const int32_t s1 = ggml_get_op_params_i32(dst, 1);
@@ -15520,6 +15539,10 @@ static bool ggml_vk_build_graph(ggml_backend_vk_context * ctx, ggml_cgraph * cgr
case GGML_OP_PAD:
ggml_vk_pad(ctx, compute_ctx, src0, node);
break;
case GGML_OP_PAD_REFLECT_1D:
ggml_vk_pad_reflect_1d(ctx, compute_ctx, src0, node);
break;
case GGML_OP_ROLL:
ggml_vk_roll(ctx, compute_ctx, src0, node);
@@ -18446,6 +18469,7 @@ static bool ggml_backend_vk_device_supports_op(ggml_backend_dev_t dev, const ggm
case GGML_OP_SCALE:
return ggml_is_contiguous(op->src[0]) && op->src[0]->type == GGML_TYPE_F32;
case GGML_OP_PAD:
case GGML_OP_PAD_REFLECT_1D:
case GGML_OP_ROLL:
return op->src[0]->type == GGML_TYPE_F32;
case GGML_OP_DIAG_MASK_INF:
@@ -19228,6 +19252,8 @@ static void ggml_vk_check_results_0(ggml_backend_vk_context * ctx, ggml_cgraph *
} else if (tensor->op == GGML_OP_PAD) {
tensor_clone = ggml_pad_ext(ggml_ctx, src_clone[0], tensor->op_params[0], tensor->op_params[1], tensor->op_params[2], tensor->op_params[3],
tensor->op_params[4], tensor->op_params[5], tensor->op_params[6], tensor->op_params[7]);
} else if (tensor->op == GGML_OP_PAD_REFLECT_1D) {
tensor_clone = ggml_pad_reflect_1d(ggml_ctx, src_clone[0], tensor->op_params[0], tensor->op_params[1]);
} else if (tensor->op == GGML_OP_REPEAT) {
tensor_clone = ggml_repeat(ggml_ctx, src_clone[0], tensor);
} else if (tensor->op == GGML_OP_REPEAT_BACK) {
@@ -0,0 +1,43 @@
#version 450
#include "types.glsl"
#include "generic_unary_head.glsl" // included to use functions like fastdiv etc.
layout(local_size_x = 512, local_size_y = 1, local_size_z = 1) in;
void main() {
const uint idx = get_idx();
if (idx >= p.ne) {
return;
}
const uint p0 = floatBitsToUint(p.param1);
const uint p1 = floatBitsToUint(p.param2);
const uint i3 = fastdiv(idx, p.ne1_012mp, fastdiv_L(p.ne1_Ls, 0));
const uint i3_offset = i3 * p.ne12 * p.ne11 * p.ne10;
const uint i2 = fastdiv(idx - i3_offset, p.ne1_01mp, fastdiv_L(p.ne1_Ls, 1));
const uint i2_offset = i2 * p.ne11 * p.ne10;
const uint i1 = fastdiv(idx - i3_offset - i2_offset, p.ne1_0mp, fastdiv_L(p.ne1_Ls, 2));
const uint i0 = idx - i3_offset - i2_offset - i1 * p.ne10;
uint src_col;
if (i0 < p0) {
src_col = p0 - i0; // left pad area
} else if (i0 < p0 + p.ne00) {
src_col = i0 - p0; // center area
} else {
src_col = 2u * p.ne00 - 2u - (i0 - p0); // right pad area
}
const uint src_idx = i3 * p.nb03 + i2 * p.nb02 + i1 * p.nb01 + src_col * p.nb00;
const uint d_idx = i3 * p.nb13 + i2 * p.nb12 + i1 * p.nb11 + i0 * p.nb10;
// copy the computed value to the destination tensor
data_d[get_doffset() + d_idx] = D_TYPE(data_a[get_aoffset() + src_idx]);
}
@@ -896,6 +896,7 @@ void process_shaders() {
string_to_spv("scale_f32", "scale.comp", {{"A_TYPE", "float"}, {"D_TYPE", "float"}, {"FLOAT_TYPE", "float"}});
string_to_spv("pad_f32", "pad.comp", {{"A_TYPE", "float"}, {"D_TYPE", "float"}});
string_to_spv("pad_reflect_1d_f32", "pad_reflect_1d.comp", {{"A_TYPE", "float"}, {"D_TYPE", "float"}});
string_to_spv("concat_i8", "concat.comp", {{"A_TYPE", "uint8_t"}, {"B_TYPE", "uint8_t"}, {"D_TYPE", "uint8_t"}});
string_to_spv("concat_i16", "concat.comp", {{"A_TYPE", "uint16_t"}, {"B_TYPE", "uint16_t"}, {"D_TYPE", "uint16_t"}});
+1 -1
View File
@@ -27,7 +27,7 @@ vendor = {
f"https://raw.githubusercontent.com/yhirose/cpp-httplib/{HTTPLIB_VERSION}/split.py": "split.py",
f"https://raw.githubusercontent.com/yhirose/cpp-httplib/{HTTPLIB_VERSION}/LICENSE": "vendor/cpp-httplib/LICENSE",
"https://raw.githubusercontent.com/sheredom/subprocess.h/9ce0d701b6fb10f8f8c4445edd31e7c60a1237e3/subprocess.h": "vendor/sheredom/subprocess.h",
"https://raw.githubusercontent.com/sheredom/subprocess.h/0dccaa9aa176dd6d7ef8afeca3c18d6e80a32795/subprocess.h": "vendor/sheredom/subprocess.h",
f"https://raw.githubusercontent.com/Cyan4973/xxHash/{XXHASH_COMMIT}/xxhash.c": "vendor/hash/xxhash/xxhash.c",
f"https://raw.githubusercontent.com/Cyan4973/xxHash/{XXHASH_COMMIT}/xxhash.h": "vendor/hash/xxhash/xxhash.h",
+363 -12
View File
@@ -275,13 +275,46 @@ subprocess_weak int subprocess_alive(struct subprocess_s *const process);
#include <unistd.h>
#endif
#if defined(__NetBSD__)
#include <sys/param.h>
#endif
/* Which spelling of the chdir file action the platform provides, if any.
POSIX 2024 standardised posix_spawn_file_actions_addchdir; implementations
that shipped it earlier called it ..._np. macOS 26 and NetBSD 10 use the
standard name, glibc 2.29+, macOS 10.15+ and FreeBSD 13.1+ use the _np name,
and AIX, NetBSD 9 and older, and OpenBSD provide neither. */
#if !defined(SUBPROCESS_ADDCHDIR_IS_POSIX)
#if (defined(__APPLE__) && MAC_OS_X_VERSION_MIN_REQUIRED >= 260000) || \
(defined(__NetBSD__) && __NetBSD_Version__ >= 1000000000)
#define SUBPROCESS_ADDCHDIR_IS_POSIX 1
#else
#define SUBPROCESS_ADDCHDIR_IS_POSIX 0
#endif
#endif
/* Whether to launch the child with fork()+exec() instead of posix_spawn(),
for platforms with no posix_spawn_file_actions_addchdir under either
spelling: the child chdir()s before exec, and a close-on-exec pipe carries
exec's errno back. Define this yourself to force either implementation. */
#if !defined(SUBPROCESS_SPAWN_VIA_FORK)
#if defined(_AIX) || defined(__OpenBSD__) || \
(defined(__NetBSD__) && (__NetBSD_Version__ < 1000000000))
#define SUBPROCESS_SPAWN_VIA_FORK 1
#else
#define SUBPROCESS_SPAWN_VIA_FORK 0
#endif
#endif
/* Whether subprocess_create_ex can honour process_cwd. glibc only gained
posix_spawn_file_actions_addchdir_np in 2.29, and macOS in 10.15; the SDKs
mark it unavailable on iOS, tvOS and watchOS, where the undefined version
macro folds to 0 and so answers correctly. Define this yourself to override
the detection, for instance on musl older than 1.1.24. */
#if !defined(SUBPROCESS_HAVE_CWD)
#if defined(__GLIBC__)
#if SUBPROCESS_SPAWN_VIA_FORK
#define SUBPROCESS_HAVE_CWD 1
#elif defined(__GLIBC__)
#if __GLIBC_PREREQ(2, 29)
#define SUBPROCESS_HAVE_CWD 1
#else
@@ -294,10 +327,13 @@ subprocess_weak int subprocess_alive(struct subprocess_s *const process);
#endif
#endif
/* Whether posix_spawn reports a failed exec back to the caller. glibc only
started doing so in 2.24; before that the child silently exits with 127. */
/* Whether a failed exec is reported back to the caller. The fork() path always
reports it through its error pipe. glibc's posix_spawn only started doing so
in 2.24; before that the child silently exits with 127. */
#if !defined(SUBPROCESS_SPAWN_REPORTS_EXEC_ERRORS)
#if defined(__GLIBC__)
#if SUBPROCESS_SPAWN_VIA_FORK
#define SUBPROCESS_SPAWN_REPORTS_EXEC_ERRORS 1
#elif defined(__GLIBC__)
#if __GLIBC_PREREQ(2, 24)
#define SUBPROCESS_SPAWN_REPORTS_EXEC_ERRORS 1
#else
@@ -342,6 +378,14 @@ typedef intptr_t subprocess_intptr_t;
typedef size_t subprocess_size_t;
#endif
/* SIZE_T is ULONG_PTR, which is not size_t: on Win32 both are 32 bits wide but
unsigned long and unsigned int are still distinct types. */
#ifdef _WIN64
typedef subprocess_size_t subprocess_ulongptr_t;
#else
typedef unsigned long subprocess_ulongptr_t;
#endif
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreserved-identifier"
@@ -351,6 +395,7 @@ typedef struct _PROCESS_INFORMATION *LPPROCESS_INFORMATION;
typedef struct _SECURITY_ATTRIBUTES *LPSECURITY_ATTRIBUTES;
typedef struct _STARTUPINFOW *LPSTARTUPINFOW;
typedef struct _OVERLAPPED *LPOVERLAPPED;
typedef struct _PROC_THREAD_ATTRIBUTE_LIST *LPPROC_THREAD_ATTRIBUTE_LIST;
#ifdef __clang__
#pragma clang diagnostic pop
@@ -402,6 +447,11 @@ struct subprocess_startup_info_s {
void *hStdError;
};
struct subprocess_startup_info_ex_s {
struct subprocess_startup_info_s startupInfo;
void *attributeList;
};
struct subprocess_overlapped_s {
uintptr_t Internal;
uintptr_t InternalHigh;
@@ -451,6 +501,14 @@ __declspec(dllimport) int __stdcall CreateProcessW(
const subprocess_wchar_t *, subprocess_wchar_t *, LPSECURITY_ATTRIBUTES,
LPSECURITY_ATTRIBUTES, int, unsigned long, void *,
const subprocess_wchar_t *, LPSTARTUPINFOW, LPPROCESS_INFORMATION);
__declspec(dllimport) int __stdcall
InitializeProcThreadAttributeList(LPPROC_THREAD_ATTRIBUTE_LIST, unsigned long,
unsigned long, subprocess_ulongptr_t *);
__declspec(dllimport) int __stdcall UpdateProcThreadAttribute(
LPPROC_THREAD_ATTRIBUTE_LIST, unsigned long, subprocess_ulongptr_t, void *,
subprocess_ulongptr_t, void *, subprocess_ulongptr_t *);
__declspec(dllimport) void __stdcall
DeleteProcThreadAttributeList(LPPROC_THREAD_ATTRIBUTE_LIST);
__declspec(dllimport) int __stdcall MultiByteToWideChar(
unsigned int, unsigned long, const char *, int, subprocess_wchar_t *, int);
__declspec(dllimport) int __stdcall CloseHandle(void *);
@@ -667,12 +725,104 @@ int subprocess_create_named_pipe_helper(void **rd, void **wr) {
}
#endif
#if !defined(_WIN32)
/* Move a pipe end off 0, 1 or 2. Duplicating a descriptor onto itself is a
no-op, so a pipe end already sitting on a standard descriptor would keep its
FD_CLOEXEC and be closed by exec, leaving the child without that stream. */
static int subprocess_fds_above_std(int fds[2]) {
int fd_flags;
int index;
int moved;
int saved_errno;
for (index = 0; index < 2; index++) {
if (fds[index] > STDERR_FILENO) {
continue;
}
moved = fcntl(fds[index], F_DUPFD, STDERR_FILENO + 1);
if (-1 != moved) {
fd_flags = fcntl(moved, F_GETFD, 0);
if ((-1 == fd_flags) ||
(-1 == fcntl(moved, F_SETFD, fd_flags | FD_CLOEXEC))) {
saved_errno = errno;
close(moved);
errno = saved_errno;
moved = -1;
}
}
if (-1 == moved) {
saved_errno = errno;
close(fds[0]);
close(fds[1]);
fds[0] = -1;
fds[1] = -1;
errno = saved_errno;
return -1;
}
close(fds[index]);
fds[index] = moved;
}
return 0;
}
/* Create pipes with close-on-exec set so later subprocesses do not inherit
descriptors belonging to subprocesses which are already running. */
static int subprocess_pipe_cloexec(int fds[2]) {
int fd_flags;
int index;
int saved_errno;
#if defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || \
defined(__OpenBSD__) || defined(__DragonFly__) || \
(defined(__sun) && defined(__SVR4))
if (0 == pipe2(fds, O_CLOEXEC)) {
return subprocess_fds_above_std(fds);
}
/* Older kernels can lack pipe2 even when the C library declares it. */
if (ENOSYS != errno) {
return -1;
}
#endif
if (0 != pipe(fds)) {
return -1;
}
for (index = 0; index < 2; index++) {
fd_flags = fcntl(fds[index], F_GETFD, 0);
if ((-1 == fd_flags) ||
(-1 == fcntl(fds[index], F_SETFD, fd_flags | FD_CLOEXEC))) {
saved_errno = errno;
close(fds[0]);
close(fds[1]);
fds[0] = -1;
fds[1] = -1;
errno = saved_errno;
return -1;
}
}
return subprocess_fds_above_std(fds);
}
#endif
int subprocess_create(const char *const commandLine[], int options,
struct subprocess_s *const out_process) {
return subprocess_create_ex(commandLine, options, SUBPROCESS_NULL,
SUBPROCESS_NULL, out_process);
}
#if SUBPROCESS_SPAWN_VIA_FORK
/* Not every platform declares execvpe: AIX exports it from libc without ever
naming it in a header, and glibc hides it behind _GNU_SOURCE. */
extern int execvpe(const char *, char *const *, char *const *);
#endif
int subprocess_create_ex(const char *const commandLine[], int options,
const char *const environment[],
const char *const process_cwd,
@@ -692,6 +842,7 @@ int subprocess_create_ex(const char *const commandLine[], int options,
subprocess_size_t bs_run;
unsigned long flags = 0;
unsigned long last_error = 0;
int attribute_list_initialized = 0;
int result = subprocess_error_unknown;
const unsigned int codePageUtf8 = 65001;
const unsigned long mbErrInvalidChars = 0x00000008;
@@ -699,6 +850,8 @@ int subprocess_create_ex(const char *const commandLine[], int options,
const unsigned long handleFlagInherit = 0x00000001;
const unsigned long createNoWindow = 0x08000000;
const unsigned long createUnicodeEnvironment = 0x00000400;
const unsigned long extendedStartupInfoPresent = 0x00080000;
const subprocess_size_t procThreadAttributeHandleList = 0x00020002;
struct subprocess_subprocess_information_s processInfo = {SUBPROCESS_NULL,
SUBPROCESS_NULL, 0,
0};
@@ -706,6 +859,11 @@ int subprocess_create_ex(const char *const commandLine[], int options,
SUBPROCESS_NULL, 1};
subprocess_wchar_t empty_environment[2] = {0, 0};
subprocess_wchar_t *used_environment = SUBPROCESS_NULL;
subprocess_ulongptr_t attribute_list_size = 0;
subprocess_size_t inherited_handle_count = 0;
LPPROC_THREAD_ATTRIBUTE_LIST attribute_list = SUBPROCESS_NULL;
void *inherited_handles[3];
struct subprocess_startup_info_ex_s startInfoEx;
struct subprocess_startup_info_s startInfo = {0,
SUBPROCESS_NULL,
SUBPROCESS_NULL,
@@ -1080,6 +1238,44 @@ int subprocess_create_ex(const char *const commandLine[], int options,
}
}
/* Restrict inheritance to this subprocess's standard streams. Without a
handle list, concurrent subprocess_create calls can inherit each other's
temporarily-inheritable child pipe handles. */
inherited_handles[inherited_handle_count++] = startInfo.hStdInput;
inherited_handles[inherited_handle_count++] = startInfo.hStdOutput;
if (startInfo.hStdError != startInfo.hStdOutput) {
inherited_handles[inherited_handle_count++] = startInfo.hStdError;
}
InitializeProcThreadAttributeList(SUBPROCESS_NULL, 1, 0,
&attribute_list_size);
if (0 == attribute_list_size) {
result = subprocess_error_spawn;
goto cleanup;
}
attribute_list = SUBPROCESS_PTR_CAST(LPPROC_THREAD_ATTRIBUTE_LIST,
_alloca(attribute_list_size));
if (!attribute_list || !InitializeProcThreadAttributeList(
attribute_list, 1, 0, &attribute_list_size)) {
result = subprocess_error_spawn;
goto cleanup;
}
attribute_list_initialized = 1;
if (!UpdateProcThreadAttribute(
attribute_list, 0, procThreadAttributeHandleList, inherited_handles,
inherited_handle_count * sizeof(inherited_handles[0]),
SUBPROCESS_NULL, SUBPROCESS_NULL)) {
result = subprocess_error_spawn;
goto cleanup;
}
startInfoEx.startupInfo = startInfo;
startInfoEx.startupInfo.cb = sizeof(startInfoEx);
startInfoEx.attributeList = attribute_list;
flags |= extendedStartupInfoPresent;
if (!CreateProcessW(
SUBPROCESS_NULL,
commandLineCombinedWide, // command line
@@ -1090,7 +1286,7 @@ int subprocess_create_ex(const char *const commandLine[], int options,
used_environment, // used environment
process_cwd_wide, // use specified current directory
SUBPROCESS_PTR_CAST(LPSTARTUPINFOW,
&startInfo), // STARTUPINFO pointer
&startInfoEx), // STARTUPINFOEX pointer
SUBPROCESS_PTR_CAST(LPPROCESS_INFORMATION, &processInfo))) {
result = subprocess_error_from_windows_error(GetLastError());
if (subprocess_error_unknown == result) {
@@ -1099,6 +1295,9 @@ int subprocess_create_ex(const char *const commandLine[], int options,
goto cleanup;
}
DeleteProcThreadAttributeList(attribute_list);
attribute_list_initialized = 0;
out_process->hProcess = processInfo.hProcess;
processInfo.hProcess = SUBPROCESS_NULL;
@@ -1128,6 +1327,10 @@ int subprocess_create_ex(const char *const commandLine[], int options,
cleanup:
last_error = GetLastError();
if (attribute_list_initialized) {
DeleteProcThreadAttributeList(attribute_list);
}
if (subprocess_error_unknown == result) {
result = subprocess_error_from_windows_error(last_error);
}
@@ -1173,15 +1376,20 @@ cleanup:
int stderrfd[2] = {-1, -1};
int fd, fd_flags;
int async_no_wait;
int actions_created = 0;
int result = subprocess_error_unknown;
int saved_errno = 0;
int posix_error;
pid_t child = 0;
extern char **environ;
char *const empty_environment[1] = {SUBPROCESS_NULL};
posix_spawn_file_actions_t actions;
char *const *used_environment;
#if SUBPROCESS_SPAWN_VIA_FORK
/* Pipe used to relay the child's exec() errno back to the parent. */
int exec_errfd[2] = {-1, -1};
#else
int actions_created = 0;
int posix_error;
posix_spawn_file_actions_t actions;
#endif
async_no_wait = subprocess_option_enable_async_no_wait ==
(options & subprocess_option_enable_async_no_wait);
@@ -1202,13 +1410,13 @@ cleanup:
memset(out_process, 0, sizeof(*out_process));
if (0 != pipe(stdinfd)) {
if (0 != subprocess_pipe_cloexec(stdinfd)) {
saved_errno = errno;
result = subprocess_error_pipe;
goto cleanup;
}
if (0 != pipe(stdoutfd)) {
if (0 != subprocess_pipe_cloexec(stdoutfd)) {
saved_errno = errno;
result = subprocess_error_pipe;
goto cleanup;
@@ -1216,7 +1424,7 @@ cleanup:
if (subprocess_option_combined_stdout_stderr !=
(options & subprocess_option_combined_stdout_stderr)) {
if (0 != pipe(stderrfd)) {
if (0 != subprocess_pipe_cloexec(stderrfd)) {
saved_errno = errno;
result = subprocess_error_pipe;
goto cleanup;
@@ -1240,6 +1448,136 @@ cleanup:
used_environment = empty_environment;
}
#if SUBPROCESS_SPAWN_VIA_FORK
/* fork()+exec() instead of posix_spawn, so the child can chdir() first.
exec_errfd[1] is close-on-exec: a successful exec closes it and the parent
reads EOF; a failed exec writes errno through it before _exit. */
if (0 != pipe(exec_errfd)) {
saved_errno = errno;
result = subprocess_error_pipe;
goto cleanup;
}
if (-1 == fcntl(exec_errfd[1], F_SETFD, FD_CLOEXEC)) {
saved_errno = errno;
result = subprocess_error_spawn;
goto cleanup;
}
child = fork();
if (child < 0) {
saved_errno = errno;
result = subprocess_error_spawn;
goto cleanup;
}
if (0 == child) {
/* Child. Everything below must stay async-signal-safe: after fork() in a
threaded process only such functions may be called before exec. */
int child_errno;
close(exec_errfd[0]);
if ((-1 == dup2(stdinfd[0], STDIN_FILENO)) ||
(-1 == dup2(stdoutfd[1], STDOUT_FILENO))) {
goto child_failed;
}
if (subprocess_option_combined_stdout_stderr ==
(options & subprocess_option_combined_stdout_stderr)) {
if (-1 == dup2(STDOUT_FILENO, STDERR_FILENO)) {
goto child_failed;
}
} else {
if (-1 == dup2(stderrfd[1], STDERR_FILENO)) {
goto child_failed;
}
}
/* The originals are only closed once they have been duplicated, so that a
pipe end that already sits on 0, 1 or 2 is not closed out from under us. */
if (stdinfd[0] > STDERR_FILENO) {
close(stdinfd[0]);
}
if (stdinfd[1] > STDERR_FILENO) {
close(stdinfd[1]);
}
if (stdoutfd[0] > STDERR_FILENO) {
close(stdoutfd[0]);
}
if (stdoutfd[1] > STDERR_FILENO) {
close(stdoutfd[1]);
}
if (stderrfd[0] > STDERR_FILENO) {
close(stderrfd[0]);
}
if (stderrfd[1] > STDERR_FILENO) {
close(stderrfd[1]);
}
if (process_cwd && (0 != chdir(process_cwd))) {
goto child_failed;
}
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wcast-qual"
#pragma clang diagnostic ignored "-Wold-style-cast"
#endif
if (subprocess_option_search_user_path ==
(options & subprocess_option_search_user_path)) {
execvpe(commandLine[0],
SUBPROCESS_CONST_CAST(char *const *, commandLine),
SUBPROCESS_CONST_CAST(char *const *, used_environment));
} else {
execve(commandLine[0],
SUBPROCESS_CONST_CAST(char *const *, commandLine),
SUBPROCESS_CONST_CAST(char *const *, used_environment));
}
#ifdef __clang__
#pragma clang diagnostic pop
#endif
child_failed:
child_errno = errno;
/* Nothing useful can be done if this write fails; the parent then sees EOF
and reports success, exactly as posix_spawn would without exec reporting. */
(void)!write(exec_errfd[1], &child_errno, sizeof(child_errno));
/* 127 is what POSIX requires posix_spawn's child to exit with when exec
fails, so both implementations look the same to a caller. */
_exit(127);
}
/* Parent. */
close(exec_errfd[1]);
exec_errfd[1] = -1;
{
int child_errno = 0;
ssize_t bytes_read;
do {
bytes_read = read(exec_errfd[0], &child_errno, sizeof(child_errno));
} while ((-1 == bytes_read) && (EINTR == errno));
close(exec_errfd[0]);
exec_errfd[0] = -1;
if (bytes_read == (ssize_t)sizeof(child_errno)) {
/* exec failed in the child. Reap it and surface the reason. */
while ((-1 == waitpid(child, SUBPROCESS_NULL, 0)) && (EINTR == errno)) {
}
child = 0;
saved_errno = child_errno;
result = subprocess_error_from_errno(child_errno);
if (subprocess_error_unknown == result) {
result = subprocess_error_spawn;
}
goto cleanup;
}
}
#else
posix_error = posix_spawn_file_actions_init(&actions);
if (0 != posix_error) {
saved_errno = posix_error;
@@ -1253,7 +1591,7 @@ cleanup:
// Set working directory
if (process_cwd) {
#if defined(__NetBSD__) || (defined(__APPLE__) && MAC_OS_X_VERSION_MIN_REQUIRED >= 260000)
#if SUBPROCESS_ADDCHDIR_IS_POSIX
posix_error = posix_spawn_file_actions_addchdir(&actions, process_cwd);
#elif !SUBPROCESS_HAVE_CWD
posix_error = ENOSYS;
@@ -1406,6 +1744,7 @@ cleanup:
#ifdef __clang__
#pragma clang diagnostic pop
#endif
#endif /* SUBPROCESS_SPAWN_VIA_FORK */
// Close the stdin read end
close(stdinfd[0]);
@@ -1480,9 +1819,21 @@ cleanup:
result = subprocess_error_from_errno(saved_errno);
}
#if SUBPROCESS_SPAWN_VIA_FORK
if (-1 != exec_errfd[0]) {
close(exec_errfd[0]);
exec_errfd[0] = -1;
}
if (-1 != exec_errfd[1]) {
close(exec_errfd[1]);
exec_errfd[1] = -1;
}
#else
if (actions_created) {
posix_spawn_file_actions_destroy(&actions);
}
#endif
if (0 != result) {
if (child) {