From fb58166e5d391643906fd0aabd58d367507426fa Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Thu, 5 Mar 2020 02:58:48 +0100 Subject: [PATCH] Add assertions Closes #292 --- include/asm/output.h | 2 + include/asm/warning.h | 1 + include/link/patch.h | 22 +++++ include/linkdefs.h | 8 +- src/asm/asmy.y | 97 +++++++++++++++++++- src/asm/globlex.c | 3 + src/asm/output.c | 206 +++++++++++++++++++++++++++--------------- src/asm/rgbasm.1 | 19 +++- src/asm/rgbasm.5 | 56 ++++++++++++ src/asm/warning.c | 2 + src/link/object.c | 54 ++++++++++- src/link/patch.c | 43 +++++++++ src/rgbds.5 | 28 ++++++ test/asm/assert.asm | 23 +++++ test/asm/assert.err | 10 ++ test/asm/assert.out | 0 test/link/assert.asm | 11 +++ test/link/assert.out | 3 + 18 files changed, 506 insertions(+), 82 deletions(-) create mode 100644 test/asm/assert.asm create mode 100644 test/asm/assert.err create mode 100644 test/asm/assert.out create mode 100644 test/link/assert.asm create mode 100644 test/link/assert.out diff --git a/include/asm/output.h b/include/asm/output.h index e3dde6a5..7be1f43e 100644 --- a/include/asm/output.h +++ b/include/asm/output.h @@ -20,6 +20,8 @@ extern struct Section *pSectionList, *pCurrentSection; void out_SetFileName(char *s); void out_CreatePatch(uint32_t type, struct Expression const *expr); +bool out_CreateAssert(enum AssertionType type, struct Expression const *expr, + char const *message); void out_WriteObject(void); #endif /* RGBDS_ASM_OUTPUT_H */ diff --git a/include/asm/warning.h b/include/asm/warning.h index 385183dc..aaf38475 100644 --- a/include/asm/warning.h +++ b/include/asm/warning.h @@ -22,6 +22,7 @@ enum WarningID { WARNING_OBSOLETE, WARNING_SHIFT, WARNING_USER, + WARNING_ASSERT, WARNING_SHIFT_AMOUNT, WARNING_TRUNCATION, diff --git a/include/link/patch.h b/include/link/patch.h index 1caaff73..85b4cab2 100644 --- a/include/link/patch.h +++ b/include/link/patch.h @@ -10,6 +10,28 @@ #ifndef RGBDS_LINK_PATCH_H #define RGBDS_LINK_PATCH_H +#include +#include + +#include "link/section.h" + +#include "linkdefs.h" + +struct Assertion { + struct Patch patch; + // enum AssertionType type; The `patch`'s field is instead re-used + struct Section *section; + char *message; + + struct Assertion *next; +}; + +/** + * Checks all assertions + * @return true if assertion failed + */ +void patch_CheckAssertions(struct Assertion *assertion); + /** * Applies all SECTIONs' patches to them */ diff --git a/include/linkdefs.h b/include/linkdefs.h index fdc02deb..75fb2b45 100644 --- a/include/linkdefs.h +++ b/include/linkdefs.h @@ -14,7 +14,13 @@ #define RGBDS_OBJECT_VERSION_STRING "RGB%1hhu" #define RGBDS_OBJECT_VERSION_NUMBER (uint8_t)9 -#define RGBDS_OBJECT_REV 1 +#define RGBDS_OBJECT_REV 2 + +enum AssertionType { + ASSERT_WARN, + ASSERT_ERROR, + ASSERT_FATAL +}; enum RPNCommand { RPN_ADD = 0x00, diff --git a/src/asm/asmy.y b/src/asm/asmy.y index 42a5b569..9c45bcf0 100644 --- a/src/asm/asmy.y +++ b/src/asm/asmy.y @@ -23,6 +23,7 @@ #include "asm/macro.h" #include "asm/main.h" #include "asm/mymath.h" +#include "asm/output.h" #include "asm/rpn.h" #include "asm/section.h" #include "asm/symbol.h" @@ -497,6 +498,7 @@ static void strsubUTF8(char *dest, const char *src, uint32_t pos, uint32_t len) int32_t nConstValue; struct SectionSpec sectSpec; struct MacroArgs *macroArg; + enum AssertionType assertType; } %type relocexpr @@ -587,6 +589,8 @@ static void strsubUTF8(char *dest, const char *src, uint32_t pos, uint32_t len) %token T_POP_LOAD T_POP_ENDL %token T_POP_FAIL %token T_POP_WARN +%token T_POP_FATAL +%token T_POP_ASSERT T_POP_STATIC_ASSERT %token T_POP_PURGE %token T_POP_POPS %token T_POP_PUSHS @@ -638,6 +642,7 @@ static void strsubUTF8(char *dest, const char *src, uint32_t pos, uint32_t len) %type op_a_r %type op_hl_ss %type op_mem_ind +%type assert_type %start asmfile %% @@ -759,6 +764,7 @@ simple_pseudoop : include | shift | fail | warn + | assert | purge | pops | pushs @@ -799,8 +805,97 @@ fail : T_POP_FAIL string { fatalerror("%s", $2); } warn : T_POP_WARN string { warning(WARNING_USER, "%s", $2); } ; +assert_type : /* empty */ { $$ = ASSERT_ERROR; } + | T_POP_WARN ',' { $$ = ASSERT_WARN; } + | T_POP_FAIL ',' { $$ = ASSERT_ERROR; } + | T_POP_FATAL ',' { $$ = ASSERT_FATAL; } +; + +assert : T_POP_ASSERT assert_type relocexpr + { + if (rpn_isKnown(&$3) && $3.nVal == 0) { + switch ($2) { + case ASSERT_FATAL: + fatalerror("Assertion failed"); + case ASSERT_ERROR: + yyerror("Assertion failed"); + break; + case ASSERT_WARN: + warning(WARNING_ASSERT, + "Assertion failed"); + break; + } + } else { + if (!out_CreateAssert($2, &$3, "")) + yyerror("Assertion creation failed: %s", + strerror(errno)); + } + rpn_Free(&$3); + } + | T_POP_ASSERT assert_type relocexpr ',' string + { + if (rpn_isKnown(&$3) && $3.nVal == 0) { + switch ($2) { + case ASSERT_FATAL: + fatalerror("Assertion failed: %s", + $5); + case ASSERT_ERROR: + yyerror("Assertion failed: %s", + $5); + break; + case ASSERT_WARN: + warning(WARNING_ASSERT, + "Assertion failed: %s", + $5); + break; + } + } else { + if (!out_CreateAssert($2, &$3, $5)) + yyerror("Assertion creation failed: %s", + strerror(errno)); + } + rpn_Free(&$3); + } + | T_POP_STATIC_ASSERT assert_type const + { + if ($3 == 0) { + switch ($2) { + case ASSERT_FATAL: + fatalerror("Assertion failed"); + case ASSERT_ERROR: + yyerror("Assertion failed"); + break; + case ASSERT_WARN: + warning(WARNING_ASSERT, + "Assertion failed"); + break; + } + } + } + | T_POP_STATIC_ASSERT assert_type const ',' string + { + if ($3 == 0) { + switch ($2) { + case ASSERT_FATAL: + fatalerror("Assertion failed: %s", + $5); + case ASSERT_ERROR: + yyerror("Assertion failed: %s", + $5); + break; + case ASSERT_WARN: + warning(WARNING_ASSERT, + "Assertion failed: %s", + $5); + break; + } + } + } +; + shift : T_POP_SHIFT { macro_ShiftCurrentArgs(); } - | T_POP_SHIFT uconst { + | T_POP_SHIFT uconst + { int32_t i = $2; while (i--) macro_ShiftCurrentArgs(); diff --git a/src/asm/globlex.c b/src/asm/globlex.c index 5044ed31..1c5a409a 100644 --- a/src/asm/globlex.c +++ b/src/asm/globlex.c @@ -496,6 +496,9 @@ const struct sLexInitString lexer_strings[] = { {"fail", T_POP_FAIL}, {"warn", T_POP_WARN}, + {"fatal", T_POP_FATAL}, + {"assert", T_POP_ASSERT}, + {"static_assert", T_POP_STATIC_ASSERT}, {"macro", T_POP_MACRO}, /* Not needed but we have it here just to protect the name */ diff --git a/src/asm/output.c b/src/asm/output.c index b2913ca9..5faa849a 100644 --- a/src/asm/output.c +++ b/src/asm/output.c @@ -47,10 +47,18 @@ struct PatchSymbol { struct PatchSymbol *pBucketNext; /* next symbol in hash table bucket */ }; +struct Assertion { + struct Patch *patch; + struct Section *section; + char *message; + struct Assertion *next; +}; + struct PatchSymbol *tHashedPatchSymbols[HASHSIZE]; struct Section *pSectionList, *pCurrentSection; struct PatchSymbol *pPatchSymbols; struct PatchSymbol **ppPatchSymbolsTail = &pPatchSymbols; +struct Assertion *assertions = NULL; char *tzObjectname; /* @@ -104,6 +112,21 @@ static uint32_t countpatches(struct Section *pSect) return r; } +/** + * Count the number of assertions used in this object + */ +static uint32_t countasserts(void) +{ + struct Assertion *assert = assertions; + uint32_t count = 0; + + while (assert) { + count++; + assert = assert->next; + } + return count; +} + /* * Write a long to a file (little-endian) */ @@ -287,62 +310,29 @@ static void addexports(void) } } -/* - * Allocate a new patchstructure and link it into the list - */ -struct Patch *allocpatch(void) +static void writerpn(uint8_t *rpnexpr, uint32_t *rpnptr, uint8_t *rpn, + uint32_t rpnlen) { - struct Patch *pPatch; - - pPatch = malloc(sizeof(struct Patch)); - - if (pPatch == NULL) - fatalerror("No memory for patch"); - - pPatch->pNext = pCurrentSection->pPatches; - pPatch->nRPNSize = 0; - pPatch->pRPN = NULL; - pCurrentSection->pPatches = pPatch; - - return pPatch; -} - -/* - * Create a new patch (includes the rpn expr) - */ -void out_CreatePatch(uint32_t type, struct Expression const *expr) -{ - struct Patch *pPatch; - uint8_t *rpnexpr; char tzSym[512]; - uint32_t rpnptr = 0, symptr; - rpnexpr = malloc(expr->nRPNPatchSize); - - if (rpnexpr == NULL) - fatalerror("No memory for patch RPN expression"); - - pPatch = allocpatch(); - pPatch->nType = type; - fstk_DumpToStr(pPatch->tzFilename, sizeof(pPatch->tzFilename)); - pPatch->nOffset = pCurrentSection->nPC; - - for (size_t offset = 0; offset < expr->nRPNLength; ) { -#define popbyte(expr) (expr)->tRPN[offset++] - uint8_t rpndata = popbyte(expr); + for (size_t offset = 0; offset < rpnlen; ) { +#define popbyte() rpn[offset++] +#define writebyte(byte) rpnexpr[(*rpnptr)++] = byte + uint8_t rpndata = popbyte(); switch (rpndata) { case RPN_CONST: - rpnexpr[rpnptr++] = RPN_CONST; - rpnexpr[rpnptr++] = popbyte(expr); - rpnexpr[rpnptr++] = popbyte(expr); - rpnexpr[rpnptr++] = popbyte(expr); - rpnexpr[rpnptr++] = popbyte(expr); + writebyte(RPN_CONST); + writebyte(popbyte()); + writebyte(popbyte()); + writebyte(popbyte()); + writebyte(popbyte()); break; case RPN_SYM: { - symptr = 0; - while ((tzSym[symptr++] = popbyte(expr)) != 0) + uint32_t symptr = 0; + + while ((tzSym[symptr++] = popbyte()) != 0) ; struct sSymbol const *sym = sym_FindSymbol(tzSym); @@ -353,27 +343,27 @@ void out_CreatePatch(uint32_t type, struct Expression const *expr) uint32_t value; value = sym_GetConstantValue(tzSym); - rpnexpr[rpnptr++] = RPN_CONST; - rpnexpr[rpnptr++] = value & 0xFF; - rpnexpr[rpnptr++] = value >> 8; - rpnexpr[rpnptr++] = value >> 16; - rpnexpr[rpnptr++] = value >> 24; + writebyte(RPN_CONST); + writebyte(value & 0xFF); + writebyte(value >> 8); + writebyte(value >> 16); + writebyte(value >> 24); } else { symptr = addsymbol(sym); - rpnexpr[rpnptr++] = RPN_SYM; - rpnexpr[rpnptr++] = symptr & 0xFF; - rpnexpr[rpnptr++] = symptr >> 8; - rpnexpr[rpnptr++] = symptr >> 16; - rpnexpr[rpnptr++] = symptr >> 24; + writebyte(RPN_SYM); + writebyte(symptr & 0xFF); + writebyte(symptr >> 8); + writebyte(symptr >> 16); + writebyte(symptr >> 24); } break; } case RPN_BANK_SYM: { struct sSymbol *sym; + uint32_t symptr = 0; - symptr = 0; - while ((tzSym[symptr++] = popbyte(expr)) != 0) + while ((tzSym[symptr++] = popbyte()) != 0) ; sym = sym_FindSymbol(tzSym); @@ -381,36 +371,103 @@ void out_CreatePatch(uint32_t type, struct Expression const *expr) break; symptr = addsymbol(sym); - rpnexpr[rpnptr++] = RPN_BANK_SYM; - rpnexpr[rpnptr++] = symptr & 0xFF; - rpnexpr[rpnptr++] = symptr >> 8; - rpnexpr[rpnptr++] = symptr >> 16; - rpnexpr[rpnptr++] = symptr >> 24; + writebyte(RPN_BANK_SYM); + writebyte(symptr & 0xFF); + writebyte(symptr >> 8); + writebyte(symptr >> 16); + writebyte(symptr >> 24); break; } case RPN_BANK_SECT: { uint16_t b; - rpnexpr[rpnptr++] = RPN_BANK_SECT; + writebyte(RPN_BANK_SECT); do { - b = popbyte(expr); - rpnexpr[rpnptr++] = b & 0xFF; + b = popbyte(); + writebyte(b & 0xFF); } while (b != 0); break; } default: - rpnexpr[rpnptr++] = rpndata; + writebyte(rpndata); break; } #undef popbyte +#undef writebyte + } +} + +/* + * Allocate a new patch structure and link it into the list + */ +static struct Patch *allocpatch(uint32_t type, struct Expression const *expr) +{ + struct Patch *pPatch; + + pPatch = malloc(sizeof(struct Patch)); + + if (!pPatch) + fatalerror("No memory for patch: %s", strerror(errno)); + pPatch->pRPN = malloc(sizeof(*pPatch->pRPN) * expr->nRPNPatchSize); + + if (!pPatch->pRPN) + fatalerror("No memory for patch's RPN expression: %s", + strerror(errno)); + + pPatch->nRPNSize = 0; + pPatch->nType = type; + pPatch->nOffset = pCurrentSection->nPC; + fstk_DumpToStr(pPatch->tzFilename, sizeof(pPatch->tzFilename)); + + writerpn(pPatch->pRPN, &pPatch->nRPNSize, expr->tRPN, expr->nRPNLength); + assert(pPatch->nRPNSize == expr->nRPNPatchSize); + + return pPatch; +} + +/* + * Create a new patch (includes the rpn expr) + */ +void out_CreatePatch(uint32_t type, struct Expression const *expr) +{ + struct Patch *pPatch = allocpatch(type, expr); + + pPatch->pNext = pCurrentSection->pPatches; + pCurrentSection->pPatches = pPatch; +} + +/** + * Creates an assert that will be written to the object file + */ +bool out_CreateAssert(enum AssertionType type, struct Expression const *expr, + char const *message) +{ + struct Assertion *assertion = malloc(sizeof(*assertion)); + + if (!assertion) + return false; + + assertion->patch = allocpatch(type, expr); + assertion->section = pCurrentSection; + assertion->message = strdup(message); + if (!assertion->message) { + free(assertion); + return false; } - assert(rpnptr == expr->nRPNPatchSize); + assertion->next = assertions; + assertions = assertion; - pPatch->pRPN = rpnexpr; - pPatch->nRPNSize = rpnptr; + return true; +} + +static void writeassert(struct Assertion *assert, FILE *f) +{ + writepatch(assert->patch, f); + fputlong(getsectid(assert->section), f); + fputstring(assert->message, f); } /* @@ -421,6 +478,7 @@ void out_WriteObject(void) FILE *f; struct PatchSymbol *pSym; struct Section *pSect; + struct Assertion *assert = assertions; addexports(); @@ -446,6 +504,12 @@ void out_WriteObject(void) pSect = pSect->pNext; } + fputlong(countasserts(), f); + while (assert) { + writeassert(assert, f); + assert = assert->next; + } + fclose(f); } diff --git a/src/asm/rgbasm.1 b/src/asm/rgbasm.1 index 5d87516f..11706c39 100644 --- a/src/asm/rgbasm.1 +++ b/src/asm/rgbasm.1 @@ -141,13 +141,21 @@ Enables literally every warning. The following warnings are actual warning flags; with each description, the corresponding warning flag is included. Note that each of these flag also has a negation (for example, .Fl Wempty-entry -enables the warning that Fl Wno-empty-entry +enables the warning that +.Fl Wno-empty-entry disables). Only the non-default flag is listed here. Ignoring the .Dq no- prefix, entries are listed alphabetically. .Bl -tag -width Ds +.It Fl Wno-assert +Warns when +.Ic WARN Ns No -type +assertions fail. (See +.Xr rgbasm 5 "Aborting the assembly process" +for +.Ic ASSERT ) . .It Fl Wbuiltin-args Warn about incorrect arguments to built-in functions, such as .Fn STRSUB @@ -182,9 +190,12 @@ This warning is enabled by Warn when shifting triggers C undefined behavior, potentially causing unpredictable behavior. Shfting behavior will be changed and this warning removed before next release. .It Fl Wno-user -Warns when the built-in function -.Fn WARN -is executed. +Warns when the +.Ic WARN +built-in is executed. (See +.Xr rgbasm 5 "Aborting the assembly process" +for +.Ic WARN ) . .El .Sh EXAMPLES You can assemble a source file in two ways. diff --git a/src/asm/rgbasm.5 b/src/asm/rgbasm.5 index b2539b7e..42246af6 100644 --- a/src/asm/rgbasm.5 +++ b/src/asm/rgbasm.5 @@ -980,6 +980,62 @@ take a string as the only argument and they will print this string out as a norm stops assembling immediately while .Ic WARN shows the message but continues afterwards. +.Pp +If you need to ensure some assumption is correct when compiling, you can use +.Ic ASSERT +and +.Ic STATIC_ASSERT . +Syntax examples are given below: +.Pp +.Bd -literal -offset indent +Function: + xor a +ASSERT LOW(Variable) == 0 + ld h, HIGH(Variable) + ld l, a + ld a, [hli] + ; You can also indent this! + ASSERT BANK(OtherFunction) == BANK(Function) + call OtherFunction +; Lowercase also works +assert Variable + 1 == OtherVariable + ld c, [hl] + ret +\&.end + ; If you specify one, a message will be printed + STATIC_ASSERT .end - Function < 256, "Function is too large!" +.Ed +.Pp +First, the difference between +.Ic ASSERT +and +.Ic STATIC_ASSERT +is that the former is evaluated by RGBASM if it can, otherwise by RGBLINK; but the latter is only ever evaluated by RGBASM. +If RGBASM cannot compute the value of the argument to +.Ic STATIC_ASSERT , +it will produce an error. +.Pp +Second, as shown above, a string can be optionally added at the end, to give insight into what the assertion is checking. +.Pp +Finally, you can add one of +.Ic WARN , FAIL +or +.Ic FATAL +as the first optional argument to either +.Ic ASSERT +or +.Ic STATIC_ASSERT . +If the assertion fails, +.Ic WARN +will cause a simple warning (controlled by +.Xr rgbasm 1 DIAGNOSTICS +flag +.Fl Wassert ) +to be emitted; +.Ic FAIL +(the default) will cause a non-fatal error; and +.Ic FATAL +immediately aborts. .Ss Including other source files Use .Ic INCLUDE diff --git a/src/asm/warning.c b/src/asm/warning.c index 253044a1..42c4829e 100644 --- a/src/asm/warning.c +++ b/src/asm/warning.c @@ -36,6 +36,7 @@ static enum WarningState const defaultWarnings[NB_WARNINGS] = { WARNING_DISABLED, /* Obsolete things */ WARNING_DISABLED, /* Shifting undefined behavior */ WARNING_ENABLED, /* User warnings */ + WARNING_ENABLED, /* Assertions */ WARNING_DISABLED, /* Strange shift amount */ WARNING_ENABLED, /* Implicit truncation loses some bits */ }; @@ -72,6 +73,7 @@ static char const *warningFlags[NB_WARNINGS_ALL] = { "obsolete", "shift", "user", + "assert", "shift-amount", "truncation", diff --git a/src/link/object.c b/src/link/object.c index e9b392ac..d42565d9 100644 --- a/src/link/object.c +++ b/src/link/object.c @@ -13,13 +13,15 @@ #include #include -#include "link/object.h" -#include "link/main.h" -#include "link/symbol.h" -#include "link/section.h" #include "link/assign.h" +#include "link/main.h" +#include "link/object.h" +#include "link/patch.h" +#include "link/section.h" +#include "link/symbol.h" #include "extern/err.h" +#include "helpers.h" #include "linkdefs.h" static struct SymbolList { @@ -28,6 +30,8 @@ static struct SymbolList { struct SymbolList *next; } *symbolLists; +static struct Assertion *assertions; + /***** Helper functions for reading object files *****/ /* @@ -232,7 +236,7 @@ static void readPatch(FILE *file, struct Patch *patch, } /** - * Reads a RGB6 section from a file. + * Reads a section from a file. * @param file The file to read from * @param section The struct to fill * @param fileName The filename to report in errors @@ -331,6 +335,29 @@ static void linkSymToSect(struct Symbol const *symbol, struct Section *section) section->nbSymbols++; } +/** + * Reads an assertion from a file + * @param file The file to read from + * @param assert The struct to fill + * @param fileName The filename to report in errors + */ +static void readAssertion(FILE *file, struct Assertion *assert, + char const *fileName, struct Section *fileSections[], + uint32_t i) +{ + char assertName[sizeof("Assertion #" EXPAND_AND_STR(UINT32_MAX))]; + uint32_t sectionID; + + snprintf(assertName, sizeof(assertName), "Assertion #%u", i); + + readPatch(file, &assert->patch, fileName, assertName, 0); + tryReadlong(sectionID, file, "%s: Cannot read assertion's section ID: %s", + fileName); + assert->section = fileSections[sectionID]; + tryReadstr(assert->message, file, "%s: Cannot read assertion's message: %s", + fileName); +} + /** * Reads an object file of any supported format * @param fileName The filename to report for errors @@ -451,12 +478,29 @@ void obj_ReadFile(char const *fileName) } } + uint32_t nbAsserts; + + tryReadlong(nbAsserts, file, "%s: Cannot read number of assertions: %s", + fileName); + verbosePrint("Reading %u assertions...\n", nbAsserts); + for (uint32_t i = 0; i < nbAsserts; i++) { + struct Assertion *assertion = malloc(sizeof(*assertion)); + + if (!assertion) + err(1, "%s: Couldn't create new assertion", fileName); + readAssertion(file, assertion, fileName, fileSections, i); + assertion->next = assertions; + assertions = assertion; + } + fclose(file); } void obj_DoSanityChecks(void) { sect_DoSanityChecks(); + + patch_CheckAssertions(assertions); } static void freeSection(struct Section *section, void *arg) diff --git a/src/link/patch.c b/src/link/patch.c index 2a1b3534..0998dda0 100644 --- a/src/link/patch.c +++ b/src/link/patch.c @@ -340,6 +340,48 @@ static int32_t computeRPNExpr(struct Patch const *patch, #undef popRPN } +void patch_CheckAssertions(struct Assertion *assert) +{ + verbosePrint("Checking assertions..."); + initRPNStack(); + + uint8_t failures = 0; + + while (assert) { + if (!computeRPNExpr(&assert->patch, assert->section)) { + switch ((enum AssertionType)assert->patch.type) { + case ASSERT_FATAL: + errx(1, "%s: %s", assert->patch.fileName, + assert->message[0] ? assert->message + : "assert failure"); + /* Not reached */ + break; /* Here so checkpatch doesn't complain */ + case ASSERT_ERROR: + fprintf(stderr, "%s: %s\n", + assert->patch.fileName, + assert->message[0] ? assert->message + : "assert failure"); + failures++; + break; + case ASSERT_WARN: + warnx("%s: %s", assert->patch.fileName, + assert->message[0] ? assert->message + : "assert failure"); + break; + } + } + struct Assertion *next = assert->next; + + free(assert); + assert = next; + } + + freeRPNStack(); + + if (failures) + errx(1, "%u assertions failed!", failures); +} + /** * Applies all of a section's patches * @param section The section to patch @@ -399,3 +441,4 @@ void patch_ApplyPatches(void) sect_ForEach(applyPatches, NULL); freeRPNStack(); } + diff --git a/src/rgbds.5 b/src/rgbds.5 index d17fb82c..3f2eb743 100644 --- a/src/rgbds.5 +++ b/src/rgbds.5 @@ -123,6 +123,34 @@ REPT NumberOfSections ENDC +ENDR + +; Assertions + +LONG NumberOfAssertions + +REPT NumberOfAssertions + + STRING SourceFile ; Name of the source file (for printing the failure). + + LONG Offset ; Offset into the section where the assertion is located. + + BYTE Type ; 0 = Prints the message but allows linking to continue + ; 1 = Prints the message and evaluates other assertions, + ; but linking fails afterwards + ; 2 = Prints the message and immediately fails linking + + LONG RPNSize ; Size of the RPN expression's buffer. + + BYTE RPN[RPNSize] ; RPN expression, same as patches. Assert fails if == 0. + + LONG SectionID ; The section number (of this object file) in which this + ; assert is defined. If it doesn't belong to any specific + ; section (like a constant), this field has the value -1. + + STRING Message ; A message displayed when the assert fails. If set to + ; the empty string, a generic message is printed instead. + ENDR .Ed .Ss RPN DATA diff --git a/test/asm/assert.asm b/test/asm/assert.asm new file mode 100644 index 00000000..fe943e61 --- /dev/null +++ b/test/asm/assert.asm @@ -0,0 +1,23 @@ +SECTION "fixed", ROM0[0] + +FixedBase: + assert FixedBase ; This should eval (and fail) at compile time + + ds 0 + static_assert @ == 0, "@ ain't 0 now? (Hint: it's {@})" + + ds 42 + assert WARN, @ - FixedBase != 42 ; This should also eval at compile time + +SECTION "floating", ROM0 + +FloatingBase: + assert FAIL, FloatingBase == 0 ; This shouldn't eval at compile time + + ds 4 + static_assert FAIL, FloatingBase != 0 ; This is not constant! + + ds 69 + static_assert FATAL, FixedBase != 0 ; This will fail... ↓ + ; The point of `FATAL` is for stuff that can, say, cause division by 0! + static_assert FAIL, 1 / FixedBase, "You dun goofed, son" ; Won't be read diff --git a/test/asm/assert.err b/test/asm/assert.err new file mode 100644 index 00000000..e4ef868d --- /dev/null +++ b/test/asm/assert.err @@ -0,0 +1,10 @@ +ERROR: assert.asm(4): + Assertion failed +warning: assert.asm(10): [-Wassert] + Assertion failed +ERROR: assert.asm(18): + Expected constant expression: 'FloatingBase' is not constant at assembly time +ERROR: assert.asm(18): + Assertion failed +ERROR: assert.asm(21): + Assertion failed diff --git a/test/asm/assert.out b/test/asm/assert.out new file mode 100644 index 00000000..e69de29b diff --git a/test/link/assert.asm b/test/link/assert.asm new file mode 100644 index 00000000..e10d485c --- /dev/null +++ b/test/link/assert.asm @@ -0,0 +1,11 @@ + +SECTION "test", ROM0 + + ds 123 + +FloatingBase: + assert WARN, FloatingBase & 0, "Worry about me, but not too much." + assert FAIL, FloatingBase & 0, "Okay, this is getting serious!" + assert FATAL, FloatingBase & 0, "It all ends now." + assert FAIL, FloatingBase & 0, "Not even time to roll credits!" + assert WARN, 0, "Still can finish the film, though!" diff --git a/test/link/assert.out b/test/link/assert.out new file mode 100644 index 00000000..f7d2cb63 --- /dev/null +++ b/test/link/assert.out @@ -0,0 +1,3 @@ +warning: assert.asm(7): Worry about me, but not too much. +assert.asm(8): Okay, this is getting serious! +error: assert.asm(9): It all ends now.