shithub: rgbds

Download patch

ref: 6842c831fdedec5deff3f1dc7fcca1465a4a9bd7
parent: 6b903059fe70a7be4b9a1b4d4e05a6765dadac0f
author: Eldred Habert <eldredhabert0@gmail.com>
date: Sat Feb 5 15:17:57 EST 2022

Allow binary AND to be sometimes constant (#976)


--- a/include/asm/section.h
+++ b/include/asm/section.h
@@ -28,7 +28,7 @@
 	uint32_t size;
 	uint32_t org;
 	uint32_t bank;
-	uint8_t align;
+	uint8_t align; // Exactly as specified in `ALIGN[]`
 	uint16_t alignOfs;
 	struct Section *next;
 	struct Patch *patches;
--- a/src/asm/rpn.c
+++ b/src/asm/rpn.c
@@ -317,7 +317,7 @@
 {
 	if (!rpn_isSymbol(expr))
 		return NULL;
-	return sym_FindScopedSymbol((char *)expr->rpn + 1);
+	return sym_FindScopedSymbol((char const *)expr->rpn + 1);
 }
 
 bool rpn_IsDiffConstant(struct Expression const *src, struct Symbol const *sym)
@@ -339,10 +339,51 @@
 	return rpn_IsDiffConstant(src1, rpn_SymbolOf(src2));
 }
 
+/**
+ * Attempts to compute a constant binary AND from non-constant operands
+ * This is possible if one operand is a symbol belonging to an `ALIGN[N]` section, and the other is
+ * a constant that only keeps (some of) the lower N bits.
+ *
+ * @return The constant result if it can be computed, or -1 otherwise.
+ */
+static int32_t tryConstMask(struct Expression const *lhs, struct Expression const *rhs)
+{
+	struct Symbol const *sym = rpn_SymbolOf(lhs);
+	struct Expression const *expr = rhs;
+
+	if (!sym || !sym_GetSection(sym)) {
+		// If the lhs isn't a symbol, try again the other way around
+		sym = rpn_SymbolOf(rhs);
+		expr = lhs;
+
+		if (!sym || !sym_GetSection(sym))
+			return -1;
+	}
+	assert(sym_IsNumeric(sym));
+
+	if (!rpn_isKnown(expr))
+		return -1;
+	// We can now safely use `expr->val`
+	struct Section const *sect = sym_GetSection(sym);
+	int32_t unknownBits = (1 << 16) - (1 << sect->align); // The max alignment is 16
+
+	// The mask must ignore all unknown bits
+	if ((expr->val & unknownBits) != 0)
+		return -1;
+
+	// `sym_GetValue()` attempts to add the section's address,
+	// but that's "-1" because the section is floating (otherwise we wouldn't be here)
+	assert(sect->org == (uint32_t)-1);
+	int32_t symbolOfs = sym_GetValue(sym) + 1;
+
+	return (symbolOfs + sect->alignOfs) & ~unknownBits;
+}
+
 void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
 		  const struct Expression *src1, const struct Expression *src2)
 {
 	expr->isSymbol = false;
+	int32_t constMaskVal;
 
 	/* First, check if the expression is known */
 	expr->isKnown = src1->isKnown && src2->isKnown;
@@ -489,6 +530,9 @@
 		struct Symbol const *symbol2 = rpn_SymbolOf(src2);
 
 		expr->val = sym_GetValue(symbol1) - sym_GetValue(symbol2);
+		expr->isKnown = true;
+	} else if (op == RPN_AND && (constMaskVal = tryConstMask(src1, src2)) != -1) {
+		expr->val = constMaskVal;
 		expr->isKnown = true;
 	} else {
 		/* If it's not known, start computing the RPN expression */
--- /dev/null
+++ b/test/asm/const-and.asm
@@ -1,0 +1,17 @@
+
+	println @ & 0 ; This should produce an error, but not crash
+
+SECTION "Test", ROM0,ALIGN[4,2]
+
+	ds 40
+Aligned:
+
+	println Aligned & $0f
+	println ~$fffc & Aligned
+	println Aligned & $1f ; Not constant
+	println @ & $0f
+
+SECTION "Unaligned", ROM0
+
+	println @ & 0
+	println @ & 1 ; Nope
--- /dev/null
+++ b/test/asm/const-and.err
@@ -1,0 +1,7 @@
+error: const-and.asm(2):
+    PC has no value outside a section
+error: const-and.asm(11):
+    Expected constant expression: 'Aligned' is not constant at assembly time
+error: const-and.asm(17):
+    Expected constant expression: PC is not constant at assembly time
+error: Assembly aborted (3 errors)!
--- /dev/null
+++ b/test/asm/const-and.out
@@ -1,0 +1,7 @@
+$0
+$A
+$A
+$0
+$A
+$0
+$0
--- a/test/link/assert.asm
+++ b/test/link/assert.asm
@@ -1,11 +1,9 @@
 
 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, FloatingBase, "Worry about me, but not too much."
+	assert FAIL, FloatingBase, "Okay, this is getting serious!"
+	assert FATAL, FloatingBase, "It all ends now."
+	assert FAIL, FloatingBase, "Not even time to roll credits!"
 	assert WARN, 0, "Still can finish the film, though!"
--- a/test/link/assert.out
+++ b/test/link/assert.out
@@ -1,4 +1,4 @@
-warning: assert.asm(7): Worry about me, but not too much.
-error: assert.asm(8): Okay, this is getting serious!
-FATAL: assert.asm(9): It all ends now.
+warning: assert.asm(5): Worry about me, but not too much.
+error: assert.asm(6): Okay, this is getting serious!
+FATAL: assert.asm(7): It all ends now.
 Linking aborted after 2 errors