Compare commits

...

15 Commits

Author SHA1 Message Date
Irmen de Jong
231b50dacb optimize certain word multiplication with bit shifting if it's a power of 2 2025-09-18 20:57:26 +02:00
Irmen de Jong
a71895cbe8 optimize pointer.field += 1 into pointer.field INC/DEC 2025-09-18 19:27:36 +02:00
Irmen de Jong
a8bede17b2 fix defer() with the arena allocator ("return values are evaluated before the defer is executed") 2025-09-17 23:59:32 +02:00
Irmen de Jong
f6c8e693a5 add offsetof() 2025-09-17 23:30:15 +02:00
Irmen de Jong
9461e4088c fixed the hashtable example and workarounds for misbehaving defer in allocators 2025-09-17 22:53:01 +02:00
Irmen de Jong
efd73fd10d implement some more in place pointer operators 2025-09-17 18:19:03 +02:00
Irmen de Jong
ddf6e84a1a fix type checks for wrong pointer types in pointer array initalizer and assignment 2025-09-16 21:18:29 +02:00
Irmen de Jong
633d6c34e2 add support for struct pointers and short-form initializers in arrays and assignments 2025-09-16 20:37:50 +02:00
Irmen de Jong
6e7fbc6683 fix crash when invalid struct name is used in pointer decl 2025-09-15 20:39:21 +02:00
Irmen de Jong
124ea1230b fix vm to understand struct instances in arrays 2025-09-14 19:04:01 +02:00
Irmen de Jong
8b48a295b6 allow struct initializers to occur in array literals 2025-09-14 18:16:46 +02:00
Irmen de Jong
d285d37fdb fix clobbers syntax in symboldumper 2025-09-13 21:47:52 +02:00
Irmen de Jong
8bb927b483 fix compiler crash with on..call statement in nested scope 2025-09-13 06:44:49 +02:00
Irmen de Jong
1af4cd0d63 fix struct initializer error checking 2025-09-12 19:55:12 +02:00
Irmen de Jong
db2f28c4cd add struct and pointer benchmark to benchmark program (btree, subscore=654, total 7420)
fix nullpointer in array initalizer
2025-09-12 17:09:44 +02:00
55 changed files with 1726 additions and 416 deletions

View File

@@ -0,0 +1,234 @@
; Binary Search Tree.
; It's a simple implementation for test/demonstration purposes of the pointer support;
; no balancing is done and memory is not freed when elements are removed.
%import textio
btree {
sub benchmark(uword max_time) -> uword {
txt.nl()
cbm.SETTIM(0,0,0)
uword score
while cbm.RDTIM16() < max_time {
bench_operations()
txt.chrout('.')
score++
}
txt.nl()
return score
}
sub bench_operations() {
arena.freeall()
btree.root = 0
for cx16.r0 in [321, 719, 194, 550, 187, 203, 520, 562, 221, 676, 97, 852, 273, 326, 589, 606, 275, 794, 63, 716]
btree.add(cx16.r0)
cx16.r0L = btree.size()
btree.process_tree_inorder()
btree.process_tree_preorder()
void btree.contains(203)
void btree.contains(204)
void btree.contains(605)
void btree.contains(606)
btree.remove(9999)
btree.remove(97)
btree.remove(187)
btree.remove(203)
btree.remove(275)
btree.remove(321)
btree.remove(520)
btree.remove(562)
btree.remove(606)
btree.remove(719)
btree.remove(794)
cx16.r0L = btree.size()
btree.process_tree_inorder()
btree.process_tree_preorder()
}
struct Node {
^^Node left
^^Node right
uword value
}
^^Node root = 0
sub add(uword value) {
^^Node node = arena.alloc(sizeof(Node))
node.value = value
node.left = node.right = 0
if root==0
root=node
else {
^^Node parent = root
repeat {
if parent.value >= value {
if parent.left!=0
parent = parent.left
else {
parent.left = node
return
}
} else {
if parent.right!=0
parent = parent.right
else {
parent.right = node
return
}
}
}
}
}
sub contains(uword value) -> bool {
^^Node r = root
while r!=0 {
if r.value==value
return true
if r.value>value
r = r.left
else
r = r.right
}
return false
}
sub size() -> ubyte {
ubyte count
if root!=0
count_node(root)
return count
sub count_node(^^Node r) {
count++
if r.left!=0 {
sys.pushw(r)
count_node(r.left)
r = sys.popw()
}
if r.right!=0 {
sys.pushw(r)
count_node(r.right)
r = sys.popw()
}
}
}
sub remove(uword value) {
; note: we don't deallocate the memory from the node, for simplicity sake
^^Node n = root
^^Node parent = 0
while n!=0 {
if n.value==value {
if n.left==0
replacechild(parent, n, n.right)
else if n.right==0
replacechild(parent, n, n.left)
else {
; Both left & right subtrees are present.
; N = node to delete.
; Find N's successor S. (N's right subtree's minimum element)
; Attach N's left subtree to S.left (S doesn't have a left child)
; Attach N's right subtree to Parent in place of N.
^^Node successor = find_successor(n)
successor.left = n.left
replacechild(parent, n, n.right)
}
return
}
parent = n
if n.value>value
n = n.left
else
n = n.right
}
sub find_successor(^^Node p) -> ^^Node {
^^Node succ = p
p = p.right
while p!=0 {
succ = p
p = p.left
}
return succ
}
sub replacechild(^^Node p, ^^Node child, ^^Node newchild) {
if p.left==child
p.left = newchild
else
p.right = newchild
}
}
sub process_tree_inorder() {
if root!=0
process_tree(root)
sub process_tree(^^Node r) {
if r.left!=0 {
sys.pushw(r)
process_tree(r.left)
r = sys.popw()
}
cx16.r0 = r.value
if r.right!=0 {
sys.pushw(r)
process_tree(r.right)
r = sys.popw()
}
}
}
sub process_tree_preorder() {
if root!=0
process_tree(root,0)
sub process_tree(^^Node r, ubyte depth) {
cx16.r0 = r.value
if r.left!=0 {
sys.pushw(r)
sys.push(depth)
process_tree(r.left, depth+1)
depth = sys.pop()
r = sys.popw()
}
if r.right!=0 {
sys.pushw(r)
sys.push(depth)
process_tree(r.right, depth+1)
depth = sys.pop()
r = sys.popw()
}
}
}
}
arena {
; extremely trivial arena allocator (that never frees)
uword buffer = memory("arena", 2000, 0)
uword next = buffer
sub alloc(ubyte size) -> uword {
defer next += size
return next
}
sub freeall() {
next = buffer
}
}

View File

@@ -16,6 +16,7 @@
%import b_textelite
%import b_maze
%import b_sprites
%import b_btree
%zeropage basicsafe
%option no_sysinit
@@ -67,10 +68,6 @@ main {
benchmark_score[benchmark_number] = circles.draw(false, 300)
benchmark_number++
; announce_benchmark("circles with kernal")
; benchmark_score[benchmark_number] = circles.draw(true, 300)
; benchmark_number++
announce_benchmark("text-elite")
benchmark_score[benchmark_number] = textelite.bench(120)
benchmark_number++
@@ -79,13 +76,17 @@ main {
benchmark_score[benchmark_number] = animsprites.benchmark(300)
benchmark_number++
announce_benchmark("btree-struct-pointers")
benchmark_score[benchmark_number] = btree.benchmark(200)
benchmark_number++
benchmark_names[benchmark_number] = 0
benchmark_score[benchmark_number] = 0
cx16.set_screen_mode(3)
txt.uppercase()
txt.color2(1, 6)
uword final_score
uword total_score
benchmark_number = 0
txt.print("\nscore benchmark\n\n")
do {
@@ -93,14 +94,14 @@ main {
txt.print_uw(benchmark_score[benchmark_number])
txt.column(6)
txt.print(benchmark_names[benchmark_number])
final_score += benchmark_score[benchmark_number]
total_score += benchmark_score[benchmark_number]
txt.nl()
benchmark_number++
} until benchmark_names[benchmark_number]==0
txt.print("\n\nfinal score : ")
txt.print_uw(final_score)
txt.nl()
txt.print("\n\ntotal score : ")
txt.print_uw(total_score)
txt.print(" (higher=better)\n")
sub announce_benchmark(str name) {
benchmark_names[benchmark_number] = name

View File

@@ -5,9 +5,10 @@ import java.nio.file.Path
import kotlin.io.path.absolute
// the automatically generated module where all string literals are interned to:
const val INTERNED_STRINGS_MODULENAME = "prog8_interned_strings"
val PROG8_CONTAINER_MODULES = arrayOf(INTERNED_STRINGS_MODULENAME) // option to add more if needed one day
// all automatically generated labels everywhere need to have the same label name prefix:
const val GENERATED_LABEL_PREFIX = "p8_label_gen_"

View File

@@ -103,6 +103,7 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"abs__float" to FSignature(true, BaseDataType.FLOAT, FParam("value", BaseDataType.FLOAT)),
"len" to FSignature(true, BaseDataType.UWORD, FParam("values", *IterableDatatypes)),
"sizeof" to FSignature(true, BaseDataType.UBYTE, FParam("object", *(BaseDataType.entries - BaseDataType.STRUCT_INSTANCE).toTypedArray())),
"offsetof" to FSignature(true, BaseDataType.UBYTE, FParam("field", BaseDataType.UBYTE)),
"sgn" to FSignature(true, BaseDataType.BYTE, FParam("value", *NumericDatatypes)),
"sqrt" to FSignature(true, null, FParam("value", *NumericDatatypes)),
"sqrt__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UBYTE)),

View File

@@ -252,6 +252,14 @@ private fun PtVariable.prefix(parent: PtNode, st: SymbolTable): PtVariable {
newValue.add(newAddr)
}
}
is PtBuiltinFunctionCall -> {
// could be a struct instance or memory slab "allocation"
if (elt.name != "prog8_lib_structalloc" && elt.name != "memory")
throw AssemblyError("weird array value element $elt")
else {
newValue.add(elt)
}
}
else -> throw AssemblyError("weird array value element $elt")
}
}

View File

@@ -1,5 +1,7 @@
package prog8.codegen.cpu6502
import prog8.code.StMemorySlabBlockName
import prog8.code.StStructInstanceBlockName
import prog8.code.SymbolTable
import prog8.code.ast.*
import prog8.code.core.*
@@ -385,7 +387,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
val name = (fcall.args[0] as PtString).value
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name ${fcall.position}"}
val slabname = PtIdentifier("prog8_slabs.prog8_memoryslab_$name", DataType.UWORD, fcall.position)
val slabname = PtIdentifier("$StMemorySlabBlockName.memory_$name", DataType.UWORD, fcall.position)
val addressOf = PtAddressOf(DataType.pointer(BaseDataType.UBYTE), false, fcall.position)
addressOf.add(slabname)
addressOf.parent = fcall
@@ -399,9 +401,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
if(discardResult)
throw AssemblyError("should not discard result of struct allocation at $fcall")
// ... don't need to pay attention to args here because struct instance is put together elsewhere we just have to get a pointer to it
val slabname = SymbolTable.labelnameForStructInstance(fcall)
val prefix = if(fcall.args.isEmpty()) "${StStructInstanceBlockName}_bss" else StStructInstanceBlockName
val labelname = PtIdentifier("$prefix.${SymbolTable.labelnameForStructInstance(fcall)}", fcall.type, fcall.position)
val addressOf = PtAddressOf(fcall.type, true, fcall.position)
addressOf.add(PtIdentifier(slabname, fcall.type, fcall.position))
addressOf.add(labelname)
addressOf.parent = fcall
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, fcall.type, expression = addressOf)
val target = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, fcall.position, null, asmgen)

View File

