Add assertions

Closes #292
This commit is contained in:
ISSOtm
2020-03-05 02:58:48 +01:00
parent 03967bd623
commit fb58166e5d
18 changed files with 506 additions and 82 deletions

View File

@@ -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 <sVal> 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 <nConstValue> op_a_r
%type <nConstValue> op_hl_ss
%type <sVal> op_mem_ind
%type <assertType> 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();

View File

@@ -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 */

View File

@@ -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);
}

View File

@@ -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.

View File

@@ -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

View File

@@ -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",