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_SetFileName(char *s);
|
||||||
void out_CreatePatch(uint32_t type, struct Expression const *expr);
|
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);
|
void out_WriteObject(void);
|
||||||
|
|
||||||
#endif /* RGBDS_ASM_OUTPUT_H */
|
#endif /* RGBDS_ASM_OUTPUT_H */
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ enum WarningID {
|
|||||||
WARNING_OBSOLETE,
|
WARNING_OBSOLETE,
|
||||||
WARNING_SHIFT,
|
WARNING_SHIFT,
|
||||||
WARNING_USER,
|
WARNING_USER,
|
||||||
|
WARNING_ASSERT,
|
||||||
WARNING_SHIFT_AMOUNT,
|
WARNING_SHIFT_AMOUNT,
|
||||||
WARNING_TRUNCATION,
|
WARNING_TRUNCATION,
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,28 @@
|
|||||||
#ifndef RGBDS_LINK_PATCH_H
|
#ifndef RGBDS_LINK_PATCH_H
|
||||||
#define 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
|
* Applies all SECTIONs' patches to them
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -14,7 +14,13 @@
|
|||||||
|
|
||||||
#define RGBDS_OBJECT_VERSION_STRING "RGB%1hhu"
|
#define RGBDS_OBJECT_VERSION_STRING "RGB%1hhu"
|
||||||
#define RGBDS_OBJECT_VERSION_NUMBER (uint8_t)9
|
#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 {
|
enum RPNCommand {
|
||||||
RPN_ADD = 0x00,
|
RPN_ADD = 0x00,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include "asm/macro.h"
|
#include "asm/macro.h"
|
||||||
#include "asm/main.h"
|
#include "asm/main.h"
|
||||||
#include "asm/mymath.h"
|
#include "asm/mymath.h"
|
||||||
|
#include "asm/output.h"
|
||||||
#include "asm/rpn.h"
|
#include "asm/rpn.h"
|
||||||
#include "asm/section.h"
|
#include "asm/section.h"
|
||||||
#include "asm/symbol.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;
|
int32_t nConstValue;
|
||||||
struct SectionSpec sectSpec;
|
struct SectionSpec sectSpec;
|
||||||
struct MacroArgs *macroArg;
|
struct MacroArgs *macroArg;
|
||||||
|
enum AssertionType assertType;
|
||||||
}
|
}
|
||||||
|
|
||||||
%type <sVal> relocexpr
|
%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_LOAD T_POP_ENDL
|
||||||
%token T_POP_FAIL
|
%token T_POP_FAIL
|
||||||
%token T_POP_WARN
|
%token T_POP_WARN
|
||||||
|
%token T_POP_FATAL
|
||||||
|
%token T_POP_ASSERT T_POP_STATIC_ASSERT
|
||||||
%token T_POP_PURGE
|
%token T_POP_PURGE
|
||||||
%token T_POP_POPS
|
%token T_POP_POPS
|
||||||
%token T_POP_PUSHS
|
%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_a_r
|
||||||
%type <nConstValue> op_hl_ss
|
%type <nConstValue> op_hl_ss
|
||||||
%type <sVal> op_mem_ind
|
%type <sVal> op_mem_ind
|
||||||
|
%type <assertType> assert_type
|
||||||
%start asmfile
|
%start asmfile
|
||||||
|
|
||||||
%%
|
%%
|
||||||
@@ -759,6 +764,7 @@ simple_pseudoop : include
|
|||||||
| shift
|
| shift
|
||||||
| fail
|
| fail
|
||||||
| warn
|
| warn
|
||||||
|
| assert
|
||||||
| purge
|
| purge
|
||||||
| pops
|
| pops
|
||||||
| pushs
|
| pushs
|
||||||
@@ -799,8 +805,97 @@ fail : T_POP_FAIL string { fatalerror("%s", $2); }
|
|||||||
warn : T_POP_WARN string { warning(WARNING_USER, "%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(); }
|
shift : T_POP_SHIFT { macro_ShiftCurrentArgs(); }
|
||||||
| T_POP_SHIFT uconst {
|
| T_POP_SHIFT uconst
|
||||||
|
{
|
||||||
int32_t i = $2;
|
int32_t i = $2;
|
||||||
while (i--)
|
while (i--)
|
||||||
macro_ShiftCurrentArgs();
|
macro_ShiftCurrentArgs();
|
||||||
|
|||||||
@@ -496,6 +496,9 @@ const struct sLexInitString lexer_strings[] = {
|
|||||||
|
|
||||||
{"fail", T_POP_FAIL},
|
{"fail", T_POP_FAIL},
|
||||||
{"warn", T_POP_WARN},
|
{"warn", T_POP_WARN},
|
||||||
|
{"fatal", T_POP_FATAL},
|
||||||
|
{"assert", T_POP_ASSERT},
|
||||||
|
{"static_assert", T_POP_STATIC_ASSERT},
|
||||||
|
|
||||||
{"macro", T_POP_MACRO},
|
{"macro", T_POP_MACRO},
|
||||||
/* Not needed but we have it here just to protect the name */
|
/* 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 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 PatchSymbol *tHashedPatchSymbols[HASHSIZE];
|
||||||
struct Section *pSectionList, *pCurrentSection;
|
struct Section *pSectionList, *pCurrentSection;
|
||||||
struct PatchSymbol *pPatchSymbols;
|
struct PatchSymbol *pPatchSymbols;
|
||||||
struct PatchSymbol **ppPatchSymbolsTail = &pPatchSymbols;
|
struct PatchSymbol **ppPatchSymbolsTail = &pPatchSymbols;
|
||||||
|
struct Assertion *assertions = NULL;
|
||||||
char *tzObjectname;
|
char *tzObjectname;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -104,6 +112,21 @@ static uint32_t countpatches(struct Section *pSect)
|
|||||||
return r;
|
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)
|
* Write a long to a file (little-endian)
|
||||||
*/
|
*/
|
||||||
@@ -287,62 +310,29 @@ static void addexports(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
static void writerpn(uint8_t *rpnexpr, uint32_t *rpnptr, uint8_t *rpn,
|
||||||
* Allocate a new patchstructure and link it into the list
|
uint32_t rpnlen)
|
||||||
*/
|
|
||||||
struct Patch *allocpatch(void)
|
|
||||||
{
|
{
|
||||||
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];
|
char tzSym[512];
|
||||||
uint32_t rpnptr = 0, symptr;
|
|
||||||
|
|
||||||
rpnexpr = malloc(expr->nRPNPatchSize);
|
for (size_t offset = 0; offset < rpnlen; ) {
|
||||||
|
#define popbyte() rpn[offset++]
|
||||||
if (rpnexpr == NULL)
|
#define writebyte(byte) rpnexpr[(*rpnptr)++] = byte
|
||||||
fatalerror("No memory for patch RPN expression");
|
uint8_t rpndata = popbyte();
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
switch (rpndata) {
|
switch (rpndata) {
|
||||||
case RPN_CONST:
|
case RPN_CONST:
|
||||||
rpnexpr[rpnptr++] = RPN_CONST;
|
writebyte(RPN_CONST);
|
||||||
rpnexpr[rpnptr++] = popbyte(expr);
|
writebyte(popbyte());
|
||||||
rpnexpr[rpnptr++] = popbyte(expr);
|
writebyte(popbyte());
|
||||||
rpnexpr[rpnptr++] = popbyte(expr);
|
writebyte(popbyte());
|
||||||
rpnexpr[rpnptr++] = popbyte(expr);
|
writebyte(popbyte());
|
||||||
break;
|
break;
|
||||||
case RPN_SYM:
|
case RPN_SYM:
|
||||||
{
|
{
|
||||||
symptr = 0;
|
uint32_t symptr = 0;
|
||||||
while ((tzSym[symptr++] = popbyte(expr)) != 0)
|
|
||||||
|
while ((tzSym[symptr++] = popbyte()) != 0)
|
||||||
;
|
;
|
||||||
|
|
||||||
struct sSymbol const *sym = sym_FindSymbol(tzSym);
|
struct sSymbol const *sym = sym_FindSymbol(tzSym);
|
||||||
@@ -353,27 +343,27 @@ void out_CreatePatch(uint32_t type, struct Expression const *expr)
|
|||||||
uint32_t value;
|
uint32_t value;
|
||||||
|
|
||||||
value = sym_GetConstantValue(tzSym);
|
value = sym_GetConstantValue(tzSym);
|
||||||
rpnexpr[rpnptr++] = RPN_CONST;
|
writebyte(RPN_CONST);
|
||||||
rpnexpr[rpnptr++] = value & 0xFF;
|
writebyte(value & 0xFF);
|
||||||
rpnexpr[rpnptr++] = value >> 8;
|
writebyte(value >> 8);
|
||||||
rpnexpr[rpnptr++] = value >> 16;
|
writebyte(value >> 16);
|
||||||
rpnexpr[rpnptr++] = value >> 24;
|
writebyte(value >> 24);
|
||||||
} else {
|
} else {
|
||||||
symptr = addsymbol(sym);
|
symptr = addsymbol(sym);
|
||||||
rpnexpr[rpnptr++] = RPN_SYM;
|
writebyte(RPN_SYM);
|
||||||
rpnexpr[rpnptr++] = symptr & 0xFF;
|
writebyte(symptr & 0xFF);
|
||||||
rpnexpr[rpnptr++] = symptr >> 8;
|
writebyte(symptr >> 8);
|
||||||
rpnexpr[rpnptr++] = symptr >> 16;
|
writebyte(symptr >> 16);
|
||||||
rpnexpr[rpnptr++] = symptr >> 24;
|
writebyte(symptr >> 24);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case RPN_BANK_SYM:
|
case RPN_BANK_SYM:
|
||||||
{
|
{
|
||||||
struct sSymbol *sym;
|
struct sSymbol *sym;
|
||||||
|
uint32_t symptr = 0;
|
||||||
|
|
||||||
symptr = 0;
|
while ((tzSym[symptr++] = popbyte()) != 0)
|
||||||
while ((tzSym[symptr++] = popbyte(expr)) != 0)
|
|
||||||
;
|
;
|
||||||
|
|
||||||
sym = sym_FindSymbol(tzSym);
|
sym = sym_FindSymbol(tzSym);
|
||||||
@@ -381,36 +371,103 @@ void out_CreatePatch(uint32_t type, struct Expression const *expr)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
symptr = addsymbol(sym);
|
symptr = addsymbol(sym);
|
||||||
rpnexpr[rpnptr++] = RPN_BANK_SYM;
|
writebyte(RPN_BANK_SYM);
|
||||||
rpnexpr[rpnptr++] = symptr & 0xFF;
|
writebyte(symptr & 0xFF);
|
||||||
rpnexpr[rpnptr++] = symptr >> 8;
|
writebyte(symptr >> 8);
|
||||||
rpnexpr[rpnptr++] = symptr >> 16;
|
writebyte(symptr >> 16);
|
||||||
rpnexpr[rpnptr++] = symptr >> 24;
|
writebyte(symptr >> 24);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case RPN_BANK_SECT:
|
case RPN_BANK_SECT:
|
||||||
{
|
{
|
||||||
uint16_t b;
|
uint16_t b;
|
||||||
|
|
||||||
rpnexpr[rpnptr++] = RPN_BANK_SECT;
|
writebyte(RPN_BANK_SECT);
|
||||||
|
|
||||||
do {
|
do {
|
||||||
b = popbyte(expr);
|
b = popbyte();
|
||||||
rpnexpr[rpnptr++] = b & 0xFF;
|
writebyte(b & 0xFF);
|
||||||
} while (b != 0);
|
} while (b != 0);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
rpnexpr[rpnptr++] = rpndata;
|
writebyte(rpndata);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#undef popbyte
|
#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;
|
return true;
|
||||||
pPatch->nRPNSize = rpnptr;
|
}
|
||||||
|
|
||||||
|
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;
|
FILE *f;
|
||||||
struct PatchSymbol *pSym;
|
struct PatchSymbol *pSym;
|
||||||
struct Section *pSect;
|
struct Section *pSect;
|
||||||
|
struct Assertion *assert = assertions;
|
||||||
|
|
||||||
addexports();
|
addexports();
|
||||||
|
|
||||||
@@ -446,6 +504,12 @@ void out_WriteObject(void)
|
|||||||
pSect = pSect->pNext;
|
pSect = pSect->pNext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fputlong(countasserts(), f);
|
||||||
|
while (assert) {
|
||||||
|
writeassert(assert, f);
|
||||||
|
assert = assert->next;
|
||||||
|
}
|
||||||
|
|
||||||
fclose(f);
|
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.
|
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,
|
Note that each of these flag also has a negation (for example,
|
||||||
.Fl Wempty-entry
|
.Fl Wempty-entry
|
||||||
enables the warning that Fl Wno-empty-entry
|
enables the warning that
|
||||||
|
.Fl Wno-empty-entry
|
||||||
disables).
|
disables).
|
||||||
Only the non-default flag is listed here.
|
Only the non-default flag is listed here.
|
||||||
Ignoring the
|
Ignoring the
|
||||||
.Dq no-
|
.Dq no-
|
||||||
prefix, entries are listed alphabetically.
|
prefix, entries are listed alphabetically.
|
||||||
.Bl -tag -width Ds
|
.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
|
.It Fl Wbuiltin-args
|
||||||
Warn about incorrect arguments to built-in functions, such as
|
Warn about incorrect arguments to built-in functions, such as
|
||||||
.Fn STRSUB
|
.Fn STRSUB
|
||||||
@@ -182,9 +190,12 @@ This warning is enabled by
|
|||||||
Warn when shifting triggers C undefined behavior, potentially causing unpredictable behavior.
|
Warn when shifting triggers C undefined behavior, potentially causing unpredictable behavior.
|
||||||
Shfting behavior will be changed and this warning removed before next release.
|
Shfting behavior will be changed and this warning removed before next release.
|
||||||
.It Fl Wno-user
|
.It Fl Wno-user
|
||||||
Warns when the built-in function
|
Warns when the
|
||||||
.Fn WARN
|
.Ic WARN
|
||||||
is executed.
|
built-in is executed. (See
|
||||||
|
.Xr rgbasm 5 "Aborting the assembly process"
|
||||||
|
for
|
||||||
|
.Ic WARN ) .
|
||||||
.El
|
.El
|
||||||
.Sh EXAMPLES
|
.Sh EXAMPLES
|
||||||
You can assemble a source file in two ways.
|
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
|
stops assembling immediately while
|
||||||
.Ic WARN
|
.Ic WARN
|
||||||
shows the message but continues afterwards.
|
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
|
.Ss Including other source files
|
||||||
Use
|
Use
|
||||||
.Ic INCLUDE
|
.Ic INCLUDE
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ static enum WarningState const defaultWarnings[NB_WARNINGS] = {
|
|||||||
WARNING_DISABLED, /* Obsolete things */
|
WARNING_DISABLED, /* Obsolete things */
|
||||||
WARNING_DISABLED, /* Shifting undefined behavior */
|
WARNING_DISABLED, /* Shifting undefined behavior */
|
||||||
WARNING_ENABLED, /* User warnings */
|
WARNING_ENABLED, /* User warnings */
|
||||||
|
WARNING_ENABLED, /* Assertions */
|
||||||
WARNING_DISABLED, /* Strange shift amount */
|
WARNING_DISABLED, /* Strange shift amount */
|
||||||
WARNING_ENABLED, /* Implicit truncation loses some bits */
|
WARNING_ENABLED, /* Implicit truncation loses some bits */
|
||||||
};
|
};
|
||||||
@@ -72,6 +73,7 @@ static char const *warningFlags[NB_WARNINGS_ALL] = {
|
|||||||
"obsolete",
|
"obsolete",
|
||||||
"shift",
|
"shift",
|
||||||
"user",
|
"user",
|
||||||
|
"assert",
|
||||||
"shift-amount",
|
"shift-amount",
|
||||||
"truncation",
|
"truncation",
|
||||||
|
|
||||||
|
|||||||
@@ -13,13 +13,15 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <limits.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/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 "extern/err.h"
|
||||||
|
#include "helpers.h"
|
||||||
#include "linkdefs.h"
|
#include "linkdefs.h"
|
||||||
|
|
||||||
static struct SymbolList {
|
static struct SymbolList {
|
||||||
@@ -28,6 +30,8 @@ static struct SymbolList {
|
|||||||
struct SymbolList *next;
|
struct SymbolList *next;
|
||||||
} *symbolLists;
|
} *symbolLists;
|
||||||
|
|
||||||
|
static struct Assertion *assertions;
|
||||||
|
|
||||||
/***** Helper functions for reading object files *****/
|
/***** 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 file The file to read from
|
||||||
* @param section The struct to fill
|
* @param section The struct to fill
|
||||||
* @param fileName The filename to report in errors
|
* @param fileName The filename to report in errors
|
||||||
@@ -331,6 +335,29 @@ static void linkSymToSect(struct Symbol const *symbol, struct Section *section)
|
|||||||
section->nbSymbols++;
|
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
|
* Reads an object file of any supported format
|
||||||
* @param fileName The filename to report for errors
|
* @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);
|
fclose(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
void obj_DoSanityChecks(void)
|
void obj_DoSanityChecks(void)
|
||||||
{
|
{
|
||||||
sect_DoSanityChecks();
|
sect_DoSanityChecks();
|
||||||
|
|
||||||
|
patch_CheckAssertions(assertions);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void freeSection(struct Section *section, void *arg)
|
static void freeSection(struct Section *section, void *arg)
|
||||||
|
|||||||
@@ -340,6 +340,48 @@ static int32_t computeRPNExpr(struct Patch const *patch,
|
|||||||
#undef popRPN
|
#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
|
* Applies all of a section's patches
|
||||||
* @param section The section to patch
|
* @param section The section to patch
|
||||||
@@ -399,3 +441,4 @@ void patch_ApplyPatches(void)
|
|||||||
sect_ForEach(applyPatches, NULL);
|
sect_ForEach(applyPatches, NULL);
|
||||||
freeRPNStack();
|
freeRPNStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
28
src/rgbds.5
28
src/rgbds.5
@@ -123,6 +123,34 @@ REPT NumberOfSections
|
|||||||
|
|
||||||
ENDC
|
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
|
ENDR
|
||||||
.Ed
|
.Ed
|
||||||
.Ss RPN DATA
|
.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