mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 10:12:06 +00:00
@@ -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 */
|
||||
|
||||
@@ -22,6 +22,7 @@ enum WarningID {
|
||||
WARNING_OBSOLETE,
|
||||
WARNING_SHIFT,
|
||||
WARNING_USER,
|
||||
WARNING_ASSERT,
|
||||
WARNING_SHIFT_AMOUNT,
|
||||
WARNING_TRUNCATION,
|
||||
|
||||
|
||||
@@ -10,6 +10,28 @@
|
||||
#ifndef RGBDS_LINK_PATCH_H
|
||||
#define RGBDS_LINK_PATCH_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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
|
||||
*/
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 */
|
||||
|
||||
206
src/asm/output.c
206
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
|
||||
@@ -13,13 +13,15 @@
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
|
||||
#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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
28
src/rgbds.5
28
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
|
||||
|
||||
23
test/asm/assert.asm
Normal file
23
test/asm/assert.asm
Normal file
@@ -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
|
||||
10
test/asm/assert.err
Normal file
10
test/asm/assert.err
Normal file
@@ -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
|
||||
0
test/asm/assert.out
Normal file
0
test/asm/assert.out
Normal file
11
test/link/assert.asm
Normal file
11
test/link/assert.asm
Normal file
@@ -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!"
|
||||
3
test/link/assert.out
Normal file
3
test/link/assert.out
Normal file
@@ -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.
|
||||
Reference in New Issue
Block a user