Compare commits

..
29 Commits
Author SHA1 Message Date
Irmen de Jong 8f8d99e3ed updated symboldumps 2025-03-07 23:58:06 +01:00
Irmen de Jong 23474360ec fix ncompare calls 2025-03-07 23:56:13 +01:00
gillham 81c255c450 Add strings.ncompare (and backing strncmp_mem) to compare up to n characters of a string. (#164)
* Add strings.ncompare (and backing strncmp_mem) to compare up to n characters of a string.

* Document strings.ncompare.
2025-03-07 23:53:56 +01:00
Irmen de Jong ef23d52ed7 better error if module name clash occurs caused by case-insensitive names 2025-03-07 21:35:35 +01:00
Irmen de Jong 220ab773aa fix asmgen error when assigning address of split word array without explicit adressof syntax 2025-03-06 23:20:42 +01:00
Irmen de Jong e3e5bff7bb add missing sprites benchmark module 2025-03-06 19:39:55 +01:00
Irmen de Jong 7b9a841b2a fix C64 graphics.plot() it was broken since 10.5 2025-03-06 00:42:17 +01:00
Irmen de Jong 40423911ef added footgun warning when calling labels as subroutine 2025-03-04 22:14:21 +01:00
Irmen de Jong 582a70b046 fix calling label as subroutine (JSR label) 2025-03-04 21:53:53 +01:00
Irmen de Jong 5b63590ebf fix symbol prefixing bug triggered by certain usage of %option no_symbol_prefixing 2025-03-03 22:26:19 +01:00
Irmen de Jong 2b6510dc19 improve error message for non-numeric when choice values 2025-03-03 19:48:45 +01:00
Irmen de Jong 9d49589d73 fix codegen for @(ptr-offset)=value writing bogus values 2025-03-02 23:40:14 +01:00
Irmen de Jong 125b66c929 fix crash in asmgen for boolean comparison with false, when not using optimizations 2025-03-01 23:57:55 +01:00
Irmen de Jong 5255f1c052 print offending source position in more of the TODO errors to help diagnosing 2025-03-01 23:16:34 +01:00
Irmen de Jong a6ba05d60c compile time evaluation of constant rangecheck 2025-03-01 13:45:11 +01:00
Irmen de Jong 41e963b04b memory() name argument should be string literal, nice error message 2025-03-01 12:38:42 +01:00
Irmen de Jong 6ff75bef29 fix verafx register address typo VERA_FX_POLY_FILL_H 2025-02-27 21:59:04 +01:00
Irmen de Jong 72c16d0d32 avoid doing useless jsr for reboot/poweroff calls 2025-02-26 20:42:38 +01:00
Irmen de Jong 94653e5c8c possible workaround for SMC issue that could make sys.reset_system() and sys.poweroff_system() not work properly 2025-02-24 23:07:51 +01:00
Irmen de Jong 3e2b2a698d Separate simple Ast and Symboltable from codeCore into new simpleAst module. VirtualMachine and Intermediate do not need them, just codeCore. 2025-02-24 22:06:52 +01:00
Irmen de Jong ae04f5aee8 %option romable now disables floating point support
because if it's your code that is running in the rom bank you can't have the floating point routines available there
2025-02-23 10:21:55 +01:00
Irmen de Jong 5c56267662 Also romable warning for inline variables. Added TODO: Romable in library files where applicable 2025-02-21 21:58:31 +01:00
Irmen de Jong e55ce5504e added %option romable to enable romable mode, but only generate a bunch of warnings for problematic codegeneration atm 2025-02-20 23:40:44 +01:00
Irmen de Jong fb1e89d9ef update docs about call convention for multi-value results (first is in A or AY, then R15...R0)
added sprites+coroutines+defer part to benchmark program
2025-02-19 22:10:12 +01:00
Irmen de Jong bc550a4549 fix optimized multi-value call result assignment 2025-02-19 20:19:20 +01:00
Irmen de Jong ebdea9cf76 optimized call convention for multi-value return and assign on regular asmsubs (6502) 2025-02-19 20:19:20 +01:00
Irmen de Jong 09ec508f82 avoid unnecessary word extension when assigning a register byte to a byte target 2025-02-19 20:19:20 +01:00
Irmen de Jong d06e9ea7f6 allow comparisons against constant values with different type 2025-02-19 20:19:20 +01:00
Mia McMahill a36bdc54fd Add Notepad++ syntax highlighting file for dark mode (#161) 2025-02-19 18:33:31 +01:00
108 changed files with 1114 additions and 474 deletions

No files matched your search

+5 -5
View File
@@ -1,11 +1,11 @@
<component name="libraryTable">
<library name="eclipse.lsp4j" type="repository">
<properties maven-id="org.eclipse.lsp4j:org.eclipse.lsp4j:0.23.1" />
<properties maven-id="org.eclipse.lsp4j:org.eclipse.lsp4j:0.24.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j/0.23.1/org.eclipse.lsp4j-0.23.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j.jsonrpc/0.23.1/org.eclipse.lsp4j.jsonrpc-0.23.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.11.0/gson-2.11.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.27.0/error_prone_annotations-2.27.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j/0.24.0/org.eclipse.lsp4j-0.24.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j.jsonrpc/0.24.0/org.eclipse.lsp4j.jsonrpc-0.24.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.12.1/gson-2.12.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.36.0/error_prone_annotations-2.36.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
+1 -1
View File
@@ -22,7 +22,7 @@
<component name="FrameworkDetectionExcludesConfiguration">
<type id="Python" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="openjdk-11" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="openjdk-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
+1
View File
@@ -17,6 +17,7 @@
<module fileurl="file://$PROJECT_DIR$/intermediate/intermediate.iml" filepath="$PROJECT_DIR$/intermediate/intermediate.iml" />
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/prog8.iml" filepath="$PROJECT_DIR$/.idea/modules/prog8.iml" />
<module fileurl="file://$PROJECT_DIR$/simpleAst/simpleAst.iml" filepath="$PROJECT_DIR$/simpleAst/simpleAst.iml" />
<module fileurl="file://$PROJECT_DIR$/virtualmachine/virtualmachine.iml" filepath="$PROJECT_DIR$/virtualmachine/virtualmachine.iml" />
</modules>
</component>
+3 -3
View File
@@ -63,10 +63,11 @@ What does Prog8 provide?
- various data types other than just bytes (16-bit words, floats, strings)
- floating point math is supported on certain targets
- access to most Kernal ROM routines as external subroutine definitions you can call normally
- tight control over Zeropage usage
- programs can be run multiple times without reloading because of automatic variable (re)initializations.
- strings can contain escaped characters but also many symbols directly if they have a petscii equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \, {, } and | are also accepted and converted to the closest petscii equivalents.
- automatic static variable allocations, automatic string and array variables and string sharing
- high-level program optimizations
- programs can be run multiple times without reloading because of automatic variable (re)initializations.
- conditional branches that map 1:1 to cpu status flags
- ``when`` statement to provide a concise jump table alternative to if/elseif chains
- ``in`` expression for concise and efficient multi-value/containment check
@@ -93,8 +94,7 @@ What does Prog8 provide?
- "c64": Commodore-64 (6502 like CPU)
- "c128": Commodore-128 (6502 like CPU - the Z80 cpu mode is not supported)
- "pet32": Commodore PET (limited support)
- "atari": Atari 8 bit such as 800XL (experimental)
- "neo": Neo6502 (experimental)
- via external configurable targets: Atari 800 XL, Neo6502, NES, C64OS, ...
- If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for different machines (just change the compiler target flag)
+2 -2
View File
@@ -12,7 +12,7 @@ circles {
sub draw(bool use_kernal, uword max_time) -> uword {
if use_kernal
void cx16.set_screen_mode(128)
cx16.set_screen_mode(128)
else
gfx_lores.graphics_mode()
@@ -33,7 +33,7 @@ circles {
}
if use_kernal
void cx16.set_screen_mode(3)
cx16.set_screen_mode(3)
else {
gfx_lores.text_mode()
}
+69
View File
@@ -0,0 +1,69 @@
%import sprites
%import coroutines
%import math
animsprites {
uword num_iterations
ubyte[64] sx
ubyte[64] sy
ubyte[64] sc
ubyte[64] dx
ubyte[64] dy
uword maximum_duration
sub benchmark(uword max_duration) -> uword {
maximum_duration = max_duration
math.rndseed(1122,9876)
cx16.set_screen_mode(3)
cx16.mouse_config2(1)
sprites.set_mousepointer_hand()
repeat 64
void coroutines.add(animsprite, 0)
cx16.mouse_config2(0)
cbm.SETTIM(0,0,0)
coroutines.run(supervisor)
sprites.reset(0, 64)
return num_iterations
}
sub supervisor() -> bool {
if cbm.RDTIM16() >= maximum_duration {
coroutines.killall()
return false
}
return true
}
sub animsprite() {
num_iterations++
; set up the sprite
ubyte sprnum = coroutines.current()
cx16.r6L, cx16.r7 = sprites.get_data_ptr(0)
sprites.init(sprnum, cx16.r6L, cx16.r7, sprites.SIZE_16, sprites.SIZE_16, sprites.COLORS_256, 0)
sx[sprnum] = math.rnd()
sy[sprnum] = math.rnd()
sc[sprnum] = math.rnd()
dx[sprnum] = if math.rnd()&1 == 1 1 else 255
dy[sprnum] = if math.rnd()&1 == 1 1 else 255
; move the sprite around
while sc[sprnum]!=0 {
animate(sprnum)
void coroutines.yield()
sprnum = coroutines.current()
}
sub animate(ubyte spr) {
defer sc[spr]--
sprites.pos(spr, sx[spr], sy[spr])
sx[spr] += dx[spr]
sy[spr] += dy[spr]
}
; end the task but replace it with a fresh animated sprite task
void coroutines.add(animsprite, 0)
}
}
+8 -3
View File
@@ -15,6 +15,7 @@
%import b_queens
%import b_textelite
%import b_maze
%import b_sprites
%zeropage basicsafe
%option no_sysinit
@@ -29,7 +30,7 @@ main {
sub start() {
ubyte benchmark_number
void cx16.set_screen_mode(3)
cx16.set_screen_mode(3)
txt.color2(1, 6)
txt.clear_screen()
@@ -74,10 +75,14 @@ main {
benchmark_score[benchmark_number] = textelite.bench(120)
benchmark_number++
announce_benchmark("sprites-coroutines-defer")
benchmark_score[benchmark_number] = animsprites.benchmark(300)
benchmark_number++
benchmark_names[benchmark_number] = 0
benchmark_score[benchmark_number] = 0
void cx16.set_screen_mode(3)
cx16.set_screen_mode(3)
txt.uppercase()
txt.color2(1, 6)
uword final_score
@@ -99,7 +104,7 @@ main {
sub announce_benchmark(str name) {
benchmark_names[benchmark_number] = name
void cx16.set_screen_mode(3)
cx16.set_screen_mode(3)
txt.uppercase()
txt.color2(1, 6)
txt.clear_screen()
+8
View File
@@ -0,0 +1,8 @@
package prog8.code
// the automatically generated module where all string literals are interned to:
const val INTERNED_STRINGS_MODULENAME = "prog8_interned_strings"
// all automatically generated labels everywhere need to have the same label name prefix:
const val GENERATED_LABEL_PREFIX = "p8_label_gen_"
@@ -11,6 +11,7 @@ class CompilationOptions(val output: OutputType,
val zpAllowed: List<UIntRange>,
val floats: Boolean,
val noSysInit: Boolean,
val romable: Boolean,
val compTarget: ICompilationTarget,
// these are set later, based on command line arguments or options in the source code:
var loadAddress: UInt,
+1
View File
@@ -6,6 +6,7 @@ plugins {
dependencies {
implementation(project(":codeCore"))
implementation(project(":simpleAst"))
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.1")
+1
View File
@@ -11,6 +11,7 @@
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="simpleAst" />
<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" />
@@ -1,6 +1,9 @@
package prog8.codegen.cpu6502
import com.github.michaelbull.result.fold
import prog8.code.GENERATED_LABEL_PREFIX
import prog8.code.IAssemblyProgram
import prog8.code.ICodeGeneratorBackend
import prog8.code.StNode
import prog8.code.StNodeType
import prog8.code.SymbolTable
@@ -39,7 +42,7 @@ class AsmGen6502(val prefixSymbols: Boolean, private val lastGeneratedLabelSeque
when(node) {
is PtAsmSub, is PtSub -> node.name = "p8s_${node.name}"
is PtBlock -> node.name = "p8b_${node.name}"
is PtLabel -> if(!node.name.startsWith(PtLabel.GENERATED_LABEL_PREFIX)) node.name = "p8l_${node.name}" // don't prefix autogenerated labels
is PtLabel -> if(!node.name.startsWith(GENERATED_LABEL_PREFIX)) node.name = "p8l_${node.name}" // don't prefix autogenerated labels
is PtConstant -> node.name = "p8c_${node.name}"
is PtVariable, is PtMemMapped, is PtSubroutineParameter -> node.name = "p8v_${node.name}"
}
@@ -90,9 +93,35 @@ class AsmGen6502(val prefixSymbols: Boolean, private val lastGeneratedLabelSeque
node.children.forEach { prefixSymbols(it) }
}
fun maybePrefixFunctionCallsAndIdentifierReferences(node: PtNode) {
if(node is PtFunctionCall) {
// function calls to subroutines defined in a block that does NOT have NoSymbolPrefixing, still have to be prefixed at the call site
val stNode = st.lookup(node.name)!!
if(stNode.astNode!!.definingBlock()?.options?.noSymbolPrefixing!=true) {
val index = node.parent.children.indexOf(node)
functionCallsToPrefix += node.parent to index
}
}
else if (node is PtIdentifier) {
// identifier references to things defined in a block that does NOT have NoSymbolPrefixing, still have to be prefixed at the referencing point
var lookupName = node.name
if(node.type.isSplitWordArray && (lookupName.endsWith("_lsb") || lookupName.endsWith("_msb"))) {
lookupName = lookupName.dropLast(4)
}
val stNode = st.lookup(lookupName) ?: throw AssemblyError("unknown identifier $node")
if(stNode.astNode!!.definingBlock()?.options?.noSymbolPrefixing!=true) {
val index = node.parent.children.indexOf(node)
nodesToPrefix += node.parent to index
}
}
node.children.forEach { maybePrefixFunctionCallsAndIdentifierReferences(it) }
}
program.allBlocks().forEach { block ->
if (!block.options.noSymbolPrefixing) {
prefixSymbols(block)
} else {
maybePrefixFunctionCallsAndIdentifierReferences(block)
}
}
@@ -109,7 +138,9 @@ class AsmGen6502(val prefixSymbols: Boolean, private val lastGeneratedLabelSeque
functionCallsToPrefix.reversed().forEach { (parent, index) ->
val node = parent.children[index]
if(node is PtFunctionCall) {
parent.children[index] = node.prefix(parent)
val prefixedName = PtIdentifier(node.name, DataType.forDt(BaseDataType.UNDEFINED), Position.DUMMY).prefix(parent, st)
val prefixedNode = node.withNewName(prefixedName.name)
parent.children[index] = prefixedNode
} else {
throw AssemblyError("expected PtFunctionCall")
}
@@ -121,14 +152,14 @@ class AsmGen6502(val prefixSymbols: Boolean, private val lastGeneratedLabelSeque
private fun prefixScopedName(name: String, type: Char): String {
if('.' !in name) {
if(name.startsWith(PtLabel.GENERATED_LABEL_PREFIX))
if(name.startsWith(GENERATED_LABEL_PREFIX))
return name
return "p8${type}_$name"
}
val parts = name.split('.')
val firstPrefixed = "p8b_${parts[0]}"
val lastPart = parts.last()
val lastPrefixed = if(lastPart.startsWith(PtLabel.GENERATED_LABEL_PREFIX)) lastPart else "p8${type}_$lastPart"
val lastPrefixed = if(lastPart.startsWith(GENERATED_LABEL_PREFIX)) lastPart else "p8${type}_$lastPart"
// the parts in between are assumed to be subroutine scopes.
val inbetweenPrefixed = parts.drop(1).dropLast(1).map{ "p8s_$it" }
val prefixed = listOf(firstPrefixed) + inbetweenPrefixed + listOf(lastPrefixed)
@@ -169,9 +200,13 @@ private fun PtVariable.prefix(parent: PtNode, st: SymbolTable): PtVariable {
else this
}
private fun PtFunctionCall.prefix(parent: PtNode): PtFunctionCall {
val newName = prefixScopedName(name, 's')
val call = PtFunctionCall(newName, void, type, position)
//private fun PtFunctionCall.prefix(targetType: Char): PtFunctionCall {
// val newName = prefixScopedName(name, targetType)
// return this.withNewName(newName)
//}
private fun PtFunctionCall.withNewName(name: String): PtFunctionCall {
val call = PtFunctionCall(name, void, type, position)
call.children.addAll(children)
call.children.forEach { it.parent = call }
call.parent = parent
@@ -687,8 +722,8 @@ class AsmGen6502Internal (
RegisterOrPair.Y -> assignmentAsmGen.assignRegisterByte(target, reg.asCpuRegister(), target.datatype.isSigned, true)
RegisterOrPair.AX,
RegisterOrPair.AY,
RegisterOrPair.XY,
in Cx16VirtualRegisters -> assignmentAsmGen.assignRegisterpairWord(target, reg)
RegisterOrPair.XY -> assignmentAsmGen.assignRegisterpairWord(target, reg)
in Cx16VirtualRegisters -> assignmentAsmGen.assignVirtualRegister(target, reg)
RegisterOrPair.FAC1 -> assignmentAsmGen.assignFAC1float(target)
RegisterOrPair.FAC2 -> assignmentAsmGen.assignFAC2float(target)
else -> throw AssemblyError("invalid register")
@@ -727,7 +762,7 @@ class AsmGen6502Internal (
}
assignExpressionToRegister(value, RegisterOrPair.A)
assignRegister(RegisterOrPair.A, target)
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, target.datatype.isSigned, false)
}
target.datatype.isWord || target.datatype.isPassByRef -> {
assignExpressionToRegister(value, RegisterOrPair.AY)
@@ -1063,25 +1098,32 @@ $repeatLabel""")
private fun translate(ret: PtReturn) {
val returnvalue = ret.children.singleOrNull()
val sub = ret.definingSub()!!
val returnRegs = sub.returnsWhatWhere()
if(returnvalue!=null) {
val sub = ret.definingSub()!!
val returnReg = sub.returnsWhatWhere().single()
if (sub.returns.single().isNumericOrBool==true) {
assignExpressionToRegister(returnvalue as PtExpression, returnReg.first.registerOrPair!!)
assignExpressionToRegister(returnvalue as PtExpression, returnRegs.single().first.registerOrPair!!)
}
else {
// all else take its address and assign that also to AY register pair
val addrofValue = PtAddressOf(returnvalue.position)
addrofValue.add(returnvalue as PtIdentifier)
addrofValue.parent = ret.parent
assignmentAsmGen.assignExpressionToRegister(addrofValue, returnReg.first.registerOrPair!!, false)
assignmentAsmGen.assignExpressionToRegister(addrofValue, returnRegs.single().first.registerOrPair!!, false)
}
}
else if(ret.children.size>1) {
// multi-value returns are passed throug cx16.R15 down to R0 (allows unencumbered use of many Rx registers if you don't return that many values)
val registersReverseOrder = Cx16VirtualRegisters.reversed()
ret.children.zip(registersReverseOrder).forEach { (value, register) ->
assignExpressionToRegister(value as PtExpression, register)
// note: multi-value returns are passed throug A or AY (for the first value) then cx16.R15 down to R0
// (this allows unencumbered use of many Rx registers if you don't return that many values)
// to avoid register clobbering, assign the first return value last in row.
val assigns = ret.children.zip(returnRegs).map { it.first to it.second }
assigns.drop(1).forEach {
val tgt = AsmAssignTarget(TargetStorageKind.REGISTER, this, it.second.second, null, it.first.position, register = it.second.first.registerOrPair!!)
assignExpressionTo(it.first as PtExpression, tgt)
}
assigns.first().also {
assignExpressionToRegister(it.first as PtExpression, it.second.first.registerOrPair!!)
}
}
out(" rts")
@@ -1342,13 +1384,14 @@ $repeatLabel""")
if(pointervar!=null && isZpVar(pointervar)) {
val varname = asmSymbolName(pointervar)
out(" ldy #${256-constOffset} ; negative offset $constOffset")
out(" dec $varname+1 | sta ($varname),y | inc $varname+1") // temporarily make MSB 1 less
out(" dec $varname+1 | sta ($varname),y | inc $varname+1") // temporarily make MSB 1 less to be able to use the negative Y offset
return true
} else {
// copy the pointer var to zp first
out(" pha")
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.forDt(BaseDataType.UWORD))
out(" ldy #${256-constOffset} ; negative offset $constOffset")
out(" dec P8ZP_SCRATCH_W2+1 | sta (P8ZP_SCRATCH_W2),y | inc P8ZP_SCRATCH_W2+1") // temporarily make MSB 1 less
out(" dec P8ZP_SCRATCH_W2+1 | pla | sta (P8ZP_SCRATCH_W2),y") // temporarily make MSB 1 less to be able to use the negative Y offset
return true
}
}
@@ -1372,13 +1415,13 @@ $repeatLabel""")
if(pointervar!=null && isZpVar(pointervar)) {
val varname = asmSymbolName(pointervar)
out(" ldy #${256-constOffset} ; negative offset $constOffset")
out(" dec $varname+1 | lda ($varname),y | inc $varname+1") // temporarily make MSB 1 less
out(" dec $varname+1 | lda ($varname),y | inc $varname+1") // temporarily make MSB 1 less to be able to use the negative Y offset
return true
} else {
// copy the pointer var to zp first
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.forDt(BaseDataType.UWORD))
out(" ldy #${256-constOffset} ; negative offset $constOffset")
out(" dec P8ZP_SCRATCH_W2+1 | lda (P8ZP_SCRATCH_W2),y | inc P8ZP_SCRATCH_W2+1") // temporarily make MSB 1 less
out(" dec P8ZP_SCRATCH_W2+1 | lda (P8ZP_SCRATCH_W2),y") // temporarily make MSB 1 less to be able to use the negative Y offset
return true
}
}
@@ -1495,7 +1538,7 @@ $repeatLabel""")
internal fun makeLabel(postfix: String): String {
generatedLabelSequenceNumber++
return "${PtLabel.GENERATED_LABEL_PREFIX}${generatedLabelSequenceNumber}_$postfix"
return "$GENERATED_LABEL_PREFIX${generatedLabelSequenceNumber}_$postfix"
}
internal fun assignConstFloatToPointerAY(number: PtNumber) {
@@ -1704,6 +1747,14 @@ $repeatLabel""")
}
return null
}
fun romableWarning(problem: String, pos: Position) {
if(options.romable) {
// until the code generation can provide an alternative, we have to report about code generated that is incompatible with ROMable code mode...
errors.warn("problem for ROMable code: $problem", pos)
out(" .error \"ROMable code selected but incompatible code was generated: $problem\"")
}
}
}
/**
@@ -1,9 +1,9 @@
package prog8.codegen.cpu6502
import prog8.code.GENERATED_LABEL_PREFIX
import prog8.code.StConstant
import prog8.code.StMemVar
import prog8.code.SymbolTable
import prog8.code.ast.PtLabel
import prog8.code.core.ICompilationTarget
@@ -362,7 +362,7 @@ or *_afterif labels.
This gets generated after certain if conditions, and only the branch instruction is needed in these cases.
*/
val autoLabelPrefix = PtLabel.GENERATED_LABEL_PREFIX
val autoLabelPrefix = GENERATED_LABEL_PREFIX
if(first=="beq +" && second=="lda #1" && third=="+") {
if((fourth.startsWith("beq $autoLabelPrefix") || fourth.startsWith("bne $autoLabelPrefix")) &&
(fourth.endsWith("_shortcut") || fourth.endsWith("_afterif") || fourth.endsWith("_shortcut:") || fourth.endsWith("_afterif:"))) {
@@ -1,6 +1,7 @@
package prog8.codegen.cpu6502
import prog8.code.ast.PtLabel
import prog8.code.GENERATED_LABEL_PREFIX
import prog8.code.IAssemblyProgram
import prog8.code.core.*
import prog8.code.target.C128Target
import prog8.code.target.C64Target
@@ -131,7 +132,7 @@ internal class AssemblyProgram(
}
private fun removeGeneratedLabelsFromMonlist() {
val pattern = Regex("""al (\w+) \S+${PtLabel.GENERATED_LABEL_PREFIX}.+?""")
val pattern = Regex("""al (\w+) \S+$GENERATED_LABEL_PREFIX.+?""")
val lines = viceMonListFile.toFile().readLines()
viceMonListFile.toFile().outputStream().bufferedWriter().use {
for (line in lines) {
@@ -236,6 +236,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
jsr $jsrfar
+ .word 0
+ .byte 0""")
asmgen.romableWarning("self-modifying code for jsrfar", fcall.position) // TODO
}
// note that by convention the values in A+Y registers are now the return value of the call.
@@ -292,6 +293,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
jsr $jsrfar
+ .word 0
+ .byte 0""")
asmgen.romableWarning("self-modifying code for jsrfar", fcall.position) // TODO
}
// note that by convention the values in A+Y registers are now the return value of the call.
@@ -346,7 +348,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
cmp ${asmgen.asmVariableName(arg2)}
+""")
}
is PtBool -> TODO("word compare against bool")
is PtBool -> TODO("word compare against bool ${arg2.position}")
is PtNumber -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
asmgen.out("""
@@ -411,12 +413,12 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
is PtArrayIndexer -> {
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
val varname = asmgen.asmVariableName(what.variable)
asmgen.out(" lda ${varname},x | lsr a | bcc + | ora #\$80 |+ | sta ${varname},x")
asmgen.out(" lda ${varname},x | lsr a | bcc + | ora #$80 |+ | sta ${varname},x")
}
is PtMemoryByte -> {
if (what.address is PtNumber) {
val number = (what.address as PtNumber).number
asmgen.out(" lda ${number.toHex()} | lsr a | bcc + | ora #\$80 |+ | sta ${number.toHex()}")
asmgen.out(" lda ${number.toHex()} | lsr a | bcc + | ora #$80 |+ | sta ${number.toHex()}")
} else {
asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
asmgen.out(" jsr prog8_lib.ror2_mem_ub")
@@ -424,7 +426,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
is PtIdentifier -> {
val variable = asmgen.asmVariableName(what)
asmgen.out(" lda $variable | lsr a | bcc + | ora #\$80 |+ | sta $variable")
asmgen.out(" lda $variable | lsr a | bcc + | ora #$80 |+ | sta $variable")
}
else -> throw AssemblyError("weird type")
}
@@ -435,13 +437,13 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
val varname = asmgen.asmVariableName(what.variable)
if(what.splitWords)
asmgen.out(" lsr ${varname}_msb,x | ror ${varname}_lsb,x | bcc + | lda ${varname}_msb,x | ora #\$80 | sta ${varname}_msb,x |+ ")
asmgen.out(" lsr ${varname}_msb,x | ror ${varname}_lsb,x | bcc + | lda ${varname}_msb,x | ora #$80 | sta ${varname}_msb,x |+ ")
else
asmgen.out(" lsr ${varname}+1,x | ror ${varname},x | bcc + | lda ${varname}+1,x | ora #\$80 | sta ${varname}+1,x |+ ")
asmgen.out(" lsr ${varname}+1,x | ror ${varname},x | bcc + | lda ${varname}+1,x | ora #$80 | sta ${varname}+1,x |+ ")
}
is PtIdentifier -> {
val variable = asmgen.asmVariableName(what)
asmgen.out(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #\$80 | sta $variable+1 |+ ")
asmgen.out(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #$80 | sta $variable+1 |+ ")
}
else -> throw AssemblyError("weird type")
}
@@ -478,6 +480,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.out("""
plp
+ ror ${'$'}ffff,x ; modified""")
asmgen.romableWarning("self-modifying code (ror)", fcall.position) // TODO
} else {
if(!what.address.isSimple()) asmgen.out(" php") // save Carry
asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
@@ -486,6 +489,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
sta (+) + 1
sty (+) + 2
+ ror ${'$'}ffff ; modified""")
asmgen.romableWarning("self-modifying code (ror)", fcall.position) // TODO
}
}
}
@@ -527,12 +531,12 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
is PtArrayIndexer -> {
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
val varname = asmgen.asmVariableName(what.variable)
asmgen.out(" lda ${varname},x | cmp #\$80 | rol a | sta ${varname},x")
asmgen.out(" lda ${varname},x | cmp #$80 | rol a | sta ${varname},x")
}
is PtMemoryByte -> {
if (what.address is PtNumber) {
val number = (what.address as PtNumber).number
asmgen.out(" lda ${number.toHex()} | cmp #\$80 | rol a | sta ${number.toHex()}")
asmgen.out(" lda ${number.toHex()} | cmp #$80 | rol a | sta ${number.toHex()}")
} else {
asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
asmgen.out(" jsr prog8_lib.rol2_mem_ub")
@@ -540,7 +544,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
is PtIdentifier -> {
val variable = asmgen.asmVariableName(what)
asmgen.out(" lda $variable | cmp #\$80 | rol a | sta $variable")
asmgen.out(" lda $variable | cmp #$80 | rol a | sta $variable")
}
else -> throw AssemblyError("weird type")
}
@@ -594,6 +598,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.out("""
plp
+ rol ${'$'}ffff,x ; modified""")
asmgen.romableWarning("self-modifying code (rol)", fcall.position) // TODO
} else {
if(!what.address.isSimple()) asmgen.out(" php") // save Carry
asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
@@ -602,6 +607,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
sta (+) + 1
sty (+) + 2
+ rol ${'$'}ffff ; modified""")
asmgen.romableWarning("self-modifying code (rol)", fcall.position) // TODO
}
}
}
@@ -1,39 +0,0 @@
package prog8.codegen.cpu6502
import prog8.code.ast.IPtSubroutine
import prog8.code.ast.PtAsmSub
import prog8.code.ast.PtSub
import prog8.code.core.AssemblyError
import prog8.code.core.DataType
import prog8.code.core.RegisterOrPair
import prog8.code.core.RegisterOrStatusflag
internal fun IPtSubroutine.returnsWhatWhere(): List<Pair<RegisterOrStatusflag, DataType>> {
when(this) {
is PtAsmSub -> {
return returns
}
is PtSub -> {
// for non-asm subroutines, determine the return registers based on the type of the return values
when(returns.size) {
0 -> return emptyList()
1 -> {
val returntype = returns.single()
val register = when {
returntype.isByteOrBool -> RegisterOrStatusflag(RegisterOrPair.A, null)
returntype.isWord -> RegisterOrStatusflag(RegisterOrPair.AY, null)
returntype.isFloat -> RegisterOrStatusflag(RegisterOrPair.FAC1, null)
else -> RegisterOrStatusflag(RegisterOrPair.AY, null)
}
return listOf(Pair(register, returntype))
}
else -> {
// TODO for multi-value results, put the first one in register(s) and only the rest in the virtual registers?
throw AssemblyError("multi-value returns from a normal subroutine are not put into registers, this routine shouldn't have been called in this scenario")
}
}
}
}
}
@@ -111,6 +111,7 @@ internal class ForLoopsAsmGen(
$modifiedLabel cmp #0 ; modified
beq $endLabel
$incdec $varname""")
asmgen.romableWarning("self-modifying code (forloop over range)", stmt.position) // TODO
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
@@ -159,6 +160,7 @@ $modifiedLabel cmp #0 ; modified
$modifiedLabel cmp #0 ; modified
bmi $loopLabel
beq $loopLabel""")
asmgen.romableWarning("self-modifying code (forloop over range)", stmt.position) // TODO
} else {
asmgen.out("""
lda $varname
@@ -167,6 +169,7 @@ $modifiedLabel cmp #0 ; modified
sta $varname
$modifiedLabel cmp #0 ; modified
bpl $loopLabel""")
asmgen.romableWarning("self-modifying code (forloop over range)", stmt.position) // TODO
}
asmgen.out(endLabel)
}
@@ -223,6 +226,7 @@ $modifiedLabel cmp #0 ; modified
lda $varname
$modifiedLabel2 cmp #0 ; modified
beq $endLabel""")
asmgen.romableWarning("self-modifying code (forloop over range)", stmt.position) // TODO
if(stepsize==1) {
asmgen.out("""
+ inc $varname
@@ -266,6 +270,7 @@ $modifiedLabel2 lda #0 ; modified
bcc $endLabel
bcs $loopLabel
$endLabel""")
asmgen.romableWarning("self-modifying code (forloop over range)", stmt.position) // TODO
} else {
asmgen.out("""
lda $varname
@@ -283,6 +288,7 @@ $modifiedLabel lda #0 ; modified
eor #$80
+ bpl $loopLabel
$endLabel""")
asmgen.romableWarning("self-modifying code (forloop over range)", stmt.position) // TODO
}
}
else {
@@ -313,6 +319,7 @@ $modifiedLabel sbc #0 ; modified
eor #$80
+ bpl $loopLabel
$endLabel""")
asmgen.romableWarning("self-modifying code (forloop over range)", stmt.position) // TODO
}
}
else -> throw AssemblyError("range expression can only be byte or word")
@@ -391,6 +398,7 @@ $endLabel""")
$loopLabel lda ${65535.toHex()} ; modified
beq $endLabel
sta ${asmgen.asmVariableName(stmt.variable)}""")
asmgen.romableWarning("self-modifying code (forloop over iterable)", stmt.position) // TODO
asmgen.translate(stmt.statements)
asmgen.out("""
inc $loopLabel+1
@@ -1,5 +1,6 @@
package prog8.codegen.cpu6502
import prog8.code.StNodeType
import prog8.code.ast.*
import prog8.code.core.*
import prog8.codegen.cpu6502.assignment.AsmAssignSource
@@ -32,8 +33,14 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
val symbol = asmgen.symbolTable.lookup(call.name)
val sub = symbol?.astNode as IPtSubroutine
val symbol = asmgen.symbolTable.lookup(call.name)!!
if(symbol.type == StNodeType.LABEL) {
require(call.void)
asmgen.out(" jsr ${asmgen.asmSymbolName(symbol.scopedName)}")
return
}
val sub = symbol.astNode as IPtSubroutine
val subAsmName = asmgen.asmSymbolName(call.name)
if(sub is PtAsmSub) {
@@ -65,6 +72,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
.word $subAsmName ; ${sub.address!!.address.toHex()}
+ .byte 0 ; modified"""
)
asmgen.romableWarning("self-modifying code for cx16 banked jsr", call.position) // TODO
}
"c64" -> {
asmgen.out("""
@@ -78,6 +86,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
.word $subAsmName ; ${sub.address!!.address.toHex()}
+ .byte 0 ; modified"""
)
asmgen.romableWarning("self-modifying code for c64 banked jsr", call.position) // TODO
}
"c128" -> {
asmgen.out("""
@@ -91,6 +100,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
.word $subAsmName ; ${sub.address!!.address.toHex()}
+ .byte 0 ; modified"""
)
asmgen.romableWarning("self-modifying code for c128 banked jsr", call.position) // TODO
}
else -> throw AssemblyError("callfar is not supported on the selected compilation target")
}
@@ -652,6 +652,7 @@ internal class ProgramAndVarsGen(
it.initializationStringValue!!.second,
it.initializationStringValue!!.first
)
asmgen.romableWarning("inlined variable (${it.dt}, ${it.name})", Position.DUMMY) // TODO
}
alignedStrings.sortedBy { it.align }.forEach {
outputStringvar(
@@ -660,13 +661,16 @@ internal class ProgramAndVarsGen(
it.initializationStringValue!!.second,
it.initializationStringValue!!.first
)
asmgen.romableWarning("inlined variable (${it.dt}, ${it.name})", Position.DUMMY) // TODO
}
notAlignedOther.sortedBy { it.type }.forEach {
staticVariable2asm(it)
asmgen.romableWarning("inlined variable (${it.dt}, ${it.name})", Position.DUMMY) // TODO
}
alignedOther.sortedBy { it.align }.sortedBy { it.type }.forEach {
staticVariable2asm(it)
asmgen.romableWarning("inlined variable (${it.dt}, ${it.name})", Position.DUMMY) // TODO
}
}
}
@@ -190,7 +190,7 @@ internal class AnyExprAsmGen(
asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC2, true)
if (!right.isSimple()) asmgen.popFAC1()
}
else -> TODO("don't know how to evaluate float expression for selected compilation target")
else -> TODO("don't know how to evaluate float expression for selected compilation target ${left.position}")
}
}
@@ -3,7 +3,6 @@ package prog8.codegen.cpu6502.assignment
import prog8.code.ast.*
import prog8.code.core.*
import prog8.codegen.cpu6502.AsmGen6502Internal
import prog8.codegen.cpu6502.returnsWhatWhere
internal enum class TargetStorageKind {
@@ -9,7 +9,6 @@ import prog8.code.ast.*
import prog8.code.core.*
import prog8.codegen.cpu6502.AsmGen6502Internal
import prog8.codegen.cpu6502.VariableAllocator
import prog8.codegen.cpu6502.returnsWhatWhere
internal class AssignmentAsmGen(
@@ -44,7 +43,7 @@ internal class AssignmentAsmGen(
if(extsub!=null) {
require(extsub.returns.size>=2)
if(extsub.returns.any { it.type.isFloat })
TODO("deal with (multiple?) FP return registers")
TODO("deal with (multiple?) FP return registers ${assignment.position}")
asmgen.translate(values)
@@ -154,7 +153,7 @@ internal class AssignmentAsmGen(
else -> throw AssemblyError("weird dt")
}
}
else TODO("array target for multi-value assignment") // Not done yet due to result register clobbering complexity
else TODO("array target for multi-value assignment ${target.position}") // Not done yet due to result register clobbering complexity
}
}
}
@@ -242,7 +241,7 @@ internal class AssignmentAsmGen(
targetDt.isSignedWord -> assignVariableWord(assign.target, variable, assign.source.datatype)
targetDt.isUnsignedWord -> {
if(assign.source.datatype.isPassByRef)
assignAddressOf(assign.target, variable, false, null, null)
assignAddressOf(assign.target, variable, false, assign.source.datatype, assign.source.array ?: PtNumber(BaseDataType.UBYTE, 0.0, assign.position))
else
assignVariableWord(assign.target, variable, assign.source.datatype)
}
@@ -610,11 +609,13 @@ internal class AssignmentAsmGen(
val sub = symbol!!.astNode as IPtSubroutine
asmgen.translateFunctionCall(value)
if(sub is PtSub && sub.returns.size>1) {
// multi-value returns are passed throug cx16.R15 down to R0 (allows unencumbered use of many Rx registers if you don't return that many values)
val registersReverseOrder = Cx16VirtualRegisters.reversed()
assign.targets.zip(registersReverseOrder).forEach { (target, register) ->
if(target.kind!=TargetStorageKind.VOID)
assignVirtualRegister(target, register)
// note: multi-value returns are passed throug A or AY (for the first value) then cx16.R15 down to R0
// (this allows unencumbered use of many Rx registers if you don't return that many values)
val returnRegs = sub.returnsWhatWhere()
assign.targets.zip(returnRegs).forEach { target ->
if(target.first.kind != TargetStorageKind.VOID) {
asmgen.assignRegister(target.second.first.registerOrPair!!, target.first)
}
}
} else {
val target = assign.target
@@ -700,13 +701,16 @@ internal class AssignmentAsmGen(
}
}
private fun assignVirtualRegister(target: AsmAssignTarget, register: RegisterOrPair) {
internal fun assignVirtualRegister(target: AsmAssignTarget, register: RegisterOrPair) {
// Note: while the virtual register R0-R15 can hold a word value,
// the actual datatype that gets assigned is determined by the assignment target.
// This can be a single byte!
when {
target.datatype.isByteOrBool -> {
if(register in Cx16VirtualRegisters) {
asmgen.out(" lda cx16.${register.toString().lowercase()}L")
} else {
TODO("LDA byte from $register")
TODO("LDA byte from $register ${target.position}")
}
assignRegisterByte(target, CpuRegister.A, false, false)
}
@@ -850,7 +854,7 @@ internal class AssignmentAsmGen(
RegisterOrPair.A -> "a"
RegisterOrPair.X -> "x"
RegisterOrPair.Y -> "y"
else -> TODO("comparison to word register")
else -> TODO("comparison to word register ${expr.position}")
}
val assignTrue = PtInlineAssembly("\tld${reg} #1", false, assign.target.position)
val assignFalse = PtInlineAssembly("\tld${reg} #0", false, assign.target.position)
@@ -1841,8 +1845,7 @@ internal class AssignmentAsmGen(
"==" -> {
val dt = expr.left.type
when {
dt.isBool -> TODO("compare bool to 0")
dt.isByte -> {
dt.isBool || dt.isByte -> {
assignExpressionToRegister(expr.left, RegisterOrPair.A, dt.isSigned)
asmgen.out("""
cmp #0
@@ -1877,8 +1880,7 @@ internal class AssignmentAsmGen(
"!=" -> {
val dt = expr.left.type
when {
dt.isBool -> TODO("compare bool to 0")
dt.isByte -> {
dt.isBool || dt.isByte -> {
assignExpressionToRegister(expr.left, RegisterOrPair.A, dt.isSigned)
asmgen.out(" beq + | lda #1")
asmgen.out("+")
@@ -2609,6 +2611,7 @@ $endLabel""")
private fun assignAddressOf(target: AsmAssignTarget, sourceName: String, msb: Boolean, arrayDt: DataType?, arrayIndexExpr: PtExpression?) {
if(arrayIndexExpr!=null) {
val arrayName = if(arrayDt!!.isSplitWordArray) sourceName+"_lsb" else sourceName // the _lsb split array comes first in memory
val constIndex = arrayIndexExpr.asConstInteger()
if(constIndex!=null) {
if (arrayDt?.isUnsignedWord==true) {
@@ -2624,10 +2627,10 @@ $endLabel""")
}
else {
if(constIndex>0) {
val offset = if(arrayDt!!.isSplitWordArray) constIndex else program.memsizer.memorySize(arrayDt, constIndex) // add arrayIndexExpr * elementsize to the address of the array variable.
asmgen.out(" lda #<($sourceName + $offset) | ldy #>($sourceName + $offset)")
val offset = if(arrayDt.isSplitWordArray) constIndex else program.memsizer.memorySize(arrayDt, constIndex) // add arrayIndexExpr * elementsize to the address of the array variable.
asmgen.out(" lda #<($arrayName + $offset) | ldy #>($arrayName + $offset)")
} else {
asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
asmgen.out(" lda #<$arrayName | ldy #>$arrayName")
}
}
assignRegisterpairWord(target, RegisterOrPair.AY)
@@ -2651,9 +2654,9 @@ $endLabel""")
else {
assignExpressionToRegister(arrayIndexExpr, RegisterOrPair.A, false)
asmgen.out("""
ldy #>$sourceName
ldy #>$arrayName
clc
adc #<$sourceName
adc #<$arrayName
bcc +
iny
+""")
@@ -965,6 +965,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.assignExpressionToRegister(pointervar, RegisterOrPair.AY)
asmgen.out(" sta (+) + 1 | sty (+) + 2")
asmgen.out("+\tinc ${'$'}ffff\t; modified")
asmgen.romableWarning("self-modifying code (access pointer)", pointervar.position) // TODO
} else {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
asmgen.out(" clc | adc #$value")
@@ -976,6 +977,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.assignExpressionToRegister(pointervar, RegisterOrPair.AY)
asmgen.out(" sta (+) + 1 | sty (+) + 2")
asmgen.out("+\tdec ${'$'}ffff\t; modified")
asmgen.romableWarning("self-modifying code (access pointer)", pointervar.position) // TODO
} else {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
asmgen.out(" sec | sbc #$value")
+1
View File
@@ -26,6 +26,7 @@ class TestCodegen: FunSpec({
zpAllowed = CompilationOptions.AllZeropageAllowed,
floats = true,
noSysInit = false,
romable = false,
compTarget = target,
loadAddress = target.PROGRAM_LOAD_ADDRESS,
memtopAddress = 0xffffu
+1
View File
@@ -6,6 +6,7 @@ plugins {
dependencies {
implementation(project(":codeCore"))
implementation(project(":simpleAst"))
implementation(project(":intermediate"))
implementation(project(":codeGenIntermediate"))
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
@@ -13,5 +13,6 @@
<orderEntry type="module" module-name="codeGenIntermediate" />
<orderEntry type="module" module-name="intermediate" />
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="simpleAst" />
</component>
</module>
@@ -3,8 +3,8 @@ package prog8.codegen.experimental
import prog8.code.SymbolTable
import prog8.code.ast.PtProgram
import prog8.code.core.CompilationOptions
import prog8.code.core.IAssemblyProgram
import prog8.code.core.ICodeGeneratorBackend
import prog8.code.IAssemblyProgram
import prog8.code.ICodeGeneratorBackend
import prog8.code.core.IErrorReporter
import prog8.codegen.intermediate.IRCodeGen
import prog8.intermediate.IRFileWriter
+1
View File
@@ -7,6 +7,7 @@ plugins {
dependencies {
implementation(project(":codeCore"))
implementation(project(":simpleAst"))
implementation(project(":intermediate"))
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
// implementation "org.jetbrains.kotlin:kotlin-reflect"
@@ -11,6 +11,7 @@
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="simpleAst" />
<orderEntry type="module" module-name="intermediate" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
@@ -41,15 +41,16 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
} else {
val normalsub = codeGen.symbolTable.lookup(values.name) as? StSub
if (normalsub != null) {
// multi-value returns are passed throug cx16.R15 down to R0 (allows unencumbered use of many Rx registers if you don't return that many values)
val registersReverseOrder = Cx16VirtualRegisters.reversed()
normalsub.returns.zip(assignmentTargets).zip(registersReverseOrder).forEach {
// note: multi-value returns are passed throug A or AY (for the first value) then cx16.R15 down to R0
// (this allows unencumbered use of many Rx registers if you don't return that many values)
val returnregs = (normalsub.astNode!! as IPtSubroutine).returnsWhatWhere()
normalsub.returns.zip(assignmentTargets).zip(returnregs).forEach {
val target = it.first.second as PtAssignTarget
if(!target.void) {
val assignSingle = PtAssignment(assignment.position, assignment.isVarInitializer)
assignSingle.add(target)
assignSingle.add(PtIdentifier("cx16.${it.second.toString().lowercase()}", it.first.first, assignment.position))
result += translateRegularAssign(assignSingle)
val reg = it.second.first
val regnum = codeGen.registers.next(irType(it.second.second))
val p = StExtSubParameter(reg, it.second.second)
result += assignCpuRegister(p, regnum, target)
}
}
}
@@ -74,7 +75,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
RegisterOrPair.AX -> IRInstruction(Opcode.LOADHAX, IRDataType.WORD, reg1=regNum)
RegisterOrPair.AY -> IRInstruction(Opcode.LOADHAY, IRDataType.WORD, reg1=regNum)
RegisterOrPair.XY -> IRInstruction(Opcode.LOADHXY, IRDataType.WORD, reg1=regNum)
in Cx16VirtualRegisters -> IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1=regNum, labelSymbol = "cx16.${returns.register.registerOrPair.toString().lowercase()}")
in Cx16VirtualRegisters -> IRInstruction(Opcode.LOADM, irType(returns.type), reg1=regNum, labelSymbol = "cx16.${returns.register.registerOrPair.toString().lowercase()}")
null -> {
TODO("assign CPU status flag ${returns.register.statusflag!!}")
}
@@ -2,6 +2,7 @@ package prog8.codegen.intermediate
import prog8.code.StNode
import prog8.code.StExtSub
import prog8.code.StNodeType
import prog8.code.StSub
import prog8.code.ast.*
import prog8.code.core.*
@@ -579,7 +580,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
internal fun translate(fcall: PtFunctionCall): ExpressionCodeResult {
val callTarget = codeGen.symbolTable.flat.getValue(fcall.name)
val callTarget = codeGen.symbolTable.lookup(fcall.name)!!
if(callTarget.scopedName in listOf("sys.push", "sys.pushw", "sys.pop", "sys.popw", "floats.push", "floats.pop")) {
// special case, these should be inlined, or even use specialized instructions. Instead of doing a normal subroutine call.
@@ -635,7 +636,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
// return value(s)
val returnRegSpecs = if(fcall.void) emptyList() else {
// TODO: for current implemenation of the call convention in case of multiple return values,
// a list of Ir virtual registers to hold the results is NOT correct (they're loaded into AY + R15..R0 instead!)
// So we use an empty list to avoid confusion here. This may change in a future version.
val returnRegSpecs = if(fcall.void || callTarget.returns.size>1) emptyList() else {
callTarget.returns.map {
val returnIrType = irType(it)
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.next(returnIrType), null)
@@ -653,7 +657,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
else
ExpressionCodeResult(result, returnRegSpec.dt, returnRegSpec.registerNum, -1)
} else {
// multi-value returns are passed throug cx16.R15 down to R0 (allows unencumbered use of many Rx registers if you don't return that many values)
// note: multi-value returns are passed throug A or AY (for the first value) then cx16.R15 down to R0
// so the actual result of the expression here is 'void' (doesn't use IR virtual registers at all)
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
@@ -671,25 +675,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
else
argRegisters.add(FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(paramDt, tr.resultReg, parameter.register)))
result += tr.chunks
when(parameter.register.registerOrPair) {
RegisterOrPair.A -> addInstr(result, IRInstruction(Opcode.STOREHA, IRDataType.BYTE, reg1=tr.resultReg), null)
RegisterOrPair.X -> addInstr(result, IRInstruction(Opcode.STOREHX, IRDataType.BYTE, reg1=tr.resultReg), null)
RegisterOrPair.Y -> addInstr(result, IRInstruction(Opcode.STOREHY, IRDataType.BYTE, reg1=tr.resultReg), null)
RegisterOrPair.AX -> addInstr(result, IRInstruction(Opcode.STOREHAX, IRDataType.WORD, reg1=tr.resultReg), null)
RegisterOrPair.AY -> addInstr(result, IRInstruction(Opcode.STOREHAY, IRDataType.WORD, reg1=tr.resultReg), null)
RegisterOrPair.XY -> addInstr(result, IRInstruction(Opcode.STOREHXY, IRDataType.WORD, reg1=tr.resultReg), null)
RegisterOrPair.FAC1 -> addInstr(result, IRInstruction(Opcode.STOREHFACZERO, IRDataType.FLOAT, fpReg1 = tr.resultFpReg), null)
RegisterOrPair.FAC2 -> addInstr(result, IRInstruction(Opcode.STOREHFACONE, IRDataType.FLOAT, fpReg1 = tr.resultFpReg), null)
in Cx16VirtualRegisters -> {
addInstr(result, IRInstruction(Opcode.STOREM, paramDt, reg1=tr.resultReg, labelSymbol = "cx16.${parameter.register.registerOrPair.toString().lowercase()}"), null)
}
null -> when(parameter.register.statusflag) {
// TODO: do the statusflag argument as last
Statusflag.Pc -> addInstr(result, IRInstruction(Opcode.LSR, paramDt, reg1=tr.resultReg), null)
else -> throw AssemblyError("weird statusflag as param")
}
else -> throw AssemblyError("unsupported register arg")
}
result += codeGen.setCpuRegister(parameter.register, paramDt, tr.resultReg, tr.resultFpReg)
}
if(callTarget.returns.size>1)
@@ -770,7 +756,18 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
else
ExpressionCodeResult(result, returnRegSpec!!.dt, finalReturnRegister, -1)
}
else -> throw AssemblyError("invalid node type")
else -> {
if(callTarget.type == StNodeType.LABEL) {
require(fcall.void)
val result = mutableListOf<IRCodeChunkBase>()
val args = FunctionCallArgs(emptyList(), emptyList())
addInstr(result, IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = args), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
else {
throw AssemblyError("invalid node type")
}
}
}
}
@@ -25,7 +25,7 @@ class IRCodeGen(
verifyNameScoping(program, symbolTable)
changeGlobalVarInits(symbolTable)
val irSymbolTable = IRSymbolTable.fromAstSymboltable(symbolTable)
val irSymbolTable = convertStToIRSt(symbolTable)
val irProg = IRProgram(program.name, irSymbolTable, options, program.encoding)
// collect global variables initializers
@@ -1759,12 +1759,20 @@ class IRCodeGen(
private fun translate(ret: PtReturn): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
if(ret.children.size>1) {
// multi-value returns are passed throug cx16.R15 down to R0 (allows unencumbered use of many Rx registers if you don't return that many values)
val registersReverseOrder = Cx16VirtualRegisters.reversed()
for ((value, register) in ret.children.zip(registersReverseOrder)) {
// note: multi-value returns are passed throug A or AY (for the first value) then cx16.R15 down to R0
// (this allows unencumbered use of many Rx registers if you don't return that many values)
// make sure to assign the first value as the last in the sequence, to avoid clobbering the AY registers afterwards
val returnRegs = ret.definingISub()!!.returnsWhatWhere()
val values = ret.children.zip(returnRegs)
for ((value, register) in values.drop(1)) {
val tr = expressionEval.translateExpression(value as PtExpression)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.STOREM, tr.dt, reg1=tr.resultReg, labelSymbol = "cx16.${register.toString().lowercase()}"), null)
result += setCpuRegister(register.first, irType(register.second), tr.resultReg, -1)
}
values.first().also { (value, register) ->
val tr = expressionEval.translateExpression(value as PtExpression)
addToResult(result, tr, tr.resultReg, -1)
result += setCpuRegister(register.first, irType(register.second), tr.resultReg, -1)
}
addInstr(result, IRInstruction(Opcode.RETURN), null)
return result
@@ -1891,7 +1899,7 @@ class IRCodeGen(
private var labelSequenceNumber = 0
internal fun createLabelName(): String {
labelSequenceNumber++
return "${PtLabel.GENERATED_LABEL_PREFIX}$labelSequenceNumber"
return "${GENERATED_LABEL_PREFIX}$labelSequenceNumber"
}
internal fun translateBuiltinFunc(call: PtBuiltinFunctionCall): ExpressionCodeResult
@@ -1913,4 +1921,28 @@ class IRCodeGen(
}
fun registerTypes(): Map<Int, IRDataType> = registers.getTypes()
fun setCpuRegister(registerOrFlag: RegisterOrStatusflag, paramDt: IRDataType, resultReg: Int, resultFpReg: Int): IRCodeChunk {
val chunk = IRCodeChunk(null, null)
when(registerOrFlag.registerOrPair) {
RegisterOrPair.A -> chunk += IRInstruction(Opcode.STOREHA, IRDataType.BYTE, reg1=resultReg)
RegisterOrPair.X -> chunk += IRInstruction(Opcode.STOREHX, IRDataType.BYTE, reg1=resultReg)
RegisterOrPair.Y -> chunk += IRInstruction(Opcode.STOREHY, IRDataType.BYTE, reg1=resultReg)
RegisterOrPair.AX -> chunk += IRInstruction(Opcode.STOREHAX, IRDataType.WORD, reg1=resultReg)
RegisterOrPair.AY -> chunk += IRInstruction(Opcode.STOREHAY, IRDataType.WORD, reg1=resultReg)
RegisterOrPair.XY -> chunk += IRInstruction(Opcode.STOREHXY, IRDataType.WORD, reg1=resultReg)
RegisterOrPair.FAC1 -> chunk += IRInstruction(Opcode.STOREHFACZERO, IRDataType.FLOAT, fpReg1 = resultFpReg)
RegisterOrPair.FAC2 -> chunk += IRInstruction(Opcode.STOREHFACONE, IRDataType.FLOAT, fpReg1 = resultFpReg)
in Cx16VirtualRegisters -> {
chunk += IRInstruction(Opcode.STOREM, paramDt, reg1=resultReg, labelSymbol = "cx16.${registerOrFlag.registerOrPair.toString().lowercase()}")
}
null -> when(registerOrFlag.statusflag) {
// TODO: do the statusflag argument as last
Statusflag.Pc -> chunk += IRInstruction(Opcode.LSR, paramDt, reg1=resultReg)
else -> throw AssemblyError("weird statusflag as param")
}
else -> throw AssemblyError("unsupported register arg")
}
return chunk
}
}
@@ -0,0 +1,146 @@
package prog8.codegen.intermediate
import prog8.code.StArrayElement
import prog8.code.StConstant
import prog8.code.StMemVar
import prog8.code.StMemorySlab
import prog8.code.StNodeType
import prog8.code.StStaticVariable
import prog8.code.SymbolTable
import prog8.code.core.DataType
import prog8.intermediate.IRStArrayElement
import prog8.intermediate.IRStConstant
import prog8.intermediate.IRStMemVar
import prog8.intermediate.IRStMemorySlab
import prog8.intermediate.IRStStaticVariable
import prog8.intermediate.IRSymbolTable
fun convertStToIRSt(sourceSt: SymbolTable?): IRSymbolTable {
val st = IRSymbolTable()
if (sourceSt != null) {
sourceSt.flat.forEach {
when(it.value.type) {
StNodeType.STATICVAR -> st.add(convert(it.value as StStaticVariable))
StNodeType.MEMVAR -> st.add(convert(it.value as StMemVar))
StNodeType.CONSTANT -> st.add(convert(it.value as StConstant))
StNodeType.MEMORYSLAB -> st.add(convert(it.value as StMemorySlab))
else -> { }
}
}
st.validate()
st.allVariables().forEach { variable ->
variable.onetimeInitializationArrayValue?.let {
it.forEach { arrayElt ->
val addrOfSymbol = arrayElt.addressOfSymbol
if (addrOfSymbol != null) {
require(addrOfSymbol.contains('.')) {
"pointer var in array should be properly scoped: ${addrOfSymbol} in ${variable.name}"
}
}
}
}
}
}
return st
}
private fun convert(variable: StStaticVariable): IRStStaticVariable {
fun convertArrayElt(elt: StArrayElement): IRStArrayElement = if(elt.boolean!=null)
IRStArrayElement(elt.boolean, null, elt.addressOfSymbol)
else
IRStArrayElement(null, elt.number, elt.addressOfSymbol)
val scopedName: String
if('.' in variable.name) {
scopedName = variable.name
return IRStStaticVariable(variable.name,
variable.dt,
variable.initializationNumericValue,
variable.initializationStringValue,
variable.initializationArrayValue?.map { convertArrayElt(it) },
variable.length,
variable.zpwish,
variable.align)
} else {
fun fixupAddressOfInArray(array: List<StArrayElement>?): List<IRStArrayElement>? {
if(array==null)
return null
val newArray = mutableListOf<IRStArrayElement>()
array.forEach {
if(it.addressOfSymbol!=null) {
val target = variable.lookup(it.addressOfSymbol!!) ?: throw NoSuchElementException("can't find variable ${it.addressOfSymbol}")
newArray.add(IRStArrayElement(null, null, target.scopedName))
} else {
newArray.add(convertArrayElt(it))
}
}
return newArray
}
scopedName = variable.scopedName
return IRStStaticVariable(scopedName,
variable.dt,
variable.initializationNumericValue,
variable.initializationStringValue,
fixupAddressOfInArray(variable.initializationArrayValue),
variable.length,
variable.zpwish,
variable.align
)
}
}
private fun convert(variable: StMemVar): IRStMemVar {
val scopedName: String
if('.' in variable.name) {
scopedName = variable.name
return IRStMemVar(
variable.name,
variable.dt,
variable.address,
variable.length
)
} else {
scopedName = try {
variable.scopedName
} catch (_: UninitializedPropertyAccessException) {
variable.name
}
return IRStMemVar(scopedName, variable.dt, variable.address, variable.length)
}
}
private fun convert(constant: StConstant): IRStConstant {
val dt = DataType.forDt(constant.dt)
val scopedName = if('.' in constant.name) {
constant.name
} else {
try {
constant.scopedName
} catch (_: UninitializedPropertyAccessException) {
constant.name
}
}
return IRStConstant(scopedName, dt, constant.value)
}
private fun convert(variable: StMemorySlab): IRStMemorySlab {
return if('.' in variable.name)
IRStMemorySlab(variable.name, variable.size, variable.align)
else
IRStMemorySlab("prog8_slabs.${variable.name}", variable.size, variable.align)
}
/*
*/
@@ -3,8 +3,8 @@ package prog8.codegen.vm
import prog8.code.SymbolTable
import prog8.code.ast.PtProgram
import prog8.code.core.CompilationOptions
import prog8.code.core.IAssemblyProgram
import prog8.code.core.ICodeGeneratorBackend
import prog8.code.IAssemblyProgram
import prog8.code.ICodeGeneratorBackend
import prog8.code.core.IErrorReporter
import prog8.codegen.intermediate.IRCodeGen
import prog8.intermediate.IRFileWriter
@@ -21,6 +21,7 @@ class TestIRPeepholeOpt: FunSpec({
CompilationOptions.AllZeropageAllowed,
floats = false,
noSysInit = true,
romable = false,
compTarget = target,
loadAddress = target.PROGRAM_LOAD_ADDRESS,
memtopAddress = 0xffffu
@@ -23,6 +23,7 @@ class TestVmCodeGen: FunSpec({
zpAllowed = CompilationOptions.AllZeropageAllowed,
floats = true,
noSysInit = false,
romable = false,
compTarget = target,
loadAddress = target.PROGRAM_LOAD_ADDRESS,
memtopAddress = 0xffffu
@@ -23,9 +23,11 @@ fun Program.constantFold(errors: IErrorReporter, options: CompilationOptions) {
val optimizer = ConstantFoldingOptimizer(this, errors)
optimizer.visit(this)
while (errors.noErrors() && optimizer.applyModifications() > 0) {
var tries=0
while (errors.noErrors() && optimizer.applyModifications() > 0 && tries++ < 100000) {
optimizer.visit(this)
}
require(tries<100000) { "endless loop in constantfold" }
if (errors.noErrors()) {
replacer.visit(this)
@@ -10,7 +10,7 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.ICompilationTarget
import prog8.code.core.IErrorReporter
import prog8.code.internedStringsModuleName
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.compiler.CallGraph
@@ -93,7 +93,7 @@ class UnusedCodeRemover(private val program: Program,
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
if("force_output" !in block.options()) {
if (block.containsNoCodeNorVars) {
if (block.name != internedStringsModuleName && "ignore_unused" !in block.options()) {
if (block.name != INTERNED_STRINGS_MODULENAME && "ignore_unused" !in block.options()) {
if (!block.statements.any { it is Subroutine && it.hasBeenInlined })
errors.info("removing unused block '${block.name}'", block.position)
}
+1
View File
@@ -10,6 +10,7 @@ plugins {
dependencies {
implementation(project(":codeCore"))
implementation(project(":simpleAst"))
implementation(project(":codeOptimizers"))
implementation(project(":compilerAst"))
implementation(project(":codeGenCpu6502"))
+2 -1
View File
@@ -17,13 +17,14 @@
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="simpleAst" />
<orderEntry type="module" module-name="compilerAst" />
<orderEntry type="module" module-name="codeOptimizers" />
<orderEntry type="module" module-name="codeGenCpu6502" />
<orderEntry type="module" module-name="codeGenExperimental" />
<orderEntry type="module" module-name="codeGenIntermediate" />
<orderEntry type="module" module-name="virtualmachine" />
<orderEntry type="module" module-name="intermediate" scope="TEST" />
<orderEntry type="module" module-name="intermediate" />
<orderEntry type="library" name="antlr.antlr4" level="project" />
</component>
</module>
+2
View File
@@ -445,6 +445,7 @@ sys {
}
asmsub save_prog8_internals() {
; TODO: Romable
%asm {{
lda P8ZP_SCRATCH_B1
sta save_SCRATCH_ZPB1
@@ -1056,6 +1057,7 @@ cx16 {
&byte r15sH = $1bff
asmsub save_virtual_registers() clobbers(A,Y) {
; TODO: Romable
%asm {{
ldy #31
- lda cx16.r0,y
+3 -3
View File
@@ -334,10 +334,10 @@ hline_zero2
; @(pixaddr) |= ormask[lsb(px) & 7]
; }
inline asmsub plot(uword plotx @XY, ubyte ploty @A) clobbers (A, X, Y) {
inline asmsub plot(uword plotx @AX, ubyte ploty @Y) clobbers (A, X, Y) {
%asm {{
stx p8b_graphics.p8v_internal_plotx
sty p8b_graphics.p8v_internal_plotx+1
sta p8b_graphics.p8v_internal_plotx
stx p8b_graphics.p8v_internal_plotx+1
jsr p8b_graphics.p8s_internal_plot
}}
}
+2
View File
@@ -460,6 +460,7 @@ sys {
}
asmsub save_prog8_internals() {
; TODO: Romable
%asm {{
lda P8ZP_SCRATCH_B1
sta save_SCRATCH_ZPB1
@@ -1072,6 +1073,7 @@ cx16 {
&byte r15sH = $cfff
asmsub save_virtual_registers() clobbers(A,Y) {
; TODO: Romable
%asm {{
ldy #31
- lda cx16.r0,y
+2
View File
@@ -352,6 +352,7 @@ asmsub str2word(str string @AY) -> word @AY {
; the number may be preceded by a + or - sign but may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
; result in AY, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
; TODO: Romable
%asm {{
_result = P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W2
@@ -539,6 +540,7 @@ asmsub internal_uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @
; output in internal_uword2decimal.decTenThousands, decThousands, decHundreds, decTens, decOnes
; (these are terminated by a zero byte so they can be easily printed)
; also returns Y = 100's, A = 10's, X = 1's
; TODO: Romable
%asm {{
+1
View File
@@ -33,6 +33,7 @@
; - call killall() to kill all tasks.
; - IMPORTANT: if you add the same subroutine multiple times, IT CANNOT DEPEND ON ANY LOCAL VARIABLES OR R0-R15 TO KEEP STATE. NOT EVEN REPEAT LOOP COUNTERS.
; Those are all shared in the different tasks! You HAVE to use a mechanism around the userdata value (pointer?) to keep separate state elsewhere!
; - IMPORTANT: ``defer`` cannot be used inside a coroutine that is reused for multiple tasks!!!
coroutines {
%option ignore_unused
+15 -6
View File
@@ -332,7 +332,7 @@ cx16 {
&ubyte VERA_FX_X_POS_S = VERA_BASE + $0009
&ubyte VERA_FX_Y_POS_S = VERA_BASE + $000a
&ubyte VERA_FX_POLY_FILL_L = VERA_BASE + $000b
&ubyte VERA_FX_POLY_FILL_H = VERA_BASE + $000cF
&ubyte VERA_FX_POLY_FILL_H = VERA_BASE + $000c
&uword VERA_FX_POLY_FILL = VERA_BASE + $000b
&ubyte VERA_FX_CACHE_L = VERA_BASE + $0009
&ubyte VERA_FX_CACHE_M = VERA_BASE + $000a
@@ -981,6 +981,7 @@ asmsub vpoke_mask(ubyte bank @A, uword address @R0, ubyte mask @X, ubyte value @
}
asmsub save_virtual_registers() clobbers(A,Y) {
; TODO: Romable
%asm {{
ldy #31
- lda cx16.r0,y
@@ -1010,6 +1011,7 @@ asmsub restore_virtual_registers() clobbers(A,Y) {
asmsub save_vera_context() clobbers(A) {
; -- use this at the start of your IRQ handler if it uses Vera registers, to save the state
; TODO: Romable
%asm {{
; note cannot store this on cpu hardware stack because this gets called as a subroutine
lda cx16.VERA_ADDR_L
@@ -1356,12 +1358,12 @@ _continue iny
sub reset_system() {
; -- Soft-reset the system back to initial power-on Basic prompt.
sys.reset_system()
goto sys.reset_system
}
sub poweroff_system() {
; -- use the SMC to shutdown the computer
void cx16.i2c_write_byte($42, $01, $00)
goto sys.poweroff_system
}
sub set_led_state(bool on) {
@@ -1448,6 +1450,7 @@ _modified
}
asmsub restore_irq() clobbers(A) {
; TODO: Romable
%asm {{
sei
lda _orig_irqvec
@@ -1527,16 +1530,21 @@ asmsub set_rasterline(uword line @AY) {
; (note: this is an asmsub on purpose! don't change into a normal sub)
%asm {{
sei
ldx #$42
- ldx #$42
ldy #2
lda #0
jmp cx16.i2c_write_byte
jsr cx16.i2c_write_byte
bra - ; to work around an issue where the routine does not in fact immediately reset the system
; !notreached!
}}
}
sub poweroff_system() {
; use the SMC to shutdown the computer
void cx16.i2c_write_byte($42, $01, $00)
; in a loop for the event where the shutdown command does not in fact immediately shutdown the system
repeat {
void cx16.i2c_write_byte($42, $01, $00)
}
}
asmsub wait(uword jiffies @AY) clobbers(X) {
@@ -1770,6 +1778,7 @@ _no_msb_size
}
asmsub save_prog8_internals() {
; TODO: Romable
%asm {{
lda P8ZP_SCRATCH_B1
sta save_SCRATCH_ZPB1
+5
View File
@@ -64,6 +64,8 @@ multiply_words .proc
; The routine also works for NEGATIVE (signed) word values, but ONLY the lower 16 bits of the result are correct then!
; Prog8 only uses those so that's not an issue, but math.mul16_last_upper() no longer gives the correct result here.
; TODO: Romable
; mult62.a
; from: https://github.com/TobyLobster/multiply_test/blob/main/tests/mult62.a
; based on Dr Jefyll, http://forum.6502.org/viewtopic.php?f=9&t=689&start=0#p19958
@@ -187,6 +189,7 @@ result .byte 0,0,0,0 ; routine could be faster if this were in Zeropage.
divmod_b_asm .proc
; signed byte division: make everything positive and fix sign afterwards
; TODO: Romable
sta P8ZP_SCRATCH_B1
tya
eor P8ZP_SCRATCH_B1
@@ -286,6 +289,7 @@ divmod_uw_asm .proc
; input: P8ZP_SCRATCH_W1 in ZP: 16 bit number, A/Y: 16 bit divisor
; output: P8ZP_SCRATCH_W2 in ZP: 16 bit remainder, A/Y: 16 bit division result
; division by zero will result in quotient = 65535 and remainder = divident
; TODO: Romable
dividend = P8ZP_SCRATCH_W1
@@ -329,6 +333,7 @@ randword .proc
; -- 16 bit pseudo random number generator into AY
; default seed = $00c2 $1137
; routine from https://codebase64.org/doku.php?id=base:x_abc_random_number_generator_8_16_bit
; TODO: Romable
inc x1
clc
x1=*+1
+2
View File
@@ -360,6 +360,7 @@ _no_msb_size
}
asmsub save_prog8_internals() {
; TODO: Romable
%asm {{
lda P8ZP_SCRATCH_B1
sta save_SCRATCH_ZPB1
@@ -603,6 +604,7 @@ cx16 {
&byte r15sH = $7fff
asmsub save_virtual_registers() clobbers(A,Y) {
; TODO: Romable
%asm {{
ldy #31
- lda cx16.r0,y
+2
View File
@@ -184,6 +184,7 @@ func_sort_uw .proc
; sorting subroutine coded by mats rosengren (mats.rosengren@esa.int)
; input: address of array to sort in P8ZP_SCRATCH_W1, length in A
; first: subtract 2 of the pointer
; TODO: Romable
asl a
sta P8ZP_SCRATCH_B1
lda P8ZP_SCRATCH_W1
@@ -246,6 +247,7 @@ func_sort_w .proc
; sorting subroutine coded by mats rosengren (mats.rosengren@esa.int)
; input: address of array to sort in P8ZP_SCRATCH_W1, length in A
; first: subtract 2 of the pointer
; TODO: Romable
asl a
sta P8ZP_SCRATCH_B1
lda P8ZP_SCRATCH_W1
+30
View File
@@ -146,6 +146,7 @@ memcopy16_up .proc
memset .proc
; -- fill memory from (P8ZP_SCRATCH_W1), length XY, with value in A.
; clobbers X, Y
; TODO: Romable
stx P8ZP_SCRATCH_B1
sty _save_reg
ldy #0
@@ -268,6 +269,7 @@ strcpy .proc
strcmp_expression .proc
; -- compare strings, result in A
; TODO: Romable
lda _arg_s2
ldy _arg_s2+1
sta P8ZP_SCRATCH_W2
@@ -302,6 +304,34 @@ _c1_zero lda (P8ZP_SCRATCH_W2),y
+ rts
.pend
strncmp_mem .proc
; -- compares strings in s1 (AY) and s2 (P8ZP_SCRATCH_W2).
; Compares up to maximum length specified in X.
; Returns -1,0,1 in A, depending on the ordering. Clobbers X & Y.
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
_loop lda (P8ZP_SCRATCH_W1),y
beq _c1_zero
cmp (P8ZP_SCRATCH_W2),y
beq _equal
bmi _less
lda #1
rts
_less lda #-1
rts
_equal dex
bne +
lda #0
rts
+ iny
bne _loop
_c1_zero lda (P8ZP_SCRATCH_W2),y
beq +
lda #-1
+ rts
.pend
strlen .proc
; -- returns the number of bytes in the string in AY, in Y. Clobbers A.
@@ -140,6 +140,7 @@ compression {
; -- Decodes "ByteRun1" (aka PackBits) RLE compressed data. Control byte value 128 ends the decoding.
; Also stops decompressing when the maxsize has been reached. Returns the size of the decompressed data.
; Instead of a source buffer, you provide a callback function that must return the next byte to compress in A.
; TODO: Romable
%asm {{
sta _cb_mod1+1
sty _cb_mod1+2
+14
View File
@@ -154,6 +154,7 @@ _found tya
asmsub rfind(uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc {
; Locates the first position of the given character in the string, starting from the right.
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
; TODO: Romable
%asm {{
stx P8ZP_SCRATCH_B1
sta _str
@@ -242,6 +243,19 @@ _str .word 0
}}
}
asmsub ncompare(uword string1 @R0, uword string2 @AY, ubyte length @X) clobbers(X, Y) -> byte @A {
; Compares two strings for sorting.
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
; Only compares the strings from index 0 up to the length argument.
%asm {{
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
lda cx16.r0
ldy cx16.r0+1
jmp prog8_lib.strncmp_mem
}}
}
asmsub lower(uword st @AY) -> ubyte @Y {
; Lowercases the petscii string in-place. Returns length of the string.
; (for efficiency, non-letter characters > 128 will also not be left intact,
+4
View File
@@ -27,6 +27,7 @@ diskio {
; Note that no list of pointers of some form is returned, the names are just squashed together.
; If you really need a list of pointers to the names, that is pretty straightforward to construct by iterating over the names
; and registering when the next one starts after the 0-byte separator.
; TODO IMPLEMENT THIS
txt.print("@TODO: list_filenames\n")
sys.clear_carry()
return 0
@@ -37,6 +38,7 @@ diskio {
sub lf_start_list(uword pattern_ptr) -> bool {
; -- start an iterative file listing with optional pattern matching.
; note: only a single iteration loop can be active at a time!
; TODO IMPLEMENT THIS
txt.print("@TODO: lf_start_list\n")
return false
}
@@ -45,11 +47,13 @@ diskio {
; -- retrieve the next entry from an iterative file listing session.
; results will be found in list_blocks, list_filename, and list_filetype.
; if it returns false though, there are no more entries (or an error occurred).
; TODO IMPLEMENT THIS
txt.print("@TODO: lf_next_entry\n")
return false
}
sub lf_end_list() {
; TODO IMPLEMENT THIS
txt.print("@TODO: lf_end_list\n")
}
+5 -1
View File
@@ -336,6 +336,9 @@ fun parseMainModule(filepath: Path,
errors.err("BASIC launcher requires output type PRG", program.toplevelModule.position)
}
if(compilerOptions.romable && compilerOptions.floats)
errors.err("When ROMable code is selected, floating point support is not available", program.toplevelModule.position)
errors.report()
return Triple(program, compilerOptions, importedFiles)
@@ -352,6 +355,7 @@ internal fun determineCompilationOptions(program: Program, compTarget: ICompilat
val allOptions = program.modules.flatMap { it.options() }.toSet()
val floatsEnabled = "enable_floats" in allOptions
var noSysInit = "no_sysinit" in allOptions
var rombale = "romable" in allOptions
var zpType: ZeropageType =
if (zpoption == null)
if (floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
@@ -408,7 +412,7 @@ internal fun determineCompilationOptions(program: Program, compTarget: ICompilat
return CompilationOptions(
outputType, launcherType,
zpType, zpReserved, zpAllowed, floatsEnabled, noSysInit,
zpType, zpReserved, zpAllowed, floatsEnabled, noSysInit, rombale,
compTarget, 0u, 0xffffu
)
}
@@ -79,9 +79,14 @@ class ModuleImporter(private val program: Program,
if("$moduleName.p8" == import.position.file)
throw SyntaxError("cannot import self", import.position)
val existing = program.modules.singleOrNull { it.name == moduleName }
if (existing!=null)
val existing = program.modules.singleOrNull { it.name.equals(moduleName, ignoreCase = true) }
if (existing!=null) {
if(existing.name != moduleName) {
errors.err("module import name '$moduleName' differs in case only from already known name '${existing.name}'", import.position)
return null
}
return existing
}
// try internal library first
val moduleResourceSrc = getModuleFromResource("$moduleName.p8", compilationTargetName)
@@ -1033,14 +1033,14 @@ internal class AstChecker(private val program: Program,
err("this directive may only occur in a block or at module level")
if(directive.args.isEmpty())
err("missing option directive argument(s)")
else if(directive.args.map{it.string in arrayOf("enable_floats", "force_output", "no_sysinit", "merge", "verafxmuls", "no_symbol_prefixing", "ignore_unused")}.any { !it })
else if(directive.args.map{it.string in arrayOf("enable_floats", "force_output", "no_sysinit", "merge", "verafxmuls", "no_symbol_prefixing", "ignore_unused", "romable")}.any { !it })
err("invalid option directive argument(s)")
if(directive.parent is Block) {
if(directive.args.any {it.string !in arrayOf("force_output", "merge", "verafxmuls", "no_symbol_prefixing", "ignore_unused")})
err("using an option that is not valid for blocks")
}
if(directive.parent is Module) {
if(directive.args.any {it.string !in arrayOf("enable_floats", "no_sysinit", "no_symbol_prefixing", "ignore_unused")})
if(directive.args.any {it.string !in arrayOf("enable_floats", "no_sysinit", "no_symbol_prefixing", "ignore_unused", "romable")})
err("using an option that is not valid for modules")
}
if(directive.args.any { it.string=="verafxmuls" } && compilerOptions.compTarget.name != Cx16Target.NAME)
@@ -1276,17 +1276,6 @@ internal class AstChecker(private val program: Program,
// str+str and str*number have already been const evaluated before we get here.
errors.err("no computational or logical expressions with strings or arrays are possible", expr.position)
}
} else {
if(expr.left is TypecastExpression && expr.right is NumericLiteral && !(expr.right.inferType(program) issimpletype BaseDataType.FLOAT)) {
val origLeftDt = (expr.left as TypecastExpression).expression.inferType(program).getOrUndef()
if(rightDt.largerSizeThan(origLeftDt) && !(expr.right as NumericLiteral).cast(origLeftDt.base, true).isValid)
errors.err("operands are not the same type", expr.right.position)
}
if(expr.right is TypecastExpression && expr.left is NumericLiteral && !(expr.left.inferType(program) issimpletype BaseDataType.FLOAT)) {
val origRightDt = (expr.right as TypecastExpression).expression.inferType(program).getOrUndef()
if(leftDt.largerSizeThan(origRightDt) && !(expr.left as NumericLiteral).cast(origRightDt.base, true).isValid)
errors.err("operands are not the same type", expr.right.position)
}
}
if(leftDt.isBool || rightDt.isBool ||
@@ -1386,8 +1375,10 @@ internal class AstChecker(private val program: Program,
if(targetStatement!=null)
checkFunctionCall(targetStatement, functionCallExpr.args, functionCallExpr.position)
val builtinFunctionName = functionCallExpr.target.nameInSource.singleOrNull()
// warn about sgn(unsigned) this is likely a mistake
if(functionCallExpr.target.nameInSource.last()=="sgn") {
if(builtinFunctionName=="sgn") {
val sgnArgType = functionCallExpr.args.first().inferType(program)
if(sgnArgType issimpletype BaseDataType.UBYTE || sgnArgType issimpletype BaseDataType.UWORD)
errors.warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCallExpr.args.first().position)
@@ -1411,7 +1402,7 @@ internal class AstChecker(private val program: Program,
}
}
if(functionCallExpr.target.nameInSource.singleOrNull() in listOf("peek", "peekw")) {
if(builtinFunctionName in listOf("peek", "peekw")) {
val pointervar = functionCallExpr.args[0] as? IdentifierReference
if(pointervar!=null)
checkPointer(pointervar)
@@ -1420,6 +1411,14 @@ internal class AstChecker(private val program: Program,
checkPointer(binexpr.left as IdentifierReference)
}
if(builtinFunctionName=="memory") {
val str = functionCallExpr.args[0] as? StringLiteral
if(str==null)
errors.err("memory name argument must be a string literal", functionCallExpr.args[0].position)
else if(str.value.isEmpty())
errors.err("memory name argument cannot be empty string", functionCallExpr.args[0].position)
}
super.visit(functionCallExpr)
}
@@ -1523,8 +1522,11 @@ internal class AstChecker(private val program: Program,
}
}
if(target is Label && args.isNotEmpty())
errors.err("cannot use arguments when calling a label", position)
if (target is Label) {
errors.warn("\uD83D\uDCA3 footgun: calling label as subroutine (JSR) is tricky", position)
if (args.isNotEmpty())
errors.err("cannot use arguments when calling a label", position)
}
if(target is Subroutine) {
if(target.isAsmSubroutine) {
@@ -1664,7 +1666,7 @@ internal class AstChecker(private val program: Program,
val constvalues = whenChoice.values!!.map { it.constValue(program) to it.position }
for((constvalue, pos) in constvalues) {
when {
constvalue == null -> errors.err("choice value must be a constant", pos)
constvalue == null -> errors.err("choice values must be constant numbers", pos)
!constvalue.type.isIntegerOrBool -> errors.err("choice value must be a byte or word", pos)
!(conditionType issimpletype constvalue.type) -> {
if(conditionType.isKnown) {
@@ -6,7 +6,7 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.code.core.*
import prog8.code.internedStringsModuleName
import prog8.code.INTERNED_STRINGS_MODULENAME
internal class VerifyFunctionArgTypes(val program: Program, val options: CompilationOptions, val errors: IErrorReporter) : IAstVisitor {
@@ -22,7 +22,7 @@ internal class VerifyFunctionArgTypes(val program: Program, val options: Compila
}
// remove unused strings from interned strings block
val internedBlock = program.allBlocks.singleOrNull { it.name == internedStringsModuleName }
val internedBlock = program.allBlocks.singleOrNull { it.name == INTERNED_STRINGS_MODULENAME }
internedBlock?.statements?.withIndex()?.reversed()?.forEach { (index, st) ->
if(st is VarDecl && st.scopedName !in allStringRefs) {
internedBlock.statements.removeAt(index)
+3 -3
View File
@@ -12,7 +12,7 @@ import io.kotest.matchers.string.shouldContain
import prog8.ast.Program
import prog8.code.core.IErrorReporter
import prog8.code.source.SourceCode
import prog8.code.internedStringsModuleName
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.compiler.ModuleImporter
import prog8.parser.ParseError
import prog8tests.helpers.*
@@ -176,7 +176,7 @@ class TestModuleImporter: FunSpec({
}
}
withClue("imported module with error in it should not be present") { program.modules.size shouldBe 1 }
program.modules[0].name shouldBe internedStringsModuleName
program.modules[0].name shouldBe INTERNED_STRINGS_MODULENAME
}
}
@@ -258,7 +258,7 @@ class TestModuleImporter: FunSpec({
}
}
withClue("imported module with error in it should not be present") { program.modules.size shouldBe 1 }
program.modules[0].name shouldBe internedStringsModuleName
program.modules[0].name shouldBe INTERNED_STRINGS_MODULENAME
importer.errors.report()
}
}
+1
View File
@@ -19,6 +19,7 @@ class TestGoldenRam: FunSpec({
CompilationOptions.AllZeropageAllowed,
floats = true,
noSysInit = false,
romable = false,
compTarget = VMTarget(),
loadAddress = 999u,
memtopAddress = 0xffffu
@@ -7,7 +7,7 @@ import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.string.shouldStartWith
import prog8.code.core.ZeropageType
import prog8.code.internedStringsModuleName
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.VMTarget
@@ -40,7 +40,7 @@ main {
}
withClue("module order in parse tree") {
moduleNames.drop(1) shouldBe listOf(
internedStringsModuleName,
INTERNED_STRINGS_MODULENAME,
"textio",
"syslib",
"conv",
@@ -101,7 +101,7 @@ main {
withClue("module order in parse tree") {
program.modules.map { it.name } shouldBe
listOf(
internedStringsModuleName,
INTERNED_STRINGS_MODULENAME,
filenameBase,
"textio", "syslib", "conv", "shared_cbm_textio_functions", "floats", "shared_floats_functions", "prog8_math", "prog8_lib"
)
+16
View File
@@ -1159,4 +1159,20 @@ main {
compileText(VMTarget(), true, src, writeAssembly = true) shouldNotBe null
compileText(C64Target(), true, src, writeAssembly = true) shouldNotBe null
}
test("boolean comparisons without optimization can be assembled") {
val src="""
main {
sub start() {
bool @shared pre_start, xxx
if (pre_start != false and xxx) {
return
} else if (pre_start != false and xxx) {
return
}
}
}"""
compileText(C64Target(), false, src, writeAssembly = true) shouldNotBe null
}
})
+38
View File
@@ -445,4 +445,42 @@ WARN name 'var1Warn' shadows occurrence at...
errors.errors[3] shouldContain "internalOk"
errors.errors[3] shouldContain "line 11"
}
test("ast node linkage and lookups ok even with no symbol prefixing") {
val src="""
main {
sub start() {
thing.routine()
}
}
thing {
%option no_symbol_prefixing
sub routine() {
other.something()
other.counter++
other.asmsomething()
}
}
other {
sub something() {
}
asmsub asmsomething() {
%asm {{
nop
rts
}}
}
uword @shared counter
}
"""
compileText(C64Target(), false, src, writeAssembly = true) shouldNotBe null
}
})
+13
View File
@@ -954,4 +954,17 @@ main {
(v4.right as NumericLiteral).type shouldBe BaseDataType.UWORD
(v4.right as NumericLiteral).number shouldBe 5
}
test("allow comparisons against constant values with different type") {
val src = """
main {
sub start() {
const uword MAX_CAVE_WIDTH = 440 ; word here to avoid having to cast to word all the time
if cx16.r0L > MAX_CAVE_WIDTH
return
}
}"""
compileText(C64Target(), false, src, writeAssembly = false) shouldNotBe null
}
})
+31 -29
View File
@@ -47,6 +47,7 @@ class TestAbstractZeropage: FunSpec({
CompilationOptions.AllZeropageAllowed,
floats = false,
noSysInit = false,
romable = false,
compTarget = C64Target(),
loadAddress = 999u,
memtopAddress = 0xffffu
@@ -67,6 +68,7 @@ class TestC64Zeropage: FunSpec({
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed,
floats = false,
noSysInit = false,
romable = false,
compTarget = c64target, loadAddress = 999u, memtopAddress = 0xffffu
))
@@ -82,33 +84,33 @@ class TestC64Zeropage: FunSpec({
}
test("testZpFloatEnable") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
var result = zp.allocate("", DataType.forDt(BaseDataType.FLOAT), null, null, errors)
result.expectError { "should be allocation error due to disabled floats" }
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u, 0xffffu))
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
result = zp2.allocate("", DataType.forDt(BaseDataType.FLOAT), null, null, errors)
result.expectError { "should be allocation error due to disabled ZP use" }
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u, 0xffffu))
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
zp3.allocate("", DataType.forDt(BaseDataType.FLOAT), null, null, errors)
}
test("testZpModesWithFloats") {
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u, 0xffffu))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u, 0xffffu))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
shouldThrow<InternalCompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u, 0xffffu))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
}
shouldThrow<InternalCompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u, 0xffffu))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
}
}
test("testZpDontuse") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
println(zp.free)
zp.availableBytes() shouldBe 0
val result = zp.allocate("", DataType.forDt(BaseDataType.BYTE), null, null, errors)
@@ -116,13 +118,13 @@ class TestC64Zeropage: FunSpec({
}
test("testFreeSpacesBytes") {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u, 0xffffu))
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
zp1.availableBytes() shouldBe 17
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
zp2.availableBytes() shouldBe 87
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
zp3.availableBytes() shouldBe 96
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
zp4.availableBytes() shouldBe 207
zp4.allocate("test", DataType.forDt(BaseDataType.UBYTE), null, null, errors)
zp4.availableBytes() shouldBe 206
@@ -131,7 +133,7 @@ class TestC64Zeropage: FunSpec({
}
test("testReservedSpace") {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
zp1.availableBytes() shouldBe 207
4u shouldNotBeIn zp1.free
35u shouldNotBeIn zp1.free
@@ -142,7 +144,7 @@ class TestC64Zeropage: FunSpec({
200u shouldBeIn zp1.free
255u shouldBeIn zp1.free
199u shouldBeIn zp1.free
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
zp2.availableBytes() shouldBe 107
4u shouldNotBeIn zp2.free
35u shouldNotBeIn zp2.free
@@ -153,14 +155,14 @@ class TestC64Zeropage: FunSpec({
200u shouldNotBeIn zp2.free
255u shouldNotBeIn zp2.free
199u shouldBeIn zp2.free
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
zp2.availableBytes() shouldBe 107
4u shouldBeIn zp3.free
35u shouldNotBeIn zp3.free
}
test("testBasicsafeAllocation") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u, 0xffffu))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
zp.availableBytes() shouldBe 17
zp.hasByteAvailable() shouldBe true
zp.hasWordAvailable() shouldBe true
@@ -182,7 +184,7 @@ class TestC64Zeropage: FunSpec({
}
test("testFullAllocation") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
zp.availableBytes() shouldBe 207
zp.hasByteAvailable() shouldBe true
zp.hasWordAvailable() shouldBe true
@@ -213,7 +215,7 @@ class TestC64Zeropage: FunSpec({
}
test("testEfficientAllocation") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u, 0xffffu))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
zp.availableBytes() shouldBe 17
zp.allocate("", DataType.forDt(BaseDataType.WORD), null, null, errors).getOrElse{throw it}.address shouldBe 0x04u
zp.allocate("", DataType.forDt(BaseDataType.UBYTE), null, null, errors).getOrElse{throw it}.address shouldBe 0x06u
@@ -231,7 +233,7 @@ class TestC64Zeropage: FunSpec({
}
test("testReservedLocations") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
withClue("zp _B1 and _REG must be next to each other to create a word") {
zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG
}
@@ -244,18 +246,18 @@ class TestCx16Zeropage: FunSpec({
val cx16target = Cx16Target()
test("testReservedLocations") {
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, cx16target, 999u, 0xffffu))
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, 999u, 0xffffu))
withClue("zp _B1 and _REG must be next to each other to create a word") {
zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG
}
}
test("testFreeSpacesBytes") {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, cx16target, 999u, 0xffffu))
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, cx16target, 999u, 0xffffu))
zp1.availableBytes() shouldBe 88
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, cx16target, 999u, 0xffffu))
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, 999u, 0xffffu))
zp2.availableBytes() shouldBe 175
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, cx16target, 999u, 0xffffu))
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, 999u, 0xffffu))
zp3.availableBytes() shouldBe 216
zp3.allocate("test", DataType.forDt(BaseDataType.UBYTE), null, null, errors)
zp3.availableBytes() shouldBe 215
@@ -264,7 +266,7 @@ class TestCx16Zeropage: FunSpec({
}
test("testReservedSpace") {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, cx16target, 999u, 0xffffu))
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, 999u, 0xffffu))
zp1.availableBytes() shouldBe 216
0x22u shouldBeIn zp1.free
0x80u shouldBeIn zp1.free
@@ -274,7 +276,7 @@ class TestCx16Zeropage: FunSpec({
}
test("preallocated zp vars") {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, cx16target, 999u, 0xffffu))
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, 999u, 0xffffu))
zp1.allocatedVariables["test"] shouldBe null
zp1.allocatedVariables["cx16.r0"] shouldNotBe null
zp1.allocatedVariables["cx16.r15"] shouldNotBe null
+2 -2
View File
@@ -7,7 +7,7 @@ import prog8.ast.AstToSourceTextConverter
import prog8.ast.Module
import prog8.ast.Program
import prog8.code.source.SourceCode
import prog8.code.internedStringsModuleName
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.parser.ParseError
import prog8.parser.Prog8Parser.parseModule
import prog8tests.helpers.DummyFunctions
@@ -42,7 +42,7 @@ class TestAstToSourceText: AnnotationSpec() {
fun testMentionsInternedStringsModule() {
val orig = SourceCode.Text("\n")
val (txt, _) = roundTrip(parseModule(orig))
txt shouldContain Regex(";.*$internedStringsModuleName")
txt shouldContain Regex(";.*$INTERNED_STRINGS_MODULENAME")
}
@Test
+3 -3
View File
@@ -15,7 +15,7 @@ import prog8.ast.statements.Block
import prog8.code.ast.PtBlock
import prog8.code.core.Position
import prog8.code.source.SourceCode
import prog8.code.internedStringsModuleName
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.target.C64Target
import prog8tests.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer
@@ -28,7 +28,7 @@ class TestProgram: FunSpec({
test("withNameBuiltinsAndMemsizer") {
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
program.modules.size shouldBe 1
program.modules[0].name shouldBe internedStringsModuleName
program.modules[0].name shouldBe INTERNED_STRINGS_MODULENAME
program.modules[0].program shouldBeSameInstanceAs program
program.modules[0].parent shouldBeSameInstanceAs program.namespace
}
@@ -64,7 +64,7 @@ class TestProgram: FunSpec({
test("withInternedStringsModule") {
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
val m = program.modules[0]
m.name shouldBe internedStringsModuleName
m.name shouldBe INTERNED_STRINGS_MODULENAME
val retVal = program.moveModuleToFront(m)
retVal shouldBeSameInstanceAs program
@@ -400,6 +400,45 @@ main {
cx16.r2 = &addresses
}
}
}"""
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
compileText(C64Target(), optimize=false, src, writeAssembly=true, errors=errors) shouldNotBe null
errors.errors.size shouldBe 0
errors.warnings.size shouldBe 0
}
test("assigning address of split arrays without explicitly taking address works") {
val src="""
main {
sub start() {
uword[] texts1 = [ 1,2,3 ]
uword[] @nosplit texts2 = [ 1,2,3 ]
cx16.r4 = texts1
cx16.r5 = texts2
; txt.print_uwhex(&texts1, true)
; txt.spc()
; txt.print_uwhex(cx16.r4, true)
; txt.nl()
; txt.print_uwhex(&texts2, true)
; txt.spc()
; txt.print_uwhex(cx16.r5, true)
; txt.nl()
cx16.r4 = &texts1
cx16.r5 = &texts2
; txt.print_uwhex(&texts1, true)
; txt.spc()
; txt.print_uwhex(cx16.r4, true)
; txt.nl()
; txt.print_uwhex(&texts2, true)
; txt.spc()
; txt.print_uwhex(cx16.r5, true)
; txt.nl()
}
}"""
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
compileText(C64Target(), optimize=false, src, writeAssembly=true, errors=errors) shouldNotBe null
@@ -78,7 +78,7 @@ class TestAsmGenSymbols: StringSpec({
fun createTestAsmGen6502(program: Program): AsmGen6502Internal {
val errors = ErrorReporterForTests()
val options = CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, true, C64Target(), 999u, 0xffffu)
val options = CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, true, false, C64Target(), 999u, 0xffffu)
val astchecker = AstChecker(program, errors, options)
astchecker.visit(program)
errors.report()
@@ -22,6 +22,13 @@ main {
txt.print("name2 fail!\n")
}
if (strings.ncompare(name, "aaa", 3)==0) or (strings.ncompare(name, "johm", 3)==0) or (strings.ncompare(name, "bbb", 3)==0) {
txt.print("name1 ok\n")
}
if (strings.ncompare(name, "aaa", 2)==0) or (strings.ncompare(name, "zzz", 2)==0) or (strings.ncompare(name, "bbb", 2)==0) {
txt.print("name2 fail!\n")
}
if name=="aaa" or name=="john" or name=="bbb"
txt.print("name1b ok\n")
if name=="aaa" or name=="zzz" or name=="bbb"
+11 -11
View File
@@ -4,9 +4,9 @@ import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.StringLiteral
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.code.ast.PtLabel
import prog8.code.GENERATED_LABEL_PREFIX
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.core.*
import prog8.code.internedStringsModuleName
import prog8.code.source.SourceCode
/*********** Everything starts from here, the Program; zero or more modules *************/
@@ -23,8 +23,8 @@ class Program(val name: String,
init {
// insert a container module for all interned strings later
val internedStringsModule = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated(internedStringsModuleName))
val block = Block(internedStringsModuleName, null, mutableListOf(), true, Position.DUMMY)
val internedStringsModule = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated(INTERNED_STRINGS_MODULENAME))
val block = Block(INTERNED_STRINGS_MODULENAME, null, mutableListOf(), true, Position.DUMMY)
val directive = Directive("%option", listOf(DirectiveArg("no_symbol_prefixing", null, Position.DUMMY)), Position.DUMMY)
block.statements.add(directive)
directive.linkParents(block)
@@ -67,7 +67,7 @@ class Program(val name: String,
}
val toplevelModule: Module
get() = modules.first { it.name!= internedStringsModuleName }
get() = modules.first { it.name!= INTERNED_STRINGS_MODULENAME }
private val internedStringsReferenceCounts = mutableMapOf<VarDecl, Int>()
@@ -81,8 +81,8 @@ class Program(val name: String,
}
val internedStringsBlock = modules
.first { it.name == internedStringsModuleName }.statements
.first { it is Block && it.name == internedStringsModuleName } as Block
.first { it.name == INTERNED_STRINGS_MODULENAME }.statements
.first { it is Block && it.name == INTERNED_STRINGS_MODULENAME } as Block
fun addNewInternedStringvar(string: StringLiteral): Pair<List<String>, VarDecl> {
val varName = "string_${internedStringsBlock.statements.size}"
@@ -93,7 +93,7 @@ class Program(val name: String,
)
internedStringsBlock.statements.add(decl)
decl.linkParents(internedStringsBlock)
return Pair(listOf(internedStringsModuleName, decl.name), decl)
return Pair(listOf(INTERNED_STRINGS_MODULENAME, decl.name), decl)
}
val existingDecl = internedStringsBlock.statements.filterIsInstance<VarDecl>().singleOrNull {
@@ -133,8 +133,8 @@ class Program(val name: String,
fun removeStrings(modules: List<Module>) {
if(removals.isNotEmpty()) {
val internedStringsBlock = modules
.first { it.name == internedStringsModuleName }.statements
.first { it is Block && it.name == internedStringsModuleName } as Block
.first { it.name == INTERNED_STRINGS_MODULENAME }.statements
.first { it is Block && it.name == INTERNED_STRINGS_MODULENAME } as Block
removals.forEach { scopedname ->
val decl = internedStringsBlock.statements.filterIsInstance<VarDecl>().single { decl -> decl.scopedName == scopedname }
val numRefs = program.internedStringsReferenceCounts.getValue(decl) - 1
@@ -151,7 +151,7 @@ class Program(val name: String,
fun makeLabel(postfix: String): String {
generatedLabelSequenceNumber++
return "${PtLabel.GENERATED_LABEL_PREFIX}${generatedLabelSequenceNumber}_$postfix"
return "$GENERATED_LABEL_PREFIX${generatedLabelSequenceNumber}_$postfix"
}
fun makeLabel(postfix: String, position: Position): Label {
@@ -9,7 +9,7 @@ import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor
import prog8.code.core.*
import prog8.code.internedStringsModuleName
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.target.encodings.JapaneseCharacterConverter
import java.io.CharConversionException
import java.util.*
@@ -112,6 +112,7 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
BaseDataType.UBYTE -> NumericLiteral(BaseDataType.UBYTE, (constval.number.toInt().inv() and 255).toDouble(), constval.position)
BaseDataType.WORD -> NumericLiteral(BaseDataType.WORD, constval.number.toInt().inv().toDouble(), constval.position)
BaseDataType.UWORD -> NumericLiteral(BaseDataType.UWORD, (constval.number.toInt().inv() and 65535).toDouble(), constval.position)
BaseDataType.LONG -> NumericLiteral(BaseDataType.LONG, constval.number.toInt().inv().toDouble(), constval.position)
else -> throw ExpressionError("can only take bitwise inversion of int", constval.position)
}
"not" -> NumericLiteral.fromBoolean(constval.number==0.0, constval.position)
@@ -1250,7 +1251,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
return false
val scope=decl.definingModule
return scope.name==internedStringsModuleName
return scope.name==INTERNED_STRINGS_MODULENAME
}
}
@@ -1359,6 +1360,14 @@ class ContainmentCheck(var element: Expression,
return NumericLiteral.fromBoolean(exists, position)
}
}
is RangeExpression -> {
if(elementConst.type.isInteger) {
val intprogression = (iterable as RangeExpression).toConstantIntegerRange()
if (intprogression!=null) {
return NumericLiteral.fromBoolean(intprogression.contains(elementConst.number.toInt()), position)
}
}
}
else -> {}
}
}
@@ -1,5 +1,5 @@
Prog8 compiler v11.1 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v11.2 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-c128.p8
@@ -246,6 +246,7 @@ strings {
lstripped (str s) -> str
ltrim (str s)
ltrimmed (str s) -> str
ncompare (uword string1 @R0, uword string2 @AY, ubyte length @X) -> clobbers (X,Y) -> byte @A
pattern_match (str string @AY, str pattern @R0) -> clobbers (Y) -> bool @A
rfind (uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc
right (uword source @AY, ubyte length @X, uword target @R1) -> clobbers (A,Y)
@@ -1,5 +1,5 @@
Prog8 compiler v11.1 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v11.2 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-c64.p8
@@ -250,7 +250,7 @@ graphics {
horizontal_line (uword xx, ubyte yy, uword length)
internal_plot (ubyte ploty @Y) -> clobbers (A,X,Y)
line (uword x1, ubyte y1, uword x2, ubyte y2)
plot (uword plotx @XY, ubyte ploty @A) -> clobbers (A,X,Y)
plot (uword plotx @AX, ubyte ploty @Y) -> clobbers (A,X,Y)
rect (uword xx, ubyte yy, uword width, ubyte height)
vertical_line (uword xx, ubyte yy, ubyte height)
}
@@ -370,6 +370,7 @@ strings {
lstripped (str s) -> str
ltrim (str s)
ltrimmed (str s) -> str
ncompare (uword string1 @R0, uword string2 @AY, ubyte length @X) -> clobbers (X,Y) -> byte @A
pattern_match (str string @AY, str pattern @R0) -> clobbers (Y) -> bool @A
rfind (uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc
right (uword source @AY, ubyte length @X, uword target @R1) -> clobbers (A,Y)
@@ -1,5 +1,5 @@
Prog8 compiler v11.1 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v11.2 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-cx16.p8
@@ -673,6 +673,7 @@ strings {
lstripped (str s) -> str
ltrim (str s)
ltrimmed (str s) -> str
ncompare (uword string1 @R0, uword string2 @AY, ubyte length @X) -> clobbers (X,Y) -> byte @A
pattern_match (str string @AY, str pattern @R0) -> clobbers (Y) -> bool @A
rfind (uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc
right (uword source @AY, ubyte length @X, uword target @R1) -> clobbers (A,Y)
@@ -1,5 +1,5 @@
Prog8 compiler v11.1 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v11.2 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-pet32.p8
@@ -199,6 +199,7 @@ strings {
lstripped (str s) -> str
ltrim (str s)
ltrimmed (str s) -> str
ncompare (uword string1 @R0, uword string2 @AY, ubyte length @X) -> clobbers (X,Y) -> byte @A
pattern_match (str string @AY, str pattern @R0) -> clobbers (Y) -> bool @A
rfind (uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc
right (uword source @AY, ubyte length @X, uword target @R1) -> clobbers (A,Y)
@@ -1,5 +1,5 @@
Prog8 compiler v11.1 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v11.2 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-virtual.p8
+3 -1
View File
@@ -28,6 +28,7 @@ You can compile programs for various machines that are built in into the compile
* Commodore PET (limited support)
* any other 65(C)02 target machine or setup can be configured to a great extent in a user written configuration file.
There are some examples included for the Atari 800 XL, NEO6502 and such.
* some users have been experimenting with a NES and a C64OS target as well.
Some language features are mentioned below, and you can also read :ref:`comparingprog8` if you
want to quickly read about how Prog8 compares to well-known other languages.
@@ -95,6 +96,8 @@ Features
and inline assembly to have full control when every register, cycle or byte matters
- Variables are all allocated statically, no memory allocation overhead
- Variable data types include signed and unsigned bytes and words, arrays, strings.
- Tight control over Zeropage usage
- Programs can be restarted after exiting (i.e. run them multiple times without having to reload everything), due to automatic variable (re)initializations.
- Conditional branches for status flags that map 1:1 to processor branch instructions for optimal efficiency
- ``when`` statement to avoid if-else chains
- ``in`` expression for concise and efficient multi-value/containment test
@@ -109,7 +112,6 @@ Features
- Identifiers can contain Unicode Letters, so ``knäckebröd``, ``приблизительно``, ``見せしめ`` and ``π`` are all valid identifiers.
- Subroutines can return more than one result value
- Advanced code optimizations to make the resulting program smaller and faster
- Programs can be restarted after exiting (i.e. run them multiple times without having to reload everything), due to automatic variable (re)initializations.
- Supports the sixteen 'virtual' 16-bit registers R0 to R15 as defined on the Commander X16. You can look at them as general purpose global variables. These are also available on the other compilation targets!
- On the Commander X16: Support for low level system features such as Vera Fx, which includes 16x16 bits multiplication in hardware and fast memory copy and fill.
- 50 Kb of available program RAM size on the C64 by default; because Basic ROM is banked out altogether
+8 -1
View File
@@ -187,7 +187,8 @@ sizeof (name) ; sizeof (number)
memory (name, size, alignment)
Returns the address of the first location of a statically "reserved" block of memory of the given size in bytes,
with the given name. The block is *uninitialized memory*; unlike other variables in Prog8 it is *not* set to zero at the start of the program!
with the given name. The name must be a string literal, it cannot be empty or be a variable.
The block is *uninitialized memory*; unlike other variables in Prog8 it is *not* set to zero at the start of the program!
(if that is required, you can do so yourself using ``memset``).
If you specify an alignment value >1, it means the block of memory will
be aligned to such a dividable address in memory, for instance an alignment of $100 means the
@@ -899,6 +900,12 @@ Provides string manipulation routines.
using ``==``, ``<`` etcetera (it will use strings.compare for you under water automatically).
This even works when dealing with uword (pointer) variables when comparing them to a string type.
``ncompare (string1, string2, length) -> ubyte result``
Compares two strings up to the number of characters in the length parameter.
Returns -1, 0 or 1 depending on whether string1 sorts before, equal or after string2.
Note that lengths of 0 or 1 evaluate the same. The first character is always compared.
A length larger than either string will function identically to compare.
``copy (from, to) -> ubyte length``
Copy a string to another, overwriting that one. Returns the length of the string that was copied.
Often you don't have to call this explicitly and can just write ``string1 = string2``
+6 -4
View File
@@ -63,10 +63,11 @@ Subroutine
Label
This is a named position in your code where you can jump to from another place.
You can jump to it with a jump statement elsewhere. It is also possible to use a
subroutine call to a label (but without parameters and return value).
A label is an identifier followed by a colon ``:``. It's ok to put the next statement on
the same line, immediately after the label.
You can jump to it with a goto statement. It is also possible to use a
subroutine call to a label (but without parameters and return value), however 🦶🔫 footgun warning:
doing that is tricky because it makes for weird control flow and interferes with defers.
Scope
Also known as 'namespace', this is a named box around the symbols defined in it.
@@ -429,6 +430,7 @@ Directives
- ``ignore_unused`` (block or module) suppress warnings about unused variables and subroutines. Instead, these will be silently stripped.
This option is useful in library modules that contain many more routines beside the ones that you actually use.
- ``verafxmuls`` (block, cx16 target only) uses Vera FX hardware word multiplication on the CommanderX16 for all word multiplications in this block. Warning: this may interfere with IRQs and other Vera operations, so use this only when you know what you're doing. It's safer to explicitly use ``verafx.muls()``.
- ``romable`` (module) *WORK-IN-PROGRESS/EXPERIMENTAL* make sure that the generated code is suitable for running in ROM (so no self-modifying code and such)
.. data:: %output <type>
@@ -1170,9 +1172,9 @@ So for instance::
asmsub multisub() -> uword @AY, bool @Pc, ubyte @X { ... }
.. sidebar:: usage of cx16.r0-cx16.r15
.. sidebar:: register usage
Subroutines with multiple return values use the "virtual registers" to return those.
Subroutines with multiple return values use cpu registers A, Y, and the R0-R15 "virtual registers" to return those.
Using those virtual registers during the calculation of the values in the return statement should be avoided.
Otherwise you risk overwriting an earlier return value in the sequence.
+3 -2
View File
@@ -155,8 +155,9 @@ Regular subroutines
- for an ``asmsub`` or ``extsub`` the subroutine's signature specifies the output registers that contain the values explicitly,
just as for a single return value.
- for regular subroutines, the compiler will use the "virtual registers" cx16.r0-cx16.r15, from r15 down to r0, for the
result values left to right. This may change in a future compiler version.
- for regular subroutines, the compiler will return the first of the return values via the cpu register ``A``` (or ``A + Y``` if it's a word value),
just like for subroutines that only return a single value.
The remainder of the return values are returned via the "virtual registers" cx16.r16-cx16.r0 (using R15 first and counting down to R0).
**Builtin functions can be different:**
+6 -7
View File
@@ -1,13 +1,16 @@
TODO
====
- Look at github PR for improved romability (see github issue 149) Also search for "TODO: Romable"
...
Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^
- Look at github PR for improved romability (see github issue 149)
- allow integer range as when choice? because 1,2,3,4,5 is already allowed, so perhaps 1 to 5 should be allowed too? However, [1,2,3,4,5] usually is the desugared equivalent of 1 to 5 and choice values can't be arrays.
- const values should always either be of type long or float, this is how they were usually treated in const expression evaluation already anyway
- Kotlin: can we use inline value classes in certain spots?
- add float support to the configurable compiler targets
- Improve the SublimeText syntax file for prog8, you can also install this for 'bat': https://github.com/sharkdp/bat?tab=readme-ov-file#adding-new-syntaxes--language-definitions
@@ -25,8 +28,6 @@ Future Things and Ideas
Maybe this routine can be made more intelligent. See usesOtherRegistersWhileEvaluating() and argumentsViaRegisters().
- Does it make codegen easier if everything is an expression? Start with the PtProgram ast , get rid of the statements there -> expressions that have Void data type
- Can we support signed % (remainder) somehow?
- instead of copy-pasting inline asmsubs, make them into a 64tass macro and use that instead.
that will allow them to be reused from custom user written assembly code as well.
- Multidimensional arrays and chained indexing, purely as syntactic sugar over regular arrays. Probaby only useful if we have typed pointers.
- make a form of "manual generics" possible like: varsub routine(T arg)->T where T is expanded to a specific type
(this is already done hardcoded for several of the builtin functions)
@@ -57,10 +58,8 @@ IR/VM
Libraries
---------
- Sorting module gnomesort_uw could be optimized more by fully rewriting it in asm? Shellshort seems consistently faster even if most of the words are already sorted.
- Add split-word array sorting routines to sorting module?
- See if the raster interrupt handler on the C64 can be tweaked to be a more stable raster irq
- add even more general raster irq routines to build some sort of "copper list" , like Oscar64 has?
- pet32 target: make syslib more complete (missing kernal routines)?
- need help with: PET disk routines (OPEN, SETLFS etc are not exposed as kernal calls)
- c128 target: make syslib more complete (missing kernal routines)?
@@ -70,9 +69,9 @@ Libraries
Optimizations
-------------
- Sorting module gnomesort_uw could be optimized more by fully rewriting it in asm? Shellshort seems consistently faster even if most of the words are already sorted.
- Compare output of some Oscar64 samples to what prog8 does for the equivalent code (see https://github.com/drmortalwombat/OscarTutorials/tree/main and https://github.com/drmortalwombat/oscar64/tree/main/samples)
- Multi-value returns of normal subroutines: use cpu register A or AY for the first one and only start using virtual registers for the rest.
Can FAC then be used for floats as well again? Those are now not supported for multi-value returns.
- Multi-value returns of normal subroutines: Can FAC then be used for floats as well again? Those are now not supported for multi-value returns.
- Optimize the IfExpression code generation to be more like regular if-else code. (both 6502 and IR) search for "TODO don't store condition as expression"
- VariableAllocator: can we think of a smarter strategy for allocating variables into zeropage, rather than first-come-first-served?
for instance, vars used inside loops first, then loopvars, then uwords used as pointers (or these first??), then the rest
+24 -15
View File
@@ -1,25 +1,34 @@
%import textio
%zeropage basicsafe
%option no_sysinit
main {
sub start() {
const uword x=128
sub start() {
uword[] texts1 = [ 1,2,3 ]
uword[] @nosplit texts2 = [ 1,2,3 ]
const uword MAX_CAVE_WIDTH = 40 ; word here to avoid having to cast to word all the time
ubyte @shared width
cx16.r4 = texts1
cx16.r5 = texts2
if cx16.r0L==x
return
if cx16.r0L>x
return
if cx16.r0L<x
return
; txt.print_uwhex(&texts1, true)
; txt.spc()
; txt.print_uwhex(cx16.r4, true)
; txt.nl()
; txt.print_uwhex(&texts2, true)
; txt.spc()
; txt.print_uwhex(cx16.r5, true)
; txt.nl()
if width<3 or width>MAX_CAVE_WIDTH ; TODO optimize this too
return
cx16.r4 = &texts1
cx16.r5 = &texts2
cx16.r0L = x ; already gets optimized to a byte value
; txt.print_uwhex(&texts1, true)
; txt.spc()
; txt.print_uwhex(cx16.r4, true)
; txt.nl()
; txt.print_uwhex(&texts2, true)
; txt.spc()
; txt.print_uwhex(cx16.r5, true)
; txt.nl()
}
}
+1 -1
View File
@@ -3,4 +3,4 @@ org.gradle.console=rich
org.gradle.parallel=true
org.gradle.daemon=true
kotlin.code.style=official
version=11.1
version=11.2
@@ -1,4 +1,4 @@
package prog8.code
package prog8
/**
* By convention, the right side of an `Either` is used to hold successful values.
@@ -1,6 +1,5 @@
package prog8.intermediate
import prog8.code.*
import prog8.code.core.*
import prog8.code.target.VMTarget
import prog8.code.target.getCompilationTargetByName
@@ -92,6 +91,7 @@ class IRFileReader {
var loadAddress = target.PROGRAM_LOAD_ADDRESS
var memtop = target.PROGRAM_MEMTOP_ADDRESS
var optimize = true
var romable = false
var outputDir = Path("")
if(text.isNotBlank()) {
@@ -114,6 +114,7 @@ class IRFileReader {
}
"outputDir" -> outputDir = Path(value)
"optimize" -> optimize = value.toBoolean()
"romable" -> romable = value.toBoolean()
else -> throw IRParseException("illegal OPTION $name")
}
}
@@ -125,8 +126,9 @@ class IRFileReader {
zeropage,
zpReserved,
zpAllowed,
false,
false,
false, // TODO always false?
false, // TODO always false?
romable,
target,
loadAddress,
memtop,
@@ -151,7 +153,7 @@ class IRFileReader {
}
}
private fun parseVarsWithoutInit(reader: XMLEventReader): List<StStaticVariable> {
private fun parseVarsWithoutInit(reader: XMLEventReader): List<IRStStaticVariable> {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="VARIABLESNOINIT") { "missing VARIABLESNOINIT" }
@@ -162,7 +164,7 @@ class IRFileReader {
emptyList()
else {
val varPattern = Regex("(?<type>.+?)(?<arrayspec>\\[.+?\\])? (?<name>.+) zp=(?<zp>.+?)\\s?(split=(?<split>.+?))?\\s?(align=(?<align>.+?))?")
val variables = mutableListOf<StStaticVariable>()
val variables = mutableListOf<IRStStaticVariable>()
text.lineSequence().forEach { line ->
// example: uword main.start.qq2 zp=DONTCARE
val match = varPattern.matchEntire(line) ?: throw IRParseException("invalid VARIABLESNOINIT $line")
@@ -179,14 +181,14 @@ class IRFileReader {
val zp = if(zpwish.isBlank()) ZeropageWish.DONTCARE else ZeropageWish.valueOf(zpwish)
// val isSplit = if(split.isBlank()) false else split.toBoolean()
val align = if(alignment.isBlank()) 0u else alignment.toUInt()
val newVar = StStaticVariable(name, dt, null, null, arraysize, zp, align.toInt(), null)
val newVar = IRStStaticVariable(name, dt, null, null, null, arraysize, zp, align.toInt())
variables.add(newVar)
}
return variables
}
}
private fun parseConstants(reader: XMLEventReader): List<StConstant> {
private fun parseConstants(reader: XMLEventReader): List<IRStConstant> {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="CONSTANTS") { "missing CONSTANTS" }
@@ -197,7 +199,7 @@ class IRFileReader {
emptyList()
else {
val constantPattern = Regex("(.+?) (.+)=(.*?)")
val constants = mutableListOf<StConstant>()
val constants = mutableListOf<IRStConstant>()
text.lineSequence().forEach { line ->
// examples:
// uword main.start.qq2=0
@@ -207,13 +209,13 @@ class IRFileReader {
throw IRParseException("unscoped name: $name")
val dt = parseDatatype(type, false)
val value = parseIRValue(valueStr)
constants.add(StConstant(name, dt.base, value, null))
constants.add(IRStConstant(name, dt, value))
}
return constants
}
}
private fun parseVariables(reader: XMLEventReader): List<StStaticVariable> {
private fun parseVariables(reader: XMLEventReader): List<IRStStaticVariable> {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="VARIABLESWITHINIT") { "missing VARIABLESWITHINIT" }
@@ -224,7 +226,7 @@ class IRFileReader {
emptyList()
else {
val varPattern = Regex("(?<type>.+?)(?<arrayspec>\\[.+?\\])? (?<name>.+)=(?<value>.*?) zp=(?<zp>.+?)\\s?(split=(?<split>.+?))?\\s?(align=(?<align>.+?))?")
val variables = mutableListOf<StStaticVariable>()
val variables = mutableListOf<IRStStaticVariable>()
text.lineSequence().forEach { line ->
// examples:
// uword main.start.qq2=0 zp=REQUIRE_ZP
@@ -245,21 +247,21 @@ class IRFileReader {
if(split.isBlank()) false else split.toBoolean()
val align = if(alignment.isBlank()) 0u else alignment.toUInt()
var initNumeric: Double? = null
var initArray: StArray? = null
var initArray: IRStArray? = null
when {
dt.isNumericOrBool -> initNumeric = parseIRValue(value)
dt.isBoolArray -> {
initArray = value.split(',').map {
val boolean = parseIRValue(it) != 0.0
StArrayElement(null, null, boolean)
IRStArrayElement(boolean, null, null)
}
}
dt.isArray -> {
initArray = value.split(',').map {
if (it.startsWith('@'))
StArrayElement(null, it.drop(1), null)
IRStArrayElement(null, null, it.drop(1))
else
StArrayElement(parseIRValue(it), null, null)
IRStArrayElement(null, parseIRValue(it), null)
}
}
dt.isString -> throw IRParseException("STR should have been converted to byte array")
@@ -268,16 +270,14 @@ class IRFileReader {
if(arraysize!=null && initArray!=null && initArray.all { it.number==0.0 }) {
initArray=null // arrays with just zeros can be left uninitialized
}
val stVar = StStaticVariable(name, dt, null, initArray, arraysize, zp, align.toInt(), null)
if(initNumeric!=null)
stVar.setOnetimeInitNumeric(initNumeric)
val stVar = IRStStaticVariable(name, dt, initNumeric, null, initArray, arraysize, zp, align.toInt())
variables.add(stVar)
}
return variables
}
}
private fun parseMemMapped(reader: XMLEventReader): List<StMemVar> {
private fun parseMemMapped(reader: XMLEventReader): List<IRStMemVar> {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="MEMORYMAPPEDVARIABLES") { "missing MEMORYMAPPEDVARIABLES" }
@@ -287,7 +287,7 @@ class IRFileReader {
return if(text.isBlank())
emptyList()
else {
val memvars = mutableListOf<StMemVar>()
val memvars = mutableListOf<IRStMemVar>()
val mappedPattern = Regex("@(.+?)(\\[.+?\\])? (.+)=(.+)")
text.lineSequence().forEach { line ->
// examples:
@@ -297,13 +297,13 @@ class IRFileReader {
val (type, arrayspec, name, address) = match.destructured
val arraysize = if(arrayspec.isNotBlank()) arrayspec.substring(1, arrayspec.length-1).toInt() else null
val dt = parseDatatype(type, arraysize!=null)
memvars.add(StMemVar(name, dt, parseIRValue(address).toUInt(), arraysize, null))
memvars.add(IRStMemVar(name, dt, parseIRValue(address).toUInt(), arraysize))
}
memvars
}
}
private fun parseSlabs(reader: XMLEventReader): List<StMemorySlab> {
private fun parseSlabs(reader: XMLEventReader): List<IRStMemorySlab> {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="MEMORYSLABS") { "missing MEMORYSLABS" }
@@ -313,13 +313,13 @@ class IRFileReader {
return if(text.isBlank())
emptyList()
else {
val slabs = mutableListOf<StMemorySlab>()
val slabs = mutableListOf<IRStMemorySlab>()
val slabPattern = Regex("(.+) (.+) (.+)")
text.lineSequence().forEach { line ->
// example: "slabname 4096 0"
val match = slabPattern.matchEntire(line) ?: throw IRParseException("invalid slab $line")
val (name, size, align) = match.destructured
slabs.add(StMemorySlab(name, size.toUInt(), align.toUInt(), null))
slabs.add(IRStMemorySlab(name, size.toUInt(), align.toUInt()))
}
slabs
}
@@ -224,6 +224,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
xml.writeCharacters("loadAddress=${irProgram.options.loadAddress.toHex()}\n")
xml.writeCharacters("memtop=${irProgram.options.memtopAddress.toHex()}\n")
xml.writeCharacters("optimize=${irProgram.options.optimize}\n")
xml.writeCharacters("romable=${irProgram.options.romable}\n")
xml.writeCharacters("outputDir=${irProgram.options.outputDir.absolute()}\n")
// other options not yet useful here?
xml.writeEndElement()
@@ -12,38 +12,6 @@ class IRSymbolTable {
private val table = mutableMapOf<String, IRStNode>()
private val asmSymbols = mutableMapOf<String, String>()
companion object {
fun fromAstSymboltable(sourceSt: SymbolTable?): IRSymbolTable {
val st = IRSymbolTable()
if (sourceSt != null) {
sourceSt.flat.forEach {
when(it.value.type) {
StNodeType.STATICVAR -> st.add(it.value as StStaticVariable)
StNodeType.MEMVAR -> st.add(it.value as StMemVar)
StNodeType.CONSTANT -> st.add(it.value as StConstant)
StNodeType.MEMORYSLAB -> st.add(it.value as StMemorySlab)
else -> { }
}
}
require(st.table.all { it.key == it.value.name })
st.allVariables().forEach { variable ->
variable.onetimeInitializationArrayValue?.let {
it.forEach { arrayElt ->
if (arrayElt.addressOfSymbol != null) {
require(arrayElt.addressOfSymbol.contains('.')) {
"pointer var in array should be properly scoped: ${arrayElt.addressOfSymbol} in ${variable.name}"
}
}
}
}
}
}
return st
}
}
fun allConstants(): Sequence<IRStConstant> =
table.asSequence().map { it.value }.filterIsInstance<IRStConstant>()
@@ -58,92 +26,8 @@ class IRSymbolTable {
fun lookup(name: String) = table[name]
fun add(variable: StStaticVariable) {
val scopedName: String
val varToadd: IRStStaticVariable
if('.' in variable.name) {
scopedName = variable.name
varToadd = IRStStaticVariable(variable.name,
variable.dt,
variable.initializationNumericValue,
variable.initializationStringValue,
variable.initializationArrayValue?.map { convertArrayElt(it) },
variable.length,
variable.zpwish,
variable.align)
} else {
fun fixupAddressOfInArray(array: List<StArrayElement>?): List<IRStArrayElement>? {
if(array==null)
return null
val newArray = mutableListOf<IRStArrayElement>()
array.forEach {
if(it.addressOfSymbol!=null) {
val target = variable.lookup(it.addressOfSymbol!!) ?: throw NoSuchElementException("can't find variable ${it.addressOfSymbol}")
newArray.add(IRStArrayElement(null, null, target.scopedName))
} else {
newArray.add(convertArrayElt(it))
}
}
return newArray
}
scopedName = variable.scopedName
varToadd = IRStStaticVariable(scopedName,
variable.dt,
variable.initializationNumericValue,
variable.initializationStringValue,
fixupAddressOfInArray(variable.initializationArrayValue),
variable.length,
variable.zpwish,
variable.align
)
}
table[scopedName] = varToadd
}
fun add(variable: StMemVar) {
val scopedName: String
val varToadd: IRStMemVar
if('.' in variable.name) {
scopedName = variable.name
varToadd = IRStMemVar(
variable.name,
variable.dt,
variable.address,
variable.length
)
} else {
scopedName = try {
variable.scopedName
} catch (_: UninitializedPropertyAccessException) {
variable.name
}
varToadd = IRStMemVar(scopedName, variable.dt, variable.address, variable.length)
}
table[scopedName] = varToadd
}
fun add(variable: StMemorySlab) {
val varToadd = if('.' in variable.name)
IRStMemorySlab(variable.name, variable.size, variable.align)
else {
IRStMemorySlab("prog8_slabs.${variable.name}", variable.size, variable.align)
}
table[varToadd.name] = varToadd
}
fun add(constant: StConstant) {
val scopedName: String
val dt = DataType.forDt(constant.dt)
if('.' in constant.name) {
scopedName = constant.name
} else {
scopedName = try {
constant.scopedName
} catch (_: UninitializedPropertyAccessException) {
constant.name
}
}
table[scopedName] = IRStConstant(scopedName, dt, constant.value)
fun add(node: IRStNode) {
table[node.name] = node
}
fun addAsmSymbol(name: String, value: String) {
@@ -157,17 +41,15 @@ class IRSymbolTable {
val vars = table.filter { it.key.startsWith(prefix) }
vars.forEach {
// check if attempt is made to delete interned strings, if so, refuse that.
if(!it.key.startsWith(internedStringsModuleName)) {
if(!it.key.startsWith(INTERNED_STRINGS_MODULENAME)) {
table.remove(it.key)
}
}
}
private fun convertArrayElt(elt: StArrayElement): IRStArrayElement = if(elt.boolean!=null)
IRStArrayElement(elt.boolean, null, elt.addressOfSymbol)
else
IRStArrayElement(null, elt.number, elt.addressOfSymbol)
fun validate() {
require(table.all { it.key == it.value.name })
}
}
@@ -178,10 +60,7 @@ enum class IRStNodeType {
CONST
}
open class IRStNode(val name: String,
val type: IRStNodeType,
val children: MutableMap<String, StNode> = mutableMapOf()
)
open class IRStNode(val name: String, val type: IRStNodeType)
class IRStMemVar(name: String,
val dt: DataType,
@@ -209,7 +88,7 @@ class IRStConstant(name: String, val dt: DataType, val value: Double) : IRStNode
class IRStStaticVariable(name: String,
val dt: DataType,
val onetimeInitializationNumericValue: Double?, // regular (every-run-time) initialization is done via regular assignments
val onetimeInitializationNumericValue: Double?, // TODO still needed? Or can go? regular (every-run-time) initialization is done via regular assignments
val onetimeInitializationStringValue: IRStString?,
val onetimeInitializationArrayValue: IRStArray?,
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
+3 -3
View File
@@ -1,9 +1,9 @@
package prog8.intermediate
import prog8.code.Either
import prog8.code.core.*
import prog8.code.left
import prog8.code.right
import prog8.Either
import prog8.left
import prog8.right
fun DataType.irTypeString(length: Int?): String {
+1
View File
@@ -21,6 +21,7 @@ class TestIRFileInOut: FunSpec({
CompilationOptions.AllZeropageAllowed,
floats = false,
noSysInit = true,
romable = false,
compTarget = target,
loadAddress = target.PROGRAM_LOAD_ADDRESS,
memtopAddress = 0xffffu,
+1
View File
@@ -1,6 +1,7 @@
include(
':parser',
':codeCore',
':simpleAst',
':intermediate',
':compilerAst',
':codeOptimizers',
+24
View File
@@ -0,0 +1,24 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("jvm")
}
dependencies {
implementation(project(":codeCore"))
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.1")
}
sourceSets {
main {
java {
srcDir("${project.projectDir}/src")
}
resources {
srcDir("${project.projectDir}/res")
}
}
}
// note: there are no unit tests yet in this module!
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/testResources" type="java-test-resource" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
</component>
</module>
@@ -1,13 +1,15 @@
package prog8.code.core
package prog8.code
import prog8.code.SymbolTable
import prog8.code.ast.PtProgram
import prog8.code.core.CompilationOptions
import prog8.code.core.IErrorReporter
interface ICodeGeneratorBackend {
fun generate(program: PtProgram,
symbolTable: SymbolTable,
options: CompilationOptions,
errors: IErrorReporter): IAssemblyProgram?
errors: IErrorReporter
): IAssemblyProgram?
}
@@ -6,9 +6,6 @@ import prog8.code.ast.PtProgram
import prog8.code.core.*
const val internedStringsModuleName = "prog8_interned_strings"
/**
* Tree structure containing all symbol definitions in the program
* (blocks, subroutines, variables (all types), memoryslabs, and labels).
@@ -260,7 +257,7 @@ class StMemorySlab(
class StSub(name: String, val parameters: List<StSubroutineParameter>, val returns: List<DataType>, astNode: PtNode) :
StNode(name, StNodeType.SUBROUTINE, astNode)
StNode(name, StNodeType.SUBROUTINE, astNode)
class StExtSub(name: String,
@@ -96,12 +96,7 @@ class PtInlineAssembly(val assembly: String, val isIR: Boolean, position: Positi
}
class PtLabel(name: String, position: Position) : PtNamedNode(name, position) {
companion object {
// all automatically generated labels everywhere need to have the same label name prefix:
const val GENERATED_LABEL_PREFIX = "p8_label_gen_"
}
}
class PtLabel(name: String, position: Position) : PtNamedNode(name, position)
class PtBreakpoint(position: Position): PtNode(position)
@@ -6,6 +6,41 @@ import prog8.code.core.*
sealed interface IPtSubroutine {
val name: String
val scopedName: String
fun returnsWhatWhere(): List<Pair<RegisterOrStatusflag, DataType>> {
when(this) {
is PtAsmSub -> {
return returns
}
is PtSub -> {
// for non-asm subroutines, determine the return registers based on the type of the return values
fun cpuRegisterFor(returntype: DataType): RegisterOrStatusflag = when {
returntype.isByteOrBool -> RegisterOrStatusflag(RegisterOrPair.A, null)
returntype.isWord -> RegisterOrStatusflag(RegisterOrPair.AY, null)
returntype.isFloat -> RegisterOrStatusflag(RegisterOrPair.FAC1, null)
else -> RegisterOrStatusflag(RegisterOrPair.AY, null)
}
when(returns.size) {
0 -> return emptyList()
1 -> {
val returntype = returns.single()
val register = cpuRegisterFor(returntype)
return listOf(Pair(register, returntype))
}
else -> {
// for multi-value results, put the first one in A or AY cpu register(s) and the rest in the virtual registers starting from R15 and counting down
val first = cpuRegisterFor(returns.first()) to returns.first()
val others = returns.drop(1)
.zip(Cx16VirtualRegisters.reversed())
.map { (type, reg) -> RegisterOrStatusflag(reg, null) to type }
return listOf(first) + others
}
}
}
}
}
}
class PtAsmSub(
File renamed without changes.
Loaded 100 of 108 files, more files were not shown because too many files have changed in this diff. Show more