shithub: rgbds

Download patch

ref: 632bc2aaecc05590502e9bda1e76f8fac38f2c8b
parent: 1d0c8fa113ba788ae41a977f30149113825e7b07
parent: 1d70c989be64fffab08902c1660b4749c7d16207
author: Eldred Habert <[email protected]>
date: Sun Feb 9 22:10:05 EST 2020

Merge pull request #476 from ISSOtm/expr_cleanup

Clean up expression evaluation

--- a/Makefile
+++ b/Makefile
@@ -54,7 +54,6 @@
 rgbasm_obj := \
 	src/asm/asmy.o \
 	src/asm/charmap.o \
-	src/asm/constexpr.o \
 	src/asm/fstack.o \
 	src/asm/globlex.o \
 	src/asm/lexer.o \
--- a/include/asm/constexpr.h
+++ /dev/null
@@ -1,35 +1,0 @@
-/*
- * This file is part of RGBDS.
- *
- * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
- *
- * SPDX-License-Identifier: MIT
- */
-
-#ifndef RGBDS_ASM_CONSTEXPR_H
-#define RGBDS_ASM_CONSTEXPR_H
-
-#include <stdint.h>
-
-struct ConstExpression {
-	union {
-		int32_t nVal;
-		struct sSymbol *pSym;
-	} u;
-	uint32_t isSym;
-};
-
-void constexpr_Symbol(struct ConstExpression *expr, char *tzSym);
-void constexpr_BankSymbol(struct ConstExpression *expr, char *tzSym);
-void constexpr_BankSection(struct ConstExpression *expr, char *tzSym);
-void constexpr_Number(struct ConstExpression *expr, int32_t i);
-void constexpr_UnaryOp(struct ConstExpression *expr,
-		       int32_t op,
-		       const struct ConstExpression *src);
-void constexpr_BinaryOp(struct ConstExpression *expr,
-			int32_t op,
-			const struct ConstExpression *src1,
-			const struct ConstExpression *src2);
-int32_t constexpr_GetConstantValue(struct ConstExpression *expr);
-
-#endif /* RGBDS_ASM_CONSTEXPR_H */
--- a/include/asm/rpn.h
+++ b/include/asm/rpn.h
@@ -10,62 +10,36 @@
 #define RGBDS_ASM_RPN_H
 
 #include <stdint.h>
+#include <stdbool.h>
 
+#include "linkdefs.h"
+
 #define MAXRPNLEN 1048576
 
 struct Expression {
-	int32_t  nVal;
-	uint8_t  *tRPN;
-	uint32_t nRPNCapacity;
-	uint32_t nRPNLength;
-	uint32_t nRPNPatchSize;
-	uint32_t nRPNOut;
-	uint32_t isReloc;
+	int32_t  nVal;          // If the expression's value is known, it's here
+	char     *reason;       // Why the expression is not known, if it isn't
+	bool     isKnown;       // Whether the expression's value is known
+	bool     isSymbol;      // Whether the expression represents a symbol
+	uint8_t  *tRPN;         // Array of bytes serializing the RPN expression
+	uint32_t nRPNCapacity;  // Size of the `tRPN` buffer
+	uint32_t nRPNLength;    // Used size of the `tRPN` buffer
+	uint32_t nRPNPatchSize; // Size the expression will take in the obj file
+	// FIXME: does this need to be part of the struct?
+	uint32_t nRPNOut;       // How many bytes have been written
 };
 
 /* FIXME: Should be defined in `asmy.h`, but impossible with POSIX Yacc */
 extern int32_t nPCOffset;
 
-uint32_t rpn_isReloc(const struct Expression *expr);
+bool rpn_isKnown(const struct Expression *expr);
+bool rpn_isSymbol(const struct Expression *expr);
 void rpn_Symbol(struct Expression *expr, char *tzSym);
 void rpn_Number(struct Expression *expr, uint32_t i);
 void rpn_LOGNOT(struct Expression *expr, const struct Expression *src);
-void rpn_LOGOR(struct Expression *expr, const struct Expression *src1,
-	       const struct Expression *src2);
-void rpn_LOGAND(struct Expression *expr, const struct Expression *src1,
-		const struct Expression *src2);
-void rpn_LOGEQU(struct Expression *expr, const struct Expression *src1,
-		const struct Expression *src2);
-void rpn_LOGGT(struct Expression *expr, const struct Expression *src1,
-	       const struct Expression *src2);
-void rpn_LOGLT(struct Expression *expr, const struct Expression *src1,
-	       const struct Expression *src2);
-void rpn_LOGGE(struct Expression *expr, const struct Expression *src1,
-	       const struct Expression *src2);
-void rpn_LOGLE(struct Expression *expr, const struct Expression *src1,
-	       const struct Expression *src2);
-void rpn_LOGNE(struct Expression *expr, const struct Expression *src1,
-	       const struct Expression *src2);
-void rpn_ADD(struct Expression *expr, const struct Expression *src1,
-	     const struct Expression *src2);
-void rpn_SUB(struct Expression *expr, const struct Expression *src1,
-	     const struct Expression *src2);
-void rpn_XOR(struct Expression *expr, const struct Expression *src1,
-	     const struct Expression *src2);
-void rpn_OR(struct Expression *expr, const struct Expression *src1,
-	    const struct Expression *src2);
-void rpn_AND(struct Expression *expr, const struct Expression *src1,
-	     const struct Expression *src2);
-void rpn_SHL(struct Expression *expr, const struct Expression *src1,
-	     const struct Expression *src2);
-void rpn_SHR(struct Expression *expr, const struct Expression *src1,
-	     const struct Expression *src2);
-void rpn_MUL(struct Expression *expr, const struct Expression *src1,
-	     const struct Expression *src2);
-void rpn_DIV(struct Expression *expr, const struct Expression *src1,
-	     const struct Expression *src2);
-void rpn_MOD(struct Expression *expr, const struct Expression *src1,
-	     const struct Expression *src2);
+void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
+		  const struct Expression *src1,
+		  const struct Expression *src2);
 void rpn_HIGH(struct Expression *expr, const struct Expression *src);
 void rpn_LOW(struct Expression *expr, const struct Expression *src);
 void rpn_UNNEG(struct Expression *expr, const struct Expression *src);
--- a/src/asm/asmy.y
+++ b/src/asm/asmy.y
@@ -18,7 +18,6 @@
 
 #include "asm/asm.h"
 #include "asm/charmap.h"
-#include "asm/constexpr.h"
 #include "asm/fstack.h"
 #include "asm/lexer.h"
 #include "asm/main.h"
@@ -510,11 +509,10 @@
 	char tzString[MAXSTRLEN + 1];
 	struct Expression sVal;
 	int32_t nConstValue;
-	struct ConstExpression sConstExpr;
 }
 
 %type	<sVal>		relocconst
-%type	<sConstExpr>	const
+%type	<nConstValue>	const
 %type	<nConstValue>	uconst
 %type	<nConstValue>	const_3bit
 %type	<sVal>		const_8bit
@@ -966,17 +964,17 @@
 
 equ		: T_LABEL T_POP_EQU const
 		{
-			sym_AddEqu($1, constexpr_GetConstantValue(&$3));
+			sym_AddEqu($1, $3);
 		}
 ;
 
 set		: T_LABEL T_POP_SET const
 		{
-			sym_AddSet($1, constexpr_GetConstantValue(&$3));
+			sym_AddSet($1, $3);
 		}
 		| T_LABEL T_POP_EQUAL const
 		{
-			sym_AddSet($1, constexpr_GetConstantValue(&$3));
+			sym_AddSet($1, $3);
 		}
 ;
 
@@ -1004,12 +1002,10 @@
 
 charmap		: T_POP_CHARMAP string comma const
 		{
-			int32_t value = constexpr_GetConstantValue(&$4);
-
-			if ((value & 0xFF) != value)
+			if (($4 & 0xFF) != $4)
 				warning(WARNING_TRUNCATION, "Expression must be 8-bit");
 
-			if (charmap_Add($2, value & 0xFF) == -1)
+			if (charmap_Add($2, $4 & 0xFF) == -1)
 				yyerror("Error parsing charmap. Either you've added too many (%i), or the input character length is too long (%i)' : %s\n", MAXCHARMAPS, CHARMAPLENGTH, strerror(errno));
 		}
 ;
@@ -1044,19 +1040,19 @@
 
 printv		: T_POP_PRINTV const
 		{
-			printf("$%X", constexpr_GetConstantValue(&$2));
+			printf("$%X", $2);
 		}
 ;
 
 printi		: T_POP_PRINTI const
 		{
-			printf("%d", constexpr_GetConstantValue(&$2));
+			printf("%d", $2);
 		}
 ;
 
 printf		: T_POP_PRINTF const
 		{
-			math_Print(constexpr_GetConstantValue(&$2));
+			math_Print($2);
 		}
 ;
 
