fix some struct type and symbol lookup errors

This commit is contained in:
Irmen de Jong
2025-05-10 23:09:59 +02:00
parent 0b789b5f0b
commit 59c378089e
16 changed files with 271 additions and 69 deletions

View File

@@ -128,7 +128,11 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
BaseDataType.UNDEFINED to DataType(BaseDataType.UNDEFINED, null, null)
)
fun forDt(dt: BaseDataType) = simpletypes.getValue(dt)
fun forDt(dt: BaseDataType): DataType {
if(dt.isStructInstance)
TODO("cannot use struct instance as a data type (yet) - use a pointer instead")
return simpletypes.getValue(dt)
}
fun arrayFor(elementDt: BaseDataType, splitwordarray: Boolean=true): DataType {
require(!elementDt.isPointer) { "use other array constructor for arrays of pointers" }
@@ -224,7 +228,7 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
if(sub!=null) "${sub.name.lowercase()}[]" else if (subType!=null) "${subType!!.scopedNameString}[]" else "${subTypeFromAntlr}[]"
}
BaseDataType.STRUCT_INSTANCE -> {
if(sub!=null) sub.name.lowercase() else if (subType!=null) subType!!.scopedNameString else "$subTypeFromAntlr"
sub?.name?.lowercase() ?: if (subType!=null) subType!!.scopedNameString else "$subTypeFromAntlr"
}
else -> base.name.lowercase()
}

View File

@@ -989,6 +989,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
else
ExpressionCodeResult(result, returnRegSpec!!.dt, finalReturnRegister, -1)
}
is StStruct -> {
throw AssemblyError("stray struct constructor should have been removed (normally it can only occur as initialization expression for a pointer variable)")
}
else -> {
if(callTarget.type == StNodeType.LABEL) {
require(fcall.void)
@@ -998,7 +1001,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
else {
throw AssemblyError("invalid node type")
throw AssemblyError("invalid node type ${callTarget.type} at ${callTarget.astNode?.position}")
}
}
}

View File

@@ -1889,7 +1889,7 @@ class IRCodeGen(
}
}
}
is PtStructDecl -> { /* do nothing */ }
is PtStructDecl -> { /* do nothing, should be found in the symbol table */ }
else -> TODO("weird block child node $child")
}
}

View File