@@ -210,7 +210,7 @@ internal class ProgramAndVarsGen(
private fun memorySlabs() {
if(symboltable.allMemorySlabs.isNotEmpty()) {
asmgen.out("; memory slabs\n .section BSS_SLABS")
asmgen.out("prog8_slabs\t.block")
asmgen.out("$StMemorySlabBlockName\t.block")
for (slab in symboltable.allMemorySlabs) {
if (slab.align > 1u)
asmgen.out("\t.align ${slab.align.toHex()}")
@@ -434,6 +434,7 @@ internal class ProgramAndVarsGen(
val (instancesNoInit, instances) = symboltable.allStructInstances.partition { it.initialValues.isEmpty() }
asmgen.out("; struct instances without initialization values, as BSS zeroed at startup\n")
asmgen.out(" .section BSS\n")
asmgen.out("${StStructInstanceBlockName}_bss .block\n")
instancesNoInit.forEach {
val structtype: StStruct = symboltable.lookup(it.structName) as StStruct
val zerovalues = structtype.fields.map { field ->
@@ -445,11 +446,17 @@ internal class ProgramAndVarsGen(
}
asmgen.out("${it.name} .dstruct ${it.structName}, ${zerovalues.joinToString(",")}\n")
}
asmgen.out(" .endblock\n")
asmgen.out(" .send BSS\n")
asmgen.out("; struct instances with initialization values\n")
asmgen.out(" .section STRUCTINSTANCES\n")
instances.forEach { asmgen.out("${it.name} .dstruct ${it.structName}, ${initValues(it).joinToString(",")}\n") }
asmgen.out("$StStructInstanceBlockName .block\n")
instances.forEach {
val instancename = it.name.substringAfter('.')
asmgen.out("$instancename .dstruct ${it.structName}, ${initValues(it).joinToString(",")}\n")
}
asmgen.out(" .endblock\n")
asmgen.out(" .send STRUCTINSTANCES\n")
}
@@ -866,7 +873,7 @@ internal class ProgramAndVarsGen(
}
}
dt.isSplitWordArray -> {
if(dt.elementType().isUnsignedWord) {
if(dt.elementType().isUnsignedWord || dt.elementType().isPointer) {
val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros)
asmgen.out("_array_$varname := ${data.joinToString()}")
asmgen.out("${varname}_lsb\t.byte <_array_$varname")
@@ -914,7 +921,7 @@ internal class ProgramAndVarsGen(
private fun zeroFilledArray(numElts: Int): StArray {
val values = mutableListOf<StArrayElement>()
repeat(numElts) {
values.add(StArrayElement(0.0, null, null))
values.add(StArrayElement(0.0, null, null,null,null))
}
return values
}
@@ -972,7 +979,7 @@ internal class ProgramAndVarsGen(
val number = it.number!!.toInt()
"$"+number.toString(16).padStart(2, '0')
}
dt.isArray && dt.elementType().isUnsignedWord -> array.map {
dt.isArray && (dt.elementType().isUnsignedWord || dt.elementType().isPointer) -> array.map {
if(it.number!=null) {
"$" + it.number!!.toInt().toString(16).padStart(4, '0')
}
@@ -984,8 +991,15 @@ internal class ProgramAndVarsGen(
else
asmgen.asmSymbolName(addrOfSymbol)
}
else
else if(it.structInstance!=null) {
asmgen.asmSymbolName("${StStructInstanceBlockName}.${it.structInstance!!}")
}
else if(it.structInstanceUninitialized!=null) {
asmgen.asmSymbolName("${StStructInstanceBlockName}_bss.${it.structInstanceUninitialized!!}")
}
else {
throw AssemblyError("weird array elt")
}
}
else -> throw AssemblyError("invalid dt")
}

View File

@@ -5,6 +5,7 @@ import prog8.code.ast.*
import prog8.code.core.*
import prog8.codegen.cpu6502.AsmGen6502Internal
import prog8.codegen.cpu6502.VariableAllocator
import kotlin.math.log2
internal class AssignmentAsmGen(
@@ -1145,6 +1146,14 @@ internal class AssignmentAsmGen(
assignExpressionToRegister(expr.left, RegisterOrPair.A, expr.type.isSigned)
if (value in asmgen.optimizedByteMultiplications)
asmgen.out(" jsr prog8_math.mul_byte_${value}")
else if(value in powersOfTwoInt) {
val shifts = log2(value.toDouble()).toInt()
if(shifts>=8) {
asmgen.out(" lda #0")
} else {
repeat(shifts) { asmgen.out(" asl a") }
}
}
else
asmgen.out(" ldy #$value | jsr prog8_math.multiply_bytes")
assignRegisterByte(target, CpuRegister.A, false, true)
@@ -1155,7 +1164,26 @@ internal class AssignmentAsmGen(
assignExpressionToRegister(expr.left, RegisterOrPair.AY, expr.type.isSigned)
asmgen.out(" jsr prog8_math.mul_word_${value}")
}
else if(value in powersOfTwoInt) {
val shifts = log2(value.toDouble()).toInt()
if(shifts>=16) {
assignExpressionToRegister(expr.left, RegisterOrPair.AY, expr.type.isSigned)
asmgen.out(" lda #0 | ldy #0")
} else {
if(target.kind==TargetStorageKind.VARIABLE && target.datatype.isWord) {
assignExpressionToVariable(expr.left, target.asmVarname, target.datatype)
repeat(shifts) { asmgen.out(" asl ${target.asmVarname} | rol ${target.asmVarname}+1") }
return true
} else {
assignExpressionToRegister(expr.left, RegisterOrPair.AY, expr.type.isSigned)
asmgen.out(" sty P8ZP_SCRATCH_REG")
repeat(shifts) { asmgen.out(" asl a | rol P8ZP_SCRATCH_REG") }
asmgen.out(" ldy P8ZP_SCRATCH_REG")
}
}
}
else {
assignExpressionToRegister(expr.left, RegisterOrPair.AY, expr.type.isSigned)
if(expr.definingBlock()!!.options.veraFxMuls){
// cx16 verafx hardware mul
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "cx16.r1")

View File

@@ -257,16 +257,26 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
internal fun inplaceModification(target: PtrTarget, operator: String, value: AsmAssignSource) {
when (operator) {
"+" -> {
if(target.dt.isByte) inplaceByteAdd(target, value)
else if(target.dt.isWord) inplaceWordAdd(target, value)
else if(target.dt.isFloat) inplaceFloatAddOrMul(target, "FADD", value)
else throw AssemblyError("weird dt ${target.dt} ${target.position}")
if(target.dt.isByte && value.number?.number==1.0 || value.number?.number==2.0) {
val amount = value.number.number.toInt()
inplaceByteInc(target, amount)
} else {
if (target.dt.isByte) inplaceByteAdd(target, value)
else if (target.dt.isWord) inplaceWordAdd(target, value)
else if (target.dt.isFloat) inplaceFloatAddOrMul(target, "FADD", value)
else throw AssemblyError("weird dt ${target.dt} ${target.position}")
}
}
"-" -> {
if(target.dt.isByte) inplaceByteSub(target, value)
else if(target.dt.isWord) inplaceWordSub(target, value)
else if(target.dt.isFloat) inplaceFloatSubOrDiv(target, "FSUB", value)
else throw AssemblyError("weird dt ${target.position}")
if(target.dt.isByte && value.number?.number==1.0 || value.number?.number==2.0) {
val amount = value.number.number.toInt()
inplaceByteDec(target, amount)
} else {
if (target.dt.isByte) inplaceByteSub(target, value)
else if (target.dt.isWord) inplaceWordSub(target, value)
else if (target.dt.isFloat) inplaceFloatSubOrDiv(target, "FSUB", value)
else throw AssemblyError("weird dt ${target.position}")
}
}
"*" -> {
if(target.dt.isByte) TODO("inplaceByteMul(target, value) ${target.position}")
@@ -282,22 +292,26 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
}
"%" -> TODO("inplace ptr %")
"<<" -> {
if(target.dt.isByte) TODO("inplaceByteShiftLeft(target, value) ${target.position}")
if(target.dt.isByte) inplaceByteShiftLeft(target, value)
else if(target.dt.isWord) inplaceWordShiftLeft(target, value)
else throw AssemblyError("weird dt ${target.position}")
}
">>" -> {
if(target.dt.isByte) TODO("inplaceByteShiftRight(target, value) ${target.position}")
if(target.dt.isByte) inplaceByteShiftRight(target, value)
else if(target.dt.isWord) inplaceWordShiftRight(target, value)
else throw AssemblyError("weird dt ${target.position}")
}
"&", "and" -> {
// byte targets are handled as direct memory access, not a pointer operation anymore however boolean targets are still to be handled here
TODO("inplace ptr &")
if(target.dt.isByteOrBool) inplaceByteAnd(target, value)
else if(target.dt.isWord) inplaceWordAnd(target, value)
else throw AssemblyError("weird dt ${target.dt} ${target.position}")
}
"|", "or" -> {
// byte targets are handled as direct memory access, not a pointer operation anymore however boolean targets are still to be handled here
TODO("inplace ptr |")
if(target.dt.isByteOrBool) inplaceByteOr(target, value)
else if(target.dt.isWord) inplaceWordOr(target, value)
else throw AssemblyError("weird dt ${target.dt} ${target.position}")
}
"^", "xor" -> {
// byte targets are handled as direct memory access, not a pointer operation anymore however boolean targets are still to be handled here
@@ -305,12 +319,6 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
else if(target.dt.isWord) inplaceWordXor(target, value)
else throw AssemblyError("weird dt ${target.dt} ${target.position}")
}
"==" -> TODO("inplace ptr ==")
"!=" -> TODO("inplace ptr !=")
"<" -> TODO("inplace ptr <")
"<=" -> TODO("inplace ptr <=")
">" -> TODO("inplace ptr >")
">=" -> TODO("inplace ptr >=")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
@@ -693,7 +701,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
val (zpPtrVar, offset) = deref(target.pointer)
if(target.dt.isSigned)
TODO("signed word shift rigth ${target.position} $value")
TODO("signed word shift right ${target.position} $value")
fun shift1unsigned() {
asmgen.out("""
@@ -720,21 +728,92 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
}
}
SourceStorageKind.VARIABLE -> {
require(value.datatype.isWord)
require(value.datatype.isByte)
val varname = value.asmVarname
TODO("<< variable")
asmgen.out(" ldx $varname")
asmgen.out("-")
shift1unsigned()
asmgen.out(" dex | bne -")
}
SourceStorageKind.EXPRESSION -> {
require(value.datatype.isWord)
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.AX)
TODO("<< expression")
require(value.datatype.isByte)
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.X)
asmgen.out("-")
shift1unsigned()
asmgen.out(" dex | bne -")
}
SourceStorageKind.REGISTER -> {
require(value.datatype.isWord)
require(value.datatype.isByte)
val register = value.register!!
asmgen.assignRegister(register, AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, null, target.position, variableAsmName = "P8ZP_SCRATCH_PTR"))
require(register.isWord())
TODO("<< register")
asmgen.assignRegister(register, AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.UBYTE, null, target.position, register = RegisterOrPair.X))
asmgen.out("-")
shift1unsigned()
asmgen.out(" dex | bne -")
}
else -> throw AssemblyError("weird source value $value")
}
}
private fun inplaceByteShiftRight(target: PtrTarget, value: AsmAssignSource) {
val (zpPtrVar, offset) = deref(target.pointer)
if(target.dt.isSigned)
TODO("signed byte shift right ${target.position} $value")
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
val number = value.number!!.number.toInt()
if(number==1) {
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
lsr a
sta ($zpPtrVar),y""")
} else if(number>1) {
asmgen.out("""
ldx #$number
ldy #$offset
lda ($zpPtrVar),y
- lsr a
dex
bne -
sta ($zpPtrVar),y""")
}
}
SourceStorageKind.VARIABLE -> {
require(value.datatype.isByte)
val varname = value.asmVarname
asmgen.out("""
ldx $varname
ldy #$offset
lda ($zpPtrVar),y
- lsr a
dex
bne -
sta ($zpPtrVar),y""")
}
SourceStorageKind.EXPRESSION -> {
require(value.datatype.isByte)
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.X)
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
- lsr a
dex
bne -
sta ($zpPtrVar),y""")
}
SourceStorageKind.REGISTER -> {
require(value.datatype.isByte)
val register = value.register!!
asmgen.assignRegister(register, AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.UWORD, null, target.position, register = RegisterOrPair.X))
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
- lsr a
dex
bne -
sta ($zpPtrVar),y""")
}
else -> throw AssemblyError("weird source value $value")
}
@@ -768,21 +847,90 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
}
}
SourceStorageKind.VARIABLE -> {
require(value.datatype.isWord)
require(value.datatype.isByte)
val varname = value.asmVarname
TODO("<< variable")
asmgen.out(" ldx $varname")
asmgen.out("-")
shift1()
asmgen.out(" dex | bne -")
}
SourceStorageKind.EXPRESSION -> {
require(value.datatype.isWord)
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.AX)
TODO("<< expression")
require(value.datatype.isByte)
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.X)
asmgen.out("-")
shift1()
asmgen.out(" dex | bne -")
}
SourceStorageKind.REGISTER -> {
require(value.datatype.isWord)
require(value.datatype.isByte)
val register = value.register!!
asmgen.assignRegister(register, AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, null, target.position, variableAsmName = "P8ZP_SCRATCH_PTR"))
require(register.isWord())
TODO("<< register")
asmgen.assignRegister(register, AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.UBYTE, null, target.position, register = RegisterOrPair.X))
asmgen.out("-")
shift1()
asmgen.out(" dex | bne -")
}
else -> throw AssemblyError("weird source value $value")
}
}
private fun inplaceByteShiftLeft(target: PtrTarget, value: AsmAssignSource) {
val (zpPtrVar, offset) = deref(target.pointer)
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
val number = value.number!!.number.toInt()
if(number==1) {
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
asl a
sta ($zpPtrVar),y""")
} else if(number>1) {
asmgen.out("""
ldx #$number
ldy #$offset
lda ($zpPtrVar),y
- asl a
dex
bne -
sta ($zpPtrVar),y""")
}
}
SourceStorageKind.VARIABLE -> {
require(value.datatype.isByte)
val varname = value.asmVarname
asmgen.out("""
ldx $varname
ldy #$offset
lda ($zpPtrVar),y
- asl a
dex
bne -
sta ($zpPtrVar),y""")
}
SourceStorageKind.EXPRESSION -> {
require(value.datatype.isByte)
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.X)
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
- asl a
dex
bne -
sta ($zpPtrVar),y""")
}
SourceStorageKind.REGISTER -> {
require(value.datatype.isByte)
val register = value.register!!
asmgen.assignRegister(register, AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.UBYTE, null, target.position, register = RegisterOrPair.X))
asmgen.out("-")
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
- asl a
dex
bne -
sta ($zpPtrVar),y""")
}
else -> throw AssemblyError("weird source value $value")
}
@@ -1046,6 +1194,46 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
}
}
private fun inplaceByteInc(target: PtrTarget, amount: Int) {
require(amount==1 || amount==2)
val (zpPtrVar, offset) = deref(target.pointer)
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02)) {
asmgen.out(" lda ($zpPtrVar)")
repeat(amount) {
asmgen.out(" inc a")
}
asmgen.out(" sta ($zpPtrVar)")
}
else {
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
clc
adc #$amount
sta ($zpPtrVar),y""")
}
}
private fun inplaceByteDec(target: PtrTarget, amount: Int) {
require(amount==1 || amount==2)
val (zpPtrVar, offset) = deref(target.pointer)
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02)) {
asmgen.out(" lda ($zpPtrVar)")
repeat(amount) {
asmgen.out(" dec a")
}
asmgen.out(" sta ($zpPtrVar)")
}
else {
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
sec
sbc #$amount
sta ($zpPtrVar),y""")
}
}
private fun inplaceByteAdd(target: PtrTarget, value: AsmAssignSource) {
val (zpPtrVar, offset) = deref(target.pointer)
when(value.kind) {
@@ -1230,48 +1418,222 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
}
}
fun assignIndexedPointer(target: AsmAssignTarget, arrayVarName: String, index: PtExpression, arrayDt: DataType) {
TODO("assign indexed pointer from array $arrayVarName at ${target.position}")
val ptrZp = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, target.scope, target.position, variableAsmName="P8ZP_SCRATCH_PTR")
assignAddressOfIndexedPointer(ptrZp, arrayVarName, arrayDt, index)
when {
target.datatype.isByteOrBool -> {
asmgen.out("""
ldy #0
lda (P8ZP_SCRATCH_PTR),y""")
asmgen.assignRegister(RegisterOrPair.A, target)
}
target.datatype.isWord || target.datatype.isPointer -> {
if(asmgen.isTargetCpu(CpuType.CPU65C02))
private fun inplaceByteOr(target: PtrTarget, value: AsmAssignSource) {
val (zpPtrVar, offset) = deref(target.pointer)
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
val number = value.number!!.number.toInt()
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out("""
ldy #1
lda (P8ZP_SCRATCH_PTR),y
tax
lda (P8ZP_SCRATCH_PTR)""")
lda ($zpPtrVar)
ora #$number
sta ($zpPtrVar)""")
else
asmgen.out("""
ldy #1
lda (P8ZP_SCRATCH_PTR),y
tax
dey
lda (P8ZP_SCRATCH_PTR),y""")
asmgen.assignRegister(RegisterOrPair.AX, target)
ldy #$offset
lda ($zpPtrVar),y
ora #$number
sta ($zpPtrVar),y""")
}
target.datatype.isLong -> {
TODO("assign long from pointer to $target ${target.position}")
SourceStorageKind.VARIABLE -> {
val varname = value.asmVarname
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out("""
lda ($zpPtrVar)
ora $varname
sta ($zpPtrVar)""")
else
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
ora $varname
sta ($zpPtrVar),y""")
}
target.datatype.isFloat -> {
// TODO optimize the float copying to avoid having to go through FAC1
SourceStorageKind.EXPRESSION -> {
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.A)
asmgen.out("""
lda P8ZP_SCRATCH_PTR
ldy P8ZP_SCRATCH_PTR+1
jsr floats.MOVFM""")
asmgen.assignRegister(RegisterOrPair.FAC1, target)
ldy #$offset
ora ($zpPtrVar),y
sta ($zpPtrVar),y""")
}
else -> throw AssemblyError("weird dt ${target.datatype}")
SourceStorageKind.REGISTER -> TODO("register | byte")
else -> throw AssemblyError("weird source value $value")
}
}
private fun inplaceWordOr(target: PtrTarget, value: AsmAssignSource) {
val (zpPtrVar, offset) = deref(target.pointer)
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
val number = value.number!!.number.toInt()
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
ora #<$number
sta ($zpPtrVar),y
iny
lda ($zpPtrVar),y
ora #>$number
sta ($zpPtrVar),y""")
}
SourceStorageKind.VARIABLE -> {
require(value.datatype.isWord)
val varname = value.asmVarname
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
ora $varname
sta ($zpPtrVar),y
lda ($zpPtrVar),y
ora $varname+1
sta ($zpPtrVar),y""")
}
SourceStorageKind.EXPRESSION -> {
require(value.datatype.isWord)
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.AX)
asmgen.out("""
ldy #$offset
ora ($zpPtrVar),y
sta ($zpPtrVar),y
iny
txa
ora ($zpPtrVar),y
sta ($zpPtrVar),y""")
}
SourceStorageKind.REGISTER -> TODO("register | word")
else -> throw AssemblyError("weird source value $value")
}
}
private fun inplaceByteAnd(target: PtrTarget, value: AsmAssignSource) {
val (zpPtrVar, offset) = deref(target.pointer)
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
val number = value.number!!.number.toInt()
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out("""
lda ($zpPtrVar)
and #$number
sta ($zpPtrVar)""")
else
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
and #$number
sta ($zpPtrVar),y""")
}
SourceStorageKind.VARIABLE -> {
val varname = value.asmVarname
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out("""
lda ($zpPtrVar)
and $varname
sta ($zpPtrVar)""")
else
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
and $varname
sta ($zpPtrVar),y""")
}
SourceStorageKind.EXPRESSION -> {
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.A)
asmgen.out("""
ldy #$offset
and ($zpPtrVar),y
sta ($zpPtrVar),y""")
}
SourceStorageKind.REGISTER -> TODO("register & byte")
else -> throw AssemblyError("weird source value $value")
}
}
private fun inplaceWordAnd(target: PtrTarget, value: AsmAssignSource) {
val (zpPtrVar, offset) = deref(target.pointer)
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
val number = value.number!!.number.toInt()
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
and #<$number
sta ($zpPtrVar),y
iny
lda ($zpPtrVar),y
and #>$number
sta ($zpPtrVar),y""")
}
SourceStorageKind.VARIABLE -> {
require(value.datatype.isWord)
val varname = value.asmVarname
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
and $varname
sta ($zpPtrVar),y
lda ($zpPtrVar),y
and $varname+1
sta ($zpPtrVar),y""")
}
SourceStorageKind.EXPRESSION -> {
require(value.datatype.isWord)
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.AX)
asmgen.out("""
ldy #$offset
and ($zpPtrVar),y
sta ($zpPtrVar),y
iny
txa
and ($zpPtrVar),y
sta ($zpPtrVar),y""")
}
SourceStorageKind.REGISTER -> TODO("register & word")
else -> throw AssemblyError("weird source value $value")
}
}
fun assignIndexedPointer(target: AsmAssignTarget, arrayVarName: String, index: PtExpression, arrayDt: DataType) {
TODO("assign indexed pointer from array $arrayVarName at ${target.position}")
// val ptrZp = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, target.scope, target.position, variableAsmName="P8ZP_SCRATCH_PTR")
// assignAddressOfIndexedPointer(ptrZp, arrayVarName, arrayDt, index)
// when {
// target.datatype.isByteOrBool -> {
// asmgen.out("""
// ldy #0
// lda (P8ZP_SCRATCH_PTR),y""")
// asmgen.assignRegister(RegisterOrPair.A, target)
// }
// target.datatype.isWord || target.datatype.isPointer -> {
// if(asmgen.isTargetCpu(CpuType.CPU65C02))
// asmgen.out("""
// ldy #1
// lda (P8ZP_SCRATCH_PTR),y
// tax
// lda (P8ZP_SCRATCH_PTR)""")
// else
// asmgen.out("""
// ldy #1
// lda (P8ZP_SCRATCH_PTR),y
// tax
// dey
// lda (P8ZP_SCRATCH_PTR),y""")
// asmgen.assignRegister(RegisterOrPair.AX, target)
// }
// target.datatype.isLong -> {
// TODO("assign long from pointer to $target ${target.position}")
// }
// target.datatype.isFloat -> {
// // TODO optimize the float copying to avoid having to go through FAC1
// asmgen.out("""
// lda P8ZP_SCRATCH_PTR
// ldy P8ZP_SCRATCH_PTR+1
// jsr floats.MOVFM""")
// asmgen.assignRegister(RegisterOrPair.FAC1, target)
// }
// else -> throw AssemblyError("weird dt ${target.datatype}")
// }
}
private fun saveOnStack(regs: RegisterOrPair) {
when(regs) {
RegisterOrPair.AX -> {

View File

@@ -137,63 +137,75 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val inplaceInstrs = mutableListOf<IRCodeChunkBase>()
val (addressReg, fieldOffset) = codeGen.evaluatePointerAddressIntoReg(inplaceInstrs, pointerDeref)
val oldvalueReg = codeGen.registers.next(targetDt)
var operandTr = ExpressionCodeResult(emptyList(), IRDataType.BYTE, -1, -1)
if(augAssign.operator!="or=" && augAssign.operator!="and=") {
// for everything except the shortcircuit boolean operators, we can evaluate the value here unconditionally
operandTr = expressionEval.translateExpression(value)
inplaceInstrs += operandTr.chunks
}
if(targetDt== IRDataType.FLOAT) {
if(fieldOffset>0u)
addInstr(inplaceInstrs, IRInstruction(Opcode.LOADFIELD, targetDt, fpReg1 = oldvalueReg, reg1 = addressReg, immediate = fieldOffset.toInt()), null)
else
addInstr(inplaceInstrs, IRInstruction(Opcode.LOADI, targetDt, fpReg1 = oldvalueReg, reg1 = addressReg), null)
when(augAssign.operator) {
"+=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ADDR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"-=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.SUBR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"*=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MULR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"/=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.DIVSR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"%=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MODR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"+" -> { /* inplace + is a no-op */ }
else -> throw AssemblyError("invalid augmented assign operator for floats ${augAssign.operator}")
if((augAssign.operator=="+=" || augAssign.operator=="-=") && value.asConstInteger()==1 || value.asConstInteger()==2) {
// INC/DEC optimization instead of ADD/SUB
loadfield(inplaceInstrs, addressReg, fieldOffset, targetDt, oldvalueReg)
val instr = if(augAssign.operator=="+=") Opcode.INC else Opcode.DEC
repeat(value.asConstInteger()!!) {
addInstr(inplaceInstrs, IRInstruction(instr, targetDt, reg1 = oldvalueReg), null)
}
} else {
if(fieldOffset>0u)
addInstr(inplaceInstrs, IRInstruction(Opcode.LOADFIELD, targetDt, reg1 = oldvalueReg, reg2 = addressReg, immediate = fieldOffset.toInt()), null)
else
addInstr(inplaceInstrs, IRInstruction(Opcode.LOADI, targetDt, reg1 = oldvalueReg, reg2 = addressReg), null)
when(augAssign.operator) {
"+=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ADDR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"-=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.SUBR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"*=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MULR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"/=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.DIVR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"%=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MODR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"|=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ORR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"&=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ANDR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"^=", "xor=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.XORR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"<<=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.LSLN, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
">>=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.LSRN, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"or=" -> {
val shortcutLabel = codeGen.createLabelName()
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTNE, labelSymbol = shortcutLabel), null)
val valueTr = expressionEval.translateExpression(value)
inplaceInstrs += valueTr.chunks
addInstr(inplaceInstrs, IRInstruction(Opcode.ORR, targetDt, reg1=oldvalueReg, reg2=valueTr.resultReg), null)
inplaceInstrs += IRCodeChunk(shortcutLabel, null)
var operandTr = ExpressionCodeResult(emptyList(), IRDataType.BYTE, -1, -1)
if(augAssign.operator!="or=" && augAssign.operator!="and=") {
// for everything except the shortcircuit boolean operators, we can evaluate the value here unconditionally
operandTr = expressionEval.translateExpression(value)
// note: the instructions to load the value will be placed after the LOADFIELD instruction so that later optimizations about what modification is actually done, are easier
}
if(targetDt== IRDataType.FLOAT) {
loadfield(inplaceInstrs, addressReg, fieldOffset, targetDt, oldvalueReg)
inplaceInstrs += operandTr.chunks
when(augAssign.operator) {
"+=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ADDR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"-=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.SUBR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"*=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MULR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"/=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.DIVSR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"%=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MODR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"+" -> { /* inplace + is a no-op */ }
else -> throw AssemblyError("invalid augmented assign operator for floats ${augAssign.operator}")
}
"and=" -> {
val shortcutLabel = codeGen.createLabelName()
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTEQ, labelSymbol = shortcutLabel), null)
val valueTr = expressionEval.translateExpression(value)
inplaceInstrs += valueTr.chunks
addInstr(inplaceInstrs, IRInstruction(Opcode.ANDR, targetDt, reg1=oldvalueReg, reg2=valueTr.resultReg), null)
inplaceInstrs += IRCodeChunk(shortcutLabel, null)
} else {
loadfield(inplaceInstrs, addressReg, fieldOffset, targetDt, oldvalueReg)
inplaceInstrs += operandTr.chunks
when(augAssign.operator) {
"+=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ADDR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"-=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.SUBR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"*=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MULR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"/=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.DIVR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"%=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MODR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"|=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ORR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"&=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ANDR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"^=", "xor=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.XORR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"<<=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.LSLN, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
">>=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.LSRN, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"or=" -> {
val shortcutLabel = codeGen.createLabelName()
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTNE, labelSymbol = shortcutLabel), null)
val valueTr = expressionEval.translateExpression(value)
inplaceInstrs += valueTr.chunks
addInstr(inplaceInstrs, IRInstruction(Opcode.ORR, targetDt, reg1=oldvalueReg, reg2=valueTr.resultReg), null)
inplaceInstrs += IRCodeChunk(shortcutLabel, null)
}
"and=" -> {
val shortcutLabel = codeGen.createLabelName()
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTEQ, labelSymbol = shortcutLabel), null)
val valueTr = expressionEval.translateExpression(value)
inplaceInstrs += valueTr.chunks
addInstr(inplaceInstrs, IRInstruction(Opcode.ANDR, targetDt, reg1=oldvalueReg, reg2=valueTr.resultReg), null)
inplaceInstrs += IRCodeChunk(shortcutLabel, null)
}
"-" -> addInstr(inplaceInstrs, IRInstruction(Opcode.NEG, targetDt, reg1 = oldvalueReg), null)
"~" -> addInstr(inplaceInstrs, IRInstruction(Opcode.INV, targetDt, reg1 = oldvalueReg), null)
"not" -> addInstr(inplaceInstrs, IRInstruction(Opcode.XOR, targetDt, reg1 = oldvalueReg, immediate = 1), null)
"+" -> { /* inplace + is a no-op */ }
else -> throw AssemblyError("invalid augmented assign operator ${augAssign.operator}")
}
"-" -> addInstr(inplaceInstrs, IRInstruction(Opcode.NEG, targetDt, reg1 = oldvalueReg), null)
"~" -> addInstr(inplaceInstrs, IRInstruction(Opcode.INV, targetDt, reg1 = oldvalueReg), null)
"not" -> addInstr(inplaceInstrs, IRInstruction(Opcode.XOR, targetDt, reg1 = oldvalueReg, immediate = 1), null)
"+" -> { /* inplace + is a no-op */ }
else -> throw AssemblyError("invalid augmented assign operator ${augAssign.operator}")
}
}
@@ -222,6 +234,42 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
return chunks
}
private fun loadfield(
inplaceInstrs: MutableList<IRCodeChunkBase>,
addressReg: Int,
fieldOffset: UByte,
targetDt: IRDataType,
oldvalueReg: Int
) {
if (targetDt == IRDataType.FLOAT) {
if (fieldOffset > 0u)
addInstr(
inplaceInstrs,
IRInstruction(Opcode.LOADFIELD, targetDt, fpReg1 = oldvalueReg, reg1 = addressReg, immediate = fieldOffset.toInt()),
null
)
else
addInstr(
inplaceInstrs,
IRInstruction(Opcode.LOADI, targetDt, fpReg1 = oldvalueReg, reg1 = addressReg),
null
)
} else {
if (fieldOffset > 0u)
addInstr(
inplaceInstrs,
IRInstruction(Opcode.LOADFIELD, targetDt, reg1 = oldvalueReg, reg2 = addressReg, immediate = fieldOffset.toInt()),
null
)
else
addInstr(
inplaceInstrs,
IRInstruction(Opcode.LOADI, targetDt, reg1 = oldvalueReg, reg2 = addressReg),
null
)
}
}
private fun fallbackAssign(origAssign: PtAugmentedAssign): IRCodeChunks {
val value: PtExpression
if(origAssign.operator in PrefixOperators) {

View File

@@ -1,5 +1,7 @@
package prog8.codegen.intermediate
import prog8.code.StMemorySlabBlockName
import prog8.code.StStructInstanceBlockName
import prog8.code.SymbolTable
import prog8.code.ast.*
import prog8.code.core.AssemblyError
@@ -52,6 +54,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"prog8_lib_square_word" -> funcSquare(call, IRDataType.WORD)
"prog8_lib_structalloc" -> funcStructAlloc(call)
"sizeof" -> throw AssemblyError("sizeof must have been replaced with a constant")
"offsetof" -> throw AssemblyError("offsetof must have been replaced with a constant")
else -> throw AssemblyError("missing builtinfunc for ${call.name}")
}
}
@@ -500,7 +503,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val name = (call.args[0] as PtString).value
val code = IRCodeChunk(null, null)
val resultReg = codeGen.registers.next(IRDataType.WORD)
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "$StMemorySlabPrefix.prog8_memoryslab_$name")
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "$StMemorySlabBlockName.memory_$name")
return ExpressionCodeResult(code, IRDataType.WORD, resultReg, -1)
}
@@ -508,7 +511,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val code = IRCodeChunk(null, null)
val resultReg = codeGen.registers.next(IRDataType.WORD)
val labelname = SymbolTable.labelnameForStructInstance(call)
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = labelname)
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "${StStructInstanceBlockName}.$labelname")
return ExpressionCodeResult(code, IRDataType.WORD, resultReg, -1)
}

