diff --git a/src/ca65/expr.h b/src/ca65/expr.h index b18fabb01..c098aa1e2 100644 --- a/src/ca65/expr.h +++ b/src/ca65/expr.h @@ -172,7 +172,7 @@ ExprNode* FuncHiByte (void); /* Handle the .HIBYTE builtin function */ ExprNode* MakeBoundedExpr (ExprNode* Expr, unsigned Size); -/* Force the given expression into a specific size of ForceRange is true */ +/* Force the given expression into a specific size if ForceRange is true */ ExprNode* BoundedExpr (ExprNode* (*ExprFunc) (void), unsigned Size); /* Parse an expression and force it within a given size if ForceRange is true */ diff --git a/src/ca65/segment.c b/src/ca65/segment.c index fa4e97dd0..26b568711 100644 --- a/src/ca65/segment.c +++ b/src/ca65/segment.c @@ -417,7 +417,7 @@ void SegDone (void) if ((F->Len == 1 && ED.AddrSize > ADDR_SIZE_ZP) || (F->Len == 2 && ED.AddrSize > ADDR_SIZE_ABS) || (F->Len == 3 && ED.AddrSize > ADDR_SIZE_FAR)) { - LIError (&F->LI, "Range error"); + LIError (&F->LI, "Range error (Address size %u does not match fragment size %u)", ED.AddrSize, F->Len); } } diff --git a/src/ca65/studyexpr.c b/src/ca65/studyexpr.c index 2a345a07c..40e9b7aab 100644 --- a/src/ca65/studyexpr.c +++ b/src/ca65/studyexpr.c @@ -43,6 +43,7 @@ /* ca65 */ #include "error.h" +#include "expr.h" #include "segment.h" #include "studyexpr.h" #include "symtab.h" @@ -187,6 +188,70 @@ static void ED_MergeAddrSize (ExprDesc* ED, const ExprDesc* Right) } +static void ED_MergeAddrSizeAND (ExprNode* Expr, ExprDesc* ED, const ExprDesc* Right) +/* Merge the address sizes of two expressions into ED, special case for AND operator */ +{ + int ConstL, ConstR; + int Size, ConstSize; + long Val, ValR; + + if (ED->AddrSize == ADDR_SIZE_DEFAULT) { + /* If ED is valid, ADDR_SIZE_DEFAULT gets always overridden, otherwise + ** it takes precedence over anything else. + */ + if (ED_IsValid (ED)) { + ED->AddrSize = Right->AddrSize; + } + } else if (Right->AddrSize == ADDR_SIZE_DEFAULT) { + /* If Right is valid, ADDR_SIZE_DEFAULT gets always overridden, + ** otherwise it takes precedence over anything else. + */ + if (!ED_IsValid (Right)) { + ED->AddrSize = Right->AddrSize; + } + } else { + /* Neither ED nor Right has a default address size, use the smaller of + ** the two. + */ + if (Right->AddrSize < ED->AddrSize) { + ED->AddrSize = Right->AddrSize; + } + } + /* Check if either side of the expression is constant */ + ConstL = IsConstExpr (Expr->Left, &Val); + ConstR = IsConstExpr (Expr->Right, &ValR); + if (!ConstL && !ConstR) { + /* If neither part of the expression is constant, the above is all we can do */ + return; + } + /* We start assuming the left side is constant, left value is in Val, right + size in Size */ + Size = Right->AddrSize; + /* Now check if the right side is constant, and if so put the right value + into Val and the Left size into Size. */ + if (ConstR) { + Val = ValR; + Size = ED->AddrSize; + } + /* Figure out the size of the constant value */ + if (IsByteRange (Val)) { + ConstSize = ADDR_SIZE_ZP; + } else if (IsWordRange (Val)) { + ConstSize = ADDR_SIZE_ABS; + } else if (IsFarRange (Val)) { + ConstSize = ADDR_SIZE_FAR; + } else { + ConstSize = ADDR_SIZE_LONG; + } + + if (Size == ADDR_SIZE_DEFAULT) { + ED->AddrSize = ConstSize; + } else { + /* use the smaller of the two sizes */ + ED->AddrSize = (ConstSize < Size) ? ConstSize : Size; + } +} + static ED_SymRef* ED_FindSymRef (ExprDesc* ED, SymEntry* Sym) /* Find a symbol reference and return it. Return NULL if the reference does @@ -489,7 +554,11 @@ static void StudyBinaryExpr (ExprNode* Expr, ExprDesc* D) /* Merge references and update address size */ ED_MergeRefs (D, &Right); - ED_MergeAddrSize (D, &Right); + if (Expr->Op == EXPR_AND) { + ED_MergeAddrSizeAND (Expr, D, &Right); + } else { + ED_MergeAddrSize (D, &Right); + } } diff --git a/test/asm/Makefile b/test/asm/Makefile index b35c30c9f..3481dae78 100644 --- a/test/asm/Makefile +++ b/test/asm/Makefile @@ -12,7 +12,7 @@ endif WORKDIR = ../testwrk/asm -SUBDIRS = cpudetect opcodes listing +SUBDIRS = cpudetect opcodes listing val err .PHONY: all continue mostlyclean clean diff --git a/test/asm/err/Makefile b/test/asm/err/Makefile new file mode 100644 index 000000000..6d2430d34 --- /dev/null +++ b/test/asm/err/Makefile @@ -0,0 +1,45 @@ +# Makefile for the tests that MUST NOT compile + +ifneq ($(shell echo),) + CMD_EXE = 1 +endif + +ifdef CMD_EXE + S = $(subst /,\,/) + NOT = - # Hack + NULLDEV = nul: + MKDIR = mkdir $(subst /,\,$1) + RMDIR = -rmdir /s /q $(subst /,\,$1) +else + S = / + NOT = ! + NULLDEV = /dev/null + MKDIR = mkdir -p $1 + RMDIR = $(RM) -r $1 +endif + +ifdef QUIET + .SILENT: + NULLERR = 2>$(NULLDEV) +endif + +CA65 := $(if $(wildcard ../../../bin/ca65*),..$S..$S..$Sbin$Sca65,ca65) + +WORKDIR = ../../../testwrk/asm/err + +.PHONY: all clean + +SOURCES := $(wildcard *.s) +TESTS = $(patsubst %.s,$(WORKDIR)/%.prg,$(SOURCES)) + +all: $(TESTS) + +$(WORKDIR): + $(call MKDIR,$(WORKDIR)) + +$(WORKDIR)/%.prg: %.s | $(WORKDIR) + $(if $(QUIET),echo asm/err/$*.s) + $(NOT) $(CA65) -o $@ $< $(NULLERR) + +clean: + @$(call RMDIR,$(WORKDIR)) diff --git a/test/asm/err/bug1538-1.s b/test/asm/err/bug1538-1.s new file mode 100644 index 000000000..334d19c4c --- /dev/null +++ b/test/asm/err/bug1538-1.s @@ -0,0 +1,3 @@ + + ; this should produce a range error + .byte $1234 & $ffff diff --git a/test/asm/readme.txt b/test/asm/readme.txt index 18354bb47..c3198c12a 100644 --- a/test/asm/readme.txt +++ b/test/asm/readme.txt @@ -17,3 +17,16 @@ Overall tests: -------------- These go into listing/. Refer to listing/readme.txt + +val: +---- + +Works very much like the /val directory used to test the compiler - individual +tests are run in the simulator and should exit with an exit code of 0 when they +pass, or either -1 or a number indicating what part of the test failed on error. + +err: +---- + +Works very much like the /err directory used to test the compiler - individual +tests are assembled and MUST NOT assemble without error. diff --git a/test/asm/val/Makefile b/test/asm/val/Makefile new file mode 100644 index 000000000..91dae9afd --- /dev/null +++ b/test/asm/val/Makefile @@ -0,0 +1,58 @@ +# Makefile for the regression tests that return an error code on failure + +ifneq ($(shell echo),) + CMD_EXE = 1 +endif + +ifdef CMD_EXE + S = $(subst /,\,/) + NULLDEV = nul: + MKDIR = mkdir $(subst /,\,$1) + RMDIR = -rmdir /s /q $(subst /,\,$1) +else + S = / + NULLDEV = /dev/null + MKDIR = mkdir -p $1 + RMDIR = $(RM) -r $1 +endif + +ifdef QUIET + .SILENT: + NULLOUT = >$(NULLDEV) + NULLERR = 2>$(NULLDEV) +endif + +SIM65FLAGS = -x 5000000000 + +CA65 := $(if $(wildcard ../../../bin/ca65*),..$S..$S..$Sbin$Sca65,ca65) +LD65 := $(if $(wildcard ../../../bin/ld65*),..$S..$S..$Sbin$Sld65,ld65) +SIM65 := $(if $(wildcard ../../../bin/sim65*),..$S..$S..$Sbin$Ssim65,sim65) + +WORKDIR = ../../../testwrk/asm/val + +.PHONY: all clean + +SOURCES := $(wildcard *.s) +TESTS = $(SOURCES:%.s=$(WORKDIR)/%.6502.prg) +TESTS += $(SOURCES:%.s=$(WORKDIR)/%.65c02.prg) + +all: $(TESTS) + +$(WORKDIR): + $(call MKDIR,$(WORKDIR)) + +define PRG_template + +$(WORKDIR)/%.$1.prg: %.s | $(WORKDIR) + $(if $(QUIET),echo asm/val/$$*.$1.prg) + $(CA65) -t sim$1 -o $$(@:.prg=.o) $$< $(NULLERR) + $(LD65) -C sim6502-asmtest.cfg -o $$@ $$(@:.prg=.o) sim$1.lib $(NULLERR) + $(SIM65) $(SIM65FLAGS) $$@ $(NULLOUT) + +endef # PRG_template + +$(eval $(call PRG_template,6502)) +$(eval $(call PRG_template,65c02)) + +clean: + @$(call RMDIR,$(WORKDIR)) diff --git a/test/asm/val/bug1538.s b/test/asm/val/bug1538.s new file mode 100644 index 000000000..95aa89e34 --- /dev/null +++ b/test/asm/val/bug1538.s @@ -0,0 +1,122 @@ + + .macpack longbranch + + .import _exit + .export _main + +offset1 = ($123456 + ((.bank(_main)) * $2345)) ; should produce $123456 + ($23 * $2345) = 1706c5 + +_main: + ldx #1 ; test counter + + ;--------------------------------------------------------------------- + ; first check if we can still force an 16bit address although we are + ; ANDing with a 8bit value +test1: +test1a: bit a:$1234 & $ff ; should produce 00 34 + lda test1a+1 + cmp #$34 + jne exiterror + + inx + lda test1a+2 + cmp #$00 + jne exiterror + + inx +test1b: bit a:$34 & $ff ; should produce 00 34 + lda test1b+1 + cmp #$34 + jne exiterror + + inx + lda test1b+2 + cmp #$00 + jne exiterror + + ;--------------------------------------------------------------------- + ; the original bug report, use an expression that contains a non constant + ; part (.bank(_main)) and limit it to 8bit size by ANDing with $ff +test2: + inx + lda #(offset1 >> 0) & $ff + cmp #$c5 + jne exiterror + + inx + lda #(offset1 >> 8) & $ff + cmp #$06 + jne exiterror + + inx + lda #(offset1 >> 16) & $ff + cmp #$17 + jne exiterror + + ;--------------------------------------------------------------------- + ; import a zeropage symbol (1 byte size) and then AND with a constant + ; 16bit value - it should not turn into a 16bit address +test3: + jmp test3chk + .importzp tmp1 +test3a: .byte tmp1 ; verify its 8bit +test3b: .byte tmp1 & $ff ; AND with $ff should work of course +test3c: .byte tmp1 & $ffff ; AND with $ffff should not change size +test3d: .byte tmp1 & $ffffff ; AND with $ffffff should not change size +test3e: .byte tmp1 & $ffffffff ; AND with $ffffffff should not change size +test3f: .word tmp1 & $ffffff ; AND with $ffffff should not change size +test3g: .word tmp1 & $ffffffff ; AND with $ffffffff should not change size +test3chk: + inx + lda test3a + cmp #tmp1 + jne exiterror + + inx + lda test3b + cmp #tmp1 + jne exiterror + + inx + lda test3c + cmp #tmp1 + jne exiterror + + inx + lda test3d + cmp #tmp1 + jne exiterror + + inx + lda test3e + cmp #tmp1 + jne exiterror + + inx + lda test3f + cmp #tmp1 + jne exiterror + + inx + lda test3f+1 + cmp #$00 + jne exiterror + + inx + lda test3g + cmp #tmp1 + jne exiterror + + inx + lda test3g+1 + cmp #$00 + jne exiterror + + ;--------------------------------------------------------------------- + ; exit OK + ldx #0 +exiterror: + txa + ldx #0 + jmp _exit + diff --git a/test/asm/val/sim6502-asmtest.cfg b/test/asm/val/sim6502-asmtest.cfg new file mode 100644 index 000000000..a969fb044 --- /dev/null +++ b/test/asm/val/sim6502-asmtest.cfg @@ -0,0 +1,35 @@ +SYMBOLS { + __EXEHDR__: type = import; + __STACKSIZE__: type = weak, value = $0800; # 2k stack +} +MEMORY { + ZP: file = "", start = $0000, size = $0100; + HEADER: file = %O, start = $0000, size = $000C; + MAIN: file = %O, define = yes, start = $0200, size = $FDF0 - __STACKSIZE__, BANK = $23; +} +SEGMENTS { + ZEROPAGE: load = ZP, type = zp; + EXEHDR: load = HEADER, type = ro; + STARTUP: load = MAIN, type = ro; + LOWCODE: load = MAIN, type = ro, optional = yes; + ONCE: load = MAIN, type = ro, optional = yes; + CODE: load = MAIN, type = ro; + RODATA: load = MAIN, type = ro; + DATA: load = MAIN, type = rw; + BSS: load = MAIN, type = bss, define = yes; +} +FEATURES { + CONDES: type = constructor, + label = __CONSTRUCTOR_TABLE__, + count = __CONSTRUCTOR_COUNT__, + segment = ONCE; + CONDES: type = destructor, + label = __DESTRUCTOR_TABLE__, + count = __DESTRUCTOR_COUNT__, + segment = RODATA; + CONDES: type = interruptor, + label = __INTERRUPTOR_TABLE__, + count = __INTERRUPTOR_COUNT__, + segment = RODATA, + import = __CALLIRQ__; +}