fix alignment of uninitialized arrays in aligned blocks (make them initialized with zeros so they don't end up in the BSS section)

fix alignment of uninitialized arrays in aligned blocks (make them initialized with zeros so they don't end up in the BSS section)
This commit is contained in:
Irmen de Jong
2023-10-03 22:54:28 +02:00
parent a8507b437d
commit b7a622c68e
11 changed files with 173 additions and 129 deletions

View File

@ -196,13 +196,10 @@ class StStaticVariable(name: String,
} }
if(onetimeInitializationNumericValue!=null) { if(onetimeInitializationNumericValue!=null) {
require(dt in NumericDatatypes) require(dt in NumericDatatypes)
require(onetimeInitializationNumericValue!=0.0) { "zero as init value should just remain uninitialized"}
} }
if(onetimeInitializationArrayValue!=null) { if(onetimeInitializationArrayValue!=null) {
require(dt in ArrayDatatypes) require(dt in ArrayDatatypes)
if(onetimeInitializationArrayValue.all { it.number!=null} ) { require(length==onetimeInitializationArrayValue.size)
require(onetimeInitializationArrayValue.any { it.number != 0.0 }) { "array of all zerors as init value should just remain uninitialized" }
}
} }
if(onetimeInitializationStringValue!=null) { if(onetimeInitializationStringValue!=null) {
require(dt == DataType.STR) require(dt == DataType.STR)

View File

@ -74,10 +74,9 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
numElements = value.value.length + 1 // include the terminating 0-byte numElements = value.value.length + 1 // include the terminating 0-byte
} }
is PtArray -> { is PtArray -> {
val array = makeInitialArray(value) initialArray = makeInitialArray(value)
initialArray = if(array.all { it.number==0.0 }) null else array // all 0 as init value -> just uninitialized
initialString = null initialString = null
numElements = array.size numElements = initialArray.size
require(node.arraySize?.toInt()==numElements) require(node.arraySize?.toInt()==numElements)
} }
else -> { else -> {

View File

@ -276,10 +276,27 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl)) return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
} }
} }
in ArrayDatatypes -> {
val replacedArrayInitializer = createConstArrayInitializerValue(decl)
if(replacedArrayInitializer!=null)
return listOf(IAstModification.ReplaceNode(decl.value!!, replacedArrayInitializer, decl))
}
else -> {
// nothing to do for this type
}
}
}
return noModifications
}
private fun createConstArrayInitializerValue(decl: VarDecl): ArrayLiteral? {
// convert the initializer range expression from a range or int, to an actual array.
when(decl.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_UW_SPLIT -> { DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_UW_SPLIT -> {
val rangeExpr = decl.value as? RangeExpression val rangeExpr = decl.value as? RangeExpression
if(rangeExpr!=null) { if(rangeExpr!=null) {
// convert the initializer range expression to an actual array
val constRange = rangeExpr.toConstantIntegerRange() val constRange = rangeExpr.toConstantIntegerRange()
if(constRange?.isEmpty()==true) { if(constRange?.isEmpty()==true) {
if(constRange.first>constRange.last && constRange.step>=0) if(constRange.first>constRange.last && constRange.step>=0)
@ -292,7 +309,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!) errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
if(constRange!=null) { if(constRange!=null) {
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE) val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
val newValue = if(eltType in ByteDatatypes) { return if(eltType in ByteDatatypes) {
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype), ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(), constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position) position = decl.value!!.position)
@ -301,13 +318,12 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(), constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position) position = decl.value!!.position)
} }
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
} }
} }
val numericLv = decl.value as? NumericLiteral val numericLv = decl.value as? NumericLiteral
if(numericLv!=null && numericLv.type== DataType.FLOAT) if(numericLv!=null && numericLv.type== DataType.FLOAT)
errors.err("arraysize requires only integers here", numericLv.position) errors.err("arraysize requires only integers here", numericLv.position)
val size = decl.arraysize?.constIndex() ?: return noModifications val size = decl.arraysize?.constIndex() ?: return null
if (rangeExpr==null && numericLv!=null) { if (rangeExpr==null && numericLv!=null) {
// arraysize initializer is empty or a single int, and we know the size; create the arraysize. // arraysize initializer is empty or a single int, and we know the size; create the arraysize.
val fillvalue = numericLv.number.toInt() val fillvalue = numericLv.number.toInt()
@ -332,8 +348,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
} }
// create the array itself, filled with the fillvalue. // create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteral(ArrayToElementTypes.getValue(decl.datatype), it.toDouble(), numericLv.position) }.toTypedArray<Expression>() val array = Array(size) {fillvalue}.map { NumericLiteral(ArrayToElementTypes.getValue(decl.datatype), it.toDouble(), numericLv.position) }.toTypedArray<Expression>()
val refValue = ArrayLiteral(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position) return ArrayLiteral(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
} }
} }
DataType.ARRAY_F -> { DataType.ARRAY_F -> {
@ -345,15 +360,14 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!) errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange() val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) { if(constRange!=null) {
val newValue = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F), return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F),
constRange.map { NumericLiteral(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(), constRange.map { NumericLiteral(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position) position = decl.value!!.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
} }
} }
val numericLv = decl.value as? NumericLiteral val numericLv = decl.value as? NumericLiteral
val size = decl.arraysize?.constIndex() ?: return noModifications val size = decl.arraysize?.constIndex() ?: return null
if(rangeExpr==null && numericLv!=null) { if(rangeExpr==null && numericLv!=null) {
// arraysize initializer is a single int, and we know the size. // arraysize initializer is a single int, and we know the size.
val fillvalue = numericLv.number val fillvalue = numericLv.number
@ -362,28 +376,22 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
else { else {
// create the array itself, filled with the fillvalue. // create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.FLOAT, it, numericLv.position) }.toTypedArray<Expression>() val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.FLOAT, it, numericLv.position) }.toTypedArray<Expression>()
val refValue = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = numericLv.position) return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = numericLv.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
} }
} }
} }
DataType.ARRAY_BOOL -> { DataType.ARRAY_BOOL -> {
val numericLv = decl.value as? NumericLiteral val numericLv = decl.value as? NumericLiteral
val size = decl.arraysize?.constIndex() ?: return noModifications val size = decl.arraysize?.constIndex() ?: return null
if(numericLv!=null) { if(numericLv!=null) {
// arraysize initializer is a single int, and we know the size. // arraysize initializer is a single int, and we know the size.
val fillvalue = if(numericLv.number==0.0) 0.0 else 1.0 val fillvalue = if(numericLv.number==0.0) 0.0 else 1.0
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.UBYTE, fillvalue, numericLv.position) }.toTypedArray<Expression>() val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.UBYTE, fillvalue, numericLv.position) }.toTypedArray<Expression>()
val refValue = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_BOOL), array, position = numericLv.position) return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_BOOL), array, position = numericLv.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
} }
} }
else -> { else -> return null
// nothing to do for this type
} }
} return null
}
return noModifications
} }
} }

