Compare commits

...

14 Commits
v8.3 ... v8.3.1

Author SHA1 Message Date
473efbe67a tweaks 2022-07-17 22:09:56 +02:00
aeabf0f324 nicer colors 2022-07-17 21:37:15 +02:00
80ab552ad8 fix wrong code for signed word >= 0 2022-07-17 19:02:56 +02:00
7d4695c5b2 cx16: graphics module y resolution corrected from 200 to 240. added 'cx16/circles' example. 2022-07-17 18:59:52 +02:00
5189eaca36 move the vm unit tests to codeGenVirtual module and remove virtualmachine dependency in the compiler module 2022-07-17 12:56:22 +02:00
cfb31377fc c64 zeropage: added a few more locations to Kernalsafe free list that should be safe
this makes $02-$21 inclusive, available for use later (x16 virtual registers are placed here on x16...)
2022-07-17 12:12:47 +02:00
a07c52e112 conv.any2uword / conf.hex2uword can now deal with iso lower and upper case letters as well. 2022-07-17 02:39:40 +02:00
8e1071aa89 fix compiler crashes: txt.chrout("a"), uword[] a = ["ls", subroutine] without & before subroutine. 2022-07-15 23:17:03 +02:00
7cb9a6ba60 diskio.status() more robust (stops at newline char instead of overwriting buffer), diskio.f_open better detects error status 2022-07-15 22:21:34 +02:00
350dc731f1 cx16: sys.reset_system() now resets vera fully as well (such as PSG sound), kernal didn't seem to do that 2022-07-14 23:44:53 +02:00
f690f58bd4 callfar() now accepts a variable as address, so it can be used to indirect JSR to a subroutine whose address is not fixed. ('goto' already could indirect JMP to a variable address.) 2022-07-14 19:29:59 +02:00
4bc65e9ef7 fix stack crash in cx16.push_vera_context() 2022-07-14 16:33:09 +02:00
2d600da8b6 fix codegen crash on certain nested typecast 2022-07-13 22:24:31 +02:00
35af53828a fix endless loop in optimizer, fix cx16 register clobbering in psg interrupt handler, fix crash on certain arrays, fix undefined symbol when it's in another imported module 2022-07-13 18:42:06 +02:00
34 changed files with 339 additions and 126 deletions

View File

