added compiler option to choose string literal deduplication yes/no -- default changed to NO

This commit is contained in:
Irmen de Jong 2021-05-13 00:35:22 +02:00
parent 74f918d911
commit 25d80f4df1
13 changed files with 115 additions and 97 deletions

View File

@ -42,6 +42,7 @@ private fun compileMain(args: Array<String>) {
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name) val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name)
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999) val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
val libDirs by cli.option(ArgType.String, fullName="libdirs", description = "list of extra paths to search in for imported modules").multiple().delimiter(File.pathSeparator) val libDirs by cli.option(ArgType.String, fullName="libdirs", description = "list of extra paths to search in for imported modules").multiple().delimiter(File.pathSeparator)
val stringDedup by cli.option(ArgType.Boolean, fullName="strdedup", description = "deduplicate identical string literals").default(false)
try { try {
cli.parse(args) cli.parse(args)
@ -69,7 +70,7 @@ private fun compileMain(args: Array<String>) {
val results = mutableListOf<CompilationResult>() val results = mutableListOf<CompilationResult>()
for(filepathRaw in moduleFiles) { for(filepathRaw in moduleFiles) {
val filepath = pathFrom(filepathRaw).normalize() val filepath = pathFrom(filepathRaw).normalize()
val compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, libdirs, outputPath) val compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, stringDedup, compilationTarget, libdirs, outputPath)
results.add(compilationResult) results.add(compilationResult)
} }
@ -106,7 +107,7 @@ private fun compileMain(args: Array<String>) {
val filepath = pathFrom(filepathRaw).normalize() val filepath = pathFrom(filepathRaw).normalize()
val compilationResult: CompilationResult val compilationResult: CompilationResult
try { try {
compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, libdirs, outputPath) compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, stringDedup, compilationTarget, libdirs, outputPath)
if(!compilationResult.success) if(!compilationResult.success)
exitProcess(1) exitProcess(1)
} catch (x: ParsingFailedError) { } catch (x: ParsingFailedError) {

View File

@ -50,6 +50,7 @@ data class CompilationOptions(val output: OutputType,
val zpReserved: List<IntRange>, val zpReserved: List<IntRange>,
val floats: Boolean, val floats: Boolean,
val noSysInit: Boolean, val noSysInit: Boolean,
val stringDedup: Boolean,
val compTarget: ICompilationTarget) { val compTarget: ICompilationTarget) {
var slowCodegenWarnings = false var slowCodegenWarnings = false
var optimize = false var optimize = false
@ -69,6 +70,7 @@ fun compileProgram(filepath: Path,
optimize: Boolean, optimize: Boolean,
writeAssembly: Boolean, writeAssembly: Boolean,
slowCodegenWarnings: Boolean, slowCodegenWarnings: Boolean,
stringDedup: Boolean,
compilationTarget: String, compilationTarget: String,
libdirs: List<String>, libdirs: List<String>,
outputDir: Path): CompilationResult { outputDir: Path): CompilationResult {
@ -90,7 +92,7 @@ fun compileProgram(filepath: Path,
try { try {
val totalTime = measureTimeMillis { val totalTime = measureTimeMillis {
// import main module and everything it needs // import main module and everything it needs
val (ast, compilationOptions, imported) = parseImports(filepath, errors, compTarget, libdirs) val (ast, compilationOptions, imported) = parseImports(filepath, errors, compTarget, stringDedup, libdirs)
compilationOptions.slowCodegenWarnings = slowCodegenWarnings compilationOptions.slowCodegenWarnings = slowCodegenWarnings
compilationOptions.optimize = optimize compilationOptions.optimize = optimize
programAst = ast programAst = ast
@ -166,7 +168,11 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
builtinFunctionReturnType(name, args, program) builtinFunctionReturnType(name, args, program)
} }
private fun parseImports(filepath: Path, errors: IErrorReporter, compTarget: ICompilationTarget, libdirs: List<String>): Triple<Program, CompilationOptions, List<Path>> { private fun parseImports(filepath: Path,
errors: IErrorReporter,
compTarget: ICompilationTarget,
stringDedup: Boolean,
libdirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
val compilationTargetName = compTarget.name val compilationTargetName = compTarget.name
println("Compiler target: $compilationTargetName. Parsing...") println("Compiler target: $compilationTargetName. Parsing...")
val bf = BuiltinFunctionsFacade(BuiltinFunctions) val bf = BuiltinFunctionsFacade(BuiltinFunctions)
@ -178,7 +184,7 @@ private fun parseImports(filepath: Path, errors: IErrorReporter, compTarget: ICo
errors.report() errors.report()
val importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map { it.source } val importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map { it.source }
val compilerOptions = determineCompilationOptions(programAst, compTarget) val compilerOptions = determineCompilationOptions(programAst, stringDedup, compTarget)
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG) if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.") throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
@ -193,7 +199,7 @@ private fun parseImports(filepath: Path, errors: IErrorReporter, compTarget: ICo
return Triple(programAst, compilerOptions, importedFiles) return Triple(programAst, compilerOptions, importedFiles)
} }
private fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions { private fun determineCompilationOptions(program: Program, stringDedup: Boolean, compTarget: ICompilationTarget): CompilationOptions {
val mainModule = program.mainModule val mainModule = program.mainModule
val outputType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" } val outputType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" }
as? Directive)?.args?.single()?.name?.uppercase() as? Directive)?.args?.single()?.name?.uppercase()
@ -240,7 +246,7 @@ private fun determineCompilationOptions(program: Program, compTarget: ICompilati
return CompilationOptions( return CompilationOptions(
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType), if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType), if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
zpType, zpReserved, floatsEnabled, noSysInit, zpType, zpReserved, floatsEnabled, noSysInit, stringDedup,
compTarget compTarget
) )
} }
@ -248,7 +254,7 @@ private fun determineCompilationOptions(program: Program, compTarget: ICompilati
private fun processAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) { private fun processAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
// perform initial syntax checks and processings // perform initial syntax checks and processings
println("Processing for target ${compilerOptions.compTarget.name}...") println("Processing for target ${compilerOptions.compTarget.name}...")
programAst.checkIdentifiers(errors, compilerOptions.compTarget) programAst.checkIdentifiers(errors, compilerOptions)
errors.report() errors.report()
programAst.constantFold(errors, compilerOptions.compTarget) programAst.constantFold(errors, compilerOptions.compTarget)
errors.report() errors.report()
@ -260,7 +266,7 @@ private fun processAst(programAst: Program, errors: IErrorReporter, compilerOpti
errors.report() errors.report()
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget) programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget)
errors.report() errors.report()
programAst.checkIdentifiers(errors, compilerOptions.compTarget) programAst.checkIdentifiers(errors, compilerOptions)
errors.report() errors.report()
} }

View File

@ -44,16 +44,16 @@ internal fun Program.verifyFunctionArgTypes() {
fixer.visit(this) fixer.visit(this)
} }
internal fun Program.checkIdentifiers(errors: IErrorReporter, compTarget: ICompilationTarget) { internal fun Program.checkIdentifiers(errors: IErrorReporter, options: CompilationOptions) {
val checker2 = AstIdentifiersChecker(this, errors, compTarget) val checker2 = AstIdentifiersChecker(this, errors, options.compTarget)
checker2.visit(this) checker2.visit(this)
if(errors.noErrors()) { if(errors.noErrors()) {
val transforms = AstVariousTransforms(this) val transforms = AstVariousTransforms(this)
transforms.visit(this) transforms.visit(this)
transforms.applyModifications() transforms.applyModifications()
val lit2decl = LiteralsToAutoVars(this) val lit2decl = LiteralsToAutoVars(this, options)
lit2decl.visit(this) lit2decl.visit(this)
lit2decl.applyModifications() lit2decl.applyModifications()
} }

View File

@ -10,14 +10,15 @@ import prog8.ast.statements.VarDecl
import prog8.ast.statements.WhenChoice import prog8.ast.statements.WhenChoice
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification import prog8.ast.walk.IAstModification
import prog8.compiler.CompilationOptions
internal class LiteralsToAutoVars(private val program: Program) : AstWalker() { internal class LiteralsToAutoVars(private val program: Program, private val options: CompilationOptions) : AstWalker() {
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> { override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
if(string.parent !is VarDecl && string.parent !is WhenChoice) { if(string.parent !is VarDecl && string.parent !is WhenChoice) {
// replace the literal string by a identifier reference to the interned string // replace the literal string by a identifier reference to the interned string
val scopedName = program.internString(string) val scopedName = program.internString(string, options.stringDedup)
val identifier = IdentifierReference(scopedName, string.position) val identifier = IdentifierReference(scopedName, string.position)
return listOf(IAstModification.ReplaceNode(string, identifier, parent)) return listOf(IAstModification.ReplaceNode(string, identifier, parent))
} }

View File

@ -381,18 +381,14 @@ internal class AsmGen(private val program: Program,
out("\n; non-zeropage variables") out("\n; non-zeropage variables")
val vars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR } val vars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
// special treatment for string types: merge strings that are identical
val encodedstringVars = vars val encodedstringVars = vars
.filter {it.datatype == DataType.STR } .filter {it.datatype == DataType.STR }
.map { .map {
val str = it.value as StringLiteralValue val str = it.value as StringLiteralValue
it to compTarget.encodeString(str.value, str.altEncoding).plus(0) it to compTarget.encodeString(str.value, str.altEncoding).plus(0)
} }
.groupBy({it.second}, {it.first}) for((decl, variables) in encodedstringVars) {
for((encoded, variables) in encodedstringVars) { outputStringvar(decl, variables)
variables.dropLast(1).forEach { out(it.name) }
val lastvar = variables.last()
outputStringvar(lastvar, encoded)
} }
// non-string variables // non-string variables
@ -402,11 +398,11 @@ internal class AsmGen(private val program: Program,
} }
} }
private fun outputStringvar(lastvar: VarDecl, encoded: List<Short>) { private fun outputStringvar(strdecl: VarDecl, bytes: List<Short>) {
val sv = lastvar.value as StringLiteralValue val sv = strdecl.value as StringLiteralValue
val altEncoding = if(sv.altEncoding) "@" else "" val altEncoding = if(sv.altEncoding) "@" else ""
out("${lastvar.name}\t; ${lastvar.datatype} $altEncoding\"${escape(sv.value).replace("\u0000", "<NULL>")}\"") out("${strdecl.name}\t; ${strdecl.datatype} $altEncoding\"${escape(sv.value).replace("\u0000", "<NULL>")}\"")
val outputBytes = encoded.map { "$" + it.toString(16).padStart(2, '0') } val outputBytes = bytes.map { "$" + it.toString(16).padStart(2, '0') }
for (chunk in outputBytes.chunked(16)) for (chunk in outputBytes.chunked(16))
out(" .byte " + chunk.joinToString()) out(" .byte " + chunk.joinToString())
} }

View File

@ -134,7 +134,7 @@ class TestC64Zeropage {
@Test @Test
fun testNames() { fun testNames() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, true, C64Target))
zp.allocate("", DataType.UBYTE, null, errors) zp.allocate("", DataType.UBYTE, null, errors)
zp.allocate("", DataType.UBYTE, null, errors) zp.allocate("", DataType.UBYTE, null, errors)
@ -147,37 +147,37 @@ class TestC64Zeropage {
@Test @Test
fun testZpFloatEnable() { fun testZpFloatEnable() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, true, C64Target))
assertFailsWith<CompilerException> { assertFailsWith<CompilerException> {
zp.allocate("", DataType.FLOAT, null, errors) zp.allocate("", DataType.FLOAT, null, errors)
} }
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, C64Target)) val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, true, C64Target))
assertFailsWith<CompilerException> { assertFailsWith<CompilerException> {
zp2.allocate("", DataType.FLOAT, null, errors) zp2.allocate("", DataType.FLOAT, null, errors)
} }
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target)) val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, true, C64Target))
zp3.allocate("", DataType.FLOAT, null, errors) zp3.allocate("", DataType.FLOAT, null, errors)
} }
@Test @Test
fun testZpModesWithFloats() { fun testZpModesWithFloats() {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, true, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, true, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, true, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, true, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, true, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, true, C64Target))
assertFailsWith<CompilerException> { assertFailsWith<CompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false, C64Target)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false, true, C64Target))
} }
assertFailsWith<CompilerException> { assertFailsWith<CompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false, C64Target)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false, true, C64Target))
} }
} }
@Test @Test
fun testZpDontuse() { fun testZpDontuse() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, C64Target)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, true, C64Target))
println(zp.free) println(zp.free)
assertEquals(0, zp.available()) assertEquals(0, zp.available())
assertFailsWith<CompilerException> { assertFailsWith<CompilerException> {
@ -187,19 +187,19 @@ class TestC64Zeropage {
@Test @Test
fun testFreeSpaces() { fun testFreeSpaces() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target)) val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, true, C64Target))
assertEquals(18, zp1.available()) assertEquals(18, zp1.available())
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target)) val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, true, C64Target))
assertEquals(85, zp2.available()) assertEquals(85, zp2.available())
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target)) val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, true, C64Target))
assertEquals(125, zp3.available()) assertEquals(125, zp3.available())
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target)) val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, true, C64Target))
assertEquals(238, zp4.available()) assertEquals(238, zp4.available())
} }
@Test @Test
fun testReservedSpace() { fun testReservedSpace() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target)) val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, true, C64Target))
assertEquals(238, zp1.available()) assertEquals(238, zp1.available())
assertTrue(50 in zp1.free) assertTrue(50 in zp1.free)
assertTrue(100 in zp1.free) assertTrue(100 in zp1.free)
@ -208,7 +208,7 @@ class TestC64Zeropage {
assertTrue(200 in zp1.free) assertTrue(200 in zp1.free)
assertTrue(255 in zp1.free) assertTrue(255 in zp1.free)
assertTrue(199 in zp1.free) assertTrue(199 in zp1.free)
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, false, C64Target)) val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, false, true, C64Target))
assertEquals(139, zp2.available()) assertEquals(139, zp2.available())
assertFalse(50 in zp2.free) assertFalse(50 in zp2.free)
assertFalse(100 in zp2.free) assertFalse(100 in zp2.free)
@ -221,7 +221,7 @@ class TestC64Zeropage {
@Test @Test
fun testBasicsafeAllocation() { fun testBasicsafeAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, true, C64Target))
assertEquals(18, zp.available()) assertEquals(18, zp.available())
assertFailsWith<ZeropageDepletedError> { assertFailsWith<ZeropageDepletedError> {
@ -244,7 +244,7 @@ class TestC64Zeropage {
@Test @Test
fun testFullAllocation() { fun testFullAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, true, C64Target))
assertEquals(238, zp.available()) assertEquals(238, zp.available())
val loc = zp.allocate("", DataType.UWORD, null, errors) val loc = zp.allocate("", DataType.UWORD, null, errors)
assertTrue(loc > 3) assertTrue(loc > 3)
@ -274,7 +274,7 @@ class TestC64Zeropage {
@Test @Test
fun testEfficientAllocation() { fun testEfficientAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, true, C64Target))
assertEquals(18, zp.available()) assertEquals(18, zp.available())
assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors)) assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors))
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors)) assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors))
@ -293,7 +293,7 @@ class TestC64Zeropage {
@Test @Test
fun testReservedLocations() { fun testReservedLocations() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, true, C64Target))
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word") assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
} }
} }
@ -303,23 +303,23 @@ class TestC64Zeropage {
class TestCx16Zeropage { class TestCx16Zeropage {
@Test @Test
fun testReservedLocations() { fun testReservedLocations() {
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, Cx16Target)) val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, true, Cx16Target))
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word") assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
} }
@Test @Test
fun testFreeSpaces() { fun testFreeSpaces() {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, Cx16Target)) val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, true, Cx16Target))
assertEquals(88, zp1.available()) assertEquals(88, zp1.available())
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, Cx16Target)) val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, true, Cx16Target))
assertEquals(175, zp3.available()) assertEquals(175, zp3.available())
val zp4 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target)) val zp4 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, true, Cx16Target))
assertEquals(216, zp4.available()) assertEquals(216, zp4.available())
} }
@Test @Test
fun testReservedSpace() { fun testReservedSpace() {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target)) val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, true, Cx16Target))
assertEquals(216, zp1.available()) assertEquals(216, zp1.available())
assertTrue(0x22 in zp1.free) assertTrue(0x22 in zp1.free)
assertTrue(0x80 in zp1.free) assertTrue(0x80 in zp1.free)