@@ -1063,7 +1059,7 @@
 if		: T_POP_IF const
 		{
 			nIFDepth++;
-			if (!constexpr_GetConstantValue(&$2)) {
+			if (!$2) {
 				/*
 				 * Continue parsing after ELSE, or at ELIF or
 				 * ENDC keyword.
@@ -1095,7 +1091,7 @@
 				 */
 				skipElif = true;
 
-				if (!constexpr_GetConstantValue(&$2)) {
+				if (!$2) {
 					/*
 					 * Continue parsing after ELSE, or at
 					 * ELIF or ENDC keyword.
@@ -1127,7 +1123,7 @@
 
 const_3bit	: const
 		{
-			int32_t value = constexpr_GetConstantValue(&$1);
+			int32_t value = $1;
 			if ((value < 0) || (value > 7))
 				yyerror("Immediate value must be 3-bit");
 			else
@@ -1190,7 +1186,7 @@
 
 const_8bit	: relocconst
 		{
-			if( (!rpn_isReloc(&$1)) && (($1.nVal < -128) || ($1.nVal > 255)) )
+			if( (rpn_isKnown(&$1)) && (($1.nVal < -128) || ($1.nVal > 255)) )
 				warning(WARNING_TRUNCATION, "Expression must be 8-bit");
 			$$ = $1;
 		}
@@ -1198,7 +1194,7 @@
 
 const_16bit	: relocconst
 		{
-			if ((!rpn_isReloc(&$1)) && (($1.nVal < -32768) || ($1.nVal > 65535)))
+			if ((rpn_isKnown(&$1)) && (($1.nVal < -32768) || ($1.nVal > 65535)))
 				warning(WARNING_TRUNCATION, "Expression must be 16-bit");
 			$$ = $1;
 		}
@@ -1223,24 +1219,24 @@
 			rpn_Number(&$$, r);
 		}
 		| T_OP_LOGICNOT relocconst %prec NEG	{ rpn_LOGNOT(&$$, &$2); }
-		| relocconst T_OP_LOGICOR relocconst	{ rpn_LOGOR(&$$, &$1, &$3); }
-		| relocconst T_OP_LOGICAND relocconst	{ rpn_LOGAND(&$$, &$1, &$3); }
-		| relocconst T_OP_LOGICEQU relocconst	{ rpn_LOGEQU(&$$, &$1, &$3); }
-		| relocconst T_OP_LOGICGT relocconst	{ rpn_LOGGT(&$$, &$1, &$3); }
-		| relocconst T_OP_LOGICLT relocconst	{ rpn_LOGLT(&$$, &$1, &$3); }
-		| relocconst T_OP_LOGICGE relocconst	{ rpn_LOGGE(&$$, &$1, &$3); }
-		| relocconst T_OP_LOGICLE relocconst	{ rpn_LOGLE(&$$, &$1, &$3); }
-		| relocconst T_OP_LOGICNE relocconst	{ rpn_LOGNE(&$$, &$1, &$3); }
-		| relocconst T_OP_ADD relocconst	{ rpn_ADD(&$$, &$1, &$3); }
-		| relocconst T_OP_SUB relocconst	{ rpn_SUB(&$$, &$1, &$3); }
-		| relocconst T_OP_XOR relocconst	{ rpn_XOR(&$$, &$1, &$3); }
-		| relocconst T_OP_OR relocconst		{ rpn_OR(&$$, &$1, &$3); }
-		| relocconst T_OP_AND relocconst	{ rpn_AND(&$$, &$1, &$3); }
-		| relocconst T_OP_SHL relocconst	{ rpn_SHL(&$$, &$1, &$3); }
-		| relocconst T_OP_SHR relocconst	{ rpn_SHR(&$$, &$1, &$3); }
-		| relocconst T_OP_MUL relocconst	{ rpn_MUL(&$$, &$1, &$3); }
-		| relocconst T_OP_DIV relocconst	{ rpn_DIV(&$$, &$1, &$3); }
-		| relocconst T_OP_MOD relocconst	{ rpn_MOD(&$$, &$1, &$3); }
+		| relocconst T_OP_LOGICOR relocconst	{ rpn_BinaryOp(RPN_LOGOR, &$$, &$1, &$3); }
+		| relocconst T_OP_LOGICAND relocconst	{ rpn_BinaryOp(RPN_LOGAND, &$$, &$1, &$3); }
+		| relocconst T_OP_LOGICEQU relocconst	{ rpn_BinaryOp(RPN_LOGEQ, &$$, &$1, &$3); }
+		| relocconst T_OP_LOGICGT relocconst	{ rpn_BinaryOp(RPN_LOGGT, &$$, &$1, &$3); }
+		| relocconst T_OP_LOGICLT relocconst	{ rpn_BinaryOp(RPN_LOGLT, &$$, &$1, &$3); }
+		| relocconst T_OP_LOGICGE relocconst	{ rpn_BinaryOp(RPN_LOGGE, &$$, &$1, &$3); }
+		| relocconst T_OP_LOGICLE relocconst	{ rpn_BinaryOp(RPN_LOGLE, &$$, &$1, &$3); }
+		| relocconst T_OP_LOGICNE relocconst	{ rpn_BinaryOp(RPN_LOGNE, &$$, &$1, &$3); }
+		| relocconst T_OP_ADD relocconst	{ rpn_BinaryOp(RPN_ADD, &$$, &$1, &$3); }
+		| relocconst T_OP_SUB relocconst	{ rpn_BinaryOp(RPN_SUB, &$$, &$1, &$3); }
+		| relocconst T_OP_XOR relocconst	{ rpn_BinaryOp(RPN_XOR, &$$, &$1, &$3); }
+		| relocconst T_OP_OR relocconst		{ rpn_BinaryOp(RPN_OR, &$$, &$1, &$3); }
+		| relocconst T_OP_AND relocconst	{ rpn_BinaryOp(RPN_AND, &$$, &$1, &$3); }
+		| relocconst T_OP_SHL relocconst	{ rpn_BinaryOp(RPN_SHL, &$$, &$1, &$3); }
+		| relocconst T_OP_SHR relocconst	{ rpn_BinaryOp(RPN_SHR, &$$, &$1, &$3); }
+		| relocconst T_OP_MUL relocconst	{ rpn_BinaryOp(RPN_MUL, &$$, &$1, &$3); }
+		| relocconst T_OP_DIV relocconst	{ rpn_BinaryOp(RPN_DIV, &$$, &$1, &$3); }
+		| relocconst T_OP_MOD relocconst	{ rpn_BinaryOp(RPN_MOD, &$$, &$1, &$3); }
 		| T_OP_ADD relocconst %prec NEG		{ $$ = $2; }
 		| T_OP_SUB relocconst %prec NEG		{ rpn_UNNEG(&$$, &$2); }
 		| T_OP_NOT relocconst %prec NEG		{ rpn_UNNOT(&$$, &$2); }
@@ -1268,57 +1264,57 @@
 		}
 		| T_OP_ROUND '(' const ')'
 		{
-			rpn_Number(&$$, math_Round(constexpr_GetConstantValue(&$3)));
+			rpn_Number(&$$, math_Round($3));
 		}
 		| T_OP_CEIL '(' const ')'
 		{
-			rpn_Number(&$$, math_Ceil(constexpr_GetConstantValue(&$3)));
+			rpn_Number(&$$, math_Ceil($3));
 		}
 		| T_OP_FLOOR '(' const ')'
 		{
-			rpn_Number(&$$, math_Floor(constexpr_GetConstantValue(&$3)));
+			rpn_Number(&$$, math_Floor($3));
 		}
 		| T_OP_FDIV '(' const comma const ')'
 		{
 			rpn_Number(&$$,
-				   math_Div(constexpr_GetConstantValue(&$3),
-					    constexpr_GetConstantValue(&$5)));
+				   math_Div($3,
+					    $5));
 		}
 		| T_OP_FMUL '(' const comma const ')'
 		{
 			rpn_Number(&$$,
-				   math_Mul(constexpr_GetConstantValue(&$3),
-					    constexpr_GetConstantValue(&$5)));
+				   math_Mul($3,
+					    $5));
 		}
 		| T_OP_SIN '(' const ')'
 		{
-			rpn_Number(&$$, math_Sin(constexpr_GetConstantValue(&$3)));
+			rpn_Number(&$$, math_Sin($3));
 		}
 		| T_OP_COS '(' const ')'
 		{
-			rpn_Number(&$$, math_Cos(constexpr_GetConstantValue(&$3)));
+			rpn_Number(&$$, math_Cos($3));
 		}
 		| T_OP_TAN '(' const ')'
 		{
-			rpn_Number(&$$, math_Tan(constexpr_GetConstantValue(&$3)));
+			rpn_Number(&$$, math_Tan($3));
 		}
 		| T_OP_ASIN '(' const ')'
 		{
-			rpn_Number(&$$, math_ASin(constexpr_GetConstantValue(&$3)));
+			rpn_Number(&$$, math_ASin($3));
 		}
 		| T_OP_ACOS '(' const ')'
 		{
-			rpn_Number(&$$, math_ACos(constexpr_GetConstantValue(&$3)));
+			rpn_Number(&$$, math_ACos($3));
 		}
 		| T_OP_ATAN '(' const ')'
 		{
-			rpn_Number(&$$, math_ATan(constexpr_GetConstantValue(&$3)));
+			rpn_Number(&$$, math_ATan($3));
 		}
 		| T_OP_ATAN2 '(' const comma const ')'
 		{
 			rpn_Number(&$$,
-				   math_ATan2(constexpr_GetConstantValue(&$3),
-					      constexpr_GetConstantValue(&$5)));
+				   math_ATan2($3,
+					      $5));
 		}
 		| T_OP_STRCMP '(' string comma string ')'
 		{
@@ -1339,7 +1335,7 @@
 
 uconst		: const
 		{
-			int32_t value = constexpr_GetConstantValue(&$1);
+			int32_t value = $1;
 			if (value < 0)
 				fatalerror("Constant mustn't be negative: %d", value);
 			$$ = value;
@@ -1346,85 +1342,16 @@
 		}
 ;
 
-const		: T_ID					{ constexpr_Symbol(&$$, $1); }
-		| T_NUMBER				{ constexpr_Number(&$$, $1); }
-		| T_OP_HIGH '(' const ')'		{ constexpr_UnaryOp(&$$, $1, &$3); }
-		| T_OP_LOW '(' const ')'		{ constexpr_UnaryOp(&$$, $1, &$3); }
-		| T_OP_BANK '(' T_ID ')'
+const		: relocconst
 		{
-			constexpr_BankSymbol(&$$, $3);
+			if (!rpn_isKnown(&$1)) {
+				yyerror("Expected constant expression: %s",
+					$1.reason);
+				$$ = 0;
+			} else {
+				$$ = $1.nVal;
+			}
 		}
-		| T_OP_BANK '(' string ')'
-		{
-			constexpr_BankSection(&$$, $3);
-		}
-		| string
-		{
-			char *s = $1;
-			int32_t length = charmap_Convert(&s);
-			constexpr_Number(&$$, str2int2(s, length));
-			free(s);
-		}
-		| T_OP_LOGICNOT const %prec NEG		{ constexpr_UnaryOp(&$$, $1, &$2); }
-		| const T_OP_LOGICOR const		{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
-		| const T_OP_LOGICAND const		{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
-		| const T_OP_LOGICEQU const		{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
-		| const T_OP_LOGICGT const		{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
-		| const T_OP_LOGICLT const		{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
-		| const T_OP_LOGICGE const		{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
-		| const T_OP_LOGICLE const		{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
-		| const T_OP_LOGICNE const		{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
-		| const T_OP_ADD const			{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
-		| const T_OP_SUB const			{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
-		| const T_OP_XOR const			{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
-		| const T_OP_OR const			{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
-		| const T_OP_AND const			{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
-		| const T_OP_SHL const			{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
-		| const T_OP_SHR const			{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
-		| const T_OP_MUL const			{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
-		| const T_OP_DIV const			{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
-		| const T_OP_MOD const			{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
-		| T_OP_ADD const %prec NEG		{ constexpr_UnaryOp(&$$, $1, &$2); }
-		| T_OP_SUB const %prec NEG		{ constexpr_UnaryOp(&$$, $1, &$2); }
-		| T_OP_NOT const %prec NEG		{ constexpr_UnaryOp(&$$, $1, &$2); }
-		| T_OP_ROUND '(' const ')'		{ constexpr_UnaryOp(&$$, $1, &$3); }
-		| T_OP_CEIL '(' const ')'		{ constexpr_UnaryOp(&$$, $1, &$3); }
-		| T_OP_FLOOR '(' const ')'		{ constexpr_UnaryOp(&$$, $1, &$3); }
-		| T_OP_FDIV '(' const comma const ')'	{ constexpr_BinaryOp(&$$, $1, &$3, &$5); }
-		| T_OP_FMUL '(' const comma const ')'	{ constexpr_BinaryOp(&$$, $1, &$3, &$5); }
-		| T_OP_SIN '(' const ')'		{ constexpr_UnaryOp(&$$, $1, &$3); }
-		| T_OP_COS '(' const ')'		{ constexpr_UnaryOp(&$$, $1, &$3); }
-		| T_OP_TAN '(' const ')'		{ constexpr_UnaryOp(&$$, $1, &$3); }
-		| T_OP_ASIN '(' const ')'		{ constexpr_UnaryOp(&$$, $1, &$3); }
-		| T_OP_ACOS '(' const ')'		{ constexpr_UnaryOp(&$$, $1, &$3); }
-		| T_OP_ATAN '(' const ')'		{ constexpr_UnaryOp(&$$, $1, &$3); }
-		| T_OP_ATAN2 '(' const comma const ')'	{ constexpr_BinaryOp(&$$, $1, &$3, &$5); }
-		| T_OP_DEF {
-				oDontExpandStrings = true;
-			} '(' T_ID ')'
-		{
-			struct sSymbol const *sym = sym_FindSymbol($4);
-			if (sym && !(sym_IsDefined(sym) && sym->type != SYM_LABEL))
-				yyerror("Label \"%s\" is not a valid argument to DEF",
-					$4);
-			constexpr_Number(&$$, !!sym);
-			oDontExpandStrings = false;
-		}
-		| T_OP_STRCMP '(' string comma string ')'
-		{
-			constexpr_Number(&$$, strcmp($3, $5));
-		}
-		| T_OP_STRIN '(' string comma string ')'
-		{
-			char *p = strstr($3, $5);
-
-			if (p != NULL)
-				constexpr_Number(&$$, p - $3 + 1);
-			else
-				constexpr_Number(&$$, 0);
-		}
-		| T_OP_STRLEN '(' string ')'		{ constexpr_Number(&$$, strlenUTF8($3)); }
-		| '(' const ')'				{ $$ = $2; }
 ;
 
 string		: T_STRING
@@ -1769,7 +1696,7 @@
 		{
 			rpn_CheckHRAM(&$4, &$4);
 
-			if ((!rpn_isReloc(&$4)) && ($4.nVal < 0 || ($4.nVal > 0xFF && $4.nVal < 0xFF00) || $4.nVal > 0xFFFF))
+			if ((rpn_isKnown(&$4)) && ($4.nVal < 0 || ($4.nVal > 0xFF && $4.nVal < 0xFF00) || $4.nVal > 0xFFFF))
 				yyerror("Source address $%x not in $FF00 to $FFFF", $4.nVal);
 
 			out_AbsByte(0xF0);
@@ -1780,7 +1707,7 @@
 		{
 			rpn_CheckHRAM(&$2, &$2);
 
-			if ((!rpn_isReloc(&$2)) && ($2.nVal < 0 || ($2.nVal > 0xFF && $2.nVal < 0xFF00) || $2.nVal > 0xFFFF))
+			if ((rpn_isKnown(&$2)) && ($2.nVal < 0 || ($2.nVal > 0xFF && $2.nVal < 0xFF00) || $2.nVal > 0xFFFF))
 				yyerror("Destination address $%x not in $FF00 to $FFFF", $2.nVal);
 
 			out_AbsByte(0xE0);
@@ -1844,7 +1771,7 @@
 		| T_Z80_LD op_mem_ind comma T_MODE_A
 		{
 			if (CurrentOptions.optimizeloads &&
-			    (!rpn_isReloc(&$2)) && ($2.nVal >= 0xFF00)) {
+			    (rpn_isKnown(&$2)) && ($2.nVal >= 0xFF00)) {
 				out_AbsByte(0xE0);
 				out_AbsByte($2.nVal & 0xFF);
 				rpn_Free(&$2);
@@ -1899,7 +1826,7 @@
 		{
 			if ($2 == REG_A) {
 				if (CurrentOptions.optimizeloads &&
-				    (!rpn_isReloc(&$4)) && ($4.nVal >= 0xFF00)) {
+				    (rpn_isKnown(&$4)) && ($4.nVal >= 0xFF00)) {
 					out_AbsByte(0xF0);
 					out_AbsByte($4.nVal & 0xFF);
 					rpn_Free(&$4);
@@ -2036,13 +1963,14 @@
 
 z80_rst		: T_Z80_RST const_8bit
 		{
-			if (rpn_isReloc(&$2)) {
+			if (!rpn_isKnown(&$2)) {
 				rpn_CheckRST(&$2, &$2);
 				out_RelByte(&$2);
-			} else if (($2.nVal & 0x38) != $2.nVal)
+			} else if (($2.nVal & 0x38) != $2.nVal) {
 				yyerror("Invalid address $%x for RST", $2.nVal);
-			else
+			} else {
 				out_AbsByte(0xC7 | $2.nVal);
+			}
 			rpn_Free(&$2);
 		}
 ;
--- a/src/asm/constexpr.c
+++ /dev/null
@@ -1,298 +1,0 @@
-/*
- * This file is part of RGBDS.
- *
- * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
- *
- * SPDX-License-Identifier: MIT
- */
-
-#include <stdint.h>
-#include <stdio.h>
-#include <string.h>
-
-#include "asm/asm.h"
-#include "asm/constexpr.h"
-#include "asm/lexer.h"
-#include "asm/main.h"
-#include "asm/mymath.h"
-#include "asm/output.h"
-#include "asm/rpn.h"
-#include "asm/symbol.h"
-#include "asm/warning.h"
-
-#include "asmy.h"
-
-void constexpr_Symbol(struct ConstExpression *expr, char *tzSym)
-{
-	struct sSymbol *sym = sym_FindSymbol(tzSym);
-
-	if (!sym) {
-		fatalerror("'%s' not defined", tzSym);
-	} else if (!sym_IsConstant(sym)) {
-		expr->u.pSym = sym;
-		expr->isSym = 1;
-	} else {
-		constexpr_Number(expr, sym_GetConstantValue(tzSym));
-	}
-}
-
-void constexpr_BankSymbol(struct ConstExpression *expr, char *tzSym)
-{
-	constexpr_Number(expr, 0);
-	struct sSymbol *sym = sym_FindSymbol(tzSym);
-
-	if (!sym) {
-		yyerror("BANK argument doesn't exist");
-	} else if (sym == pPCSymbol) {
-		if (pCurrentSection->nBank == -1)
-			yyerror("Current bank is not known yet");
-		else
-			constexpr_Number(expr, pCurrentSection->nBank);
-	} else if (sym->type != SYM_LABEL) {
-		yyerror("BANK argument must be a label");
-	} else if (sym->pSection->nBank == -1) {
-		yyerror("BANK argument's bank is not known yet'");
-	} else {
-		constexpr_Number(expr, sym->pSection->nBank);
-	}
-}
-
-void constexpr_BankSection(struct ConstExpression *expr, char *tzSectionName)
-{
-	constexpr_Number(expr, 0);
-	struct Section *pSection = out_FindSectionByName(tzSectionName);
-
-	if (!pSection)
-		yyerror("Section \"%s\" doesn't exist", tzSectionName);
-	else if (pSection->nBank == -1)
-		yyerror("Section \"%s\"'s bank is not known yet",
-			tzSectionName);
-	else
-		constexpr_Number(expr, pSection->nBank);
-}
-
-void constexpr_Number(struct ConstExpression *expr, int32_t i)
-{
-	expr->u.nVal = i;
-	expr->isSym = 0;
-}
-
-void constexpr_UnaryOp(struct ConstExpression *expr,
-		       int32_t op,
-		       const struct ConstExpression *src)
-{
-	if (src->isSym)
-		fatalerror("Non-constant operand in constant expression");
-
-	int32_t value = src->u.nVal;
-	int32_t result = 0;
-
-	switch (op) {
-	case T_OP_HIGH:
-		result = (value >> 8) & 0xFF;
-		break;
-	case T_OP_LOW:
-		result = value & 0xFF;
-		break;
-	case T_OP_LOGICNOT:
-		result = !value;
-		break;
-	case T_OP_ADD:
-		result = value;
-		break;
-	case T_OP_SUB:
-		result = -(uint32_t)value;
-		break;
-	case T_OP_NOT:
-		result = ~value;
-		break;
-	case T_OP_ROUND:
-		result = math_Round(value);
-		break;
-	case T_OP_CEIL:
-		result = math_Ceil(value);
-		break;
-	case T_OP_FLOOR:
-		result = math_Floor(value);
-		break;
-	case T_OP_SIN:
-		result = math_Sin(value);
-		break;
-	case T_OP_COS:
-		result = math_Cos(value);
-		break;
-	case T_OP_TAN:
-		result = math_Tan(value);
-		break;
-	case T_OP_ASIN:
-		result = math_ASin(value);
-		break;
-	case T_OP_ACOS:
-		result = math_ACos(value);
-		break;
-	case T_OP_ATAN:
-		result = math_ATan(value);
-		break;
-	default:
-		fatalerror("Unknown unary op");
-	}
-
-	constexpr_Number(expr, result);
-}
-
-void constexpr_BinaryOp(struct ConstExpression *expr,
-			int32_t op,
-			const struct ConstExpression *src1,
-			const struct ConstExpression *src2)
-{
-	int32_t value1;
-	int32_t value2;
-	int32_t result = 0;
-
-	if (op == T_OP_SUB && src1->isSym && src2->isSym) {
-		char *symName1 = src1->u.pSym->tzName;
-		char *symName2 = src2->u.pSym->tzName;
-
-		if (!sym_IsRelocDiffDefined(symName1, symName2))
-			fatalerror("'%s - %s' not defined", symName1, symName2);
-		value1 = sym_GetDefinedValue(symName1);
-		value2 = sym_GetDefinedValue(symName2);
-		result = value1 - value2;
-	} else if (src1->isSym || src2->isSym) {
-		fatalerror("Non-constant operand in constant expression");
-	} else {
-		value1 = src1->u.nVal;
-		value2 = src2->u.nVal;
-
-		switch (op) {
-		case T_OP_LOGICOR:
-			result = value1 || value2;
-			break;
-		case T_OP_LOGICAND:
-			result = value1 && value2;
-			break;
-		case T_OP_LOGICEQU:
-			result = value1 == value2;
-			break;
-		case T_OP_LOGICGT:
-			result = value1 > value2;
-			break;
-		case T_OP_LOGICLT:
-			result = value1 < value2;
-			break;
-		case T_OP_LOGICGE:
-			result = value1 >= value2;
-			break;
-		case T_OP_LOGICLE:
-			result = value1 <= value2;
-			break;
-		case T_OP_LOGICNE:
-			result = value1 != value2;
-			break;
-		case T_OP_ADD:
-			result = (uint32_t)value1 + (uint32_t)value2;
-			break;
-		case T_OP_SUB:
-			result = (uint32_t)value1 - (uint32_t)value2;
-			break;
-		case T_OP_XOR:
-			result = value1 ^ value2;
-			break;
-		case T_OP_OR:
-			result = value1 | value2;
-			break;
-		case T_OP_AND:
-			result = value1 & value2;
-			break;
-		case T_OP_SHL:
-		case T_OP_SHR:
-			if (value2 < 0)
-				warning(WARNING_SHIFT_AMOUNT, "Shifting %s by negative amount %d",
-					op == T_OP_SHL ? "left" : "right",
-					value2);
-			if (op == T_OP_SHR) {
-				value2 = -value2; /* Right shift == neg left */
-
-				if (value1 < 0)
-					warning(WARNING_SHIFT, "Shifting negative value %d",
-						value1);
-			}
-
-			if (value2 >= 0) {
-				// Shift left
-				if (value2 >= 32) {
-					warning(WARNING_SHIFT_AMOUNT, "Shifting left by large amount %d",
-						value2);
-					result = 0;
-				} else {
-					/*
-					 * Use unsigned to force a bitwise shift
-					 * Casting back is OK because the types
-					 * implement two's complement behavior
-					 */
-					result = (uint32_t)value1 << value2;
-				}
-			} else {
-				// Shift right
-				value2 = -value2;
-				if (value2 >= 32) {
-					warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %d",
-						value2);
-					result = value1 < 0 ? -1 : 0;
-				} else if (value1 >= 0) {
-					result = value1 >> value2;
-				} else {
-					/*
-					 * The C standard leaves shifting right
-					 * negative values undefined, so use a
-					 * left shift manually sign-extended
-					 */
-					result = (uint32_t)value1 >> value2 |
-						-((uint32_t)1 << (32 - value2));
-				}
-			}
-			break;
-		case T_OP_MUL:
-			result = (uint32_t)value1 * (uint32_t)value2;
-			break;
-		case T_OP_DIV:
-			if (value2 == 0)
-				fatalerror("Division by zero");
-			if (value1 == INT32_MIN && value2 == -1) {
-				warning(WARNING_DIV, "Division of min value by -1");
-				result = INT32_MIN;
-			} else {
-				result = value1 / value2;
-			}
-			break;
-		case T_OP_MOD:
-			if (value2 == 0)
-				fatalerror("Division by zero");
-			if (value1 == INT32_MIN && value2 == -1)
-				result = 0;
-			else
-				result = value1 % value2;
-			break;
-		case T_OP_FDIV:
-			result = math_Div(value1, value2);
-			break;
-		case T_OP_FMUL:
-			result = math_Mul(value1, value2);
-			break;
-		case T_OP_ATAN2:
-			result = math_ATan2(value1, value2);
-			break;
-		default:
-			fatalerror("Unknown binary op");
-		}
-	}
-
-	constexpr_Number(expr, result);
-}
-
-int32_t constexpr_GetConstantValue(struct ConstExpression *expr)
-{
-	if (expr->isSym)
-		fatalerror("Non-constant expression");
-	return expr->u.nVal;
-}
--- a/src/asm/globlex.c
+++ b/src/asm/globlex.c
@@ -14,7 +14,6 @@
 #include <string.h>
 
 #include "asm/asm.h"
-#include "asm/constexpr.h"
 #include "asm/lexer.h"
 #include "asm/main.h"
 #include "asm/rpn.h"
--- a/src/asm/lexer.c
+++ b/src/asm/lexer.c
@@ -15,7 +15,6 @@
 #include <ctype.h>
 
 #include "asm/asm.h"
-#include "asm/constexpr.h"
 #include "asm/fstack.h"
 #include "asm/lexer.h"
 #include "asm/main.h"
--- a/src/asm/output.c
+++ b/src/asm/output.c
@@ -800,7 +800,7 @@
 {
 	checkcodesection();
 	checksectionoverflow(1);
-	if (rpn_isReloc(expr)) {
+	if (!rpn_isKnown(expr)) {
 		pCurrentSection->tData[nPC] = 0;
 		createpatch(PATCHTYPE_BYTE, expr);
 		pCurrentSection->nPC++;
@@ -835,7 +835,7 @@
 {
 	checkcodesection();
 	checksectionoverflow(2);
-	if (rpn_isReloc(expr)) {
+	if (!rpn_isKnown(expr)) {
 		pCurrentSection->tData[nPC] = 0;
 		pCurrentSection->tData[nPC + 1] = 0;
 		createpatch(PATCHTYPE_WORD, expr);
@@ -872,7 +872,7 @@
 {
 	checkcodesection();
 	checksectionoverflow(4);
-	if (rpn_isReloc(expr)) {
+	if (!rpn_isKnown(expr)) {
 		pCurrentSection->tData[nPC] = 0;
 		pCurrentSection->tData[nPC + 1] = 0;
 		pCurrentSection->tData[nPC + 2] = 0;
@@ -895,14 +895,25 @@
 {
 	checkcodesection();
 	checksectionoverflow(1);
+	if (!rpn_isKnown(expr) || pCurrentSection->nOrg == -1) {
+		pCurrentSection->tData[nPC] = 0;
+		createpatch(PATCHTYPE_JR, expr);
+		pCurrentSection->nPC++;
+		nPC++;
+		pPCSymbol->nValue++;
+	} else {
+		/* Target is relative to the byte *after* the operand */
+		uint16_t address = pCurrentSection->nOrg + nPC + 1;
+		/* The offset wraps (jump from ROM to HRAM, for loopexample) */
+		int16_t offset = expr->nVal - address;
 
-	/* Always let the linker calculate the offset. */
-	pCurrentSection->tData[nPC] = 0;
-	createpatch(PATCHTYPE_JR, expr);
-	pCurrentSection->nPC++;
-	nPC++;
-	pPCSymbol->nValue++;
-
+		if (offset < -128 || offset > 127) {
+			yyerror("jr target out of reach (expected -129 < %d < 128)", offset);
+			out_AbsByte(0);
+		} else {
+			out_AbsByte(offset);
+		}
+	}
 	rpn_Free(expr);
 }
 
--- a/src/asm/rpn.c
+++ b/src/asm/rpn.c
@@ -11,6 +11,7 @@
  */
 
 #include <assert.h>
+#include <errno.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <string.h>
@@ -22,56 +23,35 @@
 #include "asm/output.h"
 #include "asm/warning.h"
 
-#include "linkdefs.h"
+/* Makes an expression "not known", also setting its error message */
+#define makeUnknown(expr_, ...) do { \
+	struct Expression *_expr = expr_; \
+	_expr->isKnown = false; \
+	/* If we had `asprintf` this would be great, but alas. */ \
+	_expr->reason = malloc(128); /* Use an initial reasonable size */ \
+	if (!_expr->reason) \
+		fatalerror("Can't allocate err string: %s", strerror(errno)); \
+	int size = snprintf(_expr->reason, 128, __VA_ARGS__); \
+	if (size >= 128) { /* If this wasn't enough, try again */ \
+		_expr->reason = realloc(_expr->reason, size + 1); \
+		sprintf(_expr->reason, __VA_ARGS__); \
+	} \
+} while (0)
 
-void mergetwoexpressions(struct Expression *expr, const struct Expression *src1,
-			 const struct Expression *src2)
+static uint8_t *reserveSpace(struct Expression *expr, uint32_t size)
 {
-	assert(src1->tRPN != NULL && src2->tRPN != NULL);
-
-	if (src1->nRPNLength + src2->nRPNLength > MAXRPNLEN)
-		fatalerror("RPN expression is too large");
-
-	uint32_t len = src1->nRPNLength + src2->nRPNLength;
-
-	expr->nVal = 0;
-	expr->tRPN = src1->tRPN;
-
-	if (src1->nRPNCapacity >= len) {
-		expr->nRPNCapacity = src1->nRPNCapacity;
-	} else {
-		uint32_t cap1 = src1->nRPNCapacity;
-		uint32_t cap2 = src2->nRPNCapacity;
-		uint32_t cap = (cap1 > cap2) ? cap1 : cap2;
-
-		if (len > cap)
-			cap = (cap <= MAXRPNLEN / 2) ? cap * 2 : MAXRPNLEN;
-
-		expr->nRPNCapacity = cap;
-		expr->tRPN = realloc(expr->tRPN, expr->nRPNCapacity);
-		if (expr->tRPN == NULL)
-			fatalerror("No memory for RPN expression");
-	}
-
-	memcpy(expr->tRPN + src1->nRPNLength, src2->tRPN, src2->nRPNLength);
-	free(src2->tRPN);
-
-	expr->nRPNLength = len;
-	expr->nRPNPatchSize = src1->nRPNPatchSize + src2->nRPNPatchSize;
-	expr->nRPNOut = 0;
-	expr->isReloc = src1->isReloc || src2->isReloc;
-}
-
-/*
- * Add a byte to the RPN expression
- */
-void pushbyte(struct Expression *expr, uint8_t b)
-{
-	if (expr->nRPNLength == expr->nRPNCapacity) {
-		if (expr->nRPNCapacity == 0)
-			expr->nRPNCapacity = 256;
-		else if (expr->nRPNCapacity == MAXRPNLEN)
-			fatalerror("RPN expression is too large");
+	/* This assumes the RPN length is always less than the capacity */
+	if (expr->nRPNCapacity - expr->nRPNLength < size) {
+		/* If there isn't enough room to reserve the space, realloc */
+		if (!expr->tRPN)
+			expr->nRPNCapacity = 256; /* Initial size */
+		else if (expr->nRPNCapacity >= MAXRPNLEN)
+			/*
+			 * To avoid generating humongous object files, cap the
+			 * size of RPN expressions
+			 */
+			fatalerror("RPN expression cannot grow larger than %d bytes",
+				   MAXRPNLEN);
 		else if (expr->nRPNCapacity > MAXRPNLEN / 2)
 			expr->nRPNCapacity = MAXRPNLEN;
 		else
@@ -78,11 +58,15 @@
 			expr->nRPNCapacity *= 2;
 		expr->tRPN = realloc(expr->tRPN, expr->nRPNCapacity);
 
-		if (expr->tRPN == NULL)
-			fatalerror("No memory for RPN expression");
+		if (!expr->tRPN)
+			fatalerror("Failed to grow RPN expression: %s",
+				   strerror(errno));
 	}
 
-	expr->tRPN[expr->nRPNLength++] = b;
+	uint8_t *ptr = expr->tRPN + expr->nRPNLength;
+
+	expr->nRPNLength += size;
+	return ptr;
 }
 
 /*
@@ -90,12 +74,14 @@
  */
 void rpn_Init(struct Expression *expr)
 {
+	expr->reason = NULL;
+	expr->isKnown = true;
+	expr->isSymbol = false;
 	expr->tRPN = NULL;
 	expr->nRPNCapacity = 0;
 	expr->nRPNLength = 0;
 	expr->nRPNPatchSize = 0;
 	expr->nRPNOut = 0;
-	expr->isReloc = 0;
 }
 
 /*
@@ -104,6 +90,7 @@
 void rpn_Free(struct Expression *expr)
 {
 	free(expr->tRPN);
+	free(expr->reason);
 	rpn_Init(expr);
 }
 
@@ -119,26 +106,28 @@
 }
 
 /*
- * Determine if the current expression is relocatable
+ * Determine if the current expression is known at assembly time
  */
-uint32_t rpn_isReloc(const struct Expression *expr)
+bool rpn_isKnown(const struct Expression *expr)
 {
-	return expr->isReloc;
+	return expr->isKnown;
 }
 
 /*
+ * Determine if the current expression is a symbol suitable for const diffing
+ */
+bool rpn_isSymbol(const struct Expression *expr)
+{
+	return expr->isSymbol;
+}
+
+/*
  * Add symbols, constants and operators to expression
  */
 void rpn_Number(struct Expression *expr, uint32_t i)
 {
 	rpn_Init(expr);
-	pushbyte(expr, RPN_CONST);
-	pushbyte(expr, i);
-	pushbyte(expr, i >> 8);
-	pushbyte(expr, i >> 16);
-	pushbyte(expr, i >> 24);
 	expr->nVal = i;
-	expr->nRPNPatchSize += 5;
 }
 
 void rpn_Symbol(struct Expression *expr, char *tzSym)
@@ -147,20 +136,28 @@
 
 	if (!sym || !sym_IsConstant(sym)) {
 		rpn_Init(expr);
+		expr->isSymbol = true;
+
 		sym_Ref(tzSym);
-		expr->isReloc = 1;
-		pushbyte(expr, RPN_SYM);
-		while (*tzSym)
-			pushbyte(expr, *tzSym++);
-		pushbyte(expr, 0);
-		expr->nRPNPatchSize += 5;
+		makeUnknown(expr, strcmp(tzSym, "@")
+				      ? "'%s' is not constant at assembly time"
+				      : "PC is not constant at assembly time",
+			    tzSym);
+		expr->nRPNPatchSize += 5; /* 1-byte opcode + 4-byte symbol ID */
 
+		size_t nameLen = strlen(tzSym) + 1; /* Don't forget NUL! */
+		uint8_t *ptr = reserveSpace(expr, nameLen + 1);
+		*ptr++ = RPN_SYM;
+		memcpy(ptr, tzSym, nameLen);
+
 		/* RGBLINK assumes PC is at the byte being computed... */
 		if (sym == pPCSymbol && nPCOffset) {
 			struct Expression pc = *expr, offset;
 
 			rpn_Number(&offset, nPCOffset);
-			rpn_SUB(expr, &pc, &offset);
+			rpn_BinaryOp(RPN_SUB, expr, &pc, &offset);
+			if (!rpn_isKnown(expr))
+				expr->isSymbol = true;
 		}
 	} else {
 		rpn_Number(expr, sym_GetConstantValue(tzSym));
@@ -171,17 +168,13 @@
 {
 	rpn_Init(expr);
 
-	if (pCurrentSection->nBank == -1)
-		/*
-		 * This is not really relocatable, but this makes the assembler
-		 * write this expression as a RPN patch to the object file.
-		 */
-		expr->isReloc = 1;
-	else
+	if (pCurrentSection->nBank == -1) {
+		makeUnknown(expr, "Current section's bank is not known");
+		expr->nRPNPatchSize++;
+		*reserveSpace(expr, 1) = RPN_BANK_SELF;
+	} else {
 		expr->nVal = pCurrentSection->nBank;
-
-	pushbyte(expr, RPN_BANK_SELF);
-	expr->nRPNPatchSize++;
+	}
 }
 
 void rpn_BankSymbol(struct Expression *expr, char *tzSym)
@@ -194,25 +187,26 @@
 		return;
 	}
 
+	rpn_Init(expr);
 	if (sym && sym_IsConstant(sym)) {
 		yyerror("BANK argument must be a relocatable identifier");
 	} else {
-		rpn_Init(expr);
 		sym_Ref(tzSym);
-		pushbyte(expr, RPN_BANK_SYM);
-		for (unsigned int i = 0; tzSym[i]; i++)
-			pushbyte(expr, tzSym[i]);
-		pushbyte(expr, 0);
-		expr->nRPNPatchSize += 5;
-
 		/* If the symbol didn't exist, `sym_Ref` created it */
 		struct sSymbol *pSymbol = sym_FindSymbol(tzSym);
 
-		if (pSymbol->pSection && pSymbol->pSection->nBank != -1)
-			/* Symbol's section is known and bank's fixed */
+		if (pSymbol->pSection && pSymbol->pSection->nBank != -1) {
+			/* Symbol's section is known and bank is fixed */
 			expr->nVal = pSymbol->pSection->nBank;
-		else
-			expr->isReloc = 1;
+		} else {
+			makeUnknown(expr, "\"%s\"'s bank is not known", tzSym);
+			expr->nRPNPatchSize += 5; /* opcode + 4-byte sect ID */
+
+			size_t nameLen = strlen(tzSym) + 1; /* Room for NUL! */
+			uint8_t *ptr = reserveSpace(expr, nameLen + 1);
+			*ptr++ = RPN_BANK_SYM;
+			memcpy(ptr, tzSym, nameLen);
+		}
 	}
 }
 
@@ -222,213 +216,61 @@
 
 	struct Section *pSection = out_FindSectionByName(tzSectionName);
 
-	if (pSection && pSection->nBank != -1)
+	if (pSection && pSection->nBank != -1) {
 		expr->nVal = pSection->nBank;
-	else
-		/*
-		 * This is not really relocatable, but this makes the assembler
-		 * write this expression as a RPN patch to the object file.
-		 */
-		expr->isReloc = 1;
+	} else {
+		makeUnknown(expr, "Section \"%s\"'s bank is not known",
+			    tzSectionName);
 
-	pushbyte(expr, RPN_BANK_SECT);
-	expr->nRPNPatchSize++;
+		size_t nameLen = strlen(tzSectionName) + 1; /* Room for NUL! */
+		uint8_t *ptr = reserveSpace(expr, nameLen + 1);
 
-	while (*tzSectionName) {
-		pushbyte(expr, *tzSectionName++);
-		expr->nRPNPatchSize++;
+		expr->nRPNPatchSize += nameLen + 1;
+		*ptr++ = RPN_BANK_SECT;
+		memcpy(ptr, tzSectionName, nameLen);
 	}
-
-	pushbyte(expr, 0);
-	expr->nRPNPatchSize++;
 }
 
 void rpn_CheckHRAM(struct Expression *expr, const struct Expression *src)
 {
 	*expr = *src;
-	pushbyte(expr, RPN_HRAM);
-	expr->nRPNPatchSize++;
+	expr->isSymbol = false;
+
+	if (rpn_isKnown(expr)) {
+		/* TODO */
+	} else {
+		expr->nRPNPatchSize++;
+		*reserveSpace(expr, 1) = RPN_HRAM;
+	}
 }
 
 void rpn_CheckRST(struct Expression *expr, const struct Expression *src)
 {
 	*expr = *src;
-	pushbyte(expr, RPN_RST);
-	expr->nRPNPatchSize++;
+
+	if (rpn_isKnown(expr)) {
+		/* TODO */
+	} else {
+		expr->nRPNPatchSize++;
+		*reserveSpace(expr, 1) = RPN_RST;
+	}
 }
 
 void rpn_LOGNOT(struct Expression *expr, const struct Expression *src)
 {
 	*expr = *src;
-	expr->nVal = !expr->nVal;
-	pushbyte(expr, RPN_LOGUNNOT);
-	expr->nRPNPatchSize++;
-}
+	expr->isSymbol = false;
 
-void rpn_LOGOR(struct Expression *expr, const struct Expression *src1,
-	       const struct Expression *src2)
-{
-	mergetwoexpressions(expr, src1, src2);
-	expr->nVal = (src1->nVal || src2->nVal);
-	pushbyte(expr, RPN_LOGOR);
-	expr->nRPNPatchSize++;
+	if (rpn_isKnown(expr)) {
+		expr->nVal = !expr->nVal;
+	} else {
+		expr->nRPNPatchSize++;
+		*reserveSpace(expr, 1) = RPN_LOGUNNOT;
+	}
 }
 
-void rpn_LOGAND(struct Expression *expr, const struct Expression *src1,
-		const struct Expression *src2)
-{
-	mergetwoexpressions(expr, src1, src2);
-	expr->nVal = (src1->nVal && src2->nVal);
-	pushbyte(expr, RPN_LOGAND);
-	expr->nRPNPatchSize++;
-}
-
-void rpn_HIGH(struct Expression *expr, const struct Expression *src)
-{
-	*expr = *src;
-
-	expr->nVal = (expr->nVal >> 8) & 0xFF;
-
-	pushbyte(expr, RPN_CONST);
-	pushbyte(expr, 8);
-	pushbyte(expr, 0);
-	pushbyte(expr, 0);
-	pushbyte(expr, 0);
-
-	pushbyte(expr, RPN_SHR);
-
-	pushbyte(expr, RPN_CONST);
-	pushbyte(expr, 0xFF);
-	pushbyte(expr, 0);
-	pushbyte(expr, 0);
-	pushbyte(expr, 0);
-
-	pushbyte(expr, RPN_AND);
-
-	expr->nRPNPatchSize += 12;
-}
-
-void rpn_LOW(struct Expression *expr, const struct Expression *src)
-{
-	*expr = *src;
-
-	expr->nVal = expr->nVal & 0xFF;
-
-	pushbyte(expr, RPN_CONST);
-	pushbyte(expr, 0xFF);
-	pushbyte(expr, 0);
-	pushbyte(expr, 0);
-	pushbyte(expr, 0);
-
-	pushbyte(expr, RPN_AND);
-
-	expr->nRPNPatchSize += 6;
-}
-
-void rpn_LOGEQU(struct Expression *expr, const struct Expression *src1,
-		const struct Expression *src2)
-{
-	mergetwoexpressions(expr, src1, src2);
-	expr->nVal = (src1->nVal == src2->nVal);
-	pushbyte(expr, RPN_LOGEQ);
-	expr->nRPNPatchSize++;
-}
-
-void rpn_LOGGT(struct Expression *expr, const struct Expression *src1,
-	       const struct Expression *src2)
-{
-	mergetwoexpressions(expr, src1, src2);
-	expr->nVal = (src1->nVal > src2->nVal);
-	pushbyte(expr, RPN_LOGGT);
-	expr->nRPNPatchSize++;
-}
-
-void rpn_LOGLT(struct Expression *expr, const struct Expression *src1,
-	       const struct Expression *src2)
-{
-	mergetwoexpressions(expr, src1, src2);
-	expr->nVal = (src1->nVal < src2->nVal);
-	pushbyte(expr, RPN_LOGLT);
-	expr->nRPNPatchSize++;
-}
-
-void rpn_LOGGE(struct Expression *expr, const struct Expression *src1,
-	       const struct Expression *src2)
-{
-	mergetwoexpressions(expr, src1, src2);
-	expr->nVal = (src1->nVal >= src2->nVal);
-	pushbyte(expr, RPN_LOGGE);
-	expr->nRPNPatchSize++;
-}
-
-void rpn_LOGLE(struct Expression *expr, const struct Expression *src1,
-	       const struct Expression *src2)
-{
-	mergetwoexpressions(expr, src1, src2);
-	expr->nVal = (src1->nVal <= src2->nVal);
-	pushbyte(expr, RPN_LOGLE);
-	expr->nRPNPatchSize++;
-}
-
-void rpn_LOGNE(struct Expression *expr, const struct Expression *src1,
-	       const struct Expression *src2)
-{
-	mergetwoexpressions(expr, src1, src2);
-	expr->nVal = (src1->nVal != src2->nVal);
-	pushbyte(expr, RPN_LOGNE);
-	expr->nRPNPatchSize++;
-}
-
-void rpn_ADD(struct Expression *expr, const struct Expression *src1,
-	     const struct Expression *src2)
-{
-	mergetwoexpressions(expr, src1, src2);
-	expr->nVal = ((uint32_t)src1->nVal + (uint32_t)src2->nVal);
-	pushbyte(expr, RPN_ADD);
-	expr->nRPNPatchSize++;
-}
-
-void rpn_SUB(struct Expression *expr, const struct Expression *src1,
-	     const struct Expression *src2)
-{
-	mergetwoexpressions(expr, src1, src2);
-	expr->nVal = ((uint32_t)src1->nVal - (uint32_t)src2->nVal);
-	pushbyte(expr, RPN_SUB);
-	expr->nRPNPatchSize++;
-}
-
-void rpn_XOR(struct Expression *expr, const struct Expression *src1,
-	     const struct Expression *src2)
-{
-	mergetwoexpressions(expr, src1, src2);
-	expr->nVal = (src1->nVal ^ src2->nVal);
-	pushbyte(expr, RPN_XOR);
-	expr->nRPNPatchSize++;
-}
-
-void rpn_OR(struct Expression *expr, const struct Expression *src1,
-	    const struct Expression *src2)
-{
-	mergetwoexpressions(expr, src1, src2);
-	expr->nVal = (src1->nVal | src2->nVal);
-	pushbyte(expr, RPN_OR);
-	expr->nRPNPatchSize++;
-}
-
-void rpn_AND(struct Expression *expr, const struct Expression *src1,
-	     const struct Expression *src2)
-{
-	mergetwoexpressions(expr, src1, src2);
-	expr->nVal = (src1->nVal & src2->nVal);
-	pushbyte(expr, RPN_AND);
-	expr->nRPNPatchSize++;
-}
-
 static int32_t shift(int32_t shiftee, int32_t amount)
 {
-	if (shiftee < 0)
-		warning(WARNING_SHIFT, "Shifting negative value %d", shiftee);
-
 	if (amount >= 0) {
 		// Left shift
 		if (amount >= 32) {
@@ -466,101 +308,247 @@
 	}
 }
 
-void rpn_SHL(struct Expression *expr, const struct Expression *src1,
-	     const struct Expression *src2)
+static struct sSymbol const *symbolOf(struct Expression const *expr)
 {
-	mergetwoexpressions(expr, src1, src2);
+	if (!rpn_isSymbol(expr))
+		return NULL;
+	return sym_FindSymbol((char *)expr->tRPN + 1);
+}
 
-	if (!expr->isReloc) {
-		if (src2->nVal < 0)
-			warning(WARNING_SHIFT_AMOUNT, "Shifting left by negative value: %d",
-				src2->nVal);
+static bool isDiffConstant(struct Expression const *src1,
+			   struct Expression const *src2)
+{
+	/* Check if both expressions only refer to a single symbol */
+	struct sSymbol const *symbol1 = symbolOf(src1);
+	struct sSymbol const *symbol2 = symbolOf(src2);
 
-		expr->nVal = shift(src1->nVal, src2->nVal);
-	}
+	if (!symbol1 || !symbol2
+	 || symbol1->type != SYM_LABEL || symbol2->type != SYM_LABEL)
+		return false;
 
-	pushbyte(expr, RPN_SHL);
-	expr->nRPNPatchSize++;
+	return symbol1->pSection == symbol2->pSection;
 }
 
-void rpn_SHR(struct Expression *expr, const struct Expression *src1,
-	     const struct Expression *src2)
+void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
+		  const struct Expression *src1, const struct Expression *src2)
 {
-	mergetwoexpressions(expr, src1, src2);
+	expr->isSymbol = false;
 
-	if (!expr->isReloc) {
-		if (src2->nVal < 0)
-			warning(WARNING_SHIFT_AMOUNT, "Shifting right by negative value: %d",
-				src2->nVal);
+	/* First, check if the expression is known */
+	expr->isKnown = src1->isKnown && src2->isKnown;
+	if (expr->isKnown) {
+		rpn_Init(expr); /* Init the expression to something sane */
 
-		expr->nVal = shift(src1->nVal, -src2->nVal);
-	}
+		/* If both expressions are known, just compute the value */
+		uint32_t uleft = src1->nVal, uright = src2->nVal;
 
-	pushbyte(expr, RPN_SHR);
-	expr->nRPNPatchSize++;
-}
+		switch (op) {
+		case RPN_LOGOR:
+			expr->nVal = src1->nVal || src2->nVal;
+			break;
+		case RPN_LOGAND:
+			expr->nVal = src1->nVal && src2->nVal;
+			break;
+		case RPN_LOGEQ:
+			expr->nVal = src1->nVal == src2->nVal;
+			break;
+		case RPN_LOGGT:
+			expr->nVal = src1->nVal > src2->nVal;
+			break;
+		case RPN_LOGLT:
+			expr->nVal = src1->nVal < src2->nVal;
+			break;
+		case RPN_LOGGE:
+			expr->nVal = src1->nVal >= src2->nVal;
+			break;
+		case RPN_LOGLE:
+			expr->nVal = src1->nVal <= src2->nVal;
+			break;
+		case RPN_LOGNE:
+			expr->nVal = src1->nVal != src2->nVal;
+			break;
+		case RPN_ADD:
+			expr->nVal = uleft + uright;
+			break;
+		case RPN_SUB:
+			expr->nVal = uleft - uright;
+			break;
+		case RPN_XOR:
+			expr->nVal = src1->nVal ^ src2->nVal;
+			break;
+		case RPN_OR:
+			expr->nVal = src1->nVal | src2->nVal;
+			break;
+		case RPN_AND:
+			expr->nVal = src1->nVal & src2->nVal;
+			break;
+		case RPN_SHL:
+			if (src2->nVal < 0)
+				warning(WARNING_SHIFT_AMOUNT, "Shifting left by negative amount %d",
+					src2->nVal);
 
-void rpn_MUL(struct Expression *expr, const struct Expression *src1,
-	     const struct Expression *src2)
-{
-	mergetwoexpressions(expr, src1, src2);
-	expr->nVal = ((uint32_t)src1->nVal * (uint32_t)src2->nVal);
-	pushbyte(expr, RPN_MUL);
-	expr->nRPNPatchSize++;
-}
+			expr->nVal = shift(src1->nVal, src2->nVal);
+			break;
+		case RPN_SHR:
+			if (src1->nVal < 0)
+				warning(WARNING_SHIFT, "Shifting negative value %d",
+					src1->nVal);
 
-void rpn_DIV(struct Expression *expr, const struct Expression *src1,
-	     const struct Expression *src2)
-{
-	mergetwoexpressions(expr, src1, src2);
+			if (src2->nVal < 0)
+				warning(WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %d",
+					src2->nVal);
 
-	if (!expr->isReloc) {
-		if (src2->nVal == 0)
-			fatalerror("Division by zero");
+			expr->nVal = shift(src1->nVal, -src2->nVal);
+			break;
+		case RPN_MUL:
+			expr->nVal = uleft * uright;
+			break;
+		case RPN_DIV:
+			if (src2->nVal == 0)
+				fatalerror("Division by zero");
 
-		if (src1->nVal == INT32_MIN && src2->nVal == -1) {
-			warning(WARNING_DIV, "Division of min value by -1");
-			expr->nVal = INT32_MIN;
+			if (src1->nVal == INT32_MIN
+			 && src2->nVal == -1) {
+				warning(WARNING_DIV, "Division of min value by -1");
+				expr->nVal = INT32_MIN;
+			} else {
+				expr->nVal = src1->nVal / src2->nVal;
+			}
+			break;
+		case RPN_MOD:
+			if (src2->nVal == 0)
+				fatalerror("Division by zero");
+
+			if (src1->nVal == INT32_MIN && src2->nVal == -1)
+				expr->nVal = 0;
+			else
+				expr->nVal = src1->nVal % src2->nVal;
+			break;
+
+		case RPN_UNSUB:
+		case RPN_UNNOT:
+		case RPN_LOGUNNOT:
+		case RPN_BANK_SYM:
+		case RPN_BANK_SECT:
+		case RPN_BANK_SELF:
+		case RPN_HRAM:
+		case RPN_RST:
+		case RPN_CONST:
+		case RPN_SYM:
+			fatalerror("%d is no binary operator", op);
+		}
+
+	} else if (op == RPN_SUB && isDiffConstant(src1, src2)) {
+		struct sSymbol const *symbol1 = symbolOf(src1);
+		struct sSymbol const *symbol2 = symbolOf(src2);
+
+		expr->nVal = symbol1->nValue - symbol2->nValue;
+		expr->isKnown = true;
+	} else {
+		/* If it's not known, start computing the RPN expression */
+
+		/* Convert the left-hand expression if it's constant */
+		if (src1->isKnown) {
+			uint32_t lval = src1->nVal;
+			uint8_t bytes[] = {RPN_CONST, lval, lval >> 8,
+					   lval >> 16, lval >> 24};
+			expr->nRPNPatchSize = sizeof(bytes);
+			expr->tRPN = NULL;
+			expr->nRPNCapacity = 0;
+			expr->nRPNLength = 0;
+			memcpy(reserveSpace(expr, sizeof(bytes)), bytes,
+			       sizeof(bytes));
+
+			/* Use the other expression's un-const reason */
+			expr->reason = src2->reason;
+			free(src1->reason);
 		} else {
-			expr->nVal = (src1->nVal / src2->nVal);
+			/* Otherwise just reuse its RPN buffer */
+			expr->nRPNPatchSize = src1->nRPNPatchSize;
+			expr->tRPN = src1->tRPN;
+			expr->nRPNCapacity = src1->nRPNCapacity;
+			expr->nRPNLength = src1->nRPNLength;
+			expr->reason = src1->reason;
+			free(src2->reason);
 		}
+
+		/* Now, merge the right expression into the left one */
+		uint8_t *ptr = src2->tRPN; /* Pointer to the right RPN */
+		uint32_t len = src2->nRPNLength; /* Size of the right RPN */
+		uint32_t patchSize = src2->nRPNPatchSize;
+
+		/* If the right expression is constant, merge a shim instead */
+		uint32_t rval = src2->nVal;
+		uint8_t bytes[] = {RPN_CONST, rval, rval >> 8, rval >> 16,
+				   rval >> 24};
+		if (src2->isKnown) {
+			ptr = bytes;
+			len = sizeof(bytes);
+			patchSize = sizeof(bytes);
+		}
+		/* Copy the right RPN and append the operator */
+		uint8_t *buf = reserveSpace(expr, len + 1);
+
+		memcpy(buf, ptr, len);
+		buf[len] = op;
+
+		free(src2->tRPN); /* If there was none, this is `free(NULL)` */
+		expr->nRPNPatchSize += patchSize + 1;
 	}
+}
 
-	pushbyte(expr, RPN_DIV);
-	expr->nRPNPatchSize++;
+void rpn_HIGH(struct Expression *expr, const struct Expression *src)
+{
+	*expr = *src;
+	expr->isSymbol = false;
+
+	if (rpn_isKnown(expr)) {
+		expr->nVal = (uint32_t)expr->nVal >> 8 & 0xFF;
+	} else {
+		uint8_t bytes[] = {RPN_CONST,    8, 0, 0, 0, RPN_SHR,
+				   RPN_CONST, 0xFF, 0, 0, 0, RPN_AND};
+		expr->nRPNPatchSize += sizeof(bytes);
+		memcpy(reserveSpace(expr, sizeof(bytes)), bytes, sizeof(bytes));
+	}
 }
 
-void rpn_MOD(struct Expression *expr, const struct Expression *src1,
-	     const struct Expression *src2)
+void rpn_LOW(struct Expression *expr, const struct Expression *src)
 {
-	mergetwoexpressions(expr, src1, src2);
+	*expr = *src;
+	expr->isSymbol = false;
 
-	if (!expr->isReloc) {
-		if (src2->nVal == 0)
-			fatalerror("Division by zero");
+	if (rpn_isKnown(expr)) {
+		expr->nVal = expr->nVal & 0xFF;
+	} else {
+		uint8_t bytes[] = {RPN_CONST, 0xFF, 0, 0, 0, RPN_AND};
 
-		if (src1->nVal == INT32_MIN && src2->nVal == -1)
-			expr->nVal = 0;
-		else
-			expr->nVal = (src1->nVal % src2->nVal);
+		expr->nRPNPatchSize += sizeof(bytes);
+		memcpy(reserveSpace(expr, sizeof(bytes)), bytes, sizeof(bytes));
 	}
-
-	pushbyte(expr, RPN_MOD);
-	expr->nRPNPatchSize++;
 }
 
 void rpn_UNNEG(struct Expression *expr, const struct Expression *src)
 {
 	*expr = *src;
-	expr->nVal = -(uint32_t)expr->nVal;
-	pushbyte(expr, RPN_UNSUB);
-	expr->nRPNPatchSize++;
+	expr->isSymbol = false;
+
+	if (rpn_isKnown(expr)) {
+		expr->nVal = -(uint32_t)expr->nVal;
+	} else {
+		expr->nRPNPatchSize++;
+		*reserveSpace(expr, 1) = RPN_UNSUB;
+	}
 }
 
 void rpn_UNNOT(struct Expression *expr, const struct Expression *src)
 {
 	*expr = *src;
-	expr->nVal = ~expr->nVal;
-	pushbyte(expr, RPN_UNNOT);
-	expr->nRPNPatchSize++;
+	expr->isSymbol = false;
+
+	if (rpn_isKnown(expr)) {
+		expr->nVal = ~expr->nVal;
+	} else {
+		expr->nRPNPatchSize++;
+		*reserveSpace(expr, 1) = RPN_UNNOT;
+	}
 }
--- a/src/asm/symbol.c
+++ b/src/asm/symbol.c
@@ -269,7 +269,7 @@
 		if (sym_IsConstant(psym))
 			return getvaluefield(psym);
 
-		fatalerror("Expression must have a constant value");
+		fatalerror("\"%s\" does not have a constant value", s);
 	}
 
 	yyerror("'%s' not defined", s);
--- a/test/asm/bank.err
+++ b/test/asm/bank.err
@@ -1,9 +1,9 @@
 ERROR: bank.asm(14) -> bank.asm::def_sect(8):
-    Section "ROMX_bad"'s bank is not known yet
+    Expected constant expression: Section "ROMX_bad"'s bank is not known
 ERROR: bank.asm(16) -> bank.asm::def_sect(8):
-    Section "VRAM_bad"'s bank is not known yet
+    Expected constant expression: Section "VRAM_bad"'s bank is not known
 ERROR: bank.asm(18) -> bank.asm::def_sect(8):
-    Section "SRAM_bad"'s bank is not known yet
+    Expected constant expression: Section "SRAM_bad"'s bank is not known
 ERROR: bank.asm(21) -> bank.asm::def_sect(8):
-    Section "WRAMX_bad"'s bank is not known yet
+    Expected constant expression: Section "WRAMX_bad"'s bank is not known
 error: Assembly aborted (4 errors)!
--- a/test/asm/bracketed-symbols.err
+++ b/test/asm/bracketed-symbols.err
@@ -1,4 +1,4 @@
 ERROR: bracketed-symbols.asm(16):
     Print types are only allowed for numbers
 ERROR: bracketed-symbols.asm(20):
-    Expression must have a constant value
+    "Label" does not have a constant value
--- a/test/asm/label-diff.asm
+++ b/test/asm/label-diff.asm
@@ -31,11 +31,13 @@
 	; Diffing two labels in the same SECTION as well
 	print_diff Known2, Known
 	; Diffing a constant and a "floating" label cannot work
-; ...And that causes a fatal error	print_diff Constant, Known
+	print_diff Constant, Known
 	; Diffing a constant and a ref cannot work
-; ...And that causes a fatal error	print_diff Constant, Unknown
+	print_diff Constant, Unknown
 	; Diffing a floating label and a ref cannot work
-; ...And that causes a fatal error	print_diff Known, Unknown
+	print_diff Known, Unknown
+	; Diffing two refs cannot work
+	print_diff Unknown, Unknown2
 
 	; Now let's fiddle with PC
 SECTION "fixed PC", ROM0[420]
@@ -42,9 +44,9 @@
 	; Diffing a constant and PC should work
 	print_diff Constant, @
 	; Diffing a floating label and PC cannot work
-; ...And that causes a fatal error	print_diff Known, @
+	print_diff Known, @
 	; Diffinf a ref and PC cannot work
-; ...And that causes a fatal error	print_diff Unknown, @
+	print_diff Unknown, @
 	; Diffing PC and PC should work
 	print_diff @, @
 	; Diffing PC and a label from here should work
@@ -53,11 +55,11 @@
 
 SECTION "Floating PC", ROM0
 	; Diffing a constant and PC cannot work
-; ...And that causes a fatal error	print_diff Constant, @
+	print_diff Constant, @
 	; Diffing a floating label and PC cannot work
-; ...And that causes a fatal error	print_diff Known, @
+	print_diff Known, @
 	; Diffinf a ref and PC cannot work
-; ...And that causes a fatal error	print_diff Unknown, @
+	print_diff Unknown, @
 	; Diffing PC and PC should work
 	print_diff @, @
 	; Diffing PC and a label from here should work
--- a/test/asm/label-diff.err
+++ b/test/asm/label-diff.err
@@ -1,0 +1,37 @@
+ERROR: label-diff.asm(34) -> label-diff.asm::print_diff(20):
+    Expected constant expression: 'Known' is not constant at assembly time
+ERROR: label-diff.asm(34) -> label-diff.asm::print_diff(22):
+    Expected constant expression: 'Known' is not constant at assembly time
+ERROR: label-diff.asm(36) -> label-diff.asm::print_diff(20):
+    Expected constant expression: 'Unknown' is not constant at assembly time
+ERROR: label-diff.asm(36) -> label-diff.asm::print_diff(22):
+    Expected constant expression: 'Unknown' is not constant at assembly time
+ERROR: label-diff.asm(38) -> label-diff.asm::print_diff(20):
+    Expected constant expression: 'Known' is not constant at assembly time
+ERROR: label-diff.asm(38) -> label-diff.asm::print_diff(22):
+    Expected constant expression: 'Unknown' is not constant at assembly time
+ERROR: label-diff.asm(40) -> label-diff.asm::print_diff(20):
+    Expected constant expression: 'Unknown' is not constant at assembly time
+ERROR: label-diff.asm(40) -> label-diff.asm::print_diff(22):
+    Expected constant expression: 'Unknown2' is not constant at assembly time
+ERROR: label-diff.asm(47) -> label-diff.asm::print_diff(20):
+    Expected constant expression: 'Known' is not constant at assembly time
+ERROR: label-diff.asm(47) -> label-diff.asm::print_diff(22):
+    Expected constant expression: 'Known' is not constant at assembly time
+ERROR: label-diff.asm(49) -> label-diff.asm::print_diff(20):
+    Expected constant expression: 'Unknown' is not constant at assembly time
+ERROR: label-diff.asm(49) -> label-diff.asm::print_diff(22):
+    Expected constant expression: 'Unknown' is not constant at assembly time
+ERROR: label-diff.asm(58) -> label-diff.asm::print_diff(20):
+    Expected constant expression: PC is not constant at assembly time
+ERROR: label-diff.asm(58) -> label-diff.asm::print_diff(22):
+    Expected constant expression: PC is not constant at assembly time
+ERROR: label-diff.asm(60) -> label-diff.asm::print_diff(20):
+    Expected constant expression: 'Known' is not constant at assembly time
+ERROR: label-diff.asm(60) -> label-diff.asm::print_diff(22):
+    Expected constant expression: PC is not constant at assembly time
+ERROR: label-diff.asm(62) -> label-diff.asm::print_diff(20):
+    Expected constant expression: 'Unknown' is not constant at assembly time
+ERROR: label-diff.asm(62) -> label-diff.asm::print_diff(22):
+    Expected constant expression: PC is not constant at assembly time
+error: Assembly aborted (18 errors)!
--- a/test/asm/label-diff.out
+++ b/test/asm/label-diff.out
@@ -2,8 +2,26 @@
 $1B
 $4
 $FFFFFFFC
+$0
+$0
+$0
+$0
+$0
+$0
+$0
+$0
 $FFFFFE86
 $17A
+$0
+$0
+$0
+$0
+$0
+$0
+$0
+$0
+$0
+$0
 $0
 $0
 $0
--- a/test/asm/overflow.err
+++ b/test/asm/overflow.err
@@ -2,8 +2,6 @@
     Division of min value by -1
 warning: overflow.asm(25): [-Wdiv]
     Division of min value by -1
-warning: overflow.asm(35): [-Wshift]
-    Shifting negative value -1
 warning: overflow.asm(39): [-Wlarge-constant]
     Integer constant '4294967296' is too large
 warning: overflow.asm(42): [-Wlarge-constant]
--- a/test/asm/pc-bank.err
+++ b/test/asm/pc-bank.err
@@ -1,5 +1,5 @@
 ERROR: pc-bank.asm(2):
     Source address $2a00 not in $FF00 to $FFFF
 ERROR: pc-bank.asm(11):
-    Current bank is not known yet
+    Expected constant expression: Current section's bank is not known
 error: Assembly aborted (2 errors)!
--- a/test/asm/shift.err
+++ b/test/asm/shift.err
@@ -6,18 +6,12 @@
     Shifting left by large amount 9001
 warning: shift.asm(14) -> shift.asm::test(6): [-Wshift-amount]
     Shifting left by large amount 9001
-warning: shift.asm(15) -> shift.asm::test(3): [-Wshift]
-    Shifting negative value -1
-warning: shift.asm(16) -> shift.asm::test(3): [-Wshift]
-    Shifting negative value -1
 warning: shift.asm(16) -> shift.asm::test(3): [-Wshift-amount]
     Shifting left by large amount 32
 warning: shift.asm(16) -> shift.asm::test(6): [-Wshift-amount]
     Shifting left by large amount 32
 warning: shift.asm(17) -> shift.asm::test(3): [-Wshift-amount]
-    Shifting left by negative value: -9001
-warning: shift.asm(17) -> shift.asm::test(3): [-Wshift]
-    Shifting negative value -1
+    Shifting left by negative amount -9001
 warning: shift.asm(17) -> shift.asm::test(3): [-Wshift-amount]
     Shifting right by large amount 9001
 warning: shift.asm(17) -> shift.asm::test(6): [-Wshift-amount]
@@ -52,15 +46,15 @@
     Shifting negative value -4
 warning: shift.asm(23) -> shift.asm::test(6): [-Wshift]
     Shifting negative value -4
-warning: shift.asm(24) -> shift.asm::test(3): [-Wshift-amount]
-    Shifting right by negative value: -9001
 warning: shift.asm(24) -> shift.asm::test(3): [-Wshift]
     Shifting negative value -1
 warning: shift.asm(24) -> shift.asm::test(3): [-Wshift-amount]
-    Shifting left by large amount 9001
-warning: shift.asm(24) -> shift.asm::test(6): [-Wshift-amount]
     Shifting right by negative amount -9001
+warning: shift.asm(24) -> shift.asm::test(3): [-Wshift-amount]
+    Shifting left by large amount 9001
 warning: shift.asm(24) -> shift.asm::test(6): [-Wshift]
     Shifting negative value -1
+warning: shift.asm(24) -> shift.asm::test(6): [-Wshift-amount]
+    Shifting right by negative amount -9001
 warning: shift.asm(24) -> shift.asm::test(6): [-Wshift-amount]
     Shifting left by large amount 9001