@ -29,10 +29,9 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
} else {
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
free.addAll(listOf(
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
0x16, 0x17, 0x18, 0x19, 0x1a,
0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
0x22, 0x23, 0x24, 0x25,
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25,
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
@ -48,8 +47,8 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
if (options.zeropage == ZeropageType.FLOATSAFE) {
// remove the zeropage locations used for floating point operations from the free list
free.removeAll(listOf(
0x22, 0x23, 0x24, 0x25,
0x10, 0x11, 0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
0x03, 0x04, 0x10, 0x11, 0x12,
0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a,
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,

View File

@ -45,8 +45,9 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
removeReservedFromFreePool()
// note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
// however, to be able for the compiler to "see" them as zero page variables, we have to register them here as well.
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
// However, to be able for the compiler to "see" them as zero page variables, we have to register them here as well.
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
for(reg in 0..15) {
allocatedVariables[listOf("cx16", "r${reg}")] = ZpAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables[listOf("cx16", "r${reg}s")] = ZpAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s

View File

@ -139,20 +139,19 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
throw AssemblyError("callfar only works on cx16 target at this time")
val bank = fcall.args[0].constValue(program)?.number?.toInt()
val address = fcall.args[1].constValue(program)?.number?.toInt()
if(bank==null || address==null)
throw AssemblyError("callfar (jsrfar) requires constant arguments")
if(address !in 0xa000..0xbfff)
throw AssemblyError("callfar done on address outside of cx16 banked ram")
if(bank==0)
throw AssemblyError("callfar done on bank 0 which is reserved for the kernal")
val address = fcall.args[1].constValue(program)?.number?.toInt() ?: 0
val argAddrArg = fcall.args[2]
if(bank==null)
throw AssemblyError("callfar (jsrfar) bank has to be a constant")
if(fcall.args[1].constValue(program) == null) {
assignAsmGen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY, false)
asmgen.out(" sta (+)+0 | sty (+)+1 ; store jsrfar address word")
}
if(argAddrArg.constValue(program)?.number == 0.0) {
asmgen.out("""
jsr cx16.jsrfar
.word ${address.toHex()}
+ .word ${address.toHex()}
.byte ${bank.toHex()}""")
} else {
when(argAddrArg) {
@ -162,7 +161,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
asmgen.out("""
lda ${asmgen.asmVariableName(argAddrArg.identifier)}
jsr cx16.jsrfar
.word ${address.toHex()}
+ .word ${address.toHex()}
.byte ${bank.toHex()}
sta ${asmgen.asmVariableName(argAddrArg.identifier)}""")
}
@ -170,7 +169,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
asmgen.out("""
lda ${argAddrArg.number.toHex()}
jsr cx16.jsrfar
.word ${address.toHex()}
+ .word ${address.toHex()}
.byte ${bank.toHex()}
sta ${argAddrArg.number.toHex()}""")
}

View File

@ -70,7 +70,7 @@ internal class ProgramAndVarsGen(
asmgen.out("P8ZP_SCRATCH_REG = ${zp.SCRATCH_REG}")
asmgen.out("P8ZP_SCRATCH_W1 = ${zp.SCRATCH_W1} ; word")
asmgen.out("P8ZP_SCRATCH_W2 = ${zp.SCRATCH_W2} ; word")
asmgen.out(".weak") // hack to allow user to override the following two with command line redefinition:
asmgen.out(".weak") // hack to allow user to override the following two with command line redefinition (however, just use '-esa' command line option instead!)
asmgen.out("P8ESTACK_LO = ${compTarget.machine.ESTACK_LO.toHex()}")
asmgen.out("P8ESTACK_HI = ${compTarget.machine.ESTACK_HI.toHex()}")
asmgen.out(".endweak")

View File

@ -738,10 +738,10 @@ internal class AssignmentAsmGen(private val program: Program,
return
}
if(origTypeCastExpression.type == DataType.UBYTE) {
if(valueDt in WordDatatypes && origTypeCastExpression.type == DataType.UBYTE) {
val parentTc = origTypeCastExpression.parent as? TypecastExpression
if(parentTc!=null && parentTc.type==DataType.UWORD) {
// typecast something to ubyte and directly back to uword
// typecast a word value to ubyte and directly back to uword
// generate code for lsb(value) here instead of the ubyte typecast
return assignCastViaLsbFunc(value, target)
}

View File

@ -3,6 +3,7 @@ plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm"
id "io.kotest" version "0.3.9"
}
java {
@ -31,6 +32,7 @@ dependencies {
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.3.2'
}
sourceSets {
@ -42,6 +44,22 @@ sourceSets {
srcDirs = ["${project.projectDir}/res"]
}
}
test {
java {
srcDir "${project.projectDir}/test"
}
}
}
// note: there are no unit tests in this module!
test {
// Enable JUnit 5 (Gradle 4.6+).
useJUnitPlatform()
// Always run tests, even when nothing changed.
dependsOn 'cleanTest'
// Show test results.
testLogging {
events "skipped", "failed"
}
}

View File

@ -4,11 +4,14 @@
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="module" module-name="codeAst" />
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="virtualmachine" />

View File

@ -279,6 +279,7 @@ class CodeGen(internal val program: PtProgram,
code += addConstMem(loopvarDt, loopvarAddress.toUInt(), step)
code += VmCodeInstruction(Opcode.LOADM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
} else {
// TODO WHY THID DISTINCTION?
code += VmCodeInstruction(Opcode.LOADM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
code += addConstReg(loopvarDt, indexReg, step)
code += VmCodeInstruction(Opcode.STOREM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
@ -318,6 +319,7 @@ class CodeGen(internal val program: PtProgram,
code += addConstMem(loopvarDt, loopvarAddress.toUInt(), step)
code += VmCodeInstruction(Opcode.LOADM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
} else {
// TODO WHY THIS DISTICTION ?
code += VmCodeInstruction(Opcode.LOADM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
code += addConstReg(loopvarDt, indexReg, step)
code += VmCodeInstruction(Opcode.STOREM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
@ -383,13 +385,13 @@ class CodeGen(internal val program: PtProgram,
if(value>0) {
code += VmCodeInstruction(Opcode.LOADM, dt, reg1=valueReg, value=address.toInt())
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=operandReg, value=value)
code += VmCodeInstruction(Opcode.ADDR, dt, reg1 = valueReg, reg2 = operandReg)
code += VmCodeInstruction(Opcode.ADDR, dt, reg1 = valueReg, reg2 = operandReg) // TODO USE ADDM?
code += VmCodeInstruction(Opcode.STOREM, dt, reg1=valueReg, value=address.toInt())
}
else {
code += VmCodeInstruction(Opcode.LOADM, dt, reg1=valueReg, value=address.toInt())
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=operandReg, value=-value)
code += VmCodeInstruction(Opcode.SUBR, dt, reg1 = valueReg, reg2 = operandReg)
code += VmCodeInstruction(Opcode.SUBR, dt, reg1 = valueReg, reg2 = operandReg) // TODO USE ADDM?
code += VmCodeInstruction(Opcode.STOREM, dt, reg1=valueReg, value=address.toInt())
}
}

View File

@ -0,0 +1,19 @@
package prog8tests.vm.helpers
import prog8.code.core.*
internal object DummyMemsizer : IMemSizer {
override fun memorySize(dt: DataType) = 0
override fun memorySize(arrayDt: DataType, numElements: Int) = 0
}
internal object DummyStringEncoder : IStringEncoding {
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
return emptyList()
}
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String {
return ""
}
}

View File

@ -1,5 +1,6 @@
package prog8tests.vm
import io.kotest.assertions.fail
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import prog8.code.SymbolTable
@ -7,8 +8,8 @@ import prog8.code.ast.PtProgram
import prog8.codegen.virtual.*
import prog8.vm.Opcode
import prog8.vm.VmDataType
import prog8tests.helpers.DummyMemsizer
import prog8tests.helpers.DummyStringEncoder
import prog8tests.vm.helpers.DummyMemsizer
import prog8tests.vm.helpers.DummyStringEncoder
class TestVmPeepholeOpt: FunSpec({
fun makeVmProgram(lines: List<VmCodeLine>): Pair<AssemblyProgram, VariableAllocator> {

View File

@ -30,8 +30,9 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
val literal = typecast.expression as? NumericLiteral
if (literal != null) {
val newLiteral = literal.cast(typecast.type)
if (newLiteral.isValid && newLiteral.valueOrZero() !== literal)
mods += IAstModification.ReplaceNode(typecast.expression, newLiteral.valueOrZero(), typecast)
if (newLiteral.isValid && newLiteral.valueOrZero() !== literal) {
mods += IAstModification.ReplaceNode(typecast, newLiteral.valueOrZero(), parent)
}
}
// remove redundant nested typecasts

View File

@ -34,7 +34,6 @@ dependencies {
implementation project(':codeGenCpu6502')
implementation project(':codeGenVirtual')
implementation project(':codeGenExperimental')
implementation project(':virtualmachine')
implementation 'org.antlr:antlr4-runtime:4.10.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
@ -69,7 +68,6 @@ sourceSets {
test {
java {
srcDir "${project.projectDir}/test"
srcDir "${project(':compilerAst').projectDir}/test/helpers"
}
}
}

View File

@ -23,6 +23,5 @@
<orderEntry type="module" module-name="codeGenCpu6502" />
<orderEntry type="module" module-name="codeGenExperimental" />
<orderEntry type="module" module-name="codeGenVirtual" />
<orderEntry type="module" module-name="virtualmachine" />
</component>
</module>

View File

@ -416,6 +416,9 @@ _loop
beq _stop
cmp #7 ; screencode letters A-F are 1-6
bcc _add_letter
and #127
cmp #97
bcs _try_iso ; maybe letter is iso:'a'-iso:'f' (97-102)
cmp #'g'
bcs _stop
cmp #'a'
@ -451,6 +454,11 @@ _add_letter
sta P8ZP_SCRATCH_B1
pla
jmp _calc
_try_iso
cmp #103
bcs _stop
and #63
bne _add_letter
}}
}

View File

@ -2,15 +2,20 @@
%import textio
; Bitmap pixel graphics module for the CommanderX16
; wraps the graphics functions that are in ROM.
; only black/white monochrome 320x200 for now. (i.e. truncated at the bottom)
; For full-screen 640x480 or 320x240 graphics, use the "gfx2" module instead. (but that is Cx16-specific)
; Wraps the graphics functions that are in ROM.
; Only lo-res 320x240 256 color mode for now.
; Unlike graphics module on the C64, you can use colors() to set new drawing colors for every draw operation.
; For other resolutions or other color modes, use the "gfx2" module instead. (which is Cx16-specific)
; Note: there is no color palette manipulation here, you have to do that yourself or use the "palette" module.
graphics {
const uword WIDTH = 320
const ubyte HEIGHT = 200
const ubyte HEIGHT = 240
ubyte stroke_color = 1
ubyte background_color = 0
sub enable_bitmap_mode() {
; enable bitmap screen, erase it and set colors to black/white.
@ -27,10 +32,18 @@ graphics {
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
stroke_color = pixelcolor
background_color = bgcolor
cx16.GRAPH_set_colors(pixelcolor, pixelcolor, bgcolor)
cx16.GRAPH_clear()
}
sub colors(ubyte stroke, ubyte fill) {
; this routine is only available on the cx16, other targets can't change colors on the fly
cx16.GRAPH_set_colors(stroke, fill, background_color)
stroke_color = stroke
}
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
cx16.GRAPH_draw_line(x1, y1, x2, y2)
}
@ -71,31 +84,31 @@ graphics {
cx16.r0 = xcenter + xx
cx16.r1 = ycenter + yy
cx16.FB_cursor_position2()
cx16.FB_set_pixel(1)
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter - xx
cx16.FB_cursor_position2()
cx16.FB_set_pixel(1)
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter + xx
cx16.r1 = ycenter - yy
cx16.FB_cursor_position2()
cx16.FB_set_pixel(1)
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter - xx
cx16.FB_cursor_position2()
cx16.FB_set_pixel(1)
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter + yy
cx16.r1 = ycenter + xx
cx16.FB_cursor_position2()
cx16.FB_set_pixel(1)
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter - yy
cx16.FB_cursor_position2()
cx16.FB_set_pixel(1)
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter + yy
cx16.r1 = ycenter - xx
cx16.FB_cursor_position2()
cx16.FB_set_pixel(1)
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter - yy
cx16.FB_cursor_position2()
cx16.FB_set_pixel(1)
cx16.FB_set_pixel(stroke_color)
yy++
if decisionOver2<=0 {
decisionOver2 += (yy as word)*2+1

View File

@ -90,6 +90,10 @@ psg {
; cx16.r1L = the voice number
; cx16.r2L = attack value
pushw(cx16.r0)
push(cx16.r1L)
push(cx16.r2L)
pushw(cx16.r9)
; calculate new volumes
for cx16.r1L in 0 to 15 {
when envelope_states[cx16.r1L] {
@ -138,6 +142,10 @@ psg {
cx16.VERA_DATA0 = cx16.VERA_DATA1 & %11000000 | msb(envelope_volumes[cx16.r1L])
}
cx16.pop_vera_context()
popw(cx16.r9)
pop(cx16.r2L)
pop(cx16.r1L)
popw(cx16.r0)
}
ubyte[16] envelope_states

View File

@ -735,49 +735,53 @@ IRQ_SCRATCH_ZPWORD2 .word 0
}}
}
inline asmsub push_vera_context() clobbers(A) {
asmsub push_vera_context() clobbers(A) {
; -- use this at the start of your IRQ handler if it uses Vera registers, to save the state
%asm {{
; note cannot store this on cpu hardware stack because this gets called as a subroutine
lda cx16.VERA_ADDR_L
pha
sta _vera_storage
lda cx16.VERA_ADDR_M
pha
sta _vera_storage+1
lda cx16.VERA_ADDR_H
pha
sta _vera_storage+2
lda cx16.VERA_CTRL
pha
sta _vera_storage+3
eor #1
sta cx16.VERA_CTRL
lda cx16.VERA_ADDR_L
pha
sta _vera_storage+4
lda cx16.VERA_ADDR_M
pha
sta _vera_storage+5
lda cx16.VERA_ADDR_H
pha
sta _vera_storage+6
lda cx16.VERA_CTRL
pha
sta _vera_storage+7
rts
_vera_storage: .byte 0,0,0,0,0,0,0,0
}}
}
inline asmsub pop_vera_context() clobbers(A) {
asmsub pop_vera_context() clobbers(A) {
; -- use this at the end of your IRQ handler if it uses Vera registers, to restore the state
%asm {{
pla
lda cx16.push_vera_context._vera_storage+7
sta cx16.VERA_CTRL
pla
lda cx16.push_vera_context._vera_storage+6
sta cx16.VERA_ADDR_H
pla
lda cx16.push_vera_context._vera_storage+5
sta cx16.VERA_ADDR_M
pla
lda cx16.push_vera_context._vera_storage+4
sta cx16.VERA_ADDR_L
pla
lda cx16.push_vera_context._vera_storage+3
sta cx16.VERA_CTRL
pla
lda cx16.push_vera_context._vera_storage+2
sta cx16.VERA_ADDR_H
pla
lda cx16.push_vera_context._vera_storage+1
sta cx16.VERA_ADDR_M
pla
lda cx16.push_vera_context._vera_storage+0
sta cx16.VERA_ADDR_L
rts
}}
}
@ -864,7 +868,9 @@ sys {
; Soft-reset the system back to initial power-on Basic prompt.
%asm {{
sei
stz $01 ; bank the kernal in
stz $01 ; bank the kernal in
lda #$80
sta cx16.VERA_CTRL ; reset Vera (kernal doesn't do this?)
jmp (cx16.RESET_VEC)
}}
}

View File

@ -208,14 +208,16 @@ close_end:
c64.SETLFS(11, drivenumber, 0)
void c64.OPEN() ; open 11,8,0,"filename"
if_cc {
iteration_in_progress = true
have_first_byte = false
void c64.CHKIN(11) ; use #11 as input channel
if_cc {
first_byte = c64.CHRIN() ; read first byte to test for file not found
if not c64.READST() {
have_first_byte = true
return true
if c64.READST()==0 {
iteration_in_progress = true
have_first_byte = false
void c64.CHKIN(11) ; use #11 as input channel
if_cc {
first_byte = c64.CHRIN() ; read first byte to test for file not found
if not c64.READST() {
have_first_byte = true
return true
}
}
}
}
@ -393,11 +395,14 @@ _end rts
goto io_error
while not c64.READST() {
@(messageptr) = c64.CHRIN()
first_byte = c64.CHRIN()
if first_byte=='\r' or first_byte=='\n'
break
@(messageptr) = first_byte
messageptr++
}
@(messageptr) = 0
done:
c64.CLRCHN() ; restore default i/o devices
c64.CLOSE(15)

View File

@ -665,13 +665,10 @@ greaterequalzero_sb .proc
greaterequalzero_sw .proc
lda P8ESTACK_HI+1,x
bmi equalzero_b._false
ora P8ESTACK_LO+1,x
beq equalzero_b._true
bne equalzero_b._false
bpl equalzero_b._true
bmi equalzero_b._false
.pend
memcopy16_up .proc
; -- copy memory UP from (P8ZP_SCRATCH_W1) to (P8ZP_SCRATCH_W2) of length X/Y (16-bit, X=lo, Y=hi)
; clobbers register A,X,Y

View File

@ -251,4 +251,13 @@ sub bin2uword(str string) -> uword {
}
}
sub any2uword(str string) -> uword {
; -- convert any number string (any prefix allowed) to uword.
when string[0] {
'$' -> return hex2uword(string)
'%' -> return bin2uword(string)
else -> return str2uword(string)
}
}
}

View File

@ -1 +1 @@
8.3
8.3.1

View File

@ -799,8 +799,11 @@ internal class AstChecker(private val program: Program,
fun isPassByReferenceElement(e: Expression): Boolean {
if(e is IdentifierReference) {
val decl = e.targetVarDecl(program)!!
return decl.datatype in PassByReferenceDatatypes
val decl = e.targetVarDecl(program)
return if(decl!=null)
decl.datatype in PassByReferenceDatatypes
else
true // is probably a symbol that needs addr-of
}
return e is StringLiteral
}

View File

@ -57,8 +57,10 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
if(mismatch>=0) {
val actual = argtypes[mismatch]
val expected = consideredParamTypes[mismatch]
if(expected==DataType.BOOL && actual==DataType.UBYTE && call.args[mismatch].constValue(program)?.number !in setOf(0.0, 1.0))
return Pair("argument ${mismatch + 1} type mismatch, was: $actual expected: $expected", call.args[mismatch].position)
return if(expected==DataType.BOOL && actual==DataType.UBYTE && call.args[mismatch].constValue(program)?.number in setOf(0.0, 1.0))
null // specifying a 1 or 0 as a BOOL is okay
else
Pair("argument ${mismatch + 1} type mismatch, was: $actual expected: $expected", call.args[mismatch].position)
}
if(target.isAsmSubroutine) {
if(target.asmReturnvaluesRegisters.size>1) {

View File

@ -94,6 +94,7 @@ class TestCompilerOnExamplesCx16: FunSpec({
"amiga",
"bdmusic",
"bobs",
"circles",
"cobramk3-gfx",
"colorbars",
"datetime",

View File

@ -15,6 +15,25 @@ import prog8tests.helpers.compileText
class TestSubroutines: FunSpec({
test("string arg for byte param proper errormessage and subroutineptr in array too") {
val text="""
main {
sub func(ubyte bb) {
bb++
}
sub start() {
func("abc")
uword[] commands = ["abc", func]
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), false, text, writeAssembly = true, errors=errors) shouldBe null
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "type mismatch, was: STR expected: UBYTE"
errors.errors[1] shouldContain "initialisation value has incompatible type"
}
test("stringParameter") {
val text = """
main {

View File

@ -701,11 +701,12 @@ main {
fl = bb as float
bb = fl as byte
uw = fl as uword
uw = 8888 + (bb as ubyte)
}
}
"""
val result = compileText(C64Target(), false, text, writeAssembly = true)!!
result.program.entrypoint.statements.size shouldBe 13
result.program.entrypoint.statements.size shouldBe 15
}
test("invalid typecasts of numbers") {

View File

@ -117,9 +117,9 @@ class TestC64Zeropage: FunSpec({
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target, 999u))
zp1.availableBytes() shouldBe 18
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, c64target, 999u))
zp2.availableBytes() shouldBe 85
zp2.availableBytes() shouldBe 92
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, c64target, 999u))
zp3.availableBytes() shouldBe 125
zp3.availableBytes() shouldBe 134
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target, 999u))
zp4.availableBytes() shouldBe 239
zp4.allocate(listOf("test"), DataType.UBYTE, null, null, errors)

View File

@ -197,6 +197,14 @@ interface INameScope: IStatementContainer, INamedStatement {
else
statementScope = (statementScope as Node).definingScope
}
// still not found, maybe it is a symbol in another module
for(module in (this as Node).definingModule.program.modules) {
val stmt = module.searchSymbol(name)
if(stmt!=null)
return stmt
}
return null
}

View File

@ -757,9 +757,11 @@ class ArrayLiteral(val type: InferredTypes.InferredType, // inferred because
}
}
// otherwise, select the "biggegst" datatype based on the elements in the array.
// otherwise, select the "biggest" datatype based on the elements in the array.
require(value.isNotEmpty()) { "can't determine type of empty array" }
val datatypesInArray = value.map { it.inferType(program) }
require(datatypesInArray.isNotEmpty() && datatypesInArray.all { it.isKnown }) { "can't determine type of empty array" }
if(datatypesInArray.any{ it.isUnknown })
return InferredTypes.InferredType.unknown()
val dts = datatypesInArray.map { it.getOr(DataType.UNDEFINED) }
return when {
DataType.FLOAT in dts -> InferredTypes.InferredType.known(DataType.ARRAY_F)

View File

@ -495,7 +495,9 @@ Breaking out of a loop prematurely is possible with the ``break`` statement.
languages if this is *not* the case! A for loop from ubyte 10 to ubyte 2, for example, will iterate through
all values 10, 11, 12, 13, .... 254, 255, 0 (wrapped), 1, 2. In other languages the entire loop will
be skipped in such cases. But prog8 omits the overhead of an extra loop range check and/or branch for every for loop
by assuming the normal ranges.
because the most common case is that it is not needed.
You should add an explicit range check yourself if the ending value can be less than the start value and
a full wrap-around loop is not what you want!
Conditional Execution
@ -897,14 +899,17 @@ memory(name, size, alignment)
callfar(bank, address, argumentaddress) ; NOTE: specific to cx16 target for now
Calls an assembly routine in another ram-bank on the CommanderX16 (using the ``jsrfar`` routine)
The banked RAM is located in the address range $A000-$BFFF (8 kilobyte).
Notice that bank $00 is used by the Kernal and should not be used by user code.
The banked RAM is located in the address range $A000-$BFFF (8 kilobyte), but you can specify
any address in system ram (why this can be useful is explained at the end of this paragraph)
The third argument can be used to designate the memory address
of an argument for the routine; it will be loaded into the A register and will
receive the result value returned by the routine in the A register. If you leave this at zero,
no argument passing will be done.
If the routine requires different arguments or return values, ``callfar`` cannot be used
and you'll have to set up a call to ``jsrfar`` yourself to process this.
Note: the address can be a variable or other expression, which allows you to use ``callfar`` with bank 0 to do an indirect JSR to a subroutine
whose address can vary (jump table, etc. ``goto`` can do an indirect JMP to a variable address): ``callfar(0, &routine, &argument)``
This is not very efficient though, so maybe you should write a small piece of inline assembly for this instead.
callrom(bank, address, argumentaddress) ; NOTE: specific to cx16 target for now
Calls an assembly routine in another rom-bank on the CommanderX16

View File

@ -753,14 +753,14 @@ You can still ``break`` out of such a loop if you want though.
Conditional Execution and Jumps
-------------------------------
Unconditional jump
^^^^^^^^^^^^^^^^^^
Unconditional jump: goto
^^^^^^^^^^^^^^^^^^^^^^^^
To jump to another part of the program, you use a ``goto`` statement with an addres or the name
of a label or subroutine::
goto $c000 ; address
goto name ; label or subroutine
goto $c000 ; address
goto name ; label or subroutine
uword address = $4000
goto address ; jump via address variable
@ -770,6 +770,8 @@ to another piece of code that eventually returns).
If you jump to an address variable (uword), it is doing an 'indirect' jump: the jump will be done
to the address that's currently in the variable.
Note: to do an indirect *JSR* to a routine with a varying address, you can use the ``callfar`` builtin function
(which is not very efficient) or you have to write a small piece of inline assembly.
Conditional execution

View File

@ -17,33 +17,36 @@ Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^
Compiler:
- on non-cx16 targets: have an option that if zeropage=FULL, moves the cx16 virtual registers to ZP (same location as on x16?)
needs the dynamic base address for the symbols in syslib.p8
also needs a trick to allocate them in ZP like Cx16Zeropage already does
- add item to XZeropage that enables an option that if zeropage=FULL or KERNALSAFE, moves the cx16 virtual registers to ZP, same location as on x16
(can be done on C64 only for now) Remove those addresses from the ZP free pool = allocate them in ZP like Cx16Zeropage does
Adapt the code in AstPreprocessor that relocates the registers as well.
- for uword pointer variables: allow pointer[uword] array indexing >255 , rewrite it to @(pointer+index)
DO NOT allow this for regular array indexing because normal arrays can never exceed size 256
- vm Instruction needs to know what the read-registers/memory are, and what the write-register/memory is.
this info is needed for more advanced optimizations and later code generation steps.
- vm: implement remaining sin/cos functions in math.p8
- vm: find a solution for the cx16.r0..r15 that "overlap" (r0, r0L, r0H etc) but in the vm each get their own separate variable location now
- vm: somehow deal with asmsubs otherwise the vm IR can't fully encode all of prog8
- vm: don't store symbol names in instructions to make optimizing the IR easier? but what about jumps to labels. And it's no longer readable by humans.
- vm: how to remove all unused subroutines? (in the 6502 assembly codegen, we let 64tass solve this for us)
- vm: how to remove all unused subroutines? (the 6502 assembly codegen relies on 64tass solve this for us)
- vm: rather than being able to jump to any 'address' (IPTR), use 'blocks' that have entry and exit points -> even better dead code elimination possible too
- vm: add ore optimizations in VmPeepholeOptimizer
- move the vm unit tests to codeGenVirtual module and remove virtualmachine dependency in the compiler module
- see if we can let for loops skip the loop if end<start, like other programming languages. Without adding a lot of code size/duplicating the loop condition.
this is documented behavior to now loop around but it's too easy to forget about!
Lot of work because of so many special cases in ForLoopsAsmgen.....
How is it for the vm target? -> just 2 special cases in CodeGen.
- when the vm is stable and *if* its language can get promoted to prog8 IL, the variable allocation should be changed.
It's now done before the vm code generation, but the IL should probably not depend on the allocations already performed.
So the CodeGen doesn't do VariableAlloc *before* the codegen, but as a last step.
- generate WASM from the new ast (or from vm code?) to run prog8 on a browser canvas?
- createAssemblyAndAssemble(): make it possible to actually get rid of the VarDecl nodes by fixing the rest of the code mentioned there.
but probably better to rewrite the 6502 codegen on top of the new Ast.
- simplifyConditionalExpression() should not split expression if it still results in stack-based evaluation, but how does it know?
- simplifyConditionalExpression() sometimes introduces needless assignment to r9 tempvar (what scenarios?)
- simplifyConditionalExpression() sometimes introduces needless assignment to r9 tempvar, can we detect & prevent this?
- make it possible to use cpu opcodes such as 'nop' as variable names by prefixing all asm vars with something such as ``p8v_``? Or not worth it (most 3 letter opcodes as variables are nonsensical anyway)
then we can get rid of the instruction lists in the machinedefinitions as well?
- [problematic due to using 64tass:] add a compiler option to not remove unused subroutines. this allows for building library programs. But this won't work with 64tass's .proc ...
Perhaps replace all uses of .proc/.pend by .block/.bend will fix that?
(but we lose the optimizing aspect of the assembler where it strips out unused code.
There's not really a dynamic switch possible as all assembly lib code is static and uses one or the other)
Perhaps replace all uses of .proc/.pend by .block/.bend will fix that with a compiler flag?
But all library code written in asm uses .proc already.....
- Zig-like try-based error handling where the V flag could indicate error condition? and/or BRK to jump into monitor on failure? (has to set BRK vector for that)
- add special (u)word array type (or modifier?) that puts the array into memory as 2 separate byte-arrays 1 for LSB 1 for MSB -> allows for word arrays of length 256 and faster indexing
- ast: don't rewrite by-reference parameter type to uword, but keep the original type (str, array)
@ -59,7 +62,7 @@ Libraries:
- fix the problems in atari target, and flesh out its libraries.
- c64: make the graphics.BITMAP_ADDRESS configurable (VIC banking)
- optimize several inner loops in gfx2 even further?
- add modes 2 and 3 to gfx2 (lowres 4 color and 16 color)?
- add modes 3 and perhaps even 2 to gfx2 (16 color and 4 color)?
- add a flood fill routine to gfx2?
@ -80,9 +83,7 @@ Optimizations:
- various optimizers skip stuff if compTarget.name==VMTarget.NAME. When 6502-codegen is no longer done from
the old CompilerAst, those checks should probably be removed, or be made permanent
- VariableAllocator: can we think of a smarter strategy for allocating variables into zeropage, rather than first-come-first-served
- AssignmentAsmGen.assignExpression() -> improve code gen for assigning boolean comparison expressions
Check what the vm target does here, maybe just do this as part of the vm -> 6502 codegen.
- when a for loop's loopvariable isn't referenced in the body, and the iterations are known, replace the loop by a repeatloop
- when a loopvariable of a forloop isn't referenced in the body, and the iterations are known, replace the loop by a repeatloop
but we have no efficient way right now to see if the body references a variable.
@ -92,7 +93,7 @@ STRUCTS again?
What if we were to re-introduce Structs in prog8? Some thoughts:
- can contain only numeric types (byte,word,float) - no nested structs, no reference types (strings, arrays) inside structs
- is just some syntactic sugar for a scoped set of variables -> ast transform to do exactly this before codegen
- is just some syntactic sugar for a scoped set of variables -> ast transform to do exactly this before codegen. Codegen doesn't know about struct.
- no arrays of struct -- because too slow on 6502 to access those, rather use struct of arrays instead.
can we make this a compiler/codegen only issue? i.e. syntax is just as if it was an array of structs?
or make it explicit in the syntax so that it is clear what the memory layout of it is.

89
examples/cx16/circles.p8 Normal file
View File

@ -0,0 +1,89 @@
%import graphics
; note: this program is tuned for the CX16, but with some minor modifications can run on other systems too.
main {
const ubyte MAX_NUM_CIRCLES = 80
const ubyte GROWTH_RATE = 2
uword[MAX_NUM_CIRCLES] circle_x
uword[MAX_NUM_CIRCLES] circle_y
ubyte[MAX_NUM_CIRCLES] circle_radius
ubyte num_circles = 0
ubyte background_color
sub start() {
graphics.enable_bitmap_mode()
repeat {
background_color = rnd()
graphics.clear_screen(0, background_color)
num_circles = 0
draw_circles()
}
}
sub draw_circles() {
uword @zp x
uword @zp y
ubyte @zp radius
while num_circles<MAX_NUM_CIRCLES {
x = rndw() % graphics.WIDTH
y = rndw() % graphics.HEIGHT
radius = GROWTH_RATE * 2 ; use a bit of a buffer between circles.
if not_colliding() {
radius -= GROWTH_RATE
ubyte color = rnd()
while color==background_color
color = rnd()
graphics.colors(color, 0)
while not_edge() and not_colliding() {
graphics.disc(x, y as ubyte, radius)
sys.waitvsync()
radius += GROWTH_RATE
}
circle_x[num_circles] = x
circle_y[num_circles] = y
circle_radius[num_circles] = radius - GROWTH_RATE
num_circles++
}
}
sub not_colliding() -> bool {
if num_circles==0
return true
ubyte @zp c
for c in 0 to num_circles-1 {
if distance(c) < (radius as uword) + circle_radius[c]
return false
}
return true
}
sub distance(ubyte cix) -> uword {
word dx = x as word - circle_x[cix]
word dy = y as word - circle_y[cix]
uword sqx = dx*dx as uword
uword sqy = dy*dy as uword
return sqrt16(sqx + sqy)
}
; sub distance(ubyte cix) -> uword {
; float dx = x as float - circle_x[cix]
; float dy = y as float - circle_y[cix]
; return floats.sqrt(dx*dx + dy*dy) as uword
; }
sub not_edge() -> bool {
if x as word - radius < 0
return false
if x + radius >= graphics.WIDTH
return false
if y as word - radius < 0
return false
if y + radius >= graphics.HEIGHT
return false
return true
}
}
}

View File

@ -3,20 +3,14 @@
main {
sub start() {
ubyte value1 = %1110
ubyte value2 = %1111
ubyte ci
ubyte from=10
ubyte end=1
bool[2] @shared barr = [true, false]
; if value1 and value2 ; TODO value1 isn't converted to bool in 6502 preprocess
; txt.print("ok")
; else
; txt.print("fail!")
; txt.nl()
if value1 and value2!=255 ; TODO value1 isn't converted to bool in 6502 preprocess
txt.print("ok")
else
txt.print("fail!")
for ci in from to end {
txt.print_ub(ci)
txt.spc()
}
txt.nl()
}
}