From e15a625977dd459c84a9608dde8bd8e61a2f703e Mon Sep 17 00:00:00 2001 From: Byron Hambly Date: Tue, 25 Aug 2026 10:48:14 +0200 Subject: [PATCH] Squashed 'src/simplicity/' changes from 49b96499a6..abede47eb8 abede47eb8 Merge BlockstreamResearch/Simplicity#324: Bitcoin Application for Simplicity eae2b6f2fd misc: minor changes from #324 review b2b8577164 cmake: add cmr and bitcoin/cmr to build df01c33990 Add basic CMake build support for libBitcoinSimplicity 4924ea7c6c Implementation of cmr.c for bitcoin 9782370700 Implementation of exec.c for bitcoin 512feba872 Add SIMPLICITY_FALLTHROUGH macro and enable -Wimplicit-fallthrough in CI git-subtree-dir: src/simplicity git-subtree-split: abede47eb835f5d39568cc705cefe5bf9e6ee769 --- CMakeLists.txt | 38 +++++++++ Makefile | 4 +- bitcoin/cmr.c | 22 +++++ bitcoin/exec.c | 136 ++++++++++++++++++++++++++++++ cmr.c | 41 +++++++++ cmr.h | 24 ++++++ dag.c | 9 +- elements/cmr.c | 25 +----- eval.c | 4 +- include/simplicity/bitcoin/cmr.h | 23 +++++ include/simplicity/bitcoin/exec.h | 40 +++++++++ simplicity_assert.h | 14 +++ 12 files changed, 351 insertions(+), 29 deletions(-) create mode 100644 CMakeLists.txt create mode 100644 bitcoin/cmr.c create mode 100644 bitcoin/exec.c create mode 100644 cmr.c create mode 100644 cmr.h create mode 100644 include/simplicity/bitcoin/cmr.h create mode 100644 include/simplicity/bitcoin/exec.h diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000000..e1d146c2dd9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 3.16) + +project(BitcoinSimplicity) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_EXTENSIONS OFF) + +add_library(BitcoinSimplicity STATIC + bitstream.c + cmr.c + dag.c + deserialize.c + eval.c + frame.c + jets-secp256k1.c + jets.c + rsort.c + sha256.c + type.c + typeInference.c + bitcoin/env.c + bitcoin/exec.c + bitcoin/bitcoinJets.c + bitcoin/cmr.c + bitcoin/ops.c + bitcoin/primitive.c + bitcoin/txEnv.c +) + +option(PRODUCTION "Enable production build" ON) +if (PRODUCTION) + target_compile_definitions(BitcoinSimplicity PRIVATE "PRODUCTION") +endif() + +target_include_directories(BitcoinSimplicity PUBLIC + $ + $ + ) diff --git a/Makefile b/Makefile index dcc9a4f7f50..e3d90ac515f 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -CORE_OBJS := bitstream.o dag.o deserialize.o eval.o frame.o jets.o jets-secp256k1.o rsort.o sha256.o type.o typeInference.o -BITCOIN_OBJS := bitcoin/env.o bitcoin/ops.o bitcoin/bitcoinJets.o bitcoin/primitive.o bitcoin/txEnv.o +CORE_OBJS := bitstream.o cmr.o dag.o deserialize.o eval.o frame.o jets.o jets-secp256k1.o rsort.o sha256.o type.o typeInference.o +BITCOIN_OBJS := bitcoin/env.o bitcoin/exec.o bitcoin/ops.o bitcoin/bitcoinJets.o bitcoin/primitive.o bitcoin/cmr.o bitcoin/txEnv.o ELEMENTS_OBJS := elements/env.o elements/exec.o elements/ops.o elements/elementsJets.o elements/primitive.o elements/cmr.o elements/txEnv.o TEST_OBJS := test.o ctx8Pruned.o ctx8Unpruned.o hashBlock.o regression4.o schnorr0.o schnorr6.o typeSkipTest.o elements/checkSigHashAllTx1.o diff --git a/bitcoin/cmr.c b/bitcoin/cmr.c new file mode 100644 index 00000000000..82135b27ed5 --- /dev/null +++ b/bitcoin/cmr.c @@ -0,0 +1,22 @@ +#include + +#include "../cmr.h" +#include "primitive.h" + +/* Deserialize a Simplicity 'program' and compute its CMR. + * + * Caution: no typechecking is performed, only a well-formedness check. + * + * If at any time malloc fails then '*error' is set to 'SIMPLICITY_ERR_MALLOC' and 'false' is returned, + * Otherwise, 'true' is returned indicating that the result was successfully computed and returned in the '*error' value. + * + * If the operation completes successfully then '*error' is set to 'SIMPLICITY_NO_ERROR', and the 'cmr' array is filled in with the program's computed CMR. + * + * Precondition: NULL != error; + * unsigned char cmr[32] + * unsigned char program[program_len] + */ +bool simplicity_bitcoin_computeCmr( simplicity_err* error, unsigned char* cmr + , const unsigned char* program, size_t program_len) { + return simplicity_computeCmr(error, cmr, simplicity_bitcoin_decodeJet, program, program_len); +} diff --git a/bitcoin/exec.c b/bitcoin/exec.c new file mode 100644 index 00000000000..e1a7f5da046 --- /dev/null +++ b/bitcoin/exec.c @@ -0,0 +1,136 @@ +#include + +#include +#include +#include "primitive.h" +#include "txEnv.h" +#include "../deserialize.h" +#include "../eval.h" +#include "../limitations.h" +#include "../simplicity_alloc.h" +#include "../simplicity_assert.h" +#include "../typeInference.h" + +/* Deserialize a Simplicity 'program' with its 'witness' data and execute it in the environment of the 'ix'th input of 'tx' with `taproot`. + * + * If at any time malloc fails then '*error' is set to 'SIMPLICITY_ERR_MALLOC' and 'false' is returned, + * meaning we were unable to determine the result of the simplicity program. + * Otherwise, 'true' is returned indicating that the result was successfully computed and returned in the '*error' value. + * + * If deserialization, analysis, or execution fails, then '*error' is set to some simplicity_err. + * In particular, if the cost analysis exceeds the budget, or exceeds BUDGET_MAX, then '*error' is set to 'SIMPLICITY_ERR_EXEC_BUDGET'. + * On the other hand, if the cost analysis is less than or equal to minCost, then '*error' is set to 'SIMPLICITY_ERR_OVERWEIGHT'. + * + * Note that minCost and budget parameters are in WU, while the cost analysis will be performed in milliWU. + * Thus the minCost and budget specify a half open interval (minCost, budget] of acceptable cost values in milliWU. + * Setting minCost to 0 effectively disables the minCost check as every Simplicity program has a non-zero cost analysis. + * + * If 'amr != NULL' and the annotated Merkle root of the decoded expression doesn't match 'amr' then '*error' is set to 'SIMPLICITY_ERR_AMR'. + * + * Otherwise '*error' is set to 'SIMPLICITY_NO_ERROR'. + * + * If 'ihr != NULL' and '*error' is set to 'SIMPLICITY_NO_ERROR', then the identity hash of the root of the decoded expression is written to 'ihr'. + * Otherwise if 'ihr != NULL' and '*error' is not set to 'SIMPLICITY_NO_ERROR', then 'ihr' may or may not be written to. + * + * Precondition: NULL != error; + * NULL != ihr implies unsigned char ihr[32] + * NULL != tx; + * NULL != taproot; + * 0 <= minCost <= budget; + * NULL != amr implies unsigned char amr[32] + * unsigned char program[program_len] + * unsigned char witness[witness_len] + */ +extern bool simplicity_bitcoin_execSimplicity( simplicity_err* error, unsigned char* ihr + , const bitcoinTransaction* tx, uint_fast32_t ix, const bitcoinTapEnv* taproot + , int64_t minCost, int64_t budget + , const unsigned char* amr + , const unsigned char* program, size_t program_len + , const unsigned char* witness, size_t witness_len) { + simplicity_assert(NULL != error); + simplicity_assert(NULL != tx); + simplicity_assert(NULL != taproot); + simplicity_assert(0 <= minCost); + simplicity_assert(minCost <= budget); + simplicity_assert(NULL != program || 0 == program_len); + simplicity_assert(NULL != witness || 0 == witness_len); + + combinator_counters census; + dag_node* dag = NULL; + int_fast32_t dag_len; + sha256_midstate amr_hash; + + if (amr) sha256_toMidstate(amr_hash.s, amr); + + { + bitstream stream = initializeBitstream(program, program_len); + dag_len = simplicity_decodeMallocDag(&dag, simplicity_bitcoin_decodeJet, &census, &stream); + if (dag_len <= 0) { + simplicity_assert(dag_len < 0); + *error = (simplicity_err)dag_len; + return IS_PERMANENT(*error); + } + simplicity_assert(NULL != dag); + simplicity_assert((uint_fast32_t)dag_len <= DAG_LEN_MAX); + *error = simplicity_closeBitstream(&stream); + } + + if (IS_OK(*error)) { + if (0 != memcmp(taproot->scriptCMR.s, dag[dag_len-1].cmr.s, sizeof(uint32_t[8]))) { + *error = SIMPLICITY_ERR_CMR; + } + } + + if (IS_OK(*error)) { + type* type_dag = NULL; + *error = simplicity_mallocTypeInference(&type_dag, simplicity_bitcoin_mallocBoundVars, dag, (uint_fast32_t)dag_len, &census); + if (IS_OK(*error)) { + simplicity_assert(NULL != type_dag); + if (0 != dag[dag_len-1].sourceType || 0 != dag[dag_len-1].targetType) { + *error = SIMPLICITY_ERR_TYPE_INFERENCE_NOT_PROGRAM; + } + } + if (IS_OK(*error)) { + bitstream witness_stream = initializeBitstream(witness, witness_len); + *error = simplicity_fillWitnessData(dag, type_dag, (uint_fast32_t)dag_len, &witness_stream); + if (IS_OK(*error)) { + *error = simplicity_closeBitstream(&witness_stream); + if (SIMPLICITY_ERR_BITSTREAM_TRAILING_BYTES == *error) *error = SIMPLICITY_ERR_WITNESS_TRAILING_BYTES; + if (SIMPLICITY_ERR_BITSTREAM_ILLEGAL_PADDING == *error) *error = SIMPLICITY_ERR_WITNESS_ILLEGAL_PADDING; + } + } + if (IS_OK(*error)) { + sha256_midstate ihr_buf; + *error = simplicity_verifyNoDuplicateIdentityHashes(&ihr_buf, dag, type_dag, (uint_fast32_t)dag_len); + if (IS_OK(*error) && ihr) sha256_fromMidstate(ihr, ihr_buf.s); + } + if (IS_OK(*error) && amr) { + static_assert(DAG_LEN_MAX <= SIZE_MAX / sizeof(analyses), "analysis array too large."); + static_assert(1 <= DAG_LEN_MAX, "DAG_LEN_MAX is zero."); + static_assert(DAG_LEN_MAX - 1 <= UINT32_MAX, "analysis array index does not fit in uint32_t."); + analyses *analysis = simplicity_malloc((size_t)dag_len * sizeof(analyses)); + if (analysis) { + simplicity_computeAnnotatedMerkleRoot(analysis, dag, type_dag, (uint_fast32_t)dag_len); + if (0 != memcmp(amr_hash.s, analysis[dag_len-1].annotatedMerkleRoot.s, sizeof(uint32_t[8]))) { + *error = SIMPLICITY_ERR_AMR; + } + } else { + /* malloc failed which counts as a transient error. */ + *error = SIMPLICITY_ERR_MALLOC; + } + simplicity_free(analysis); + } + if (IS_OK(*error)) { + txEnv env = simplicity_bitcoin_build_txEnv(tx, taproot, ix); + static_assert(BUDGET_MAX <= UBOUNDED_MAX, "BUDGET_MAX doesn't fit in ubounded."); + *error = evalTCOProgram( dag, type_dag, (size_t)dag_len + , minCost <= BUDGET_MAX ? (ubounded)minCost : BUDGET_MAX + , &(ubounded){budget <= BUDGET_MAX ? (ubounded)budget : BUDGET_MAX} + , &env); + } + simplicity_free(type_dag); + } + + simplicity_free(dag); + return IS_PERMANENT(*error); +} diff --git a/cmr.c b/cmr.c new file mode 100644 index 00000000000..e02ae4b6668 --- /dev/null +++ b/cmr.c @@ -0,0 +1,41 @@ +#include "cmr.h" + +#include "limitations.h" +#include "simplicity_alloc.h" +#include "simplicity_assert.h" + +/* Deserialize a Simplicity 'program' and compute its CMR. + * + * Caution: no typechecking is performed, only a well-formedness check. + * + * If at any time malloc fails then '*error' is set to 'SIMPLICITY_ERR_MALLOC' and 'false' is returned, + * Otherwise, 'true' is returned indicating that the result was successfully computed and returned in the '*error' value. + * + * If the operation completes successfully then '*error' is set to 'SIMPLICITY_NO_ERROR', and the 'cmr' array is filled in with the program's computed CMR. + * + * Precondition: NULL != error; + * unsigned char cmr[32] + * unsigned char program[program_len] + */ +bool simplicity_computeCmr( simplicity_err* error, unsigned char* cmr, simplicity_callback_decodeJet decodeJet + , const unsigned char* program, size_t program_len) { + simplicity_assert(NULL != error); + simplicity_assert(NULL != cmr); + simplicity_assert(NULL != program || 0 == program_len); + + bitstream stream = initializeBitstream(program, program_len); + dag_node* dag = NULL; + int_fast32_t dag_len = simplicity_decodeMallocDag(&dag, decodeJet, NULL, &stream); + if (dag_len <= 0) { + simplicity_assert(dag_len < 0); + *error = (simplicity_err)dag_len; + } else { + simplicity_assert(NULL != dag); + simplicity_assert((uint_fast32_t)dag_len <= DAG_LEN_MAX); + *error = simplicity_closeBitstream(&stream); + sha256_fromMidstate(cmr, dag[dag_len-1].cmr.s); + } + + simplicity_free(dag); + return IS_PERMANENT(*error); +} diff --git a/cmr.h b/cmr.h new file mode 100644 index 00000000000..9e7b0f86523 --- /dev/null +++ b/cmr.h @@ -0,0 +1,24 @@ +#ifndef SIMPLICITY_CMR_H +#define SIMPLICITY_CMR_H + +#include +#include +#include +#include "deserialize.h" + +/* Deserialize a Simplicity 'program' and compute its CMR. + * + * Caution: no typechecking is performed, only a well-formedness check. + * + * If at any time malloc fails then '*error' is set to 'SIMPLICITY_ERR_MALLOC' and 'false' is returned, + * Otherwise, 'true' is returned indicating that the result was successfully computed and returned in the '*error' value. + * + * If the operation completes successfully then '*error' is set to 'SIMPLICITY_NO_ERROR', and the 'cmr' array is filled in with the program's computed CMR. + * + * Precondition: NULL != error; + * unsigned char cmr[32] + * unsigned char program[program_len] + */ +extern bool simplicity_computeCmr( simplicity_err* error, unsigned char* cmr, simplicity_callback_decodeJet decodeJet + , const unsigned char* program, size_t program_len); +#endif diff --git a/dag.c b/dag.c index d09cd2740cb..f26b647eff7 100644 --- a/dag.c +++ b/dag.c @@ -116,6 +116,7 @@ sha256_midstate simplicity_computeWordCMR(const bitstring* value, size_t n) { case 0: i = getBit(value, 0); break; case 1: i = 2 + ((1U * getBit(value, 0) << 1) | getBit(value, 1)); break; case 2: i = 6 + ((1U * getBit(value, 0) << 3) | (1U * getBit(value, 1) << 2) | (1U * getBit(value, 2) << 1) | getBit(value, 3)); break; + default: SIMPLICITY_UNREACHABLE; } memcpy(stack_ptr, &word_cmr[i], sizeof(uint32_t[8])); } else { @@ -174,7 +175,7 @@ void simplicity_computeCommitmentMerkleRoot(dag_node* dag, const uint_fast32_t i case PAIR: memcpy(block + j, dag[dag[i].child[1]].cmr.s, sizeof(uint32_t[8])); j = 0; - /*@fallthrough@*/ + SIMPLICITY_FALLTHROUGH; case DISCONNECT: /* Only the first child is used in the CMR. */ case INJL: case INJR: @@ -182,6 +183,7 @@ void simplicity_computeCommitmentMerkleRoot(dag_node* dag, const uint_fast32_t i case DROP: memcpy(block + j, dag[dag[i].child[0]].cmr.s, sizeof(uint32_t[8])); simplicity_sha256_compression(dag[i].cmr.s, block); + SIMPLICITY_FALLTHROUGH; case IDEN: case UNIT: case WITNESS: @@ -224,13 +226,14 @@ static void computeIdentityHashRoots(sha256_midstate* ihr, const dag_node* dag, case DISCONNECT: memcpy(block + j, ihr[dag[i].child[1]].s, sizeof(uint32_t[8])); j = 0; - /*@fallthrough@*/ + SIMPLICITY_FALLTHROUGH; case INJL: case INJR: case TAKE: case DROP: memcpy(block + j, ihr[dag[i].child[0]].s, sizeof(uint32_t[8])); simplicity_sha256_compression(ihr[i].s, block); + SIMPLICITY_FALLTHROUGH; case IDEN: case UNIT: case HIDDEN: @@ -420,6 +423,7 @@ simplicity_err simplicity_verifyCanonicalOrder(dag_node* dag, const uint_fast32_ continue; } if (bottom == child) bottom++; + SIMPLICITY_FALLTHROUGH; case IDEN: case UNIT: case WITNESS: @@ -444,6 +448,7 @@ simplicity_err simplicity_verifyCanonicalOrder(dag_node* dag, const uint_fast32_ continue; } if (bottom == child) bottom++; + SIMPLICITY_FALLTHROUGH; case INJL: case INJR: case TAKE: diff --git a/elements/cmr.c b/elements/cmr.c index dd73be4ed93..3f7dedebe40 100644 --- a/elements/cmr.c +++ b/elements/cmr.c @@ -1,9 +1,6 @@ #include -#include "../deserialize.h" -#include "../limitations.h" -#include "../simplicity_alloc.h" -#include "../simplicity_assert.h" +#include "../cmr.h" #include "primitive.h" /* Deserialize a Simplicity 'program' and compute its CMR. @@ -21,23 +18,5 @@ */ bool simplicity_elements_computeCmr( simplicity_err* error, unsigned char* cmr , const unsigned char* program, size_t program_len) { - simplicity_assert(NULL != error); - simplicity_assert(NULL != cmr); - simplicity_assert(NULL != program || 0 == program_len); - - bitstream stream = initializeBitstream(program, program_len); - dag_node* dag = NULL; - int_fast32_t dag_len = simplicity_decodeMallocDag(&dag, simplicity_elements_decodeJet, NULL, &stream); - if (dag_len <= 0) { - simplicity_assert(dag_len < 0); - *error = (simplicity_err)dag_len; - } else { - simplicity_assert(NULL != dag); - simplicity_assert((uint_fast32_t)dag_len <= DAG_LEN_MAX); - *error = simplicity_closeBitstream(&stream); - sha256_fromMidstate(cmr, dag[dag_len-1].cmr.s); - } - - simplicity_free(dag); - return IS_PERMANENT(*error); + return simplicity_computeCmr(error, cmr, simplicity_elements_decodeJet, program, program_len); } diff --git a/eval.c b/eval.c index 6c9e9dc05e8..706e09b4c78 100644 --- a/eval.c +++ b/eval.c @@ -462,7 +462,7 @@ static simplicity_err runTCO(evalState state, call* stack, const dag_node* dag, skip(state.activeWriteFrame, pad( INJR == dag[pc].tag , type_dag[INJ_B(dag, type_dag, pc)].bitSize , type_dag[INJ_C(dag, type_dag, pc)].bitSize)); - /*@fallthrough@*/ + SIMPLICITY_FALLTHROUGH; case TAKE: simplicity_debug_assert(calling); /* TAIL_CALL(dag[pc].child[0], SAME_TCO); */ @@ -496,7 +496,7 @@ static simplicity_err runTCO(evalState state, call* stack, const dag_node* dag, } else { writeValue(state.activeWriteFrame, &dag[pc].compactValue, dag[pc].targetType, type_dag); } - /*@fallthrough@*/ + SIMPLICITY_FALLTHROUGH; case UNIT: simplicity_debug_assert(calling); if (get_tco_flag(&stack[pc])) { diff --git a/include/simplicity/bitcoin/cmr.h b/include/simplicity/bitcoin/cmr.h new file mode 100644 index 00000000000..2a1f82aaef4 --- /dev/null +++ b/include/simplicity/bitcoin/cmr.h @@ -0,0 +1,23 @@ +#ifndef SIMPLICITY_BITCOIN_CMR_H +#define SIMPLICITY_BITCOIN_CMR_H + +#include +#include +#include + +/* Deserialize a Simplicity 'program' and compute its CMR. + * + * Caution: no typechecking is performed, only a well-formedness check. + * + * If at any time malloc fails then '*error' is set to 'SIMPLICITY_ERR_MALLOC' and 'false' is returned, + * Otherwise, 'true' is returned indicating that the result was successfully computed and returned in the '*error' value. + * + * If the operation completes successfully then '*error' is set to 'SIMPLICITY_NO_ERROR', and the 'cmr' array is filled in with the program's computed CMR. + * + * Precondition: NULL != error; + * unsigned char cmr[32] + * unsigned char program[program_len] + */ +extern bool simplicity_bitcoin_computeCmr( simplicity_err* error, unsigned char* cmr + , const unsigned char* program, size_t program_len); +#endif diff --git a/include/simplicity/bitcoin/exec.h b/include/simplicity/bitcoin/exec.h new file mode 100644 index 00000000000..787f2dd9298 --- /dev/null +++ b/include/simplicity/bitcoin/exec.h @@ -0,0 +1,40 @@ +#ifndef SIMPLICITY_BITCOIN_EXEC_H +#define SIMPLICITY_BITCOIN_EXEC_H + +#include +#include +#include +#include +#include + +/* Deserialize a Simplicity 'program' with its 'witness' data and execute it in the environment of the 'ix'th input of 'tx' with `taproot`. + * + * If at any time malloc fails then '*error' is set to 'SIMPLICITY_ERR_MALLOC' and 'false' is returned, + * meaning we were unable to determine the result of the simplicity program. + * Otherwise, 'true' is returned indicating that the result was successfully computed and returned in the '*error' value. + * + * If deserialization, analysis, or execution fails, then '*error' is set to some simplicity_err. + * + * If 'amr != NULL' and the annotated Merkle root of the decoded expression doesn't match 'amr' then '*error' is set to 'SIMPLICITY_ERR_AMR'. + * + * Otherwise '*error' is set to 'SIMPLICITY_NO_ERROR'. + * + * If 'ihr != NULL' and '*error' is set to 'SIMPLICITY_NO_ERROR', then the identity hash of the root of the decoded expression is written to 'ihr'. + * Otherwise if 'ihr != NULL' and '*error' is not set to 'SIMPLICITY_NO_ERROR', then 'ihr' may or may not be written to. + * + * Precondition: NULL != error; + * NULL != ihr implies unsigned char ihr[32] + * NULL != tx; + * NULL != taproot; + * 0 <= minCost <= budget; + * NULL != amr implies unsigned char amr[32] + * unsigned char program[program_len] + * unsigned char witness[witness_len] + */ +extern bool simplicity_bitcoin_execSimplicity( simplicity_err* error, unsigned char* ihr + , const bitcoinTransaction* tx, uint_fast32_t ix, const bitcoinTapEnv* taproot + , int64_t minCost, int64_t budget + , const unsigned char* amr + , const unsigned char* program, size_t program_len + , const unsigned char* witness, size_t witness_len); +#endif diff --git a/simplicity_assert.h b/simplicity_assert.h index a321c938ea8..e74b41f7717 100644 --- a/simplicity_assert.h +++ b/simplicity_assert.h @@ -34,4 +34,18 @@ # define SIMPLICITY_UNREACHABLE assert(NULL == "SIMPLICITY_UNCREACHABLE was reached") #endif +/* Defines a FALLTHROUGH macro to annotate intentional switch fallthroughs, silencing -Wimplicit-fallthrough + * warnings on compilers that support the 'fallthrough' attribute. + * No-op on compilers (e.g. MSVC) that don't support it. + */ +#if defined(__has_attribute) +# if __has_attribute(fallthrough) +# define SIMPLICITY_FALLTHROUGH __attribute__((fallthrough)) +# endif +#endif + +#ifndef SIMPLICITY_FALLTHROUGH +# define SIMPLICITY_FALLTHROUGH ((void)0) +#endif + #endif /* SIMPLICITY_SIMPLICITY_ASSERT_H */