View File

@@ -47,10 +47,16 @@ private fun convert(struct: StStruct): IRStStructDef =
IRStStructDef(struct.scopedNameString, struct.fields, struct.size)
private fun convertArrayElt(elt: StArrayElement): IRStArrayElement = if(elt.boolean!=null)
IRStArrayElement(elt.boolean, null, elt.addressOfSymbol)
else
IRStArrayElement(null, elt.number, elt.addressOfSymbol)
private fun convertArrayElt(elt: StArrayElement): IRStArrayElement {
return if (elt.boolean != null)
IRStArrayElement(elt.boolean, null, null)
else if(elt.number!=null)
IRStArrayElement(null, elt.number, null)
else {
val symbol = elt.addressOfSymbol ?: (StStructInstanceBlockName + "." + (elt.structInstance ?: elt.structInstanceUninitialized))
IRStArrayElement(null, null, symbol)
}
}
private fun convert(variable: StStaticVariable): IRStStaticVariable {
@@ -72,7 +78,8 @@ private fun convert(variable: StStaticVariable): IRStStaticVariable {
val newArray = mutableListOf<IRStArrayElement>()
array.forEach {
if(it.addressOfSymbol!=null) {
val target = variable.lookup(it.addressOfSymbol!!) ?: throw NoSuchElementException("can't find variable ${it.addressOfSymbol}")
val target = variable.lookup(it.addressOfSymbol!!) ?:
throw NoSuchElementException("can't find variable ${it.addressOfSymbol}")
newArray.add(IRStArrayElement(null, null, target.scopedNameString))
} else {
newArray.add(convertArrayElt(it))
@@ -129,11 +136,11 @@ private fun convert(constant: StConstant): IRStConstant {
}
private fun convert(variable: StMemorySlab): IRStMemorySlab {
return if('.' in variable.name)
IRStMemorySlab(variable.name, variable.size, variable.align)
private fun convert(mem: StMemorySlab): IRStMemorySlab {
return if('.' in mem.name)
IRStMemorySlab(mem.name, mem.size, mem.align)
else
IRStMemorySlab("$StMemorySlabPrefix.${variable.name}", variable.size, variable.align)
IRStMemorySlab("$StMemorySlabBlockName.${mem.name}", mem.size, mem.align)
}
@@ -142,8 +149,8 @@ private fun convert(instance: StStructInstance, fields: Iterable<Pair<DataType,
val elt = convertArrayElt(value)
IRStructInitValue(field.first.base, elt)
}
return IRStStructInstance(instance.name, instance.structName, values, instance.size)
return if('.' in instance.name)
IRStStructInstance(instance.name, instance.structName, values, instance.size)
else
IRStStructInstance("${StStructInstanceBlockName}.${instance.name}", instance.structName, values, instance.size)
}
internal const val StMemorySlabPrefix = "prog8_slabs" // TODO also add ".prog8_memoryslab_" ?

View File

@@ -26,7 +26,7 @@ class VarConstantValueTypeAdjuster(
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.parent is AnonymousScope)
throw FatalAstException("vardecl may no longer occur in anonymousscope")
throw FatalAstException("vardecl may no longer occur in anonymousscope ${decl.position}")
try {
val declConstValue = decl.value?.constValue(program)

View File

@@ -8,7 +8,7 @@ import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.PROG8_CONTAINER_MODULES
import prog8.code.core.ICompilationTarget
import prog8.code.core.IErrorReporter
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 != INTERNED_STRINGS_MODULENAME && "ignore_unused" !in block.options()) {
if (block.name !in PROG8_CONTAINER_MODULES && "ignore_unused" !in block.options()) {
if (!block.statements.any { it is Subroutine && it.hasBeenInlined })
errors.info("removing unused block '${block.name}'", block.position)
}

View File

@@ -5,6 +5,7 @@ import prog8.ast.FatalAstException
import prog8.ast.Program
import prog8.ast.SyntaxError
import prog8.ast.expressions.*
import prog8.ast.statements.StructDecl
import prog8.ast.statements.VarDecl
import prog8.code.core.*
import kotlin.math.*
@@ -15,6 +16,7 @@ internal val constEvaluatorsForBuiltinFuncs: Map<String, ConstExpressionCaller>
"abs" to ::builtinAbs,
"len" to ::builtinLen,
"sizeof" to ::builtinSizeof,
"offsetof" to ::builtinOffsetof,
"sgn" to ::builtinSgn,
"sqrt__ubyte" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, false) { sqrt(it.toDouble()) } },
"sqrt__uword" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, false) { sqrt(it.toDouble()) } },
@@ -90,6 +92,25 @@ private fun builtinAbs(args: List<Expression>, position: Position, program: Prog
else throw SyntaxError("abs requires one integer argument", position)
}
private fun builtinOffsetof(args: List<Expression>, position: Position, program: Program): NumericLiteral {
// 1 arg, "Struct.field"
if(args.size!=1)
throw SyntaxError("offsetof requires one argument", position)
val identifier = (args[0] as? IdentifierReference)?.nameInSource
if(identifier==null || identifier.size<2)
throw CannotEvaluateException("offsetof","argument should be an identifier of the form Struct.field")
val structname = identifier.dropLast(1)
val fieldname = identifier.last()
val struct = args[0].definingScope.lookup(structname) as? StructDecl
if(struct==null)
throw SyntaxError("cannot find struct '$structname'", args[0].position)
val offset = struct.offsetof(fieldname, program.memsizer)
if(offset==null)
throw SyntaxError("no such field '${identifier.joinToString(".")}'", args[0].position)
return NumericLiteral.optimalInteger(offset.toInt(), position)
}
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program): NumericLiteral {
// 1 arg, type = anything, result type = ubyte or uword
if(args.size!=1)

View File

@@ -5,6 +5,7 @@ import prog8.ast.*
import prog8.ast.expressions.Expression
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.Directive
import prog8.code.SymbolTable
import prog8.code.SymbolTableMaker
import prog8.code.ast.PtProgram
import prog8.code.ast.printAst
@@ -176,17 +177,21 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
println("*********** COMPILER AST END *************\n")
}
var symbolTable: SymbolTable
val (intermediateAst, simplifiedAstDuration2) = measureTimedValue {
val intermediateAst = SimplifiedAstMaker(program, args.errors).transform()
val stMaker = SymbolTableMaker(intermediateAst, compilationOptions)
val symbolTable = stMaker.make()
symbolTable = stMaker.make()
postprocessSimplifiedAst(intermediateAst, symbolTable, compilationOptions, args.errors)
args.errors.report()
symbolTable = stMaker.make() // need an updated ST because the postprocessing changes stuff
if (compilationOptions.optimize) {
optimizeSimplifiedAst(intermediateAst, compilationOptions, symbolTable, args.errors)
args.errors.report()
symbolTable = stMaker.make() // need an updated ST because the optimization changes stuff
}
if (args.printAst2) {
@@ -204,6 +209,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
createAssemblyDuration = measureTime {
if (!createAssemblyAndAssemble(
intermediateAst,
symbolTable,
args.errors,
compilationOptions,
program.generatedLabelSequenceNumber
@@ -558,6 +564,7 @@ private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOpt
}
private fun createAssemblyAndAssemble(program: PtProgram,
symbolTable: SymbolTable,
errors: IErrorReporter,
compilerOptions: CompilationOptions,
lastGeneratedLabelSequenceNr: Int
@@ -572,10 +579,6 @@ private fun createAssemblyAndAssemble(program: PtProgram,
else
throw NotImplementedError("no code generator for cpu ${compilerOptions.compTarget.cpu}")
// need to make a new symboltable here to capture possible changes made by optimization steps performed earlier!
val stMaker = SymbolTableMaker(program, compilerOptions)
val symbolTable = stMaker.make()
val assembly = asmgen.generate(program, symbolTable, compilerOptions, errors)
errors.report()

View File

@@ -1269,17 +1269,23 @@ internal class AstChecker(private val program: Program,
val arrayspec = ArrayIndex.forArray(array)
checkValueTypeAndRangeArray(array.type.getOrUndef(), arrayspec, array)
} else {
errors.err("undefined array type (multiple element types?)", array.position)
errors.err("undefined array type (multiple or incompatible element types?)", array.position)
}
if(array.parent is VarDecl) {
if (!array.value.all { it is NumericLiteral || it is AddressOf || (it is FunctionCallExpression && it.target.targetStructDecl()!=null) }) {
if (!array.value.all { it is NumericLiteral || it is AddressOf || it is StaticStructInitializer }) {
errors.err("initialization value contains non-constant elements", array.value[0].position)
}
if(array.value.any { it is FunctionCallExpression }) {
errors.err("it is not yet possible to use struct initializations in an array, you have to do it one by one for now", array.value[0].position)
// TODO this is because later in the simplified AST the allocate struct variable is still missing somehow
val elementDt = (array.parent as VarDecl).datatype.elementType()
if(elementDt.isPointer) {
// all elements in the initializer array should be of the same element type
array.value.forEach {
val valueDt = it.inferType(program).getOrUndef()
if(!valueDt.isUnsignedWord && valueDt != elementDt) {
errors.err("struct initializer element has invalid type, expected $elementDt or uword but got $valueDt", it.position)
}
}
}
} else if(array.parent is ForLoop) {
@@ -1898,34 +1904,6 @@ internal class AstChecker(private val program: Program,
}
}
if(target is StructDecl) {
// it's a static struct inializer, check the values
if(args.isNotEmpty()) {
args.forEach {
if(it is IdentifierReference) {
val target = it.targetVarDecl()
if(target!=null && target.datatype.isPointer) {
errors.err("a pointer variable cannot be used in a static initialization value because its value is only known at runtime (use 0 here, and assign it later manually)", it.position)
}
}
}
if (!args.all { it is NumericLiteral || it is AddressOf || (it is TypecastExpression && it.expression is NumericLiteral)})
errors.err("initialization value contains non-constant elements", args[0].position)
if (target.fields.size != args.size)
errors.err("initialization value needs to have same number of values as the struct has fields, or be empty: expected ${target.fields.size} or 0, got ${args.size}", args[0].position)
else
target.fields.zip(args).withIndex().forEach { (index, fv) ->
val (field, value) = fv
val valueDt = value.inferType(program)
if(valueDt isNotAssignableTo field.first) {
errors.err("value #${index+1} has incompatible type $valueDt for field '${field.second}' (${field.first})", value.position)
}
}
}
// TODO rest?
}
args.forEach{
checkLongType(it)
if(it.inferType(program).isStructInstance)
@@ -2396,6 +2374,7 @@ internal class AstChecker(private val program: Program,
else
cast.valueOrZero().number
}
is StaticStructInitializer -> it.structname.hashCode() and 0xffff
else -> -9999999
}
}
@@ -2420,7 +2399,7 @@ internal class AstChecker(private val program: Program,
else -> throw FatalAstException("invalid type $targetDt")
}
if (!correct) {
if (value.parent is VarDecl && !value.value.all { it is NumericLiteral || it is AddressOf })
if (value.parent is VarDecl && !value.value.all { it is NumericLiteral || it is AddressOf || it is StaticStructInitializer })
errors.err("initialization value contains non-constant elements", value.value[0].position)
else
errors.err("array element out of range for type $targetDt", value.position)
@@ -2481,7 +2460,7 @@ internal class AstChecker(private val program: Program,
else if (targetDatatype.isPointer) {
if(sourceDatatype.isPointer) {
if(!(sourceDatatype isAssignableTo targetDatatype))
errors.err("cannot assign different pointer type, expected $targetDatatype got $sourceDatatype", position)
errors.err("cannot assign different pointer type, expected $targetDatatype or uword but got $sourceDatatype", position)
} else if(sourceDatatype.isString && targetDatatype.sub?.isByte==true) {
// assigning a string to a byte pointer is allowed.
} else if(!sourceDatatype.isUnsignedWord && !sourceDatatype.isStructInstance)
@@ -2506,6 +2485,7 @@ internal class AstChecker(private val program: Program,
}
override fun visit(onGoto: OnGoto) {
val t = onGoto.index.inferType(program)
if(!onGoto.index.inferType(program).getOrUndef().isUnsignedByte) {
errors.err("on..goto index must be an unsigned byte", onGoto.index.position)
}
@@ -2517,6 +2497,34 @@ internal class AstChecker(private val program: Program,
else
errors.err("no support for getting the target value of pointer array indexing like this yet. Split the expression by using an intermediate variable.", deref.position) // this may never occur anymore since more ArrayIndexedPtrDereference got rewritten
}
override fun visit(initializer: StaticStructInitializer) {
val args = initializer.args
if(args.isNotEmpty()) {
args.forEach {
if(it is IdentifierReference) {
val target = it.targetVarDecl()
if(target!=null && target.datatype.isPointer) {
errors.err("a pointer variable cannot be used in a static initialization value because its value is only known at runtime (use 0 here, and assign it later manually)", it.position)
}
}
}
if (!args.all { it is NumericLiteral || it is AddressOf || (it is TypecastExpression && it.expression is NumericLiteral)})
errors.err("initialization value contains non-constant elements", args[0].position)
val struct = initializer.structname.targetStructDecl()
if(struct!=null) {
require(args.size==struct.fields.size)
struct.fields.zip(args).withIndex().forEach { (index, fv) ->
val (field, value) = fv
val valueDt = value.inferType(program)
if(valueDt isNotAssignableTo field.first)
errors.err("value #${index+1} has incompatible type $valueDt for field '${field.second}' (${field.first})", value.position)
}
}
}
super.visit(initializer)
}
}
internal fun checkUnusedReturnValues(call: FunctionCallStatement, target: Statement, errors: IErrorReporter) {

View File

@@ -169,10 +169,12 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
override fun visit(functionCallStatement: FunctionCallStatement) = visitFunctionCall(functionCallStatement)
override fun visit(initializer: StaticStructInitializer) {
val fields = initializer.structname.targetStructDecl()!!.fields
if(initializer.args.isNotEmpty() && initializer.args.size != fields.size) {
val pos = (if(initializer.args.any()) initializer.args[0] else initializer).position
invalidNumberOfArgsError(pos, initializer.args.size, fields.map { it.second }, true)
val struct = initializer.structname.targetStructDecl()
if(struct!=null) {
if (initializer.args.isNotEmpty() && initializer.args.size != struct.fields.size) {
val pos = (if (initializer.args.any()) initializer.args[0] else initializer).position
invalidNumberOfArgsError(pos, initializer.args.size, struct.fields.map { it.second }, true)
}
}
}

View File

@@ -605,11 +605,15 @@ _after:
}
val replacementScope = AnonymousScope(if(conditionVar==null)
mutableListOf(ifSt, jumplistArray)
mutableListOf(ifSt)
else
mutableListOf(conditionVar, assignIndex!!, ifSt, jumplistArray)
mutableListOf(conditionVar, assignIndex!!, ifSt)
, ongoto.position)
return listOf(IAstModification.ReplaceNode(ongoto, replacementScope, parent))
val declscope = parent.definingScope
return listOf(
IAstModification.ReplaceNode(ongoto, replacementScope, parent),
IAstModification.InsertFirst(jumplistArray, declscope)
)
}
override fun after(deref: PtrDereference, parent: Node): Iterable<IAstModification> {
@@ -872,4 +876,68 @@ _after:
errors.err(error, ifExpr.condition.position)
return noModifications
}
override fun after(array: ArrayLiteral, parent: Node): Iterable<IAstModification> {
fun convertArrayIntoStructInitializer(array: ArrayLiteral, struct: ISubType): StaticStructInitializer {
val structname = IdentifierReference(struct.scopedNameString.split("."), array.position)
return StaticStructInitializer(structname, array.value.toMutableList(), array.position)
}
fun checkNumberOfElements(struct: StructDecl, array: ArrayLiteral): Boolean {
val numValues = array.value.size
if (numValues>0 && struct.fields.size != numValues) {
if (numValues < struct.fields.size) {
val missing = struct.fields.drop(numValues).joinToString(", ") { it.second }
errors.err("invalid number of field values: expected ${struct.fields.size} or 0 but got ${numValues}, missing: $missing", array.position)
} else
errors.err("invalid number of field values: expected ${struct.fields.size} or 0 but got ${numValues}", array.position)
return false
}
return true
}
if(parent is VarDecl) {
if (parent.datatype.isPointerArray && parent.datatype.elementType().subType!=null) {
val struct = parent.datatype.elementType().subType as StructDecl
val allremovals = mutableListOf<VarDecl>()
var noErrors = true
var changes = false
array.value.withIndex().forEach { (index, elt) ->
if(elt is ArrayLiteral) {
noErrors = noErrors and checkNumberOfElements(struct, elt)
if(noErrors) {
array.value[index] = convertArrayIntoStructInitializer(elt, struct)
changes = true
}
} else if(elt is IdentifierReference) {
val arrayvar = elt.targetVarDecl()!!.value as ArrayLiteral
noErrors = noErrors and checkNumberOfElements(struct, arrayvar)
if(noErrors) {
array.value[index] = convertArrayIntoStructInitializer(arrayvar, struct)
allremovals += elt.targetVarDecl()!!
changes = true
}
}
}
if(changes && noErrors) {
array.linkParents(parent)
return allremovals.map { IAstModification.Remove(it, it.parent as IStatementContainer) }
}
}
}
else if(parent is Assignment) {
val targetDt = parent.target.inferType(program).getOrUndef()
if(targetDt.isPointer && targetDt.subType!=null) {
val struct = targetDt.subType as StructDecl
if(checkNumberOfElements(struct, array)) {
val initializser = convertArrayIntoStructInitializer(array, struct)
return listOf(IAstModification.ReplaceNode(array, initializser, parent))
}
}
}
return noModifications
}
}

View File

@@ -21,6 +21,8 @@ internal fun postprocessSimplifiedAst(
private fun processSubtypesIntoStReferences(program: PtProgram, st: SymbolTable) {
fun getStStruct(subType: ISubType): StStruct {
if(subType is StStruct)
return subType
val stNode = st.lookup(subType.scopedNameString) as? StStruct
if(stNode != null)
return stNode
@@ -28,7 +30,7 @@ private fun processSubtypesIntoStReferences(program: PtProgram, st: SymbolTable)
throw FatalAstException("cannot find in ST: ${subType.scopedNameString} $subType")
}
fun fixSubtype(type: DataType) {
fun fixSubtypeIntoStType(type: DataType) {
if(type.subType!=null && type.subType !is StStruct) {
type.subType = getStStruct(type.subType!!)
}
@@ -36,13 +38,21 @@ private fun processSubtypesIntoStReferences(program: PtProgram, st: SymbolTable)
fun fixSubtypes(node: PtNode) {
when(node) {
is IPtVariable -> fixSubtype(node.type)
is PtPointerDeref -> fixSubtype(node.type)
is PtStructDecl -> node.fields.forEach { fixSubtype(it.first) }
is PtAsmSub -> node.returns.forEach { fixSubtype(it.second) }
is PtExpression -> fixSubtype(node.type)
is PtSubSignature -> node.returns.forEach { fixSubtype(it) }
is PtSubroutineParameter -> fixSubtype(node.type)
is IPtVariable -> {
fixSubtypeIntoStType(node.type)
// if it's an array, fix the subtypes of its elements as well
if(node.type.isArray && node is PtVariable) {
(node.value as? PtArray)?.let {array ->
array.children.forEach { fixSubtypes(it) }
}
}
}
is PtPointerDeref -> fixSubtypeIntoStType(node.type)
is PtStructDecl -> node.fields.forEach { fixSubtypeIntoStType(it.first) }
is PtAsmSub -> node.returns.forEach { fixSubtypeIntoStType(it.second) }
is PtExpression -> fixSubtypeIntoStType(node.type)
is PtSubSignature -> node.returns.forEach { fixSubtypeIntoStType(it) }
is PtSubroutineParameter -> fixSubtypeIntoStType(node.type)
else -> { /* has no datatype */ }
}
}
@@ -175,7 +185,7 @@ private fun integrateDefers(subdefers: Map<PtSub, List<PtDefer>>, program: PtPro
is PtNumber,
is PtRange,
is PtString -> true
is PtIdentifier -> true // actually PtIdentifier IS "complex" this time (it's a variable that might change) but it's kinda annoying to give a warning message for this very common case
// note that PtIdentifier als is "complex" this time (it's a variable that might change)
else -> false
}
@@ -192,7 +202,6 @@ private fun integrateDefers(subdefers: Map<PtSub, List<PtDefer>>, program: PtPro
}
// complex return value, need to store it before calling the defer block
errors.warn("using defer with nontrivial return value(s) incurs stack overhead", ret.children.first { !notComplex(it as PtExpression)}.position)
val pushAndPopCalls = ret.children.map { makePushPopFunctionCalls(it as PtExpression) }
val pushCalls = pushAndPopCalls.map { it.first }.reversed() // push in reverse order
val popCalls = pushAndPopCalls.map { it.second }

View File

@@ -627,9 +627,12 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
return if(initializer.args.isEmpty())
noModifications
else {
val struct = initializer.structname.targetStructDecl()!!
val paramsPossibleDatatypes = struct.fields.map { listOf(it.first) }
fixupArgumentList(paramsPossibleDatatypes, initializer.args, initializer)
val struct = initializer.structname.targetStructDecl()
if(struct!=null) {
val paramsPossibleDatatypes = struct.fields.map { listOf(it.first) }
fixupArgumentList(paramsPossibleDatatypes, initializer.args, initializer)
}
else noModifications
}
}

View File

@@ -124,8 +124,12 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
return if(parent is IStatementContainer)
listOf(ScopeFlatten(scope, parent as IStatementContainer))
else
else {
if(scope.statements.any {it is VarDecl}) {
throw FatalAstException("there are leftover vardecls in the nested scope at ${scope.position}, they should have been moved/placed in the declaration scope (subroutine) by now")
}
noModifications
}
}
private class ScopeFlatten(val scope: AnonymousScope, val into: IStatementContainer) : IAstModification {

View File

@@ -10,6 +10,7 @@ import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import prog8.ast.Program
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.PROG8_CONTAINER_MODULES
import prog8.code.core.IErrorReporter
import prog8.code.source.SourceCode
import prog8.compiler.ModuleImporter
@@ -49,7 +50,7 @@ class TestModuleImporter: FunSpec({
withClue(".file should point to specified path") {
error1.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
}
program.modules.size shouldBe 1
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
val error2 = importer.importMainModule(srcPathAbs).getErrorOrElse { error("should have import error") }
withClue(".file should be normalized") {
"${error2.file}" shouldBe "${error2.file.normalize()}"
@@ -57,7 +58,7 @@ class TestModuleImporter: FunSpec({
withClue(".file should point to specified path") {
error2.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
}
program.modules.size shouldBe 1
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
}
test("testDirectory") {
@@ -75,7 +76,7 @@ class TestModuleImporter: FunSpec({
it.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
}
}
program.modules.size shouldBe 1
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
shouldThrow<FileSystemException> { importer.importMainModule(srcPathAbs) }
.let {
@@ -86,7 +87,7 @@ class TestModuleImporter: FunSpec({
it.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
}
}
program.modules.size shouldBe 1
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
}
}
@@ -101,7 +102,7 @@ class TestModuleImporter: FunSpec({
val path = assumeReadableFile(searchIn[0], fileName)
val module = importer.importMainModule(path.absolute()).getOrElse { throw it }
program.modules.size shouldBe 2
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size+1
module shouldBeIn program.modules
module.program shouldBe program
}
@@ -118,7 +119,7 @@ class TestModuleImporter: FunSpec({
}
val module = importer.importMainModule(path).getOrElse { throw it }
program.modules.size shouldBe 2
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size+1
module shouldBeIn program.modules
module.program shouldBe program
}
@@ -131,7 +132,7 @@ class TestModuleImporter: FunSpec({
assumeReadableFile(searchIn, path)
val module = importer.importMainModule(path).getOrElse { throw it }
program.modules.size shouldBe 2
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size+1
module shouldBeIn program.modules
module.program shouldBe program
}
@@ -152,7 +153,7 @@ class TestModuleImporter: FunSpec({
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
}
}
program.modules.size shouldBe 1
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
}
}
@@ -170,9 +171,10 @@ class TestModuleImporter: FunSpec({
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 4 }
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
}
}
}
withClue("imported module with error in it should not be present") { program.modules.size shouldBe 1 }
withClue("imported module with error in it should not be present") { program.modules.size shouldBe PROG8_CONTAINER_MODULES.size }
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
program.modules[0].name shouldBe INTERNED_STRINGS_MODULENAME
}
}
@@ -203,14 +205,14 @@ class TestModuleImporter: FunSpec({
withClue(count[n] + " call / NO .p8 extension") { errors.noErrors() shouldBe false }
errors.errors.single() shouldContain "0:0: no module found with name i_do_not_exist"
errors.report()
program.modules.size shouldBe 1
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
val result2 = importer.importImplicitLibraryModule(filenameWithExt)
withClue(count[n] + " call / with .p8 extension") { result2 shouldBe null }
withClue(count[n] + " call / with .p8 extension") { importer.errors.noErrors() shouldBe false }
errors.errors.single() shouldContain "0:0: no module found with name i_do_not_exist.p8"
errors.report()
program.modules.size shouldBe 1
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
}
}
}
@@ -232,7 +234,7 @@ class TestModuleImporter: FunSpec({
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
}
}
program.modules.size shouldBe 1
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
}
}
@@ -254,7 +256,7 @@ class TestModuleImporter: FunSpec({
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
}
}
withClue("imported module with error in it should not be present") { program.modules.size shouldBe 1 }
withClue("imported module with error in it should not be present") { program.modules.size shouldBe PROG8_CONTAINER_MODULES.size }
program.modules[0].name shouldBe INTERNED_STRINGS_MODULENAME
importer.errors.report()
}

View File

@@ -223,6 +223,7 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
"textelite",
"pointers/animalgame",
"pointers/binarytree",
"pointers/hashtable",
"pointers/sortedlist",
"pointers/sorting"
),
@@ -253,6 +254,7 @@ class TestCompilerOnExamplesVirtual: FunSpec({
"sincos",
"pointers/animalgame",
"pointers/binarytree",
"pointers/hashtable",
"pointers/sortedlist",
"pointers/fountain-virtual",
"pointers/sorting"

View File

@@ -1731,7 +1731,34 @@ main {
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
}
test("type error for invalid bool field initializer") {
test("struct initializers in array") {
val src="""
%option enable_floats
main {
struct Node {
ubyte id
str name
uword array
bool flag
float speed
}
sub start() {
^^Node[] @shared nodes = [
^^Node:[1,"one", 1000, true, 1.111 ],
^^Node:[2,"two", 2000, false, 2.222 ],
^^Node:[3,"three", 3000, true, 3.333 ],
^^Node:[],
^^Node:[],
^^Node:[],
]
}
}"""
compileText(C64Target(), false, src, outputDir) shouldNotBe null
compileText(VMTarget(), false, src, outputDir) shouldNotBe null
}
test("type error for invalid field initializer") {
val src="""
main {
struct Enemy {
@@ -1743,9 +1770,9 @@ main {
sub start() {
^^Enemy @shared e1 = ^^Enemy: []
^^Enemy @shared e2 = ^^Enemy: [1,2,3,true]
^^Enemy @shared e3 = ^^Enemy: [1,2,3,4] ; TODO type error for the boolean
^^Enemy @shared e4 = ^^Enemy: [1,2,3,4.555] ; TODO type error for the boolean
^^Enemy @shared e3 = ^^Enemy: [1,2,3,4]
^^Enemy @shared e4 = ^^Enemy: [1,2,3,4.444]
e3.elite = 99
e4.elite = 3.444
}
@@ -1753,9 +1780,78 @@ main {
val errors=ErrorReporterForTests()
compileText(VMTarget(), false, src, outputDir, errors=errors) shouldBe null
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "doesn't match target type"
errors.errors[1] shouldContain "doesn't match target type"
errors.errors.size shouldBe 4
errors.errors[0] shouldContain "value #4 has incompatible type"
errors.errors[1] shouldContain "value #4 has incompatible type"
errors.errors[2] shouldContain "doesn't match target type"
errors.errors[3] shouldContain "doesn't match target type"
}
test("long and short form struct initializers") {
val src="""
%option enable_floats
main {
struct Node {
ubyte id
str name
uword array
bool flag
float perc
}
sub start() {
^^Node[] @shared nodeswithtype = [
^^Node: [1,"one", 1000, true, 1.111],
^^Node: [],
]
^^Node[] @shared nodeswithout = [
[2,"two", 2000, false, 2.222],
[],
]
^^Node @shared nptrwithtype = ^^Node : [1, "one", 1000, false, 3.333]
^^Node @shared nptrwithouttype = [1, "one", 1000, false, 3.333]
}
}"""
compileText(C64Target(), false, src, outputDir) shouldNotBe null
compileText(VMTarget(), false, src, outputDir) shouldNotBe null
}
test("type error for wrong type in pointer array and assignment") {
val src="""
main {
struct Node {
ubyte id
}
struct Foobar {
bool thing
}
sub start() {
^^Node[] onlynodes = [
^^Node: [],
^^Foobar: []
]
uword multipleok = [
^^Node: [],
^^Foobar: []
]
^^Node node = ^^Foobar: []
}
}"""
val errors=ErrorReporterForTests()
compileText(C64Target(), false, src, outputDir, errors=errors) shouldBe null
errors.errors.size shouldBe 6
errors.errors[0] shouldContain "11:30: initialization value for pointer array"
errors.errors[1] shouldContain "11:30: undefined array type" // a bit redundant but can't be helped
errors.errors[2] shouldContain "13:13: struct initializer element has invalid type"
errors.errors[3] shouldContain "16:28: invalid assignment value"
errors.errors[4] shouldContain "16:28: undefined array type"
errors.errors[5] shouldContain "21:23: cannot assign different pointer type"
}
test("local and global struct pointer qualified name lookups") {
@@ -1982,8 +2078,8 @@ main {
val errors = ErrorReporterForTests()
compileText(VMTarget(), false, src, outputDir, errors = errors, writeAssembly = false) shouldBe null
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "7:24: cannot assign different pointer type, expected ^^uword got ^^ubyte"
errors.errors[1] shouldContain "10:24: cannot assign different pointer type, expected ^^ubyte got ^^uword"
errors.errors[0] shouldContain "7:24: cannot assign different pointer type, expected ^^uword or uword but got ^^ubyte"
errors.errors[1] shouldContain "10:24: cannot assign different pointer type, expected ^^ubyte or uword but got ^^uword"
}
test("passing nosplit array of structpointers to a subroutine in various forms should be param type ptr to struct") {

View File

@@ -88,8 +88,8 @@ class TestSymbolTable: FunSpec({
val stVar1 = StStaticVariable("initialized", DataType.UBYTE, null, null, null, ZeropageWish.DONTCARE, 0u, false,node)
stVar1.setOnetimeInitNumeric(99.0)
val stVar2 = StStaticVariable("uninitialized", DataType.UBYTE, null, null, null, ZeropageWish.DONTCARE, 0u, false, node)
val arrayInitNonzero = listOf(StArrayElement(1.1, null, null), StArrayElement(2.2, null, null), StArrayElement(3.3, null, null))
val arrayInitAllzero = listOf(StArrayElement(0.0, null, null), StArrayElement(0.0, null, null), StArrayElement(0.0, null, null))
val arrayInitNonzero = listOf(StArrayElement(1.1, null, null, null, null), StArrayElement(2.2, null, null, null, null), StArrayElement(3.3, null, null,null, null))
val arrayInitAllzero = listOf(StArrayElement(0.0, null, null, null, null), StArrayElement(0.0, null, null,null, null), StArrayElement(0.0, null, null,null, null))
val stVar3 = StStaticVariable("initialized", DataType.arrayFor(BaseDataType.UWORD), null, arrayInitNonzero, 3u, ZeropageWish.DONTCARE, 0u, false, node)
val stVar4 = StStaticVariable("initialized", DataType.arrayFor(BaseDataType.UWORD), null, arrayInitAllzero, 3u, ZeropageWish.DONTCARE, 0u, false, node)
val stVar5 = StStaticVariable("uninitialized", DataType.arrayFor(BaseDataType.UWORD), null, null, 3u, ZeropageWish.DONTCARE, 0u, false, node)

View File

@@ -5,7 +5,7 @@ import io.kotest.matchers.string.shouldContain
import prog8.ast.AstToSourceTextConverter
import prog8.ast.Module
import prog8.ast.Program
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.PROG8_CONTAINER_MODULES
import prog8.code.source.SourceCode
import prog8.parser.ParseError
import prog8.parser.Prog8Parser.parseModule
@@ -38,10 +38,12 @@ class TestAstToSourceText: AnnotationSpec() {
}
@Test
fun testMentionsInternedStringsModule() {
fun testMentionsProg8ContainerModules() {
val orig = SourceCode.Text("\n")
val (txt, _) = roundTrip(parseModule(orig))
txt shouldContain Regex(";.*$INTERNED_STRINGS_MODULENAME")
PROG8_CONTAINER_MODULES.forEach {
txt shouldContain Regex(";.*$it")
}
}
@Test

View File

@@ -13,10 +13,11 @@ import io.kotest.matchers.types.shouldBeSameInstanceAs
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.statements.Block
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.PROG8_CONTAINER_MODULES
import prog8.code.ast.PtBlock
import prog8.code.core.Position
import prog8.code.source.SourceCode
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.target.C64Target
import prog8tests.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer
@@ -30,7 +31,7 @@ class TestProgram: FunSpec({
context("Constructor") {
test("withNameBuiltinsAndMemsizer") {
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
program.modules.size shouldBe 1
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
program.modules[0].name shouldBe INTERNED_STRINGS_MODULENAME
program.modules[0].program shouldBeSameInstanceAs program
program.modules[0].parent shouldBeSameInstanceAs program.namespace
@@ -45,7 +46,7 @@ class TestProgram: FunSpec({
val retVal = program.addModule(m1)
retVal shouldBeSameInstanceAs program
program.modules.size shouldBe 2
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size + 1
m1 shouldBeIn program.modules
m1.program shouldBeSameInstanceAs program
m1.parent shouldBeSameInstanceAs program.namespace
@@ -163,7 +164,7 @@ datablock2 ${'$'}8000 {
val result = compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=true)!!
result.compilerAst.allBlocks.size shouldBeGreaterThan 5
result.compilerAst.modules.drop(2).all { it.isLibrary } shouldBe true
result.compilerAst.modules.drop(PROG8_CONTAINER_MODULES.size+1).all { it.isLibrary } shouldBe true
val mainMod = result.compilerAst.modules[0]
mainMod.name shouldStartWith "on_the_fly"
result.compilerAst.modules[1].name shouldBe "prog8_interned_strings"
@@ -183,7 +184,7 @@ datablock2 ${'$'}8000 {
blocks[1].name shouldBe "p8_sys_startup"
blocks[2].name shouldBe "p8b_otherblock1"
blocks[3].name shouldBe "p8b_otherblock2"
blocks[4].name shouldBe "prog8_interned_strings"
blocks[4].name shouldBe INTERNED_STRINGS_MODULENAME
blocks[5].name shouldBe "txt"
blocks[5].library shouldBe true
blocks[13].name shouldBe "p8b_datablock2"

View File

@@ -1157,5 +1157,21 @@ main {
decls.all { it.datatype.isPointerArray } shouldBe true
}
test("on..call in nested scope compiles correctly with temp variable introduced") {
val src="""
main {
sub start() {
if_cs {
on cx16.r0L call (func,func)
}
}
sub func() {
}
}"""
compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=false) shouldNotBe null
}
})

View File

@@ -213,7 +213,7 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
}
output(") ")
if(subroutine.asmClobbers.isNotEmpty()) {
output("-> clobbers (")
output(" clobbers (")
val regs = subroutine.asmClobbers.toList().sorted()
for(r in regs) {
output(r.toString())
@@ -545,9 +545,9 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
}
override fun visit(onGoto: OnGoto) {
output(if(onGoto.isCall) "selectcall " else "selectgoto ")
output("on ")
onGoto.index.accept(this)
output(" from (")
output(if(onGoto.isCall) " call (" else " goto (")
onGoto.labels.forEachIndexed { idx, label ->
label.accept(this)
if(idx!=onGoto.labels.lastIndex)

View File

@@ -6,6 +6,7 @@ import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.code.GENERATED_LABEL_PREFIX
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.PROG8_CONTAINER_MODULES
import prog8.code.core.*
import prog8.code.source.SourceCode
@@ -22,17 +23,19 @@ class Program(val name: String,
val namespace: GlobalNamespace = GlobalNamespace(_modules)
init {
// insert a container module for all interned strings later
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)
internedStringsModule.statements.add(block)
// insert container modules for all interned strings and struct instances
PROG8_CONTAINER_MODULES.forEach { containername ->
val module = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated(containername))
val block = Block(containername, 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)
module.statements.add(block)
_modules.add(0, internedStringsModule)
internedStringsModule.linkParents(namespace)
internedStringsModule.program = this
_modules.add(0, module)
module.linkParents(namespace)
module.program = this
}
}
fun addModule(module: Module): Program {
@@ -67,7 +70,7 @@ class Program(val name: String,
}
val toplevelModule: Module
get() = modules.first { it.name!= INTERNED_STRINGS_MODULENAME }
get() = modules.first { it.name !in PROG8_CONTAINER_MODULES }
private val internedStringsReferenceCounts = mutableMapOf<VarDecl, Int>()

View File

@@ -150,7 +150,7 @@ private class SymbolDumper(val skipLibraries: Boolean): IAstVisitor {
}
output(") ")
if(subroutine.asmClobbers.isNotEmpty()) {
output("-> clobbers (")
output(" clobbers (")
val regs = subroutine.asmClobbers.toList().sorted()
for(r in regs) {
output(r.toString())

View File

@@ -1123,7 +1123,7 @@ class ArrayLiteral(val type: InferredTypes.InferredType, // inferred because
}
// otherwise, select the "biggest" datatype based on the elements in the array.
require(value.isNotEmpty()) { "can't determine type of empty array" }
if(value.isEmpty()) return InferredTypes.unknown()
val datatypesInArray = value.map { it.inferType(program) }
if(datatypesInArray.any{ it.isUnknown })
return InferredTypes.unknown()
@@ -1318,9 +1318,30 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
BuiltinFunctionPlaceholder(nameInSource[0], position, parent)
else
definingScope.lookup(nameInSource)
fun targetVarDecl(): VarDecl? = targetStatement() as? VarDecl
fun targetSubroutine(): Subroutine? = targetStatement() as? Subroutine
fun targetStructDecl(): StructDecl? = targetStatement() as? StructDecl
fun targetVarDecl(): VarDecl? {
// follows aliases
val t = targetStatement()
return if(t is Alias)
t.target.targetVarDecl()
else
t as? VarDecl
}
fun targetSubroutine(): Subroutine? {
// follows aliases
val t = targetStatement()
return if(t is Alias)
t.target.targetSubroutine()
else
t as? Subroutine
}
fun targetStructDecl(): StructDecl? {
// follows aliases
val t = targetStatement()
return if(t is Alias)
t.target.targetStructDecl()
else
t as? StructDecl
}
fun targetStructFieldRef(): StructFieldRef? {
if(nameInSource.size<2) return null
return targetStatement() as? StructFieldRef
@@ -1820,7 +1841,13 @@ class StaticStructInitializer(var structname: IdentifierReference,
override fun referencesIdentifier(nameInSource: List<String>): Boolean = structname.referencesIdentifier(nameInSource) || args.any{it.referencesIdentifier(nameInSource)}
override fun inferType(program: Program) = InferredTypes.knownFor(BaseDataType.UWORD)
override fun inferType(program: Program): InferredTypes.InferredType {
val struct = structname.targetStructDecl()
return if(struct==null)
InferredTypes.unknown()
else
InferredTypes.knownFor(DataType.pointer(struct))
}
}

View File

@@ -333,7 +333,7 @@ class VarDecl(
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && (value==null || node===value))
value = replacement // note: any datatype differences between the value and the decl itself, will be fixed by a separate ast walker step
replacement.parent = this
replacement.linkParents(this)
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
@@ -414,6 +414,15 @@ class StructDecl(override val name: String, val fields: Array<Pair<DataType, Str
override fun getFieldType(name: String): DataType? = fields.firstOrNull { it.second==name }?.first
override val scopedNameString by lazy { scopedName.joinToString(".") }
fun offsetof(fieldname: String, sizer: IMemSizer): UByte? {
fields.fold(0) { offset, field ->
if (field.second == fieldname)
return offset.toUByte()
offset + sizer.memorySize(field.first, 1)
}
return null
}
}
class StructFieldRef(val pointer: IdentifierReference, val struct: StructDecl, val type: DataType, override val name: String, override val position: Position): Statement(), INamedStatement {

View File

@@ -52,10 +52,7 @@ needs_sphinx = '5.3'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [ 'sphinxcontrib.jquery', 'sphinx_rtd_dark_mode']
# user starts in light mode
default_dark_mode = False
extensions = [ 'sphinxcontrib.jquery', 'sphinx_rtd_dark_mode', 'sphinx.ext.imgconverter']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -177,6 +174,9 @@ texinfo_documents = [
# -- Extension configuration -------------------------------------------------
# user starts in light mode
default_dark_mode = False
# -- Options for to do extension ----------------------------------------------
# todo_include_todos = True

View File

@@ -118,6 +118,11 @@ mkword (msb, lsb)
Don't get confused by how the system actually stores this 16-bit word value in memory (which is
in little-endian format, so lsb first then msb)
offsetof (Struct.field)
The offset in bytes of the given field in the struct. The first field will always have offset 0.
Usually you just reference the fields directly but in some cases it might be useful to know how many
bytes from the start of the structure a field is located at.
peek (address)
same as @(address) - reads the byte at the given address in memory.
@@ -668,7 +673,7 @@ but perhaps the provided ones can be of service too.
Returns next random byte 0-255 from the pseudo-RNG sequence.
Does not work in ROM code; use rnd_rom instead.
``rnd ()``
``rnd_rom ()``
Returns next random byte 0-255 from the pseudo-RNG sequence.
Works in ROM code, but make sure to initialize the seed values using rndseed_rom.

View File

@@ -5,9 +5,15 @@
Porting Guide
*************
Here is a guide for porting Prog8 to other compilation targets.
Here is a guide for making Prog8 work for other compilation targets.
Answers to the questions below are used to configure the new target and supporting libraries.
**It is not required to change the compiler itself to make it support new compilation targets.**
A few of the most commonly used ones are built-in (such as c64, cx16), but you can use
separate configuration files to create new targets that the compiler can use.
See :ref:`customizable_target` for details about this. You still need to provide most of the
information asked for in this porting guide and code that into the configuration file.
.. note::
The assembly code that prog8 generates is not suitable to be put into ROM. (It contains
embedded variables, and self-modifying code).
@@ -25,7 +31,7 @@ Memory Map
Zeropage
========
#. *Absolute requirement:* Provide four times 2 consecutive bytes (i.e. four 16-bit words) in the zeropage that are free to use at all times.
#. *Absolute requirement:* Provide four words (16 bit byte pairs) in the zeropage that are free to use at all times.
#. Provide list of any additional free zeropage locations for a normal running system (BASIC + Kernal enabled)
#. Provide list of any additional free zeropage locations when BASIC is off, but floating point routines should still work
#. Provide list of any additional free zeropage locations when only the Kernal remains enabled
@@ -43,14 +49,13 @@ RAM, ROM, I/O
#. what part(s) of the address space is memory-mapped I/O registers?
#. is there a block of "high ram" available (ram that is not the main ram used to load programs in) that could be used for variables?
#. is there a banking system? How does it work (how do you select Ram/Rom banks)? How is the default bank configuration set?
Note that prog8 itself has no notion of banking, but this knowledge may be required for proper system initialization.
Character encodings
-------------------
#. if not PETSCII or CBM screencodes: provide the primary character encoding table that the system uses (i.e. how is text represented in memory)
#. provide the primary character encoding table that the system uses (i.e. how is text represented in memory. For example, PETSCII)
#. provide alternate character encodings (if any)
#. what are the system's standard character screen dimensions?
#. is there a screen character matrix directly accessible in Ram? What's it address? Same for color attributes if any.
#. is there a screen character matrix directly accessible in RAM? What's it address? Same for color attributes if any.
ROM routines
@@ -63,7 +68,7 @@ The more the merrier.
Floating point
==============
Prog8 can support floating point math *if* the target system has floating point math routines in ROM. If that is the case:
Prog8 can support floating point math *if* the target system has suitable floating point math routines in ROM. If that is the case:
#. what is the binary representation format of the floating point numbers? (how many bytes, how the bits are set up)
#. what are the valid minimum negative and maximum positive floating point values?

View File

@@ -1303,6 +1303,11 @@ It's possible to write a defer for a block of statements, but the advice is to k
If a piece of inlined assembly somehow causes the routine to exit, the compiler cannot detect this,
and defers won't be handled in such cases.
.. attention::
Using defer always has a slight code overhead.
If you are returning non-constant values in a routine that uses defer, the compiler even has to insert some additional
code that uses the cpu stack to save some temporary values.
Library routines and builtin functions
--------------------------------------

View File

@@ -18,8 +18,8 @@ Structs and Pointers
Due to some limitations in the language parser, not all pointer related syntax is currently supported
if it is a pointer to a struct type.
The compiler tries its best to give a descriptive error message but sometimes there is still a
parser limitation that has to be worked around at the moment. For example, this syntax is not supported
right now and will result in a parse error::
parser limitation that has to be worked around at the moment. For example, this pointer arithmetic
indexing syntax is not supported right now and will result in a parse error::
^^Node np
np[2].field = 9999
@@ -30,6 +30,8 @@ Structs and Pointers
^^Node thirdnode = &&np[2]
thirdnode.field = 9999
*Note: this example is not regular array indexing syntax, it's pointer arithmetic by indexing on the pointer itself. Regular array syntax is supported just fine for arrays containing pointers etc.*
Legacy untyped pointers (uword)
@@ -139,12 +141,13 @@ You can copy the whole contents of a struct to another one by assigning the dere
e1^^ = e2^^ ; copies all fields of e2 into e1
The struct type creates a new name scape, so accessing the fields of a struct is done as usual with the dotted notation::
The struct type creates a new name scape, so accessing the fields of a struct is done as usual with the dotted notation.
Because it implies pointer dereferencing you can usually omit the explicit `^^`, prog8 will know what it means::
if e1.ypos > 300
e1.health -= 10
; notice that that implicitly dereferences the pointer variable, actually it is doing this:
; explicit dereferencing notation:
if e1^^.ypos > 300
e1^^.health -= 10
@@ -184,13 +187,40 @@ called multiple times, or inside a loop, the struct *will be the same instance e
Read below if you need *dynamic* struct allocation!
You write a static struct initialization expression like this:
``^^Node : [1,2,3,4]``
statically places an instance of struct 'Node' in memory, with its fields set to 1,2,3,4 and returns the address of this struct.
``^^Node : [1,"one", 1000, true, 1.111]``
statically places an instance of struct 'Node' in memory, with its fields set to 1, "one", 1000 etcetera and returns the address of this struct.
The values in the initialization array must correspond exactly with the first to last declared fields in the struct type.
``^^Node : []``
(without values) Places a 'Node' instance in BSS variable space instead, which gets zeroed out at program startup.
Returns the address of this empty struct.
It is also possible to put struct initializer inside arrays to make them all statically initialized and accessible via the array::
^^Node[] allnodes = [
^^Node: [1,"one", 1000, true, 1.111],
^^Node: [2,"two", 2000, false, 2.222],
^^Node: [],
^^Node: [],
]
Short form initializers
^^^^^^^^^^^^^^^^^^^^^^^
If the required type can be inferred from the context you can also omit the struct pointer type prefix altogether.
The initializer value then is syntactically the same as an array, but Prog8 internally turns it back into a proper
struct initializer value based on the the type of the array element or pointer variable it is assigned to.
So you can write the above in short form as::
^^Node nodepointer = [1,2,3,4]
^^Node[] allnodes = [
[1,"one", 1000, true, 1.111],
[2,"two", 2000, false, 2.222],
[],
[]
]
Dynamic allocation of structs
=============================

View File

@@ -16,6 +16,7 @@ Currently these machines can be selected as a compilation target (via the ``-tar
- 'c128': the Commodore 128 (*limited support*)
- 'pet32': the Commodore PET 4032 (*limited support*)
- 'virtual': a builtin virtual machine
- custom targets via a separate configuration file (see :ref:`customizable_target`)
This chapter explains some relevant system details of the c64 and cx16 machines.

View File

@@ -58,21 +58,15 @@ and for example the below code omits line 5::
STRUCTS and TYPED POINTERS
--------------------------
- allow struct initialization syntax in an array such as [ Node(), Node(), Node() ], update sorting example to use list of countries like that
- fix code size regressions (if any left)
- optimize deref in PointerAssignmentsGen: optimize 'forceTemporary' to only use a temporary when the offset is >0
- update structpointers.rst docs with 6502 specific things?
- the type of struct initializer arrays should not be uword[] but ^^struct[] ?
- implement the remaining TODO's in PointerAssignmentsGen.
- optimize deref in PointerAssignmentsGen: optimize 'forceTemporary' to only use a temporary when the offset is >0
- optimize addUnsignedByteOrWordToAY in PointerAssignmentsGen a bit more
- optimize the float copying in assignIndexedPointer() (also word?)
- implement even more struct instance assignments (via memcopy) in CodeDesugarer (see the TODO) (add to documentation as well, paragraph 'Structs')
- try to optimize pointer arithmetic used in peek/poke a bit more so the routines in sorting module can use typed pointers without increasing code size, see test.p8 in commit d394dc1e
- should @(wordpointer) be equivalent to wordpointer^^ (that would require a LOT of code rewrite that now knows that @() is strictly byte based) ?
or do an implicit cast @(wpointer as ubyte^^) ? And/or add a warning about that?
- add struct and pointer benchmark to benchmark program?
- optimize addUnsignedByteOrWordToAY in PointerAssignmentsGen a bit more
- support for typed function pointers? (&routine could be typed by default as well then)
- support @nosplit pointer arrays?
- support pointer to pointer?
- support for typed function pointers? (&routine could be typed by default as well then)
- really fixing the pointer dereferencing issues (cursed hybrid beween IdentifierReference, PtrDereferece and PtrIndexedDereference) may require getting rid of scoped identifiers altogether and treat '.' as a "scope or pointer following operator"
- (later, nasty parser problem:) support chaining pointer dereference on function calls that return a pointer. (type checking now fails on stuff like func().field and func().next.field)
@@ -80,6 +74,18 @@ STRUCTS and TYPED POINTERS
Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^
- optimization conflict detection:
1 fun applyModificationsSafely(modifications: List<IAstModification>) {
2 // Can check for conflicts before applying
3 modifications.groupBy { getTargetNode(it) }.forEach { target, mods ->
4 if (mods.size > 1) {
5 // Handle or report conflicting modifications
6 }
7 }
8 modifications.forEach { it.perform() }
9 }
- improve ANTLR grammar with better error handling (according to Qwen AI)
- allow memory() to occur in array initializer
- %breakpoint after an assignment is parsed as part of the expression (x % breakpoint), that should not happen
- when a complete block is removed because unused, suppress all info messages about everything in the block being removed
- fix the line, cols in Position, sometimes they count from 0 sometimes from 1
@@ -109,6 +115,26 @@ Future Things and Ideas
IR/VM
-----
- is it possible to use LOADFIELD/STOREFIELD instructions more?
- make multiple classes of registers and maybe also categorize by life time , to prepare for better register allocation in the future
SYSCALL_ARGS, // Reserved for syscall arguments (r99000-99099, r99100-99199)
FUNCTION_PARAMS, // For passing function parameters
FUNCTION_RETURNS, // For function return values
TEMPORARY, // Short-lived temporary values
LOCAL_VARIABLES, // Local variables within functions
GLOBAL_VARIABLES, // Global/static variables
HARDWARE_MAPPED, // Mapped to CPU hardware registers
LOOP_INDICES, // Used as loop counters
ADDRESS_CALCULATION // Used for pointer arithmetic
Categorizing registers by lifetime can significantly improve allocation:
- Short-lived: Temporary registers used in expressions
- Medium-lived: Local variables within a function
Registers could be categorized by how frequently they're accessed:
- Hot Registers: Frequently accessed (should be allocated to faster physical registers)
- Warm Registers: Moderately accessed
- Cold Registers: Rarely accessed (can be spilled to memory if needed)
We already have type-based pools
- byte, word, float registers
- pointer dt's are all reduced to just an uword (in the irTypeString method) - is this okay or could it be beneficial to reintroduce the actual pointer type information? See commit 88b074c208450c58aa32469745afa03e4c5f564a
- change the instruction format so an indirect register (a pointer) can be used more often, at least for the inplace assignment operators that operate on pointer
- getting it in shape for code generation...: the IR file should be able to encode every detail about a prog8 program (the VM doesn't have to actually be able to run all of it though!)

View File

@@ -282,9 +282,10 @@ For instance ``30_000.999_999`` is a valid floating point number 30000.999999.
Arrays
^^^^^^
Arrays can be created from a list of booleans, bytes, words, floats, or addresses of other variables
(such as explicit address-of expressions, strings, or other array variables) - values in an array literal
always have to be constants. A trailing comma is allowed, sometimes this is easier when copying values
Arrays can be created from a list of booleans, bytes, words, floats, addresses of other variables
(such as explicit address-of expressions, strings, or other array variables), and struct initializers.
The values in an array literal always have to be constants.
A trailing comma is allowed, sometimes this is easier when copying values
or when adding more stuff to the array later. Here are some examples of arrays::
byte[10] array ; array of 10 bytes, initially set to 0

View File

@@ -0,0 +1,231 @@
; Doubly Linked List with Hash Table Cache
; Demonstrates advanced pointer usage with a combination of doubly linked lists and hash tables
; for efficient data storage and retrieval
%import strings
%import textio
%zeropage basicsafe
main {
sub start() {
txt.lowercase()
txt.print("Doubly Linked List with Hash Table Cache Demo\n")
txt.print("============================================\n\n")
; Initialize the data structure
cache.init()
; Add some sample data
txt.print("Adding sample data...\n")
cache.add("Alice", "Engineer", 30)
cache.add("Bob", "Designer", 25)
cache.add("Charlie", "Manager", 35)
cache.add("Diana", "Developer", 28)
cache.add("Eve", "Analyst", 32)
txt.print("\nForward traversal:\n")
cache.print_forward()
txt.print("\nBackward traversal:\n")
cache.print_backward()
txt.print("\nSearching for specific entries:\n")
txt.print("Looking for 'Charlie': ")
^^cache.Entry entry = cache.find("Charlie")
if entry!=0 {
txt.print(entry.name)
txt.print(" - ")
txt.print(entry.job)
txt.print(" (")
txt.print_ub(entry.age)
txt.print(" years old)\n")
} else {
txt.print("Not found\n")
}
txt.print("Looking for 'Frank': ")
entry = cache.find("Frank")
if entry!=0 {
txt.print("Found\n")
} else {
txt.print("Not found\n")
}
txt.print("\nRemoving 'Bob'...\n")
void cache.remove("Bob")
txt.print("Forward traversal after removal:\n")
cache.print_forward()
txt.print("\nDemonstrating hash collision handling...\n")
; Add entries that will likely collide in the hash table
cache.add("A", "First", 20)
cache.add("B", "Second", 21)
cache.add("C", "Third", 22)
cache.add("D", "Fourth", 23)
cache.add("E", "Fifth", 24)
txt.print("Added entries with potential hash collisions.\n")
txt.print("Forward traversal:\n")
cache.print_forward()
}
}
cache {
struct Entry {
^^Entry next ; Next entry in the list
^^Entry prev ; Previous entry in the list
^^Entry hash_next ; Next entry in the hash bucket
str name ; Name (key)
str job ; Job description
ubyte age ; Age
}
const ubyte HASH_TABLE_SIZE = 16
^^Entry[HASH_TABLE_SIZE] hash_table ; Hash table for fast lookups
^^Entry head = 0 ; Head of the doubly linked list
^^Entry tail = 0 ; Tail of the doubly linked list
uword count = 0 ; Number of entries
sub init() {
; Initialize hash table buckets to null
sys.memsetw(hash_table, HASH_TABLE_SIZE, 0)
}
sub add(str name, str job, ubyte age) {
; Create new entry
^^Entry new_entry = arena.alloc(sizeof(Entry))
^^ubyte name_copy = arena.alloc(strings.length(name) + 1)
^^ubyte job_copy = arena.alloc(strings.length(job) + 1)
void strings.copy(name, name_copy)
void strings.copy(job, job_copy)
new_entry.name = name_copy
new_entry.job = job_copy
new_entry.age = age
new_entry.next = 0
new_entry.prev = 0
new_entry.hash_next = 0
; Add to the end of the doubly linked list
if head == 0 {
; First entry
head = new_entry
tail = new_entry
} else {
; Add to the end
tail.next = new_entry
new_entry.prev = tail
tail = new_entry
}
; Add to hash table
ubyte bucket = strings.hash(name) % HASH_TABLE_SIZE
new_entry.hash_next = hash_table[bucket]
hash_table[bucket] = new_entry
count++
txt.print("Added: ")
txt.print(name)
txt.print("\n")
}
sub find(str name) -> ^^Entry {
; Find entry using hash table for O(1) average case
ubyte bucket = strings.hash(name) % HASH_TABLE_SIZE
^^Entry current = hash_table[bucket]
while current != 0 {
if strings.compare(current.name, name) == 0
return current
current = current.hash_next
}
return 0 ; Not found
}
sub remove(str name) -> bool {
; Find the entry
^^Entry to_remove = find(name)
if to_remove == 0
return false ; Not found
; Remove from doubly linked list
if to_remove.prev != 0
to_remove.prev.next = to_remove.next
else
head = to_remove.next ; Was the head
if to_remove.next != 0
to_remove.next.prev = to_remove.prev
else
tail = to_remove.prev ; Was the tail
; Remove from hash table
ubyte bucket = strings.hash(name) % HASH_TABLE_SIZE
if hash_table[bucket] == to_remove {
hash_table[bucket] = to_remove.hash_next
} else {
^^Entry current = hash_table[bucket]
while current.hash_next != 0 {
if current.hash_next == to_remove {
current.hash_next = to_remove.hash_next
break
}
current = current.hash_next
}
}
count--
txt.print("Removed: ")
txt.print(name)
txt.print("\n")
return true
}
sub print_forward() {
^^Entry current = head
while current != 0 {
txt.print("- ")
txt.print(current.name)
txt.print(" (")
txt.print(current.job)
txt.print(", ")
txt.print_ub(current.age)
txt.print(")\n")
current = current.next
}
txt.print("Total entries: ")
txt.print_uw(count)
txt.print("\n")
}
sub print_backward() {
^^Entry current = tail
while current != 0 {
txt.print("- ")
txt.print(current.name)
txt.print(" (")
txt.print(current.job)
txt.print(", ")
txt.print_ub(current.age)
txt.print(")\n")
current = current.prev
}
txt.print("Total entries: ")
txt.print_uw(count)
txt.print("\n")
}
}
arena {
; Simple arena allocator
uword buffer = memory("arena", 8000, 0)
uword next = buffer
sub alloc(ubyte size) -> uword {
defer next += size
return next
}
}

View File

@@ -11,34 +11,32 @@ main{
uword area ; 1000 km^2
}
^^Country[100] countries ; won't be fully filled
ubyte num_countries
^^Country[] countries = [
^^Country:["Indonesia", 285.72, 1904],
^^Country:["Congo", 112.83, 2344],
^^Country:["Vietnam", 101.60, 331],
^^Country:["United States", 347.28, 9372],
^^Country:["Iran", 92.42, 1648],
^^Country:["Turkey", 87.69, 783],
^^Country:["Brazil", 212.81, 8515],
^^Country:["Bangladesh", 175.69, 147],
^^Country:["Germany", 84.08, 357],
^^Country:["Japan", 123.10, 377],
^^Country:["India", 1463.87, 3287],
^^Country:["China", 1416.10, 9596],
^^Country:["Philippines", 116.79, 300],
^^Country:["Russia", 143.99, 17098],
^^Country:["Pakistan", 255.22, 881],
^^Country:["Nigeria", 237.53, 923],
^^Country:["Ethiopia", 135.47, 1104],
^^Country:["Mexico", 131.95, 1964],
^^Country:["Thailand", 71.62, 513],
^^Country:["Egypt", 118.37, 1002],
]
sub start() {
txt.lowercase()
; because pointer array initialization is not supported yet, we have to add the countries in separate statements for now
add(^^Country:["Indonesia", 285.72, 1904])
add(^^Country:["Congo", 112.83, 2344])
add(^^Country:["Vietnam", 101.60, 331])
add(^^Country:["United States", 347.28, 9372])
add(^^Country:["Iran", 92.42, 1648])
add(^^Country:["Turkey", 87.69, 783])
add(^^Country:["Brazil", 212.81, 8515])
add(^^Country:["Bangladesh", 175.69, 147])
add(^^Country:["Germany", 84.08, 357])
add(^^Country:["Japan", 123.10, 377])
add(^^Country:["India", 1463.87, 3287])
add(^^Country:["China", 1416.10, 9596])
add(^^Country:["Philippines", 116.79, 300])
add(^^Country:["Russia", 143.99, 17098])
add(^^Country:["Pakistan", 255.22, 881])
add(^^Country:["Nigeria", 237.53, 923])
add(^^Country:["Ethiopia", 135.47, 1104])
add(^^Country:["Mexico", 131.95, 1964])
add(^^Country:["Thailand", 71.62, 513])
add(^^Country:["Egypt", 118.37, 1002])
txt.print("UNSORTED:\n")
dump()
@@ -57,7 +55,7 @@ main{
sub sort_by_name() {
; stupid slow bubble sort
ubyte n = num_countries
ubyte n = len(countries)
do {
ubyte newn=0
ubyte i
@@ -73,7 +71,7 @@ main{
sub sort_by_population() {
; stupid slow bubble sort
ubyte n = num_countries
ubyte n = len(countries)
do {
ubyte newn=0
ubyte i
@@ -89,7 +87,7 @@ main{
sub sort_by_area() {
; stupid slow bubble sort
ubyte n = num_countries
ubyte n = len(countries)
do {
ubyte newn=0
ubyte i
@@ -125,10 +123,5 @@ main{
txt.nl()
}
}
sub add(^^Country c) {
countries[num_countries] = c
num_countries++
}
}

View File

@@ -1,42 +1,34 @@
%import textio
%zeropage basicsafe
main {
sub start() {
sys.set_carry()
txt.print_ub(if_cc 0 else 1)
txt.nl()
sys.clear_carry()
txt.print_ub(if_cc 0 else 1)
txt.nl()
sys.set_carry()
txt.print_ub(if_cs 0 else 1)
txt.nl()
sys.clear_carry()
txt.print_ub(if_cs 0 else 1)
txt.nl()
^^uword @shared ptr
add1()
add2()
sub1()
sub2()
sub add1() {
ptr += 5
cx16.r0 = ptr + 5
cx16.r0 = peekw(ptr + 5)
}
sub add2() {
ptr += cx16.r0L
cx16.r0 = ptr + cx16.r0L
cx16.r0 = peekw(ptr + cx16.r0L)
}
sub sub1() {
ptr -= 5
cx16.r0 = ptr - 5
cx16.r0 = peekw(ptr - 5)
}
sub sub2() {
ptr -= cx16.r0L
cx16.r0 = ptr - cx16.r0L
cx16.r0 = peekw(ptr - cx16.r0L)
}
}
}
;%import textio
;%zeropage basicsafe
;
;main {
; struct Node {
; ubyte id
; str name
; uword array
; }
;
; ^^Node @shared @zp node = 2000
;
; sub start() {
; txt.print_uw(node)
; txt.spc()
; cx16.r0 = &node.array ; TODO don't clobber node pointer itself!!
; txt.print_uw(cx16.r0)
; txt.spc()
; txt.print_uw(node)
; txt.nl()
; }
;}

View File

@@ -3,4 +3,4 @@ org.gradle.console=rich
org.gradle.parallel=true
org.gradle.daemon=true
kotlin.code.style=official
version=12.0-BETA1
version=12.0-BETA2

View File

@@ -241,8 +241,10 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
if(floats) it.number.toString()
else it.number.toInt().toHex()
}
else
else if(it.addressOfSymbol!=null)
"@${it.addressOfSymbol}"
else
throw InternalCompilerException("weird array value")
}
private fun writeVariables() {
@@ -283,14 +285,18 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
lsbValue = variable.onetimeInitializationArrayValue.joinToString(",") {
if(it.number!=null)
(it.number.toInt() and 255).toHex()
else
else if (it.addressOfSymbol!=null)
"@<${it.addressOfSymbol}"
else
throw InternalCompilerException("weird array value")
}
msbValue = variable.onetimeInitializationArrayValue.joinToString(",") {
if(it.number!=null)
(it.number.toInt() shr 8).toHex()
else
else if(it.addressOfSymbol!=null)
"@>${it.addressOfSymbol}"
else
throw InternalCompilerException("weird array value")
}
}
xml.writeCharacters("ubyte[${variable.length}] ${variable.name}_lsb=$lsbValue zp=${variable.zpwish} split=true")
@@ -388,8 +394,10 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
value.dt.isInteger || value.dt.isPointer -> {
if(value.value.number!=null)
value.value.number.toInt().toHex()
else
else if(value.value.addressOfSymbol!=null)
"@${value.value.addressOfSymbol}"
else
throw InternalCompilerException("weird field value")
}
value.dt == BaseDataType.FLOAT -> {
value.value.number.toString()

View File

@@ -1,6 +1,6 @@
package prog8.intermediate
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.PROG8_CONTAINER_MODULES
import prog8.code.core.BaseDataType
import prog8.code.core.DataType
import prog8.code.core.Encoding
@@ -49,10 +49,9 @@ class IRSymbolTable {
val prefix = "$label."
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(INTERNED_STRINGS_MODULENAME)) {
// check if attempt is made to delete fixed modules, if so, refuse that.
if(!PROG8_CONTAINER_MODULES.any { containername -> it.key.startsWith(containername)})
table.remove(it.key)
}
}
}
@@ -127,7 +126,10 @@ class IRStArrayElement(val bool: Boolean?, val number: Double?, val addressOfSym
init {
if(bool!=null) require(number==null && addressOfSymbol==null)
if(number!=null) require(bool==null && addressOfSymbol==null)
if(addressOfSymbol!=null) require(number==null || bool==null)
if(addressOfSymbol!=null) {
require(number==null || bool==null)
require('.' in addressOfSymbol) { "addressOfSymbol must be a scoped name" }
}
}
}
@@ -135,7 +137,7 @@ class IRStStructDef(name: String, val fields: List<Pair<DataType, String>>, val
class IRStStructInstance(name: String, val structName: String, val values: List<IRStructInitValue>, val size: UInt): IRStNode(name, IRStNodeType.STRUCTINSTANCE) {
init {
require('.' in structName)
require('.' in structName) { "structName must be a scoped name" }
}
}

View File

@@ -123,7 +123,7 @@ class SymbolTable(astProgram: PtProgram) : StNode(astProgram.name, StNodeType.GL
val scopehash = call.parent.hashCode().toUInt().toString(16)
val pos = "${call.position.line}_${call.position.startCol}"
val hash = call.position.file.hashCode().toUInt().toString(16)
return "prog8_struct_${structname.replace('.', '_')}_${hash}_${pos}_${scopehash}"
return "${structname.replace('.', '_')}_${hash}_${scopehash}_${pos}"
}
}
}
@@ -338,13 +338,18 @@ class StExtSub(name: String,
class StSubroutineParameter(val name: String, val type: DataType, val register: RegisterOrPair?)
class StExtSubParameter(val register: RegisterOrStatusflag, val type: DataType)
class StArrayElement(val number: Double?, val addressOfSymbol: String?, val boolean: Boolean?) {
class StArrayElement(val number: Double?, val addressOfSymbol: String?, val structInstance: String?, val structInstanceUninitialized: String?, val boolean: Boolean?) {
init {
if(number!=null) require(addressOfSymbol==null && boolean==null)
if(addressOfSymbol!=null) require(number==null && boolean==null)
if(boolean!=null) require(addressOfSymbol==null && number==null)
if(number!=null) require(addressOfSymbol==null && boolean==null && structInstance==null && structInstanceUninitialized==null)
if(addressOfSymbol!=null) require(number==null && boolean==null && structInstance==null && structInstanceUninitialized==null)
if(structInstance!=null) require(number==null && boolean==null && addressOfSymbol==null && structInstanceUninitialized==null)
if(structInstanceUninitialized!=null) require(number==null && boolean==null && addressOfSymbol==null && structInstance==null)
if(boolean!=null) require(addressOfSymbol==null && number==null &&structInstance==null && structInstanceUninitialized==null)
}
}
typealias StString = Pair<String, Encoding>
typealias StArray = List<StArrayElement>
const val StMemorySlabBlockName = "prog8_slabs"
const val StStructInstanceBlockName = "prog8_struct_instances"

View File

@@ -82,7 +82,7 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
numElements = (value.value.length + 1).toUInt() // include the terminating 0-byte
}
is PtArray -> {
initialArray = makeInitialArray(value)
initialArray = makeInitialArray(value, scope)
initialString = null
initialNumeric = null
numElements = initialArray.size.toUInt()
@@ -119,22 +119,12 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
val size = (node.args[1] as PtNumber).number.toUInt()
val align = (node.args[2] as PtNumber).number.toUInt()
// don't add memory slabs in nested scope, just put them in the top level of the ST
scope.first().add(StMemorySlab("prog8_memoryslab_$slabname", size, align, node))
scope.first().add(StMemorySlab("memory_$slabname", size, align, node))
}
else if(node.name=="prog8_lib_structalloc") {
val struct = node.type.subType!!
if(struct is StStruct) {
val label = SymbolTable.labelnameForStructInstance(node)
val initialValues = node.args.map {
when(it) {
is PtAddressOf -> StArrayElement(null, it.identifier!!.name, null)
is PtBool -> StArrayElement(null, null, it.value)
is PtNumber -> StArrayElement(it.number, null, null)
else -> throw AssemblyError("invalid structalloc argument type $it")
}
}
val scopedName = if(struct.astNode!=null) (struct.astNode as PtNamedNode).scopedName else struct.scopedNameString
scope.first().add(StStructInstance(label, scopedName, initialValues, struct.size, null))
val instance = handleStructAllocation(node)
if(instance!=null) {
scope.first().add(instance) // don't add struct instances in nested scope, just put them in the top level of the ST
}
}
null
@@ -153,20 +143,50 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
scope.removeLast()
}
private fun makeInitialArray(value: PtArray): List<StArrayElement> {
private fun handleStructAllocation(node: PtBuiltinFunctionCall): StStructInstance? {
val struct = node.type.subType as? StStruct ?: return null
val initialValues = node.args.map {
when(it) {
is PtAddressOf -> StArrayElement(null, it.identifier!!.name, null, null,null)
is PtBool -> StArrayElement(null, null, null, null, it.value)
is PtNumber -> StArrayElement(it.number, null, null, null, null)
else -> throw AssemblyError("invalid structalloc argument type $it")
}
}
val label = SymbolTable.labelnameForStructInstance(node)
val scopedStructName = if(struct.astNode!=null) (struct.astNode as PtNamedNode).scopedName else struct.scopedNameString
return StStructInstance(label, scopedStructName, initialValues, struct.size, null)
}
private fun makeInitialArray(value: PtArray, scope: ArrayDeque<StNode>): List<StArrayElement> {
return value.children.map {
when(it) {
is PtAddressOf -> {
when {
it.isFromArrayElement -> TODO("address-of array element $it in initial array value")
else -> StArrayElement(null, it.identifier!!.name, null)
else -> StArrayElement(null, it.identifier!!.name, null, null,null)
}
}
is PtNumber -> StArrayElement(it.number, null, null)
is PtBool -> StArrayElement(null, null, it.value)
is PtNumber -> StArrayElement(it.number, null, null,null,null)
is PtBool -> StArrayElement(null, null, null,null,it.value)
is PtBuiltinFunctionCall -> {
val labelname = SymbolTable.labelnameForStructInstance(it)
StArrayElement(null, labelname, null)
if(it.name=="prog8_lib_structalloc") {
val instance = handleStructAllocation(it)
if(instance==null) {
val label = SymbolTable.labelnameForStructInstance(it)
if (it.args.isEmpty())
StArrayElement(null, null, null, label, null)
else
StArrayElement(null, null, label, null, null)
} else {
scope.first().add(instance) // don't add struct instances in nested scope, just put them in the top level of the ST
if (it.args.isEmpty())
StArrayElement(null, null, null, instance.name, null)
else
StArrayElement(null, null, instance.name, null, null)
}
} else
TODO("support for initial array element via ${it.name} ${it.position}")
}
else -> throw AssemblyError("invalid array element $it")
}

View File

@@ -14,7 +14,7 @@
<keywords keywords="&amp;;&amp;&amp;;&amp;&lt;;&amp;&gt;;-&gt;;@;^^;alias;and;as;asmsub;break;clobbers;continue;do;downto;else;extsub;false;for;goto;if;if_cc;if_cs;if_eq;if_mi;if_ne;if_neg;if_nz;if_pl;if_pos;if_vc;if_vs;if_z;in;inline;not;on;or;repeat;return;step;sub;to;true;unroll;until;when;while;xor;~" ignore_case="false" />
<keywords2 keywords="%address;%align;%asm;%asmbinary;%asminclude;%breakpoint;%encoding;%import;%ir;%jmptable;%launcher;%memtop;%option;%output;%zeropage;%zpallowed;%zpreserved;@align64;@alignpage;@alignword;@bank;@dirty;@nosplit;@nozp;@requirezp;@shared;@split;@zp;atascii:;c64os:;cp437:;default:;iso16:;iso5:;iso:;kata:;petscii:;sc:" />
<keywords3 keywords="^^bool;^^byte;^^float;^^long;^^str;^^ubyte;^^uword;^^word;bool;byte;const;float;long;str;struct;ubyte;uword;void;word" />
<keywords4 keywords="abs;bmx;call;callfar;callfar2;cbm;clamp;cmp;conv;cx16;defer;diskio;divmod;floats;len;lsb;lsw;math;max;memory;min;mkword;msb;msw;peek;peekbool;peekf;peekw;poke;pokebool;pokef;pokew;psg;rol;rol2;ror;ror2;rrestore;rrestorex;rsave;rsavex;setlsb;setmsb;sgn;sizeof;sqrt;strings;sys;txt;verafx" />
<keywords4 keywords="abs;bmx;call;callfar;callfar2;cbm;clamp;cmp;conv;cx16;defer;diskio;divmod;floats;len;lsb;lsw;math;max;memory;min;mkword;msb;msw;offsetof;peek;peekbool;peekf;peekw;poke;pokebool;pokef;pokew;psg;rol;rol2;ror;ror2;rrestore;rrestorex;rsave;rsavex;setlsb;setmsb;sgn;sizeof;sqrt;strings;sys;txt;verafx" />
</highlighting>
<extensionMap>
<mapping ext="p8" />

View File

@@ -27,7 +27,7 @@
<Keywords name="Keywords1">void const&#x000D;&#x000A;str&#x000D;&#x000A;byte ubyte bool&#x000D;&#x000A;long word uword&#x000D;&#x000A;float&#x000D;&#x000A;zp shared split nosplit requirezp nozp struct</Keywords>
<Keywords name="Keywords2">%address&#x000D;&#x000A;%asm&#x000D;&#x000A;%ir&#x000D;&#x000A;%asmbinary&#x000D;&#x000A;%asminclude&#x000D;&#x000A;%align&#x000D;&#x000A;%breakpoint&#x000D;&#x000A;%encoding&#x000D;&#x000A;%import&#x000D;&#x000A;%jmptable&#x000D;&#x000A;%memtop&#x000D;&#x000A;%launcher&#x000D;&#x000A;%option&#x000D;&#x000A;%output&#x000D;&#x000A;%zeropage&#x000D;&#x000A;%zpreserved&#x000D;&#x000A;%zpallowed</Keywords>
<Keywords name="Keywords3">inline sub asmsub extsub&#x000D;&#x000A;clobbers&#x000D;&#x000A;asm&#x000D;&#x000A;if&#x000D;&#x000A;when else&#x000D;&#x000A;if_cc if_cs if_eq if_mi if_neg if_nz if_pl if_pos if_vc if_vs if_z&#x000D;&#x000A;for in step do while repeat unroll&#x000D;&#x000A;break continue return goto</Keywords>
<Keywords name="Keywords4">alias abs call callfar callfar2 clamp cmp defer divmod len lsb lsw lsl lsr memory mkword min max msb msw peek peekw peekf peekbool poke pokew pokef pokebool rsave rsavex rrestore rrestorex rnd rndw rol rol2 ror ror2 setlsb setmsb sgn sizeof sqrtw</Keywords>
<Keywords name="Keywords4">alias abs call callfar callfar2 clamp cmp defer divmod len lsb lsw lsl lsr memory mkword min max msb msw peek peekw peekf peekbool poke pokew pokef pokebool rsave rsavex rrestore rrestorex rnd rndw rol rol2 ror ror2 setlsb setmsb sgn sizeof offsetof sqrtw</Keywords>
<Keywords name="Keywords5">true false&#x000D;&#x000A;not and or xor&#x000D;&#x000A;as to downto |&gt;</Keywords>
<Keywords name="Keywords6"></Keywords>
<Keywords name="Keywords7"></Keywords>

View File

@@ -27,7 +27,7 @@
<Keywords name="Keywords1">void const&#x000D;&#x000A;str&#x000D;&#x000A;byte ubyte bool&#x000D;&#x000A;long word uword&#x000D;&#x000A;float&#x000D;&#x000A;zp shared split nosplit requirezp nozp struct</Keywords>
<Keywords name="Keywords2">%address&#x000D;&#x000A;%asm&#x000D;&#x000A;%ir&#x000D;&#x000A;%asmbinary&#x000D;&#x000A;%asminclude&#x000D;&#x000A;%align&#x000D;&#x000A;%breakpoint&#x000D;&#x000A;%encoding&#x000D;&#x000A;%import&#x000D;&#x000A;%jmptable&#x000D;&#x000A;%memtop&#x000D;&#x000A;%launcher&#x000D;&#x000A;%option&#x000D;&#x000A;%output&#x000D;&#x000A;%zeropage&#x000D;&#x000A;%zpreserved&#x000D;&#x000A;%zpallowed</Keywords>
<Keywords name="Keywords3">inline sub asmsub extsub&#x000D;&#x000A;clobbers&#x000D;&#x000A;asm&#x000D;&#x000A;if&#x000D;&#x000A;when else&#x000D;&#x000A;if_cc if_cs if_eq if_mi if_neg if_nz if_pl if_pos if_vc if_vs if_z&#x000D;&#x000A;for in step do while repeat unroll&#x000D;&#x000A;break continue return goto</Keywords>
<Keywords name="Keywords4">alias abs call callfar callfar2 clamp cmp defer divmod len lsb lsw lsl lsr memory mkword min max msb msw peek peekw peekf peekbool poke pokew pokef pokebool rsave rsavex rrestore rrestorex rnd rndw rol rol2 ror ror2 setlsb setmsb sgn sizeof sqrtw</Keywords>
<Keywords name="Keywords4">alias abs call callfar callfar2 clamp cmp defer divmod len lsb lsw lsl lsr memory mkword min max msb msw peek peekw peekf peekbool poke pokew pokef pokebool rsave rsavex rrestore rrestorex rnd rndw rol rol2 ror ror2 setlsb setmsb sgn sizeof offsetof sqrtw</Keywords>
<Keywords name="Keywords5">true false&#x000D;&#x000A;not and or xor&#x000D;&#x000A;as to downto |&gt;</Keywords>
<Keywords name="Keywords6"></Keywords>
<Keywords name="Keywords7"></Keywords>

View File

@@ -156,7 +156,7 @@ contexts:
- match: (\b(const)\b)
scope: storage.modifier.prog8
support:
- match: (\b(abs|atan|ceil|cos|cos8u|cos8|cos16u|cos16|deg|floor|ln|log2|rad|round|sin|sgn|sin8u|sin8|sin16u|sin16|sqrt16|sqrt|tan|any|all|len|max|min|reverse|sum|sort|memcopy|memset|memsetw|leftstr|rightstr|strlen|strcmp|strncmp|substr|exit|lsb|msb|lsw|msw|mkword|rnd|rndw|rndf|rol|rol2|ror|ror2|rsave|rrestore|read_flags|sizeof|set_carry|clear_carry|set_irqd|clear_irqd|swap)\b)
- match: (\b(abs|atan|ceil|cos|cos8u|cos8|cos16u|cos16|deg|floor|ln|log2|rad|round|sin|sgn|sin8u|sin8|sin16u|sin16|sqrt16|sqrt|tan|any|all|len|max|min|reverse|sum|sort|memcopy|memset|memsetw|leftstr|rightstr|strlen|strcmp|strncmp|substr|exit|lsb|msb|lsw|msw|mkword|rnd|rndw|rndf|rol|rol2|ror|ror2|rsave|rrestore|read_flags|sizeof|offsetof|set_carry|clear_carry|set_irqd|clear_irqd|swap)\b)
scope: support.function.prog8
variable:
- match: (\b\w+\b)

View File

@@ -14,7 +14,7 @@ syn keyword prog8BuiltInFunc len
" Miscellaneous functions
syn keyword prog8BuiltInFunc cmp divmod lsb msb lsw msw mkword min max peek peekw peekf peekbool poke pokew pokef pokebool rsave rsavex rrestore rrestorex
syn keyword prog8BuiltInFunc rol rol2 ror ror2 sizeof setlsb setmsb
syn keyword prog8BuiltInFunc rol rol2 ror ror2 sizeof offsetof setlsb setmsb
syn keyword prog8BuiltInFunc memory call callfar callfar2 clamp defer alias