View File

@ -109,7 +109,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
program.processAstBeforeAsmGeneration(compilationOptions, args.errors) program.processAstBeforeAsmGeneration(compilationOptions, args.errors)
args.errors.report() args.errors.report()
val intermediateAst = IntermediateAstMaker(program).transform() val intermediateAst = IntermediateAstMaker(program, args.errors).transform()
// println("*********** COMPILER AST RIGHT BEFORE ASM GENERATION *************") // println("*********** COMPILER AST RIGHT BEFORE ASM GENERATION *************")
// printProgram(program) // printProgram(program)
// println("*********** AST RIGHT BEFORE ASM GENERATION *************") // println("*********** AST RIGHT BEFORE ASM GENERATION *************")

View File

@ -824,6 +824,8 @@ internal class AstChecker(private val program: Program,
err("missing option directive argument(s)") err("missing option directive argument(s)")
else if(directive.args.map{it.name in arrayOf("enable_floats", "force_output", "no_sysinit", "align_word", "align_page", "merge", "splitarrays", "no_symbol_prefixing")}.any { !it }) else if(directive.args.map{it.name in arrayOf("enable_floats", "force_output", "no_sysinit", "align_word", "align_page", "merge", "splitarrays", "no_symbol_prefixing")}.any { !it })
err("invalid option directive argument(s)") err("invalid option directive argument(s)")
if(directive.args.any {it.name=="align_word"} && directive.args.any { it.name=="align_page"})
err("conflicting alignment options")
} }
else -> throw SyntaxError("invalid directive ${directive.directive}", directive.position) else -> throw SyntaxError("invalid directive ${directive.directive}", directive.position)
} }