View File

@ -7,7 +7,6 @@ import prog8.ast.base.VarDeclType
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor import prog8.ast.walk.IAstVisitor
import java.util.*
class AstToSourceCode(val output: (text: String) -> Unit, val program: Program): IAstVisitor { class AstToSourceCode(val output: (text: String) -> Unit, val program: Program): IAstVisitor {

View File

@ -256,7 +256,7 @@ class Program(val name: String,
get() = mainModule.loadAddress get() = mainModule.loadAddress
var actualLoadAddress: Int = 0 var actualLoadAddress: Int = 0
private val internedStrings = mutableMapOf<Pair<String, Boolean>, List<String>>() private val internedStringsUnique = mutableMapOf<Pair<String, Boolean>, List<String>>()
val internedStringsModuleName = "prog8_interned_strings" val internedStringsModuleName = "prog8_interned_strings"
init { init {
@ -280,22 +280,40 @@ class Program(val name: String,
return mainBlocks[0].subScope("start") as Subroutine return mainBlocks[0].subScope("start") as Subroutine
} }
fun internString(string: StringLiteralValue): List<String> { fun internString(string: StringLiteralValue, dedup: Boolean): List<String> {
val key = Pair(string.value, string.altEncoding)
val existing = internedStrings[key]
if(existing!=null)
return existing
val decl = VarDecl(VarDeclType.VAR, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, "string_${internedStrings.size}", string, fun getScopedName(string: StringLiteralValue): List<String> {
isArray = false, autogeneratedDontRemove = true, position = string.position) val internedStringsBlock = modules
val internedStringsBlock = modules.first { it.name==internedStringsModuleName }.statements.first { it is Block && it.name == internedStringsModuleName} .first { it.name == internedStringsModuleName }.statements
(internedStringsBlock as Block).statements.add(decl) .first { it is Block && it.name == internedStringsModuleName } as Block
decl.linkParents(internedStringsBlock) val varName = "string_${internedStringsBlock.statements.size}"
val scopedName = listOf(internedStringsModuleName, decl.name) val decl = VarDecl(
internedStrings[key] = scopedName VarDeclType.VAR, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, varName, string,
return scopedName isArray = false, autogeneratedDontRemove = true, position = string.position
)
internedStringsBlock.statements.add(decl)
decl.linkParents(internedStringsBlock)
val scopedName = listOf(internedStringsModuleName, decl.name)
return scopedName
}
val key = Pair(string.value, string.altEncoding)
if(dedup) {
val existing = internedStringsUnique[key]
if (existing != null)
return existing
val scopedName = getScopedName(string)
internedStringsUnique[key] = scopedName
return scopedName
} else {
// don't deduplicate string literals
return getScopedName(string)
}
} }
fun allBlocks(): List<Block> = modules.flatMap { it.statements.filterIsInstance<Block>() } fun allBlocks(): List<Block> = modules.flatMap { it.statements.filterIsInstance<Block>() }
override val position: Position = Position.DUMMY override val position: Position = Position.DUMMY

View File

@ -13,7 +13,6 @@ import prog8.parser.prog8Parser
import java.io.CharConversionException import java.io.CharConversionException
import java.io.File import java.io.File
import java.nio.file.Path import java.nio.file.Path
import java.util.*
/***************** Antlr Extension methods to create AST ****************/ /***************** Antlr Extension methods to create AST ****************/

View File

@ -6,7 +6,7 @@ import prog8.ast.base.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor import prog8.ast.walk.IAstVisitor
import java.util.Objects import java.util.*
import kotlin.math.abs import kotlin.math.abs

View File

@ -5,6 +5,7 @@ TODO
- possible idea: option to mark vardecls 'shared' to indicate they should not be optimized away because they're shared with assembly code? - possible idea: option to mark vardecls 'shared' to indicate they should not be optimized away because they're shared with assembly code?
However: who even needs variables declared in prog8 code that are only used by assembly??? However: who even needs variables declared in prog8 code that are only used by assembly???
- github issue: make strings no longer immutable? Deduplication selectable via command line switch? - github issue: make strings no longer immutable? Deduplication selectable via command line switch?
IMPROVE DOCUMENTATION ABOUT STRINGS AND DEDUP.
- test all examples before release of the new version - test all examples before release of the new version

View File

@ -1,27 +1,24 @@
%import textio ; txt.* %import textio ; txt.*
main { %zeropage basicsafe
sub start() { main {
; ATTENTION: uncomment only one problematic line at a time! sub start() {
txt.print("a")
; Normal string literals, i.e. PETSCII encoding txt.print("a")
; --------------------------------------------- txt.print("bb")
txt.print("\"") ; fine txt.print("bb")
txt.print("\n") ; fine txt.print("\n")
txt.print("\r") ; fine txt.print("\n\n")
txt.print("\\") ; yields CharConversionException ; txt.print("hello\n")
txt.print("xyz\\") ; yields prog8.compiler.AssemblyError ; txt.print("hello\n")
; txt.print("hello\n")
; @-strings, i.e. translated into ; txt.print("hello\n")
; the alternate character encoding (Screencodes/pokes) ; txt.print("hello22\n")
; ---------------------------------------------------- ; txt.print("hello22\n")
txt.print(@"\"") ; fine ; txt.print("hello22\n")
txt.print(@"\n") ; yields CharConversionException ; txt.print("hello22\n")
txt.print(@"xyz\n") ; yields prog8.compiler.AssemblyError ; txt.print("hello666\n")
txt.print(@"\r") ; yields CharConversionException ; txt.print("hello666\n")
txt.print(@"xyz\r") ; yields prog8.compiler.AssemblyError ; txt.print("hello666\n")
txt.print(@"\\") ; yields CharConversionException ; txt.print("hello666\n")
txt.print(@"\\") ; yields prog8.compiler.AssemblyError }
}
; there may be more...
}
}

View File

@ -29,7 +29,7 @@ class RequestParser : Take {
val form = RqFormBase(request) val form = RqFormBase(request)
val names = form.names() val names = form.names()
val a = form.param("a").single() val a = form.param("a").single()
val compilationResult = compileProgram(Path.of(a), true, true, true, "c64", emptyList<String>(), Path.of(".")) val compilationResult = compileProgram(Path.of(a), true, true, true, false, "c64", emptyList<String>(), Path.of("."))
return RsJson(Jsonding()) return RsJson(Jsonding())
} }
} }