@@ -27,6 +27,9 @@ class IRUnusedCodeRemover(
// we could clean up the SymbolTable as well, but ONLY if these symbols aren't referenced somewhere still in an instruction or variable initializer value
val prefix = "$blockLabel."
val blockVars = irprog.st.allVariables().filter { it.name.startsWith(prefix) }
// check if there are symbols referenced elsewhere that we should not prune (even though the rest of the block is empty)
blockVars.forEach { stVar ->
irprog.allSubs().flatMap { it.chunks }.forEach { chunk ->
chunk.instructions.forEach { ins ->
@@ -47,6 +50,19 @@ class IRUnusedCodeRemover(
}
}
val blockStructs = irprog.st.allStructDefs().filter { it.name.startsWith(prefix) }
blockStructs.forEach { struct ->
irprog.st.allStructInstances().forEach { instance ->
if(instance.structName == struct.name)
return // a struct instance is declared using this struct type
}
irprog.st.allVariables().forEach { variable ->
if(variable.dt.isPointer || variable.dt.isStructInstance)
if(struct.name == variable.dt.subType!!.scopedNameString)
return // a variable exists with the struct as (pointer) type
}
}
irprog.st.removeTree(blockLabel)
}

View File

@@ -16,8 +16,8 @@ fun convertStToIRSt(sourceSt: SymbolTable?): IRSymbolTable {
StNodeType.MEMORYSLAB -> st.add(convert(it.value as StMemorySlab))
StNodeType.STRUCTINSTANCE -> {
val instance = it.value as StStructInstance
val struct = st.lookup(instance.structName) as IRStStructDef
st.add(convert(instance, struct))
val struct = sourceSt.lookup(instance.structName) as StStruct
st.add(convert(instance, struct.fields))
}
StNodeType.STRUCT -> st.add(convert(it.value as StStruct))
else -> { }
@@ -137,8 +137,8 @@ private fun convert(variable: StMemorySlab): IRStMemorySlab {
}
private fun convert(instance: StStructInstance, struct: IRStStructDef): IRStStructInstance {
val values = struct.fields.zip(instance.initialValues).map { (field, value) ->
private fun convert(instance: StStructInstance, fields: List<Pair<DataType, String>>): IRStStructInstance {
val values = fields.zip(instance.initialValues).map { (field, value) ->
val elt = convertArrayElt(value)
IRStructInitValue(field.first.base, elt)
}

View File

@@ -181,14 +181,20 @@ class UnusedCodeRemover(private val program: Program,
val declIndex = (parent as IStatementContainer).statements.indexOf(decl)
val singleUseIndex = (parent as IStatementContainer).statements.indexOf(singleUse.parent)
if(declIndex==singleUseIndex-1) {
if("ignore_unused" !in decl.definingBlock.options())
errors.info("replaced unused variable '${decl.name}' with void call, maybe this can be removed altogether", decl.position)
val fcall = assignment.value as IFunctionCall
val voidCall = FunctionCallStatement(fcall.target, fcall.args, true, fcall.position)
return listOf(
IAstModification.ReplaceNode(decl, voidCall, parent),
IAstModification.Remove(assignment, assignment.parent as IStatementContainer)
)
val callStruct = (assignment.value as IFunctionCall).target.targetStructDecl()
if(callStruct!=null) {
// don't turn a struct instance allocation call to a void call, instead, remove everything
return listOf(IAstModification.Remove(assignment, assignment.parent as IStatementContainer))
} else {
if("ignore_unused" !in decl.definingBlock.options())
errors.info("replaced unused variable '${decl.name}' with void call, maybe this can be removed altogether", decl.position)
val fcall = assignment.value as IFunctionCall
val voidCall = FunctionCallStatement(fcall.target, fcall.args, true, fcall.position)
return listOf(
IAstModification.ReplaceNode(decl, voidCall, parent),
IAstModification.Remove(assignment, assignment.parent as IStatementContainer)
)
}
}
}
} else {

View File

@@ -569,11 +569,17 @@ internal class AstChecker(private val program: Program,
if (p.type.isPointer) {
if (p.type.subType == null && p.type.subTypeFromAntlr!=null) errors.err("cannot find struct type ${p.type.subTypeFromAntlr?.joinToString(".")}", p.position)
}
if(p.type.isStructInstance)
errors.err("structs can only be passed via a pointer", p.position)
}
for((index, r) in subroutine.returntypes.withIndex()) {
if(r.isPointer && r.subType==null && r.subTypeFromAntlr!=null)
err("return type #${index+1}: cannot find struct type ${r.subTypeFromAntlr?.joinToString(".")}")
if(r.isStructInstance)
err("structs can only be returned via a pointer")
}
}
@@ -1603,6 +1609,10 @@ internal class AstChecker(private val program: Program,
errors.err("function doesn't return a value", functionCallExpr.position)
}
}
else if(targetStatement is StructDecl) {
if(functionCallExpr.parent is IStatementContainer)
errors.err("static struct instance allocation can only occur as an initializer for a pointer variable", functionCallExpr.position)
}
if(builtinFunctionName in listOf("peek", "peekw")) {
val pointervar = functionCallExpr.args[0] as? IdentifierReference
@@ -1632,6 +1642,10 @@ internal class AstChecker(private val program: Program,
checkFunctionCall(targetStatement, functionCallStatement.args, functionCallStatement.position)
checkUnusedReturnValues(functionCallStatement, targetStatement, errors)
if(targetStatement is StructDecl) {
errors.err("static struct instance allocation can only occur as an initializer for a pointer variable", functionCallStatement.position)
}
if(functionCallStatement.void) {
when(targetStatement) {
is BuiltinFunctionPlaceholder -> {
@@ -1808,11 +1822,14 @@ internal class AstChecker(private val program: Program,
}
}
}
// TODO rest
// TODO rest?
}
args.forEach{
checkLongType(it)
if(it.inferType(program).isStructInstance)
errors.err("structs can only be passed via a pointer", it.position)
}
}
@@ -2019,9 +2036,9 @@ internal class AstChecker(private val program: Program,
errors.err("unable to determine type of dereferenced pointer expression", deref.position)
}
override fun visit(deref: PtrIndexedDereference) {
override fun visit(idxderef: PtrIndexedDereference) {
if(compilerOptions.compTarget.name != VMTarget.NAME)
TODO("typed pointers are not yet supported in the 6502 compilation targets (only virtual) ${deref.position}")
TODO("typed pointers are not yet supported in the 6502 compilation targets (only virtual) ${idxderef.position}")
// TODO ast checks for this one?
}

View File

@@ -398,11 +398,11 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
val (target, _) = srcCall.target.targetNameAndType(program)
val iType = srcCall.inferType(program)
val subtypeStruct = iType.getOrUndef().subType
val call =
if(iType.isStructInstance) {
// a call to a struct yields a struct instance and means: allocate a statically initialized struct instance of that type
val struct = iType.getOrUndef().subType!!
val pointertype = DataType.pointerToType(struct)
if(subtypeStruct!=null) {
// a call to a struct yields a pointer to a struct instance and means: allocate a statically initialized struct instance of that type
val pointertype = DataType.pointerToType(subtypeStruct)
PtBuiltinFunctionCall("structalloc", false, true, pointertype, srcCall.position)
} else {
// regular function call

View File

@@ -292,7 +292,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
val possibleTargetDt = it.first.possibleDatatypes.first()
val targetDt = if(possibleTargetDt.isPointer) BaseDataType.UWORD else possibleTargetDt // use UWORD instead of a pointer type (using words for pointers is allowed without further casting)
val argIdt = it.second.inferType(program)
if (argIdt.isKnown) {
if (argIdt.isKnown && !targetDt.isStructInstance) {
val argDt = argIdt.getOrUndef()
if (argDt.base !in it.first.possibleDatatypes) {
val identifier = it.second as? IdentifierReference

View File

@@ -447,6 +447,14 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
return listOf(IAstModification.ReplaceNode(functionCallExpr, cast, parent))
}
}
if(parent is IStatementContainer) {
val targetStruct = functionCallExpr.target.targetStructDecl()
if (targetStruct != null) {
// static struct instance allocation can only occur as an initializer for a pointer variable
return listOf(IAstModification.Remove(functionCallExpr, parent as IStatementContainer))
}
}
return noModifications
}

View File

@@ -1,11 +1,18 @@
package prog8tests.compiler
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.engine.spec.tempdir
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.types.instanceOf
import prog8.code.ast.PtReturn
import prog8.code.ast.PtSubSignature
import prog8.code.target.C64Target
import prog8.code.target.VMTarget
import prog8.vm.VmRunner
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
import kotlin.io.path.readText
@@ -38,4 +45,169 @@ thing {
VmRunner().runProgram(virtfile.readText(), true)
}
test("passing struct instances to subroutines and returning a struct instance is not allowed") {
val src="""
main {
sub start() {
}
struct Node {
bool flag
}
sub faulty(Node arg) -> Node {
return cx16.r0
}
}
"""
val errors = ErrorReporterForTests()
compileText(VMTarget(), false, src, outputDir, errors=errors)
val err = errors.errors
err.size shouldBe 3
err[0] shouldContain "uword doesn't match"
err[1] shouldContain "structs can only be passed via a pointer"
err[2] shouldContain "structs can only be returned via a pointer"
}
test("pointers in subroutine return values") {
val src="""
main {
sub start() {
^^thing.Node @shared ptr = thing.new()
}
}
thing {
struct Node {
bool flag
^^Node next
}
sub new() -> ^^Node {
cx16.r0++
^^Node pointer = 2000
return pointer
}
}"""
compileText(VMTarget(), false, src, outputDir) shouldNotBe null
// TODO compileText(C64Target(), false, src, outputDir) shouldNotBe null
}
test("creating instances") {
val src="""
main {
struct MyNode {
bool flag
^^MyNode next
}
sub start() {
^^MyNode @shared m1 = MyNode()
^^MyNode @shared m2 = MyNode(true, 0)
^^thing.Node @shared n1 = thing.Node()
^^thing.Node @shared n2 = thing.Node(true, 0)
}
}
thing {
struct Node {
bool flag
^^Node next
}
}"""
compileText(VMTarget(), false, src, outputDir) shouldNotBe null
compileText(VMTarget(), true, src, outputDir) shouldNotBe null
// TODO compileText(C64Target(), false, src, outputDir) shouldNotBe null
// TODO compileText(C64Target(), true, src, outputDir) shouldNotBe null
}
test("creating instances with optimization should all be removed") {
val src="""
main {
struct MyNode {
bool flag
^^MyNode next
}
sub start() {
^^MyNode m1 = MyNode()
^^MyNode m2 = MyNode(true, 0)
^^thing.Node n1 = thing.Node()
^^thing.Node n2 = thing.Node(true, 0)
}
}
thing {
struct Node {
bool flag
^^Node next
}
}"""
var result = compileText(VMTarget(), true, src, outputDir)!!
withClue("all variables should have been optimized away") {
val start = result.codegenAst!!.entrypoint()!!
start.children.size shouldBe 2
start.children[0] shouldBe instanceOf<PtSubSignature>()
start.children[1] shouldBe instanceOf<PtReturn>()
}
// TODO compileText(C64Target(), true, src, outputDir) shouldNotBe null
}
test("creating instances should have correct number of args") {
val src="""
main {
struct Node {
bool flag
ubyte value
^^Node next
}
sub start() {
^^Node ptr = Node(true) ; error
}
}"""
val errors = ErrorReporterForTests()
compileText(VMTarget(), false, src, outputDir, errors=errors)
val err = errors.errors
err.size shouldBe 1
err[0] shouldContain("expected 3 or 0, got 1")
}
test("pointer uword compatibility") {
val src="""
main {
struct MyNode {
bool flag
^^MyNode next
}
sub start() {
cx16.r0 = MyNode()
^^MyNode @shared ptr1 = cx16.r0
ptr1 = 2000
ptr1 = 20
ptr1 = 20.2222
}
}"""
val errors = ErrorReporterForTests()
compileText(VMTarget(), false, src, outputDir, errors=errors)
val err = errors.errors
err.size shouldBe 1
err[0] shouldContain("15:16: can only assign uword or correct pointer type to a pointer")
}
})

View File

@@ -73,7 +73,7 @@ class TestSubroutines: FunSpec({
func.statements.isEmpty() shouldBe true
}
test("cannot call a subroutine via pointer") {
test("cannot call a subroutine via a pointer") {
val src="""
main {
sub start() {

View File

@@ -1458,7 +1458,8 @@ class FunctionCallExpression(override var target: IdentifierReference,
return InferredTypes.unknown() // has multiple return types... so not a single resulting datatype possible
}
is StructDecl -> {
return InferredTypes.knownFor(DataType.structInstance(stmt))
// calling a struct is syntax for allocating a static instance, and returns a pointer to that (not the instance itself)
return InferredTypes.knownFor(DataType.pointerToType(stmt))
}
else -> return InferredTypes.unknown()
}

View File

@@ -109,7 +109,10 @@ object InferredTypes {
}
type.isPointerArray -> InferredType.known(DataType.arrayOfPointersTo(type.sub, type.subType))
type.isStructInstance -> {
InferredType.known(DataType.structInstance(type.subType!!))
if(type.subType!=null)
InferredType.known(DataType.structInstance(type.subType!!))
else
InferredType.known(DataType.structInstanceFromAntlr(type.subTypeFromAntlr!!))
}
else -> throw IllegalArgumentException("invalid type $type")
}

View File

@@ -1,47 +1,16 @@
%import textio
main {
struct MyNode {
bool flag
^^MyNode next
}
sub start() {
^^uword ptr = 2000
cx16.r0 = MyNode()
ptr^^ <<= 2
ptr^^ >>= 3
^^MyNode @shared ptr1 = cx16.r0
pokew(2000, 1111)
cx16.r0 ^= $ffff
txt.print_uw(ptr^^)
txt.nl()
ptr^^ += 5
txt.print_uw(ptr^^)
txt.nl()
ptr^^ -= 9
txt.print_uw(ptr^^)
txt.nl()
ptr^^ *= 3
txt.print_uw(ptr^^)
txt.nl()
ptr^^ /= 3
txt.print_uw(ptr^^)
txt.nl()
ptr^^ |= $7f0f
txt.print_uwhex(ptr^^,true)
txt.nl()
ptr^^ &= $f0f0
txt.print_uwhex(ptr^^,true)
txt.nl()
ptr^^ ^= $ffff
txt.print_uwhex(ptr^^,true)
txt.nl()
; const uword buffer = $2000
; uword @shared addr = &buffer[2]
;
; const ubyte width = 100
; ubyte @shared i
; ubyte @shared j
; uword @shared addr2 = &buffer[i * width + j]
; txt.print_uw(addr)
ptr1 = 2000
ptr1 = 20
ptr1 = 20.2222
}
}

View File

@@ -1,10 +1,10 @@
package prog8.intermediate
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.core.BaseDataType
import prog8.code.core.DataType
import prog8.code.core.Encoding
import prog8.code.core.ZeropageWish
import prog8.code.core.BaseDataType
// In the Intermediate Representation, all nesting has been removed.
@@ -30,6 +30,9 @@ class IRSymbolTable {
fun allStructInstances(): Sequence<IRStStructInstance> =
table.asSequence().map { it.value }.filterIsInstance<IRStStructInstance>()
fun allStructDefs(): Sequence<IRStStructDef> =
table.asSequence().map { it.value }.filterIsInstance<IRStStructDef>()
fun lookup(name: String): IRStNode? = table[name]
fun add(node: IRStNode) {