/* Unit tests of datastruct.c Copyright © 2022-2024 Samuel Lidén Borell Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "../datastruct.c" #include "unittest.h" #include "testcommon.h" #include "alltests.h" static void check_common(struct Csbe *csbe) { tsoftassert(!csbe->type); /* should be cleared by csbe_typedef_done */ tsoftassert(csbe->error == CSBEE_OK); } static void check_typedef(struct Csbe *csbe, struct CsbeTypedef *td) { tassert(td); check_common(csbe); tsoftassert(!td->next); tsoftassert(!csbe->dd); tsoftassert(!csbe->fd); } static void test_typedef_simple(void) { struct Csbe *csbe; struct CsbeTypedef *td; csbe = tnewcsbe(tnewcfg(CSBEMT_EXEC)); CHK(csbe_set_num_typedefs(csbe, 1)); csbe_typedef_start(csbe, 0); csbe_type_simple(csbe, CSBET_U16); td = csbe->td; csbe_typedef_end(csbe); /* Check the result */ check_typedef(csbe, td); tsoftassert(!td->type.is_struct); tsoftassert(!td->type.is_array); tsoftassert(td->type.simple_kind == CSBET_U16); } static void test_typedef_array(void) { struct Csbe *csbe; struct CsbeTypedef *td; csbe = tnewcsbe(tnewcfg(CSBEMT_EXEC)); CHK(csbe_set_num_typedefs(csbe, 1)); csbe_typedef_start(csbe, 0); CHK(csbe_type_array(csbe, 5)); csbe_type_simple(csbe, CSBET_U16); td = csbe->td; csbe_typedef_end(csbe); /* Check the result */ check_typedef(csbe, td); tsoftassert(!td->type.is_struct); tassert(td->type.is_array); tsoftassert(td->type.array_length == 5); tsoftassert(td->type.simple_kind == CSBET_U16); } static void test_typedef_array_multidim(void) { struct Csbe *csbe; struct CsbeTypedef *td; csbe = tnewcsbe(tnewcfg(CSBEMT_EXEC)); CHK(csbe_set_num_typedefs(csbe, 1)); csbe_typedef_start(csbe, 0); CHK(csbe_type_array(csbe, 5)); CHK(csbe_type_array(csbe, 11)); CHK(csbe_type_array(csbe, 7)); csbe_type_simple(csbe, CSBET_U16); td = csbe->td; csbe_typedef_end(csbe); /* Check the result */ check_typedef(csbe, td); tsoftassert(!td->type.is_struct); tassert(td->type.is_array); tsoftassert(td->type.array_length == 5*11*7); tsoftassert(td->type.simple_kind == CSBET_U16); } static void test_multiply_arraylengths(void) { struct Csbe *c; unsigned x; c = tnewcsbe(tnewcfg(CSBEMT_EXEC)); if (SOFTCHK(csbe_multiply_arraylengths(c, &x, 0, 0))) tsoftassert(x == 0); if (SOFTCHK(csbe_multiply_arraylengths(c, &x, 0, 1))) tsoftassert(x == 0); if (SOFTCHK(csbe_multiply_arraylengths(c, &x, 1, 0))) tsoftassert(x == 0); if (SOFTCHK(csbe_multiply_arraylengths(c, &x, 3, 5))) tsoftassert(x == 15); tsoftassert(csbe_multiply_arraylengths(c, &x, CSBE_ARRAY_MAX/3, 3) == CSBEE_TYPE_TOO_LARGE); } static void test_typedef_struct(void) { struct Csbe *csbe; struct CsbeTypedef *td; csbe = tnewcsbe(tnewcfg(CSBEMT_EXEC)); CHK(csbe_set_num_typedefs(csbe, 1)); csbe_typedef_start(csbe, 0); CHK(csbe_type_struct_start(csbe, 3, CSBEPM_CPACK)); { /* Array of int64 */ CHK(csbe_type_array(csbe, 3)); { CHK(csbe_type_array(csbe, 29)); { CHK(csbe_type_array(csbe, 31)); csbe_type_simple(csbe, CSBET_I64); } } /* Nested structs/arrays */ CHK(csbe_type_struct_start(csbe, 2, CSBEPM_CPACK)); { csbe_type_simple(csbe, CSBET_DPTR); CHK(csbe_type_array(csbe, 3)); { CHK(csbe_type_struct_start(csbe, 1, CSBEPM_CPACK)); { csbe_type_simple(csbe, CSBET_I8); } } CHK(csbe_type_struct_end(csbe)); } CHK(csbe_type_struct_end(csbe)); /* Simple type */ csbe_type_simple(csbe, CSBET_BOOL); } csbe_type_struct_end(csbe); td = csbe->td; csbe_typedef_end(csbe); /* Check the result */ check_typedef(csbe, td); tassert(td->type.is_struct); tsoftassert(!td->type.is_array); tsoftassert(!td->type.containing_type); tassert(td->type.num_fields == 3); tassert(td->type.elemtypes); /* Element 0: Array of int64 */ tassert(!td->type.elemtypes[0].is_struct); tassert(td->type.elemtypes[0].is_array); tassert(td->type.elemtypes[0].array_length == 3*29*31); tassert(td->type.elemtypes[0].simple_kind == CSBET_I64); /* Element 1: Nested structs */ tassert(td->type.elemtypes[1].is_struct); tassert(!td->type.elemtypes[1].is_array); tassert(td->type.elemtypes[1].num_fields == 2); tassert(td->type.elemtypes[1].elemtypes); { struct CsbeType *nested, *deepnested; nested = td->type.elemtypes[1].elemtypes; tassert(!nested[0].is_struct); tassert(!nested[0].is_array); tassert(nested[0].simple_kind == CSBET_DPTR); tassert(nested[1].is_struct); tassert(nested[1].is_array); tassert(nested[1].array_length == 3); tassert(nested[1].num_fields == 1); deepnested = nested[1].elemtypes; tassert(deepnested); tassert(!deepnested[0].is_struct); tassert(!deepnested[0].is_array); tassert(deepnested[0].simple_kind == CSBET_I8); } /* Element 2: Simple type */ tassert(!td->type.elemtypes[2].is_struct); tassert(!td->type.elemtypes[2].is_array); tassert(td->type.elemtypes[2].simple_kind == CSBET_BOOL); } static void check_named_field(struct Csbe *csbe, struct CsbeTypedef *tdstruct, struct CsbeTypedef *tdfield) { check_common(csbe); /* Check field */ tassert(tdfield); tassert(tdfield->type.is_defined); /* Check struct */ tassert(tdstruct); tassert(tdstruct->type.is_defined); tassert(!tdstruct->type.is_unbound); tassert(tdstruct->type.is_struct); tsoftassert(!tdstruct->type.is_array); tassert(tdstruct->type.num_fields == 1); tassert(tdstruct->type.elemtypes); } static void check_struct_of_u64(const struct CsbeType *type) { tassert(type->is_struct); tassert(!type->is_array); tassert(type->num_fields == 1); tassert(!type->elemtypes[0].is_struct); tassert(!type->elemtypes[0].is_array); tassert(type->elemtypes[0].simple_kind == CSBET_U64); } static void test_typedef_named(void) { struct Csbe *csbe; struct CsbeTypedef *tdstruct, *tdfield; csbe = tnewcsbe(tnewcfg(CSBEMT_EXEC)); CHK(csbe_set_num_typedefs(csbe, 2)); csbe_typedef_start(csbe, 0); CHK(csbe_type_struct_start(csbe, 1, CSBEPM_CPACK)); { csbe_type_simple(csbe, CSBET_U64); } CHK(csbe_type_struct_end(csbe)); tdfield = csbe->td; csbe_typedef_end(csbe); csbe_typedef_start(csbe, 1); CHK(csbe_type_struct_start(csbe, 1, CSBEPM_CPACK)); { csbe_type_named(csbe, 0); } CHK(csbe_type_struct_end(csbe)); tdstruct = csbe->td; csbe_typedef_end(csbe); CHK(csbe_defs_done(csbe)); /* Check the result */ check_named_field(csbe, tdstruct, tdfield); check_struct_of_u64(&tdstruct->type.elemtypes[0]); } static void test_typedef_named_forwardref(void) { struct Csbe *csbe; struct CsbeTypedef *tdstruct, *tdfield; csbe = tnewcsbe(tnewcfg(CSBEMT_EXEC)); CHK(csbe_set_num_typedefs(csbe, 2)); csbe_typedef_start(csbe, 0); CHK(csbe_type_struct_start(csbe, 1, CSBEPM_CPACK)); { csbe_type_named(csbe, 1); } CHK(csbe_type_struct_end(csbe)); tdstruct = csbe->td; csbe_typedef_end(csbe); csbe_typedef_start(csbe, 1); CHK(csbe_type_struct_start(csbe, 1, CSBEPM_CPACK)); { csbe_type_simple(csbe, CSBET_U64); } CHK(csbe_type_struct_end(csbe)); tdfield = csbe->td; csbe_typedef_end(csbe); CHK(csbe_defs_done(csbe)); /* Check the result */ check_named_field(csbe, tdstruct, tdfield); check_struct_of_u64(&tdstruct->type.elemtypes[0]); } static void test_typedef_namedarray_forwardref(void) { struct Csbe *csbe; struct CsbeTypedef *tdstruct, *tdfield; csbe = tnewcsbe(tnewcfg(CSBEMT_EXEC)); CHK(csbe_set_num_typedefs(csbe, 2)); csbe_typedef_start(csbe, 0); CHK(csbe_type_struct_start(csbe, 1, CSBEPM_CPACK)); { CHK(csbe_type_array(csbe, 3)); CHK(csbe_type_array(csbe, 5)); csbe_type_named(csbe, 1); } CHK(csbe_type_struct_end(csbe)); tdstruct = csbe->td; csbe_typedef_end(csbe); csbe_typedef_start(csbe, 1); CHK(csbe_type_array(csbe, 7)); CHK(csbe_type_array(csbe, 11)); csbe_type_simple(csbe, CSBET_U64); tdfield = csbe->td; csbe_typedef_end(csbe); CHK(csbe_defs_done(csbe)); /* Check the result */ check_named_field(csbe, tdstruct, tdfield); tassert(!tdstruct->type.elemtypes[0].is_struct); tassert(tdstruct->type.elemtypes[0].is_array); tassert(tdstruct->type.elemtypes[0].array_length == 3*5*7*11); tassert(tdstruct->type.elemtypes[0].simple_kind == CSBET_U64); } static void test_func_minimal(void) { struct Csbe *csbe; struct FuncIR *fb; struct Ebb *ebb; struct OpChunk *chunk; csbe = tnewcsbe(tnewcfg(CSBEMT_EXEC)); CHK(csbe_set_num_funcdefs(csbe, 1)); CHK(csbe_funcdef_start(csbe, 0, 0, CSBEABI_C_NORMAL, 0)); csbe_type_void(csbe); csbe_funcdef_end(csbe); /* End of defs */ CHK(csbe_defs_done(csbe)); CHK(csbe_funcbody_start(csbe, 0, NULL, NULL, 1, 0)); CHK(csbe_ebb_start(csbe, 0, CSBEEF_DEFAULT)); CHK(csbe_op_start(csbe, CSBEO_RETURN_VOID)); csbe_op_end(csbe); tsoftassert(csbe->current_opnum == 1); ebb = csbe->current_ebb; fb = csbe->funcbody; CHK(csbe_funcbody_end(csbe)); /* Check the result */ check_common(csbe); tassert(fb != NULL); tsoftassert(fb->num_opchunks == 1); chunk = fb->first_opchunk; tassert(chunk != NULL); tassert(fb->last_opchunk == chunk); tassert(chunk->next == NULL); tassert(chunk->num_ops == 1); tsoftassert(chunk->ops[0].op == CSBEO_RETURN_VOID); tassert(ebb != NULL); tassert(ebb->first_op == 0); tassert(ebb->flags == (CSBEEF_INTERN_DEFINED|CSBEEF_INTERN_UNCOND_JUMP)); } static void test_func_basic_ops(void) { struct Csbe *csbe; struct FuncIR *fb; struct Ebb *ebb; struct OpChunk *chunk; enum VariablesInTest { v_arg0_uint, v_arg1_byte, v_tmp, v_ret, num_vars }; csbe = tnewcsbe(tnewcfg(CSBEMT_EXEC)); /* TODO symbol names? */ CHK(csbe_set_num_funcdefs(csbe, 1)); /* func f(uint,byte) -> bool */ CHK(csbe_funcdef_start(csbe, 0, 2, CSBEABI_C_NORMAL, 0)); csbe_type_simple(csbe, CSBET_BOOL); csbe_type_simple(csbe, CSBET_UINT); csbe_type_simple(csbe, CSBET_U8); csbe_funcdef_end(csbe); CHK(csbe_defs_done(csbe)); CHK(csbe_funcbody_start(csbe, 0, NULL, NULL, 1, num_vars)); /* Variable definitions */ csbe_funcbody_paramdef(csbe, 0, v_arg0_uint); csbe_funcbody_paramdef(csbe, 0, v_arg1_byte); csbe_funcbody_vardef_start(csbe, 0, v_tmp, 3); csbe_type_simple(csbe, CSBET_UINT); csbe_funcbody_vardef_end(csbe); csbe_funcbody_vardef_start(csbe, 0, v_ret, 4); csbe_type_simple(csbe, CSBET_BOOL); csbe_funcbody_vardef_end(csbe); /* Operations: tmp <- arg0 + 123 ret <- tmp == arg1 return ret */ CHK(csbe_ebb_start(csbe, 0, CSBEEF_DEFAULT)); { CHK(csbe_op_start(csbe, CSBEO_ADD)); csbe_operand_var_in(csbe, v_arg0_uint, CSBE_OPV_DISCARD); csbe_operand_immed(csbe, CSBET_UINT, 123); csbe_operand_var_out(csbe, v_tmp, 0); csbe_op_end(csbe); CHK(csbe_op_start(csbe, CSBEO_EQ)); csbe_operand_var_in(csbe, v_tmp, CSBE_OPV_DISCARD); csbe_operand_var_in(csbe, v_arg1_byte, CSBE_OPV_DISCARD); csbe_operand_var_out(csbe, v_ret, 0); csbe_op_end(csbe); CHK(csbe_op_start(csbe, CSBEO_RETURN_ARG)); csbe_operand_var_in(csbe, v_ret, CSBE_OPV_DISCARD); csbe_op_end(csbe); } tsoftassert(csbe->current_opnum == 3); ebb = csbe->current_ebb; fb = csbe->funcbody; CHK(csbe_funcbody_end(csbe)); /* Check the result */ check_common(csbe); tassert(fb != NULL); tsoftassert(fb->num_opchunks == 1); chunk = fb->first_opchunk; tassert(chunk != NULL); tassert(fb->last_opchunk == chunk); tassert(chunk->next == NULL); tassert(chunk->num_ops == 3); tsoftassert(chunk->ops[0].op == CSBEO_ADD); tsoftassert(chunk->ops[1].op == CSBEO_EQ); tsoftassert(chunk->ops[2].op == CSBEO_RETURN_ARG); tassert(ebb != NULL); tassert(ebb->first_op == 0); tassert(ebb->flags == (CSBEEF_INTERN_DEFINED|CSBEEF_INTERN_UNCOND_JUMP)); } static void test_func_ebb_flow(void) { struct Csbe *csbe; struct FuncIR *fb; struct OpChunk *chunk; csbe = tnewcsbe(tnewcfg(CSBEMT_EXEC)); CHK(csbe_set_num_funcdefs(csbe, 1)); CHK(csbe_funcdef_start(csbe, 0, 0, CSBEABI_C_NORMAL, 0)); csbe_type_void(csbe); csbe_funcdef_end(csbe); CHK(csbe_defs_done(csbe)); CHK(csbe_funcbody_start(csbe, 0, NULL, NULL, 3, 0)); CHK(csbe_ebb_start(csbe, 0, CSBEEF_DEFAULT)); CHK(csbe_ebb_start(csbe, 2, CSBEEF_FLOWINTO)); CHK(csbe_ebb_start(csbe, 1, CSBEEF_FLOWINTO)); fb = csbe->funcbody; CHK(csbe_funcbody_end(csbe)); /* Check the result */ check_common(csbe); tassert(fb != NULL); tsoftassert(fb->num_opchunks == 1); chunk = fb->first_opchunk; tassert(fb->num_ebb == 3); /* EBB number 0 and 2 should have implicit jumps to the next started EBB */ tassert(fb->ebbs[0].first_op == 0); tassert(fb->ebbs[0].end_op == 1); tsoftassert(chunk->ops[0].op == CSBEO_JUMP); tsoftassert(chunk->ops[0].opnd[0] == 2); tassert(fb->ebbs[2].first_op == 1); tassert(fb->ebbs[2].end_op == 2); tsoftassert(chunk->ops[1].op == CSBEO_JUMP); tsoftassert(chunk->ops[1].opnd[0] == 1); tassert(fb->ebbs[1].first_op == 2); tassert(fb->ebbs[1].end_op == 2); } static void test_type_narrowing_check(void) { struct CsbeType from = { /*is_struct=*/0, /*is_array=*/0, /*is_defined=*/1, /*is_unbound=*/0, /*num_fields=*/1, /*saved_slots_left=*/0, /*array_length=*/0, /*simple_kind=*/CSBET_BOOL, /*packmode=*/CSBEPM_CPACK, /*elemtypes=*/NULL, /*containing_type=*/NULL }; struct CsbeType to = from; tsoftassert(types_same(&from, &to, 0)); tsoftassert(types_same(&from, &to, 1)); /* Incompatible: bool might be smaller than i8 */ from.simple_kind = CSBET_I8; to.simple_kind = CSBET_BOOL; tsoftassert(! types_same(&from, &to, 0)); tsoftassert(! types_same(&from, &to, 1)); /* Exactly the same type */ from.simple_kind = CSBET_I8; to.simple_kind = CSBET_I8; tsoftassert(types_same(&from, &to, 0)); tsoftassert(types_same(&from, &to, 1)); /* Incompatible: Smaller absolute value */ from.simple_kind = CSBET_U8; to.simple_kind = CSBET_I8; tsoftassert(! types_same(&from, &to, 0)); tsoftassert(! types_same(&from, &to, 1)); /* Incompatible: From signed to unsigned */ from.simple_kind = CSBET_I8; to.simple_kind = CSBET_U8; tsoftassert(! types_same(&from, &to, 0)); tsoftassert(! types_same(&from, &to, 1)); /* Compatible, but not same */ from.simple_kind = CSBET_U8; to.simple_kind = CSBET_I16; tsoftassert(! types_same(&from, &to, 0)); tsoftassert(types_same(&from, &to, 1)); /* Compatible, but not same */ from.simple_kind = CSBET_U8; to.simple_kind = CSBET_SSIZE; /* system-dependent type */ tsoftassert(! types_same(&from, &to, 0)); tsoftassert(types_same(&from, &to, 1)); /* Exactly the same type */ from.simple_kind = CSBET_U32; from.is_array = 1; from.array_length = 123; to.simple_kind = CSBET_U32; to.is_array = 1; to.array_length = 123; tsoftassert(types_same(&from, &to, 0)); tsoftassert(types_same(&from, &to, 1)); /* Incompatible: Arrays/struct must match exactly */ to.simple_kind = CSBET_U64; tsoftassert(! types_same(&from, &to, 0)); tsoftassert(! types_same(&from, &to, 1)); } const TestFunctionInfo tests_datastruct[] = { TEST_INFO(test_typedef_simple) TEST_INFO(test_typedef_array) TEST_INFO(test_typedef_array_multidim) TEST_INFO(test_multiply_arraylengths) TEST_INFO(test_typedef_struct) TEST_INFO(test_typedef_named) TEST_INFO(test_typedef_named_forwardref) TEST_INFO(test_typedef_namedarray_forwardref) TEST_INFO(test_func_minimal) TEST_INFO(test_func_basic_ops) TEST_INFO(test_func_ebb_flow) TEST_INFO(test_type_narrowing_check) TEST_END };