mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-11-24 20:40:13 +00:00
Add a WIP WebGPU environment. It disallows OpUndef
Add SPV_ENV_WEBGPU_0 for work-in-progress WebGPU. val: Disallow OpUndef in WebGPU env Silence unused variable warnings when !defined(SPIRV_EFFCE) Limit visibility of validate_instruction.cpp's symbols Only InstructionPass needs to be visible so all other functions are put in an anonymous namespace inside the libspirv namespace.
This commit is contained in:
parent
e7ace1b280
commit
ba602c9059
@ -412,6 +412,7 @@ typedef enum {
|
||||
SPV_ENV_OPENCL_EMBEDDED_2_2, // OpenCL Embedded Profile 2.2 latest revision.
|
||||
SPV_ENV_UNIVERSAL_1_3, // SPIR-V 1.3 latest revision, no other restrictions.
|
||||
SPV_ENV_VULKAN_1_1, // Vulkan 1.1 latest revision.
|
||||
SPV_ENV_WEBGPU_0, // Work in progress WebGPU 1.0.
|
||||
} spv_target_env;
|
||||
|
||||
// SPIR-V Validator can be parameterized with the following Universal Limits.
|
||||
|
@ -81,6 +81,7 @@ spv_result_t spvExtInstTableGet(spv_ext_inst_table* pExtInstTable,
|
||||
case SPV_ENV_OPENGL_4_5:
|
||||
case SPV_ENV_UNIVERSAL_1_3:
|
||||
case SPV_ENV_VULKAN_1_1:
|
||||
case SPV_ENV_WEBGPU_0:
|
||||
*pExtInstTable = &kTable_1_0;
|
||||
return SPV_SUCCESS;
|
||||
default:
|
||||
|
@ -58,6 +58,8 @@ const char* spvTargetEnvDescription(spv_target_env env) {
|
||||
return "SPIR-V 1.3";
|
||||
case SPV_ENV_VULKAN_1_1:
|
||||
return "SPIR-V 1.3 (under Vulkan 1.1 semantics)";
|
||||
case SPV_ENV_WEBGPU_0:
|
||||
return "SPIR-V 1.3 (under WIP WebGPU semantics)";
|
||||
}
|
||||
assert(0 && "Unhandled SPIR-V target environment");
|
||||
return "";
|
||||
@ -87,6 +89,7 @@ uint32_t spvVersionForTargetEnv(spv_target_env env) {
|
||||
return SPV_SPIRV_VERSION_WORD(1, 2);
|
||||
case SPV_ENV_UNIVERSAL_1_3:
|
||||
case SPV_ENV_VULKAN_1_1:
|
||||
case SPV_ENV_WEBGPU_0:
|
||||
return SPV_SPIRV_VERSION_WORD(1, 3);
|
||||
}
|
||||
assert(0 && "Unhandled SPIR-V target environment");
|
||||
@ -154,6 +157,9 @@ bool spvParseTargetEnv(const char* s, spv_target_env* env) {
|
||||
} else if (match("opengl4.5")) {
|
||||
if (env) *env = SPV_ENV_OPENGL_4_5;
|
||||
return true;
|
||||
} else if (match("webgpu0")) {
|
||||
if (env) *env = SPV_ENV_WEBGPU_0;
|
||||
return true;
|
||||
} else {
|
||||
if (env) *env = SPV_ENV_UNIVERSAL_1_0;
|
||||
return false;
|
||||
@ -179,6 +185,7 @@ bool spvIsVulkanEnv(spv_target_env env) {
|
||||
case SPV_ENV_OPENCL_2_2:
|
||||
case SPV_ENV_OPENCL_EMBEDDED_2_2:
|
||||
case SPV_ENV_UNIVERSAL_1_3:
|
||||
case SPV_ENV_WEBGPU_0:
|
||||
return false;
|
||||
case SPV_ENV_VULKAN_1_0:
|
||||
case SPV_ENV_VULKAN_1_1:
|
||||
|
@ -37,6 +37,7 @@ spv_context spvContextCreate(spv_target_env env) {
|
||||
case SPV_ENV_UNIVERSAL_1_2:
|
||||
case SPV_ENV_UNIVERSAL_1_3:
|
||||
case SPV_ENV_VULKAN_1_1:
|
||||
case SPV_ENV_WEBGPU_0:
|
||||
break;
|
||||
default:
|
||||
return nullptr;
|
||||
|
@ -166,6 +166,14 @@ ValidationState_t::ValidationState_t(const spv_const_context ctx,
|
||||
memory_model_(SpvMemoryModelMax),
|
||||
in_function_(false) {
|
||||
assert(opt && "Validator options may not be Null.");
|
||||
|
||||
switch (context_->target_env) {
|
||||
case SPV_ENV_WEBGPU_0:
|
||||
features_.bans_op_undef = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
spv_result_t ValidationState_t::ForwardDeclareId(uint32_t id) {
|
||||
|
@ -58,7 +58,7 @@ enum ModuleLayoutSection {
|
||||
/// This class manages the state of the SPIR-V validation as it is being parsed.
|
||||
class ValidationState_t {
|
||||
public:
|
||||
// Features that can optionally be turned on by a capability.
|
||||
// Features that can optionally be turned on by a capability or environment.
|
||||
struct Feature {
|
||||
bool declare_int16_type = false; // Allow OpTypeInt with 16 bit width?
|
||||
bool declare_float16_type = false; // Allow OpTypeFloat with 16 bit width?
|
||||
@ -74,6 +74,9 @@ class ValidationState_t {
|
||||
|
||||
// Permit group oerations Reduce, InclusiveScan, ExclusiveScan
|
||||
bool group_ops_reduce_and_scans = false;
|
||||
|
||||
// Disallows the use of OpUndef
|
||||
bool bans_op_undef = false;
|
||||
};
|
||||
|
||||
ValidationState_t(const spv_const_context context,
|
||||
@ -570,7 +573,7 @@ class ValidationState_t {
|
||||
bool in_function_;
|
||||
|
||||
/// The state of optional features. These are determined by capabilities
|
||||
/// declared by the module.
|
||||
/// declared by the module and the environment.
|
||||
Feature features_;
|
||||
|
||||
/// Maps function ids to function stat objects.
|
||||
|
@ -37,11 +37,7 @@
|
||||
#include "val/function.h"
|
||||
#include "val/validation_state.h"
|
||||
|
||||
using libspirv::AssemblyGrammar;
|
||||
using libspirv::CapabilitySet;
|
||||
using libspirv::DiagnosticStream;
|
||||
using libspirv::ExtensionSet;
|
||||
using libspirv::ValidationState_t;
|
||||
namespace libspirv {
|
||||
|
||||
namespace {
|
||||
|
||||
@ -86,8 +82,7 @@ CapabilitySet EnablingCapabilitiesForOp(const ValidationState_t& state,
|
||||
case SpvOpGroupFMaxNonUniformAMD:
|
||||
case SpvOpGroupUMaxNonUniformAMD:
|
||||
case SpvOpGroupSMaxNonUniformAMD:
|
||||
if (state.HasExtension(libspirv::kSPV_AMD_shader_ballot))
|
||||
return CapabilitySet();
|
||||
if (state.HasExtension(kSPV_AMD_shader_ballot)) return CapabilitySet();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -183,11 +178,7 @@ ExtensionSet RequiredExtensions(const ValidationState_t& state,
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace libspirv {
|
||||
|
||||
// Return SPV_ERROR_INVALID_BINARY and emit a diagnostic if the instruction
|
||||
// Returns SPV_ERROR_INVALID_BINARY and emits a diagnostic if the instruction
|
||||
// is explicitly reserved in the SPIR-V core spec. Otherwise return
|
||||
// SPV_SUCCESS.
|
||||
spv_result_t ReservedCheck(ValidationState_t& _,
|
||||
@ -211,6 +202,26 @@ spv_result_t ReservedCheck(ValidationState_t& _,
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
// Returns SPV_ERROR_INVALID_BINARY and emits a diagnostic if the instruction
|
||||
// is invalid because of an execution environment constraint.
|
||||
spv_result_t EnvironmentCheck(ValidationState_t& _,
|
||||
const spv_parsed_instruction_t* inst) {
|
||||
const SpvOp opcode = static_cast<SpvOp>(inst->opcode);
|
||||
switch (opcode) {
|
||||
case SpvOpUndef:
|
||||
if (_.features().bans_op_undef) {
|
||||
return _.diag(SPV_ERROR_INVALID_BINARY) << "OpUndef is disallowed";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
// Returns SPV_ERROR_INVALID_CAPABILITY and emits a diagnostic if the
|
||||
// instruction is invalid because the required capability isn't declared
|
||||
// in the module.
|
||||
spv_result_t CapabilityCheck(ValidationState_t& _,
|
||||
const spv_parsed_instruction_t* inst) {
|
||||
const SpvOp opcode = static_cast<SpvOp>(inst->opcode);
|
||||
@ -269,9 +280,9 @@ spv_result_t ExtensionCheck(ValidationState_t& _,
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
// Checks that the instruction can be used in this target environment.
|
||||
// Assumes that CapabilityCheck has checked direct capability dependencies
|
||||
// for the opcode.
|
||||
// Checks that the instruction can be used in this target environment's base
|
||||
// version. Assumes that CapabilityCheck has checked direct capability
|
||||
// dependencies for the opcode.
|
||||
spv_result_t VersionCheck(ValidationState_t& _,
|
||||
const spv_parsed_instruction_t* inst) {
|
||||
const auto opcode = static_cast<SpvOp>(inst->opcode);
|
||||
@ -516,6 +527,8 @@ void CheckIfKnownExtension(ValidationState_t& _,
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
spv_result_t InstructionPass(ValidationState_t& _,
|
||||
const spv_parsed_instruction_t* inst) {
|
||||
const SpvOp opcode = static_cast<SpvOp>(inst->opcode);
|
||||
@ -582,6 +595,7 @@ spv_result_t InstructionPass(ValidationState_t& _,
|
||||
|
||||
if (auto error = ExtensionCheck(_, inst)) return error;
|
||||
if (auto error = ReservedCheck(_, inst)) return error;
|
||||
if (auto error = EnvironmentCheck(_, inst)) return error;
|
||||
if (auto error = CapabilityCheck(_, inst)) return error;
|
||||
if (auto error = LimitCheckIdBound(_, inst)) return error;
|
||||
if (auto error = LimitCheckStruct(_, inst)) return error;
|
||||
|
@ -47,6 +47,9 @@ bool Validate(const std::vector<uint32_t>& bin) {
|
||||
}
|
||||
|
||||
void Match(const std::string& checks, ir::IRContext* context) {
|
||||
// Silence unused warnings with !defined(SPIRV_EFFCE)
|
||||
(void)checks;
|
||||
|
||||
std::vector<uint32_t> bin;
|
||||
context->module()->ToBinary(&bin, true);
|
||||
EXPECT_TRUE(Validate(bin));
|
||||
|
@ -42,6 +42,9 @@ bool Validate(const std::vector<uint32_t>& bin) {
|
||||
}
|
||||
|
||||
void Match(const std::string& checks, ir::IRContext* context) {
|
||||
// Silence unused warnings with !defined(SPIRV_EFFCE)
|
||||
(void)checks;
|
||||
|
||||
std::vector<uint32_t> bin;
|
||||
context->module()->ToBinary(&bin, true);
|
||||
EXPECT_TRUE(Validate(bin));
|
||||
|
@ -91,6 +91,7 @@ INSTANTIATE_TEST_CASE_P(
|
||||
{"opencl2.0embedded", true, SPV_ENV_OPENCL_EMBEDDED_2_0},
|
||||
{"opencl2.1embedded", true, SPV_ENV_OPENCL_EMBEDDED_2_1},
|
||||
{"opencl2.2embedded", true, SPV_ENV_OPENCL_EMBEDDED_2_2},
|
||||
{"webgpu0", true, SPV_ENV_WEBGPU_0},
|
||||
{"opencl2.3", false, SPV_ENV_UNIVERSAL_1_0},
|
||||
{"opencl3.0", false, SPV_ENV_UNIVERSAL_1_0},
|
||||
{"vulkan1.2", false, SPV_ENV_UNIVERSAL_1_0},
|
||||
|
@ -218,7 +218,7 @@ inline std::vector<spv_target_env> AllTargetEnvironments() {
|
||||
SPV_ENV_OPENGL_4_1, SPV_ENV_OPENGL_4_2,
|
||||
SPV_ENV_OPENGL_4_3, SPV_ENV_OPENGL_4_5,
|
||||
SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_3,
|
||||
SPV_ENV_VULKAN_1_1,
|
||||
SPV_ENV_VULKAN_1_1, SPV_ENV_WEBGPU_0,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -60,7 +60,7 @@ add_spvtools_unittest(TARGET val_ijklmnop
|
||||
LIBS ${SPIRV_TOOLS}
|
||||
)
|
||||
|
||||
add_spvtools_unittest(TARGET val_stuv
|
||||
add_spvtools_unittest(TARGET val_stuvw
|
||||
SRCS
|
||||
val_ssa_test.cpp
|
||||
val_state_test.cpp
|
||||
@ -68,6 +68,7 @@ add_spvtools_unittest(TARGET val_stuv
|
||||
val_type_unique_test.cpp
|
||||
val_validation_state_test.cpp
|
||||
val_version_test.cpp
|
||||
val_webgpu_test.cpp
|
||||
${VAL_TEST_COMMON_SRCS}
|
||||
LIBS ${SPIRV_TOOLS}
|
||||
)
|
||||
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Common validation fixtures for unit tests
|
||||
// Validation tests for decorations
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "source/val/decoration.h"
|
||||
|
@ -34,6 +34,8 @@ class ValidateBase : public ::testing::Test,
|
||||
// Returns the a spv_const_binary struct
|
||||
spv_const_binary get_const_binary();
|
||||
|
||||
// Checks that 'code' is valid SPIR-V text representation and stores the
|
||||
// binary version for further method calls.
|
||||
void CompileSuccessfully(std::string code,
|
||||
spv_target_env env = SPV_ENV_UNIVERSAL_1_0);
|
||||
|
||||
@ -43,8 +45,7 @@ class ValidateBase : public ::testing::Test,
|
||||
// This function overwrites the word at the given index with a new word.
|
||||
void OverwriteAssembledBinary(uint32_t index, uint32_t word);
|
||||
|
||||
// Performs validation on the SPIR-V code and compares the result of the
|
||||
// spvValidate function
|
||||
// Performs validation on the SPIR-V code.
|
||||
spv_result_t ValidateInstructions(spv_target_env env = SPV_ENV_UNIVERSAL_1_0);
|
||||
|
||||
// Performs validation. Returns the status and stores validation state into
|
||||
|
@ -63,6 +63,7 @@ std::string version(spv_target_env env) {
|
||||
return "1.2";
|
||||
case SPV_ENV_UNIVERSAL_1_3:
|
||||
case SPV_ENV_VULKAN_1_1:
|
||||
case SPV_ENV_WEBGPU_0:
|
||||
return "1.3";
|
||||
default:
|
||||
return "0";
|
||||
@ -100,6 +101,7 @@ INSTANTIATE_TEST_CASE_P(Universal, ValidateVersion,
|
||||
make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_OPENGL_4_2, vulkan_spirv, true),
|
||||
make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_OPENGL_4_3, vulkan_spirv, true),
|
||||
make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_OPENGL_4_5, vulkan_spirv, true),
|
||||
make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_WEBGPU_0, vulkan_spirv, true),
|
||||
|
||||
make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_UNIVERSAL_1_0, vulkan_spirv, false),
|
||||
make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_UNIVERSAL_1_1, vulkan_spirv, true),
|
||||
@ -112,6 +114,7 @@ INSTANTIATE_TEST_CASE_P(Universal, ValidateVersion,
|
||||
make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_OPENGL_4_2, vulkan_spirv, false),
|
||||
make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_OPENGL_4_3, vulkan_spirv, false),
|
||||
make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_OPENGL_4_5, vulkan_spirv, false),
|
||||
make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_WEBGPU_0, vulkan_spirv, true),
|
||||
|
||||
make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_0, vulkan_spirv, false),
|
||||
make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_1, vulkan_spirv, false),
|
||||
@ -124,6 +127,7 @@ INSTANTIATE_TEST_CASE_P(Universal, ValidateVersion,
|
||||
make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_OPENGL_4_2, vulkan_spirv, false),
|
||||
make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_OPENGL_4_3, vulkan_spirv, false),
|
||||
make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_OPENGL_4_5, vulkan_spirv, false),
|
||||
make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_WEBGPU_0, vulkan_spirv, true),
|
||||
|
||||
make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_0, vulkan_spirv, false),
|
||||
make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_1, vulkan_spirv, false),
|
||||
@ -135,7 +139,8 @@ INSTANTIATE_TEST_CASE_P(Universal, ValidateVersion,
|
||||
make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_OPENGL_4_1, vulkan_spirv, false),
|
||||
make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_OPENGL_4_2, vulkan_spirv, false),
|
||||
make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_OPENGL_4_3, vulkan_spirv, false),
|
||||
make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_OPENGL_4_5, vulkan_spirv, false)
|
||||
make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_OPENGL_4_5, vulkan_spirv, false),
|
||||
make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_WEBGPU_0, vulkan_spirv, true)
|
||||
)
|
||||
);
|
||||
|
||||
|
47
test/val/val_webgpu_test.cpp
Normal file
47
test/val/val_webgpu_test.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright (c) 2018 Google Inc.
|
||||
//
|
||||
// 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.
|
||||
|
||||
// Validation tests for WebGPU env specific checks
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include "val_fixtures.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using std::string;
|
||||
using testing::HasSubstr;
|
||||
|
||||
using ValidateWebGPU = spvtest::ValidateBase<bool>;
|
||||
|
||||
TEST_F(ValidateWebGPU, OpUndefIsDisallowed) {
|
||||
string spirv = R"(
|
||||
OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
%float = OpTypeFloat 32
|
||||
%1 = OpUndef %float
|
||||
)";
|
||||
|
||||
CompileSuccessfully(spirv);
|
||||
|
||||
// Control case: OpUndef is allowed in SPIR-V 1.3
|
||||
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
|
||||
|
||||
// Control case: OpUndef is disallowed in the WebGPU env
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
EXPECT_THAT(getDiagnosticString(), HasSubstr("OpUndef is disallowed"));
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
@ -50,9 +50,9 @@ Options:
|
||||
different type with compatible layout and
|
||||
members.
|
||||
--version Display validator version information.
|
||||
--target-env {vulkan1.0|vulkan1.1|opencl2.2|spv1.0|spv1.1|spv1.2|spv1.3}
|
||||
--target-env {vulkan1.0|vulkan1.1|opencl2.2|spv1.0|spv1.1|spv1.2|spv1.3|webgpu0}
|
||||
Use Vulkan 1.0, Vulkan 1.1, OpenCL 2.2, SPIR-V 1.0,
|
||||
SPIR-V 1.1, SPIR-V 1.2 or SPIR-V 1.3 validation rules.
|
||||
SPIR-V 1.1, SPIR-V 1.2, SPIR-V 1.3 or WIP WebGPU validation rules.
|
||||
)",
|
||||
argv0, argv0);
|
||||
}
|
||||
@ -91,14 +91,15 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
} else if (0 == strcmp(cur_arg, "--version")) {
|
||||
printf("%s\n", spvSoftwareVersionDetailsString());
|
||||
printf("Targets:\n %s\n %s\n %s\n %s\n %s\n %s\n %s\n",
|
||||
printf("Targets:\n %s\n %s\n %s\n %s\n %s\n %s\n %s\n %s\n",
|
||||
spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_0),
|
||||
spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_1),
|
||||
spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_2),
|
||||
spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_3),
|
||||
spvTargetEnvDescription(SPV_ENV_OPENCL_2_2),
|
||||
spvTargetEnvDescription(SPV_ENV_VULKAN_1_0),
|
||||
spvTargetEnvDescription(SPV_ENV_VULKAN_1_1));
|
||||
spvTargetEnvDescription(SPV_ENV_VULKAN_1_1),
|
||||
spvTargetEnvDescription(SPV_ENV_WEBGPU_0));
|
||||
continue_processing = false;
|
||||
return_code = 0;
|
||||
} else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
|
||||
|
Loading…
Reference in New Issue
Block a user