Compare commits

...

8 Commits

Author SHA1 Message Date
Irmen de Jong
866313209b fixed zp vars 0 initialization 2025-07-24 00:17:31 +02:00
Irmen de Jong
e2901cca1b fix virtual diskio save_raw() 2025-07-21 22:10:50 +02:00
Irmen de Jong
dd7adde387 fix virtual diskio.f_write 2025-07-21 20:52:11 +02:00
Irmen de Jong
2f90c53ad0 started changing libs to typed pointers 2025-07-20 23:59:59 +02:00
Irmen de Jong
720988ae72 proper warnings for using pure builtin functions as a statement (discarding the result)
swallow a defer warning for a very common use case
2025-07-18 22:37:07 +02:00
Irmen de Jong
ea5deeefbd new links to Codebase64 website 2025-07-17 23:06:39 +02:00
Irmen de Jong
054c98da7c add link to extra prog8 compilation targets 2025-07-15 00:17:04 +02:00
Irmen de Jong
a4a1b563aa sdks 2025-07-14 21:54:56 +02:00
23 changed files with 256 additions and 57 deletions

View File

@@ -328,10 +328,9 @@ internal class ProgramAndVarsGen(
if (initializers.isNotEmpty()) {
asmgen.out("prog8_init_vars\t.block")
initializers.forEach { assign ->
if((assign.value as? PtNumber)?.number != 0.0 || allocator.isZpVar(assign.target.identifier!!.name))
val constvalue = assign.value as? PtNumber
if(constvalue==null || constvalue.number!=0.0 || allocator.isZpVar(assign.target.identifier!!.name))
asmgen.translate(assign)
else
throw AssemblyError("non-zp variable should not be initialized to zero; it will be zeroed as part of BSS clear")
// the other variables that should be set to zero are done so as part of the BSS section clear.
}
asmgen.out(" rts\n .bend")

View File

@@ -75,7 +75,7 @@ class IRCodeGen(
initsToRemove += block to initialization
}
is PtNumber -> {
require(initValue.number!=0.0) { "variable should not be initialized with 0, it will already be zeroed as part of BSS clear, initializer=$initialization" }
require(initValue.number!=0.0 || variable.zpwish!=ZeropageWish.NOT_IN_ZEROPAGE) {"non-zp variable should not be initialized with 0, it will already be zeroed as part of BSS clear, initializer=$initialization" }
variable.setOnetimeInitNumeric(initValue.number)
initsToRemove += block to initialization
}

View File

@@ -19,7 +19,7 @@ class StatementOptimizer(private val program: Program,
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in functions.purefunctionNames) {
if("ignore_unused" !in parent.definingBlock.options())
errors.info("statement has no effect (function return value is discarded)", functionCallStatement.position)
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
}
}

View File

@@ -376,7 +376,7 @@ hline_filled_right .byte 0, %10000000, %11000000, %11100000, %11110000, %1111
_ormask .byte 128, 64, 32, 16, 8, 4, 2, 1
; note: this can be even faster if we also have a 320 word x-lookup table, but hey, that's a lot of memory.
; see http://codebase64.org/doku.php?id=base:various_techniques_to_calculate_adresses_fast_common_screen_formats_for_pixel_graphics
; see https://codebase64.net/doku.php?id=base:various_techniques_to_calculate_adresses_fast_common_screen_formats_for_pixel_graphics
; the y lookup tables encodes this formula: BITMAP_ADDRESS + 320*(py>>3) + (py & 7) (y from 0..199)
; We use the 64tass syntax for range expressions to calculate this table on assembly time.

View File

@@ -3,7 +3,7 @@
;
; some more interesting routines can be found here:
; http://6502org.wikidot.com/software-math
; http://codebase64.org/doku.php?id=base:6502_6510_maths
; https://codebase64.net/doku.php?id=base:6502_6510_maths
; https://github.com/TobyLobster/multiply_test
; https://github.com/TobyLobster/sqrt_test
@@ -353,7 +353,7 @@ _divisor .word ?
randword .proc
; -- 16 bit pseudo random number generator into AY
; default seed = $00c2 $1137. NOTE: uses self-modifying code so won't work in ROM (use randword_rom instead)
; routine from https://codebase64.org/doku.php?id=base:x_abc_random_number_generator_8_16_bit
; routine from https://codebase64.net/doku.php?id=6502_6510_maths:x_abc_random_number_generator_8_16_bit
inc x1
clc
x1=*+1
@@ -377,7 +377,7 @@ b1=*+1
randword_rom .proc
; -- 16 bit pseudo random number generator into AY. Can run from ROM.
; NOTE: you have to set the initial seed using randseed_rom! (a good default seed = $00c2 $1137)
; routine from https://codebase64.org/doku.php?id=base:x_abc_random_number_generator_8_16_bit
; routine from https://codebase64.net/doku.php?id=6502_6510_maths:x_abc_random_number_generator_8_16_bit
inc _x1
clc
lda _x1

View File

@@ -380,7 +380,7 @@ _quadrant_region_to_direction:
asmsub atan2(ubyte x1 @R0, ubyte y1 @R1, ubyte x2 @R2, ubyte y2 @R3) -> ubyte @A {
;; Calculate the angle, in a 256-degree circle, between two points into A.
;; The points (x1, y1) and (x2, y2) have to use *unsigned coordinates only* from the positive quadrant in the carthesian plane!
;; https://www.codebase64.org/doku.php?id=base:8bit_atan2_8-bit_angle
;; http://codebase64.net/doku.php?id=base:8bit_atan2_8-bit_angle
;; This uses 2 large lookup tables so uses a lot of memory but is super fast.
%asm {{

View File

@@ -138,8 +138,8 @@ diskio {
; you can call this multiple times to append more data
repeat num_bytes {
%ir {{
loadm.w r0,diskio.f_write.bufferpointer
loadi.b r99100,r0
loadm.w r99000,diskio.f_write.bufferpointer
loadi.b r99100,r99000
syscall 55 (r99100.b): r99100.b
storem.b r99100,$ff02
}}
@@ -216,12 +216,12 @@ diskio {
}
; like save() but omits the 2 byte prg header.
sub save_raw(uword filenameptr, uword startaddress, uword savesize) -> bool {
sub save_raw(uword filenameptr, uword start_address, uword savesize) -> bool {
%ir {{
load.b r99100,1
loadm.w r99000,diskio.save.filenameptr
loadm.w r99001,diskio.save.start_address
loadm.w r99002,diskio.save.savesize
loadm.w r99000,diskio.save_raw.filenameptr
loadm.w r99001,diskio.save_raw.start_address
loadm.w r99002,diskio.save_raw.savesize
syscall 42 (r99100.b, r99000.w, r99001.w, r99002.w): r99100.b
returnr.b r99100
}}

View File

@@ -468,7 +468,7 @@ private fun processAst(program: Program, errors: IErrorReporter, compilerOptions
errors.report()
program.constantFold(errors, compilerOptions)
errors.report()
program.reorderStatements(errors)
program.reorderStatements(compilerOptions, errors)
errors.report()
program.desugaring(errors, compilerOptions)
errors.report()

View File

@@ -1470,6 +1470,17 @@ internal class AstChecker(private val program: Program,
}
override fun visit(functionCallStatement: FunctionCallStatement) {
if(functionCallStatement.target.nameInSource.size==1) {
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in program.builtinFunctions.purefunctionNames) {
if("ignore_unused" !in functionCallStatement.parent.definingBlock.options()) {
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
return
}
}
}
// most function calls, even to builtin functions, are still regular FunctionCall nodes here.
// they get converted to the more specialized node type in BeforeAsmTypecastCleaner
val targetStatement = functionCallStatement.target.checkFunctionOrLabelExists(program, functionCallStatement, errors)

View File

@@ -20,8 +20,8 @@ internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: Compila
checker.visit(this)
}
internal fun Program.reorderStatements(errors: IErrorReporter) {
val reorder = StatementReorderer(this, errors)
internal fun Program.reorderStatements(options: CompilationOptions, errors: IErrorReporter) {
val reorder = StatementReorderer(this, options, errors)
reorder.visit(this)
if(errors.noErrors()) {
reorder.applyModifications()

View File

@@ -165,8 +165,11 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
}
}
if(srcAssign.origin == AssignmentOrigin.VARINIT && srcAssign.parent is Block && srcAssign.value.constValue(program)?.number==0.0)
throw FatalAstException("should not have a redundant block-level variable=0 assignment; it will be zeroed as part of BSS clear")
if(srcAssign.origin == AssignmentOrigin.VARINIT && srcAssign.parent is Block && srcAssign.value.constValue(program)?.number==0.0) {
val zeropages = srcAssign.target.targetIdentifiers().mapNotNull { it.targetVarDecl()?.zeropage }
if(zeropages.any {it==ZeropageWish.NOT_IN_ZEROPAGE})
throw FatalAstException("should not have a redundant block-level variable=0 assignment for a non-ZP variable; it will be zeroed as part of BSS clear")
}
val assign = PtAssignment(srcAssign.position, srcAssign.origin==AssignmentOrigin.VARINIT)
val multi = srcAssign.target.multi

View File

@@ -133,7 +133,7 @@ private fun integrateDefers(subdefers: Map<PtSub, List<PtDefer>>, program: PtPro
is PtNumber,
is PtRange,
is PtString -> true
// note that unlike most other times, PtIdentifier IS "complex" this time (it's a variable that might change)
is PtIdentifier -> true // actually PtIdentifier IS "complex" this time (it's a variable that might change) but it's kinda annoying to give a warning message for this very common case
else -> false
}

View File

@@ -5,13 +5,11 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.AssociativeOperators
import prog8.code.core.BaseDataType
import prog8.code.core.DataType
import prog8.code.core.IErrorReporter
import prog8.code.core.*
internal class StatementReorderer(
val program: Program,
val options: CompilationOptions,
val errors: IErrorReporter
) : AstWalker() {
// Reorders the statements in a way the compiler needs.
@@ -114,11 +112,18 @@ internal class StatementReorderer(
}
private fun canSkipInitializationWith0(decl: VarDecl): Boolean {
// if the variable is declared in a block, we can omit the init with 0 because
if(decl.parent is Block) {
// if the variable is declared in a block and is NOT in ZEROPAGE, we can omit the init with 0 because
// the variable will be initialized to zero when the BSS section is cleared as a whole.
if(decl.parent is Block)
if (decl.zeropage == ZeropageWish.NOT_IN_ZEROPAGE)
return true
// block level zp var that is not in zeropage, doesn't have to be cleared (will be done as part of bss clear at startup)
// note: subroutine level var HAS to be cleared because it needs to be zero at every subroutine call!
if (decl.zeropage == ZeropageWish.DONTCARE && options.zeropage == ZeropageType.DONTUSE)
return true
}
// if there is an assignment to the variable below it (regular assign, or For loop),
// and there is nothing important in between, we can skip the initialization.
val statements = (decl.parent as? IStatementContainer)?.statements ?: return false

View File

@@ -5,6 +5,7 @@ import io.kotest.engine.spec.tempdir
import io.kotest.inspectors.shouldForAll
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.Assignment
import prog8.ast.statements.FunctionCallStatement
@@ -13,6 +14,7 @@ import prog8.code.core.BuiltinFunctions
import prog8.code.core.RegisterOrPair
import prog8.code.core.isNumeric
import prog8.code.target.Cx16Target
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
class TestBuiltinFunctions: FunSpec({
@@ -103,5 +105,31 @@ main {
compileText(Cx16Target(), true, src, outputDir, writeAssembly = true) shouldNotBe null
}
test("warning for return value discarding of pure functions") {
val src="""
main {
sub start() {
word @shared ww = 2222
abs(ww)
sgn(ww)
sqrt(ww)
min(ww, 0)
max(ww, 0)
clamp(ww, 0, 319)
}
}"""
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
compileText(Cx16Target(), true, src, outputDir, errors=errors, writeAssembly = false) shouldNotBe null
errors.warnings.size shouldBe 6
errors.warnings[0] shouldContain "statement has no effect"
errors.warnings[1] shouldContain "statement has no effect"
errors.warnings[2] shouldContain "statement has no effect"
errors.warnings[3] shouldContain "statement has no effect"
errors.warnings[4] shouldContain "statement has no effect"
errors.warnings[5] shouldContain "statement has no effect"
}
})

View File

@@ -10,7 +10,10 @@ import prog8.ast.statements.Assignment
import prog8.ast.statements.AssignmentOrigin
import prog8.ast.statements.ForLoop
import prog8.ast.statements.VarDecl
import prog8.code.ast.PtAssignment
import prog8.code.ast.PtNumber
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
@@ -106,6 +109,7 @@ class TestVariables: FunSpec({
test("global var init with array lookup should sometimes be const") {
val src="""
%zeropage dontuse
main {
bool[] barray = [true, false, true, false]
@@ -220,4 +224,85 @@ main {
(st[5] as Assignment).target.identifier?.nameInSource shouldBe listOf("v1")
(st[6] as Assignment).target.identifier?.nameInSource shouldBe listOf("v0")
}
test("nondirty zp variables should be explicitly initialized to 0") {
val src="""
main {
ubyte @shared @requirezp zpvar
ubyte @shared @requirezp @dirty dirtyzpvar
sub start() {
ubyte @shared @requirezp zpvar2
ubyte @shared @requirezp @dirty dirtyzpvar2
}
}"""
val result = compileText(Cx16Target(), false, src, outputDir, writeAssembly = true)!!.codegenAst
val main = result!!.allBlocks().first { it.name=="p8b_main" }
main.children.size shouldBe 4
val zeroassignlobal = main.children.single { it is PtAssignment } as PtAssignment
(zeroassignlobal.value as PtNumber).number shouldBe 0.0
zeroassignlobal.target.identifier!!.name shouldBe "p8b_main.p8v_zpvar"
val st = result.entrypoint()!!.children
st.size shouldBe 4
val zeroassign = st.single { it is PtAssignment } as PtAssignment
(zeroassign.value as PtNumber).number shouldBe 0.0
zeroassign.target.identifier!!.name shouldBe "p8b_main.p8s_start.p8v_zpvar2"
}
test("nondirty non zp variables in block scope should not be explicitly initialized to 0 (bss clear takes care of it)") {
val src="""
%zeropage dontuse
main {
ubyte @shared v1
ubyte @shared @dirty dv1
sub start() {
ubyte @shared v2
ubyte @shared @dirty dv2
}
}"""
val result = compileText(Cx16Target(), false, src, outputDir, writeAssembly = true)!!.codegenAst
// block level should not be intialized to 0 (will be done by BSS clear)
val main = result!!.allBlocks().first { it.name=="p8b_main" }
main.children.size shouldBe 3
main.children.any { it is PtAssignment } shouldBe false
// subroutine should be initialized to 0 because that needs to be done on every call to the subroutine
val st = result.entrypoint()!!.children
st.size shouldBe 4
val zeroassign = st.single { it is PtAssignment } as PtAssignment
(zeroassign.value as PtNumber).number shouldBe 0.0
zeroassign.target.identifier!!.name shouldBe "p8b_main.p8s_start.p8v_v2"
}
test("nondirty explicit non zp variables in block scope should not be explicitly initialized to 0 (bss clear takes care of it)") {
val src="""
main {
ubyte @shared @nozp v1
ubyte @shared @dirty @nozp dv1
sub start() {
ubyte @shared @nozp v2
ubyte @shared @dirty @nozp dv2
}
}"""
val result = compileText(Cx16Target(), false, src, outputDir, writeAssembly = true)!!.codegenAst
// block level should not be intialized to 0 (will be done by BSS clear)
val main = result!!.allBlocks().first { it.name=="p8b_main" }
main.children.size shouldBe 3
main.children.any { it is PtAssignment } shouldBe false
// subroutine should be initialized to 0 because that needs to be done on every call to the subroutine
val st = result.entrypoint()!!.children
st.size shouldBe 4
val zeroassign = st.single { it is PtAssignment } as PtAssignment
(zeroassign.value as PtNumber).number shouldBe 0.0
zeroassign.target.identifier!!.name shouldBe "p8b_main.p8s_start.p8v_v2"
}
})

View File

@@ -5,7 +5,7 @@
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="jdk" jdkName="Python 3.12" jdkType="Python SDK" />
<orderEntry type="jdk" jdkName="Python 3" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -27,7 +27,7 @@ of these library modules automatically as required.
.. note::
Several algorithms and math routines in Prog8's assembly library files are adapted from
code publicly available on https://www.codebase64.org/
code publicly available on https://www.codebase64.net/
.. _builtinfunctions:

View File

@@ -65,6 +65,11 @@ Various things:
and it does it at run time. In this demo a jump table is not only created in the library,
but also in the main program and copied into the library for its use.
`Additional custom compilation targets (such as VIC-20) <https://github.com/gillham/prog8targets>`_
Various custom targets for Prog8 that are not (yet?) part of the Prog8 examples themselves.
These additional compilation targets may be in varying state of completeness.
Perhaps most recognisable at the time of adding this link, are the various VIC-20 targets.
.. image:: _static/curious.png
:align: center

View File

@@ -1,6 +1,10 @@
TODO
====
Since fixing the missing zp-var initialization, programs grew in size again (assem)
Are there any redundant block-level variable initializations to 0 that we can remove in peephole optimization for example?
STRUCTS: are being developed in their own separate branch for now, called "structs".
Idea is to make it feature complete in the IR/Virtual target, then merge it to master?, and then start building the 6502 code generation for it.

View File

@@ -1,27 +1,84 @@
%option no_sysinit
%zeropage kernalsafe
%import textio
%zpallowed 224,255
main {
uword @shared @requirezp zpvar1
uword @shared @requirezp zpvar2
uword @shared @requirezp zpvar3
uword @shared @requirezp zpvar4
uword @shared @requirezp zpvar5
uword @shared @requirezp @dirty dzpvar1
uword @shared @requirezp @dirty dzpvar2
uword @shared @requirezp @dirty dzpvar3
uword @shared @requirezp @dirty dzpvar4
uword @shared @requirezp @dirty dzpvar5
uword @shared @nozp var1
uword @shared @nozp var2
uword @shared @nozp var3
uword @shared @nozp var4
uword @shared @nozp var5
uword @shared @nozp @dirty dvar1
uword @shared @nozp @dirty dvar2
uword @shared @nozp @dirty dvar3
uword @shared @nozp @dirty dvar4
uword @shared @nozp @dirty dvar5
sub start() {
uword uw = 9999
word sw = -2222
ubyte ub = 42
byte sb = -99
bool bb = true
txt.print("address start of zpvars: ")
txt.print_uw(&zpvar1)
txt.nl()
txt.print("address start of normal vars: ")
txt.print_uw(&var1)
txt.nl()
cx16.r0 = uw
cx16.r0s = sw
cx16.r0L = ub
cx16.r0H = ub
cx16.r0sL = sb
cx16.r0sH = sb
cx16.r0bL = bb
cx16.r0bH = bb
txt.print("non-dirty zp should all be 0: ")
txt.print_uw(zpvar1)
txt.spc()
txt.print_uw(zpvar2)
txt.spc()
txt.print_uw(zpvar3)
txt.spc()
txt.print_uw(zpvar4)
txt.spc()
txt.print_uw(zpvar5)
txt.nl()
txt.print("non-dirty should all be 0: ")
txt.print_uw(var1)
txt.spc()
txt.print_uw(var2)
txt.spc()
txt.print_uw(var3)
txt.spc()
txt.print_uw(var4)
txt.spc()
txt.print_uw(var5)
txt.nl()
uw = cx16.r0
sw = cx16.r0s
ub = cx16.r0L
ub = cx16.r0H
sb = cx16.r0sL
sb = cx16.r0sH
bb = cx16.r0bL
bb = cx16.r0bH
txt.print("dirty zp may be random: ")
txt.print_uw(dzpvar1)
txt.spc()
txt.print_uw(dzpvar2)
txt.spc()
txt.print_uw(dzpvar3)
txt.spc()
txt.print_uw(dzpvar4)
txt.spc()
txt.print_uw(dzpvar5)
txt.nl()
txt.print("dirty may be random: ")
txt.print_uw(dvar1)
txt.spc()
txt.print_uw(dvar2)
txt.spc()
txt.print_uw(dvar3)
txt.spc()
txt.print_uw(dvar4)
txt.spc()
txt.print_uw(dvar5)
txt.nl()
repeat {}
}
}

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View File

@@ -11,8 +11,10 @@ Intermediate Representation instructions for the IR Virtual machine.
Specs of the virtual machine this will run on:
Program to execute is not stored in the system memory, it's just a separate list of instructions.
65536 virtual registers, 16 bits wide, can also be used as 8 bits. r0-r65535
65536 virtual floating point registers (64 bits double precision) fr0-fr65535
100K virtual registers, 16 bits wide, can also be used as 8 bits. r0-r99999
reserved 99000 - 99099 : WORD registers for syscall arguments and response value(s)
reserved 99100 - 99199 : BYTE registers for syscall arguments and response value(s)
100K virtual floating point registers (64 bits double precision) fr0-fr99999
65536 bytes of memory. Thus memory pointers (addresses) are limited to 16 bits.
Value stack, max 128 entries of 1 byte each.
Status flags: Carry, Zero, Negative. NOTE: status flags are only affected by the CMP instruction or explicit CLC/SEC,

View File

@@ -200,7 +200,7 @@ class StStaticVariable(name: String,
// Certain codegens might want to put them back into the variable directly.
// For strings and arrays this doesn't occur - these are always already specced at creation time.
require(number!=0.0) { "variable should not be initialized with 0, it will already be zeroed as part of BSS clear" }
require(number!=0.0 || zpwish!=ZeropageWish.NOT_IN_ZEROPAGE) { "non-zp variable should not be initialized with 0, it will already be zeroed as part of BSS clear" }
initializationNumericValue = number
}