mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-22 19:22:05 +00:00
@@ -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",
|
||||
|
||||
|
||||
Reference in New Issue
Block a user