no longer take AddressOf a str-variable that is a subroutine's parameter with str type (it's just an address/uword already)

This commit is contained in:
Irmen de Jong
2021-10-25 23:01:07 +02:00
parent 69a8813a3d
commit 5e1459564a
6 changed files with 70 additions and 51 deletions

View File

@@ -5,11 +5,13 @@ import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.internedStringsModuleName
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.ast.walk.IAstVisitor
import prog8.compiler.astprocessing.isInRegularRAMof
import prog8.compiler.astprocessing.isSubroutineParameter
import prog8.compiler.target.ICompilationTarget
@@ -31,6 +33,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
}
}
}
return noModifications
}
@@ -77,7 +80,6 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
if(!subroutine.isAsmSubroutine) {
// change 'str' parameters into 'uword' (just treat it as an address)
// TODO fix [TypecastsAdder] to treat str param vars as uword instead of adding casts/addressof (which is wrong for str *parameters*)
val stringParams = subroutine.parameters.filter { it.type==DataType.STR }
val parameterChanges = stringParams.map {
val uwordParam = SubroutineParameter(it.name, DataType.UWORD, it.position)
@@ -167,7 +169,8 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
// see if we can remove superfluous typecasts (outside of expressions)
// such as casting byte<->ubyte, word<->uword
// Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of.
// Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of,
// UNLESS it's a str parameter in the containing subroutine - then we remove the typecast altogether
val sourceDt = typecast.expression.inferType(program).getOr(DataType.UNDEFINED)
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes) {
@@ -176,22 +179,23 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
}
}
// Note: for various reasons (most importantly, code simplicity), the code generator assumes/requires
// that the types of assignment values and their target are the same,
// and that the types of both operands of a binaryexpression node are the same.
// So, it is not easily possible to remove the typecasts that are there to make these conditions true.
// The only place for now where we can do this is for:
// asmsub register pair parameter.
if(sourceDt in PassByReferenceDatatypes) {
if(typecast.type==DataType.UWORD) {
if(typecast.expression is IdentifierReference) {
return listOf(IAstModification.ReplaceNode(
val identifier = typecast.expression as? IdentifierReference
if(identifier!=null) {
return if(identifier.isSubroutineParameter(program)) {
listOf(IAstModification.ReplaceNode(
typecast,
AddressOf(typecast.expression as IdentifierReference, typecast.position),
typecast.expression,
parent
))
))
} else {
listOf(IAstModification.ReplaceNode(
typecast,
AddressOf(identifier, typecast.position),
parent
))
}
} else if(typecast.expression is IFunctionCall) {
return listOf(IAstModification.ReplaceNode(
typecast,

View File

@@ -105,7 +105,8 @@ fun compileProgram(filepath: Path,
)
postprocessAst(programAst, errors, compilationOptions)
// printAst(programAst)
// println("*********** AST BEFORE ASSEMBLYGEN *************")
// printAst(programAst)
if (writeAssembly) {
val result = writeAssembly(programAst, errors, outputDir, compilationOptions)
@@ -346,7 +347,7 @@ private fun writeAssembly(programAst: Program,
programAst.processAstBeforeAsmGeneration(errors, compilerOptions.compTarget)
errors.report()
printAst(programAst) // TODO
// printAst(programAst)
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
val assembly = asmGeneratorFor(compilerOptions.compTarget,

View File

@@ -209,3 +209,12 @@ internal fun AssignTarget.isInRegularRAMof(machine: IMachineDefinition): Boolean
else -> return true
}
}
internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolean {
val vardecl = this.targetVarDecl(program)
if(vardecl!=null && vardecl.autogeneratedDontRemove) {
return vardecl.definingSubroutine?.parameters?.any { it.name==vardecl.name } == true
}
return false
}

View File

@@ -135,11 +135,13 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
TypecastExpression(pair.second, requiredType, true, pair.second.position),
call as Node)
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
// we allow STR/ARRAY values in place of UWORD parameters. Take their address instead.
if(pair.second is IdentifierReference) {
// We allow STR/ARRAY values in place of UWORD parameters.
// Take their address instead, UNLESS it's a str parameter in the containing subroutine
val identifier = pair.second as? IdentifierReference
if(identifier?.isSubroutineParameter(program)==false) {
modifications += IAstModification.ReplaceNode(
call.args[index],
AddressOf(pair.second as IdentifierReference, pair.second.position),
AddressOf(identifier, pair.second.position),
call as Node)
}
} else if(pair.second is NumericLiteralValue) {

View File

@@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.*
import prog8.compiler.target.C64Target
import prog8tests.helpers.ErrorReporterForTests
@@ -17,29 +18,6 @@ import kotlin.test.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestSubroutines {
@Test
fun arrayParameterNotYetAllowed_ButShouldPerhapsBe() {
// note: the *parser* accepts this as it is valid *syntax*,
// however, it's not (yet) valid for the compiler
val text = """
main {
sub start() {
}
asmsub asmfunc(ubyte[] thing @AY) {
}
sub func(ubyte[22] thing) {
}
}
"""
val errors = ErrorReporterForTests()
compileText(C64Target, false, text, errors, false).assertFailure("currently array dt in signature is invalid") // TODO should not be invalid?
assertEquals(0, errors.warnings.size)
assertContains(errors.errors.single(), ".p8:9:16: Non-string pass-by-reference types cannot occur as a parameter type directly")
}
@Test
fun stringParameter() {
val text = """
@@ -80,11 +58,11 @@ class TestSubroutines {
assertEquals(DataType.STR, paramvar.datatype)
val t2var = func.statements[1] as VarDecl
assertEquals("t2", t2var.name)
assertTrue(t2var.value is IdentifierReference, "str param in function body should be treated as plain uword")
assertEquals("thing", (t2var.value as IdentifierReference).nameInSource.single())
assertTrue(t2var.value is TypecastExpression, "str param in function body should not be transformed by normal compiler steps")
assertEquals(DataType.UWORD, (t2var.value as TypecastExpression).type)
val call = func.statements[2] as FunctionCallStatement
assertEquals("asmfunc", call.target.nameInSource.single())
assertTrue(call.args.single() is IdentifierReference, "str param in function body should be treated as plain uword")
assertTrue(call.args.single() is IdentifierReference, "str param in function body should not be transformed by normal compiler steps")
assertEquals("thing", (call.args.single() as IdentifierReference).nameInSource.single())
}
@@ -131,14 +109,37 @@ class TestSubroutines {
assertEquals(DataType.UWORD, paramvar.datatype, "pre-asmgen should have changed str to uword type")
val t2var = func.statements[1] as VarDecl
assertEquals("t2", t2var.name)
assertTrue(t2var.value is IdentifierReference, "str param in function body should be treated as plain uword")
assertTrue(t2var.value is IdentifierReference, "str param in function body should be treated as plain uword before asmgen")
assertEquals("thing", (t2var.value as IdentifierReference).nameInSource.single())
val call = func.statements[2] as FunctionCallStatement
assertEquals("asmfunc", call.target.nameInSource.single())
assertTrue(call.args.single() is IdentifierReference, "str param in function body should be treated as plain uword")
assertTrue(call.args.single() is IdentifierReference, "str param in function body should be treated as plain uword and not been transformed")
assertEquals("thing", (call.args.single() as IdentifierReference).nameInSource.single())
}
@Test
fun arrayParameterNotYetAllowed_ButShouldPerhapsBe() {
// note: the *parser* accepts this as it is valid *syntax*,
// however, it's not (yet) valid for the compiler
val text = """
main {
sub start() {
}
asmsub asmfunc(ubyte[] thing @AY) {
}
sub func(ubyte[22] thing) {
}
}
"""
val errors = ErrorReporterForTests()
compileText(C64Target, false, text, errors, false).assertFailure("currently array dt in signature is invalid") // TODO should not be invalid?
assertEquals(0, errors.warnings.size)
assertContains(errors.errors.single(), ".p8:9:16: Non-string pass-by-reference types cannot occur as a parameter type directly")
}
@Test
@Disabled("TODO: allow array parameter in signature") // TODO allow this?
fun arrayParameter() {

View File

@@ -11,10 +11,11 @@ main {
@($2002) = 'c'
@($2003) = 0
asmfunc("text")
asmfunc("text12345")
asmfunc(text)
asmfunc($2000)
func("text")
txt.nl()
func("text12345")
func(text)
func($2000)
}
@@ -27,10 +28,11 @@ main {
}}
}
; TODO fix asmgen when using 'str' type
sub func(uword thing) {
sub func(str thing) {
uword t2 = thing as uword
ubyte length = string.length(thing)
txt.print_uwhex(thing, true)
txt.nl()
txt.print_ub(length)
txt.nl()
txt.print(thing)