View File

@ -19,7 +19,7 @@ import kotlin.io.path.isRegularFile
/** /**
* Convert 'old' compiler-AST into the 'new' simplified AST with baked types. * Convert 'old' compiler-AST into the 'new' simplified AST with baked types.
*/ */
class IntermediateAstMaker(private val program: Program) { class IntermediateAstMaker(private val program: Program, private val errors: IErrorReporter) {
fun transform(): PtProgram { fun transform(): PtProgram {
val ptProgram = PtProgram( val ptProgram = PtProgram(
program.name, program.name,
@ -398,13 +398,26 @@ class IntermediateAstMaker(private val program: Program) {
} }
private fun transform(srcVar: VarDecl): PtNode { private fun transform(srcVar: VarDecl): PtNode {
return when(srcVar.type) { when(srcVar.type) {
VarDeclType.VAR -> { VarDeclType.VAR -> {
val value = if(srcVar.value!=null) transformExpression(srcVar.value!!) else null val value = if(srcVar.value!=null) transformExpression(srcVar.value!!) else null
PtVariable(srcVar.name, srcVar.datatype, srcVar.zeropage, value, srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position) if(srcVar.datatype in ArrayDatatypes) {
if(value==null) {
val blockOptions = srcVar.definingBlock.options()
if("align_page" in blockOptions || "align_word" in blockOptions) {
errors.warn("converting uninitialized array to explicit zeros because of block alignment option", srcVar.position)
val zeros = PtArray(srcVar.datatype, srcVar.position)
repeat(srcVar.arraysize!!.constIndex()!!) {
zeros.children.add(PtNumber(ArrayToElementTypes.getValue(srcVar.datatype), 0.0, srcVar.position))
} }
VarDeclType.CONST -> PtConstant(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number, srcVar.position) return PtVariable(srcVar.name, srcVar.datatype, srcVar.zeropage, zeros, srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
VarDeclType.MEMORY -> PtMemMapped(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number.toUInt(), srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position) }
}
}
return PtVariable(srcVar.name, srcVar.datatype, srcVar.zeropage, value, srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
}
VarDeclType.CONST -> return PtConstant(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number, srcVar.position)
VarDeclType.MEMORY -> return PtMemMapped(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number.toUInt(), srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
} }
} }

View File

@ -83,6 +83,27 @@ class TestSymbolTable: FunSpec({
st.allMemorySlabs.single().scopedName shouldBe "block1.sub1.slab1" st.allMemorySlabs.single().scopedName shouldBe "block1.sub1.slab1"
} }
test("static vars") {
val node = PtIdentifier("dummy", DataType.UBYTE, Position.DUMMY)
val stVar1 = StStaticVariable("initialized", DataType.UBYTE, 99.0, null, null, null, ZeropageWish.DONTCARE, node)
val stVar2 = StStaticVariable("uninitialized", DataType.UBYTE, null, null, null, null, ZeropageWish.DONTCARE, node)
val arrayInitNonzero = listOf(StArrayElement(1.1, null), StArrayElement(2.2, null), StArrayElement(3.3, null))
val arrayInitAllzero = listOf(StArrayElement(0.0, null), StArrayElement(0.0, null), StArrayElement(0.0, null))
val stVar3 = StStaticVariable("initialized", DataType.ARRAY_UW, null, null, arrayInitNonzero, 3, ZeropageWish.DONTCARE, node)
val stVar4 = StStaticVariable("initialized", DataType.ARRAY_UW, null, null, arrayInitAllzero, 3, ZeropageWish.DONTCARE, node)
val stVar5 = StStaticVariable("uninitialized", DataType.ARRAY_UW, null, null, null, 3, ZeropageWish.DONTCARE, node)
stVar1.uninitialized shouldBe false
stVar1.length shouldBe null
stVar2.uninitialized shouldBe true
stVar2.length shouldBe null
stVar3.uninitialized shouldBe false
stVar3.length shouldBe 3
stVar4.uninitialized shouldBe false
stVar4.length shouldBe 3
stVar5.uninitialized shouldBe true
stVar5.length shouldBe 3
}
}) })

View File

@ -8,6 +8,7 @@ import prog8.code.ast.*
import prog8.code.core.DataType import prog8.code.core.DataType
import prog8.code.target.C64Target import prog8.code.target.C64Target
import prog8.compiler.astprocessing.IntermediateAstMaker import prog8.compiler.astprocessing.IntermediateAstMaker
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText import prog8tests.helpers.compileText
class TestIntermediateAst: FunSpec({ class TestIntermediateAst: FunSpec({
@ -26,8 +27,9 @@ class TestIntermediateAst: FunSpec({
} }
""" """
val target = C64Target() val target = C64Target()
val errors = ErrorReporterForTests()
val result = compileText(target, false, text, writeAssembly = false)!! val result = compileText(target, false, text, writeAssembly = false)!!
val ast = IntermediateAstMaker(result.compilerAst).transform() val ast = IntermediateAstMaker(result.compilerAst, errors).transform()
ast.name shouldBe result.compilerAst.name ast.name shouldBe result.compilerAst.name
ast.allBlocks().any() shouldBe true ast.allBlocks().any() shouldBe true
val entry = ast.entrypoint() ?: fail("no main.start() found") val entry = ast.entrypoint() ?: fail("no main.start() found")

View File

@ -75,7 +75,7 @@ class TestAsmGenSymbols: StringSpec({
fun createTestAsmGen6502(program: Program): AsmGen6502Internal { fun createTestAsmGen6502(program: Program): AsmGen6502Internal {
val errors = ErrorReporterForTests() val errors = ErrorReporterForTests()
val options = CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, true, C64Target(), 999u) val options = CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, true, C64Target(), 999u)
val ptProgram = IntermediateAstMaker(program).transform() val ptProgram = IntermediateAstMaker(program, errors).transform()
val st = SymbolTableMaker(ptProgram, options).make() val st = SymbolTableMaker(ptProgram, options).make()
return AsmGen6502Internal(ptProgram, st, options, errors) return AsmGen6502Internal(ptProgram, st, options, errors)
} }

View File

@ -35,6 +35,8 @@ Compiler:
- OR.... make all this more generic and use some %segment option to create real segments for 64tass? - OR.... make all this more generic and use some %segment option to create real segments for 64tass?
- (need separate step in codegen and IR to write the "golden" variables) - (need separate step in codegen and IR to write the "golden" variables)
- need variable alignment tag instead of block alignment tag, you want to align the data not the code in the block perse
- ir: block alignment doesn't translate well to variables in the block (the actual stuff that needs to be aligned in memory) but: need variable alignment tag instead of block alignment tag, really
- ir: idea: (but LLVM IR simply keeps the variables, so not a good idea then?...): replace all scalar variables by an allocated register. Keep a table of the variable to register mapping (including the datatype) - ir: idea: (but LLVM IR simply keeps the variables, so not a good idea then?...): replace all scalar variables by an allocated register. Keep a table of the variable to register mapping (including the datatype)
global initialization values are simply a list of LOAD instructions. global initialization values are simply a list of LOAD instructions.
Variables replaced include all subroutine parameters! So the only variables that remain as variables are arrays and strings. Variables replaced include all subroutine parameters! So the only variables that remain as variables are arrays and strings.

View File

@ -1,20 +1,20 @@
%import textio %import textio
%import gfx2
%import verafx
%zeropage basicsafe %zeropage basicsafe
main { main {
sub start() { sub start() {
gfx2.screen_mode(4) alignblock.flags[0] = 222
gfx2.disc(160, 120, 100, 2) cx16.r0++
; verafx.transparency(true) cx16.r1++
gfx2.position(0, 70) txt.print_uwhex(alignblock.flags, true)
repeat 3000 { txt.spc()
gfx2.next_pixel(7) txt.print_ub(alignblock.flags[0])
repeat 10 txt.nl()
gfx2.next_pixel(0) ; transparent!
}
verafx.transparency(false)
} }
} }
alignblock {
%option align_page
ubyte[10] flags
}