mirror of
https://github.com/irmen/prog8.git
synced 2024-05-29 01:41:32 +00:00
507 lines
21 KiB
Kotlin
507 lines
21 KiB
Kotlin
package prog8.compiler
|
|
|
|
import com.github.michaelbull.result.onFailure
|
|
import prog8.ast.IBuiltinFunctions
|
|
import prog8.ast.Program
|
|
import prog8.ast.base.AstException
|
|
import prog8.ast.expressions.Expression
|
|
import prog8.ast.expressions.NumericLiteral
|
|
import prog8.ast.printProgram
|
|
import prog8.ast.printSymbols
|
|
import prog8.ast.statements.Directive
|
|
import prog8.code.SymbolTableMaker
|
|
import prog8.code.ast.PtProgram
|
|
import prog8.code.ast.printAst
|
|
import prog8.code.core.*
|
|
import prog8.code.optimize.optimizeIntermediateAst
|
|
import prog8.code.target.*
|
|
import prog8.codegen.vm.VmCodeGen
|
|
import prog8.compiler.astprocessing.*
|
|
import prog8.optimizer.*
|
|
import prog8.parser.ParseError
|
|
import java.nio.file.Path
|
|
import kotlin.io.path.Path
|
|
import kotlin.io.path.nameWithoutExtension
|
|
import kotlin.math.round
|
|
import kotlin.system.exitProcess
|
|
import kotlin.system.measureTimeMillis
|
|
|
|
|
|
class CompilationResult(val compilerAst: Program, // deprecated, use codegenAst instead
|
|
val codegenAst: PtProgram?,
|
|
val compilationOptions: CompilationOptions,
|
|
val importedFiles: List<Path>)
|
|
|
|
class CompilerArguments(val filepath: Path,
|
|
val optimize: Boolean,
|
|
val writeAssembly: Boolean,
|
|
val warnSymbolShadowing: Boolean,
|
|
val quietAssembler: Boolean,
|
|
val asmListfile: Boolean,
|
|
val includeSourcelines: Boolean,
|
|
val experimentalCodegen: Boolean,
|
|
val dumpVariables: Boolean,
|
|
val dumpSymbols: Boolean,
|
|
val varsHighBank: Int?,
|
|
val varsGolden: Boolean,
|
|
val slabsHighBank: Int?,
|
|
val slabsGolden: Boolean,
|
|
val compilationTarget: String,
|
|
val splitWordArrays: Boolean,
|
|
val breakpointCpuInstruction: String?,
|
|
val printAst1: Boolean,
|
|
val printAst2: Boolean,
|
|
val strictBool: Boolean,
|
|
val symbolDefs: Map<String, String>,
|
|
val sourceDirs: List<String> = emptyList(),
|
|
val outputDir: Path = Path(""),
|
|
val errors: IErrorReporter = ErrorReporter())
|
|
|
|
|
|
fun compileProgram(args: CompilerArguments): CompilationResult? {
|
|
|
|
val compTarget =
|
|
when(args.compilationTarget) {
|
|
C64Target.NAME -> C64Target()
|
|
C128Target.NAME -> C128Target()
|
|
Cx16Target.NAME -> Cx16Target()
|
|
PETTarget.NAME -> PETTarget()
|
|
AtariTarget.NAME -> AtariTarget()
|
|
VMTarget.NAME -> VMTarget()
|
|
else -> throw IllegalArgumentException("invalid compilation target")
|
|
}
|
|
|
|
var compilationOptions: CompilationOptions
|
|
var ast: PtProgram? = null
|
|
var resultingProgram: Program? = null
|
|
var importedFiles: List<Path>
|
|
|
|
try {
|
|
val totalTime = measureTimeMillis {
|
|
val (program, options, imported) = parseMainModule(args.filepath, args.errors, compTarget, args.sourceDirs)
|
|
compilationOptions = options
|
|
|
|
with(compilationOptions) {
|
|
warnSymbolShadowing = args.warnSymbolShadowing
|
|
optimize = args.optimize
|
|
asmQuiet = args.quietAssembler
|
|
asmListfile = args.asmListfile
|
|
includeSourcelines = args.includeSourcelines
|
|
experimentalCodegen = args.experimentalCodegen
|
|
dumpVariables = args.dumpVariables
|
|
dumpSymbols = args.dumpSymbols
|
|
breakpointCpuInstruction = args.breakpointCpuInstruction
|
|
varsHighBank = args.varsHighBank
|
|
varsGolden = args.varsGolden
|
|
slabsHighBank = args.slabsHighBank
|
|
slabsGolden = args.slabsGolden
|
|
splitWordArrays = args.splitWordArrays
|
|
outputDir = args.outputDir.normalize()
|
|
symbolDefs = args.symbolDefs
|
|
strictBool = args.strictBool
|
|
}
|
|
resultingProgram = program
|
|
importedFiles = imported
|
|
|
|
processAst(program, args.errors, compilationOptions)
|
|
// println("*********** COMPILER AST RIGHT BEFORE OPTIMIZING *************")
|
|
// printProgram(program)
|
|
|
|
if (compilationOptions.optimize) {
|
|
optimizeAst(
|
|
program,
|
|
compilationOptions,
|
|
args.errors,
|
|
BuiltinFunctionsFacade(BuiltinFunctions),
|
|
)
|
|
}
|
|
|
|
postprocessAst(program, args.errors, compilationOptions)
|
|
|
|
// println("*********** COMPILER AST BEFORE ASSEMBLYGEN *************")
|
|
// printProgram(program)
|
|
|
|
determineProgramLoadAddress(program, compilationOptions, args.errors)
|
|
args.errors.report()
|
|
|
|
if (args.writeAssembly) {
|
|
|
|
// re-initialize memory areas with final compilationOptions
|
|
compilationOptions.compTarget.machine.initializeMemoryAreas(compilationOptions)
|
|
program.processAstBeforeAsmGeneration(compilationOptions, args.errors)
|
|
args.errors.report()
|
|
|
|
if(args.printAst1) {
|
|
println("\n*********** COMPILER AST *************")
|
|
printProgram(program)
|
|
println("*********** COMPILER AST END *************\n")
|
|
}
|
|
|
|
val intermediateAst = IntermediateAstMaker(program, args.errors).transform()
|
|
val stMaker = SymbolTableMaker(intermediateAst, compilationOptions)
|
|
val symbolTable = stMaker.make()
|
|
if(compilationOptions.optimize) {
|
|
optimizeIntermediateAst(intermediateAst, compilationOptions, symbolTable, args.errors)
|
|
args.errors.report()
|
|
}
|
|
|
|
if(args.printAst2) {
|
|
println("\n*********** INTERMEDIATE AST *************")
|
|
printAst(intermediateAst, true, ::println)
|
|
println("*********** INTERMEDIATE AST END *************\n")
|
|
}
|
|
|
|
if(!createAssemblyAndAssemble(intermediateAst, args.errors, compilationOptions)) {
|
|
System.err.println("Error in codegeneration or assembler")
|
|
return null
|
|
}
|
|
ast = intermediateAst
|
|
} else {
|
|
if(args.printAst1) {
|
|
println("\n*********** COMPILER AST *************")
|
|
printProgram(program)
|
|
println("*********** COMPILER AST END *************\n")
|
|
}
|
|
if(args.printAst2) {
|
|
System.err.println("There is no intermediate Ast available if assembly generation is disabled.")
|
|
}
|
|
}
|
|
}
|
|
|
|
System.out.flush()
|
|
System.err.flush()
|
|
val seconds = totalTime/1000.0
|
|
println("\nTotal compilation+assemble time: ${round(seconds*100.0)/100.0} sec.")
|
|
return CompilationResult(resultingProgram!!, ast, compilationOptions, importedFiles)
|
|
} catch (px: ParseError) {
|
|
System.out.flush()
|
|
System.err.print("\n\u001b[91m") // bright red
|
|
System.err.println("${px.position.toClickableStr()} parse error: ${px.message}".trim())
|
|
System.err.print("\u001b[0m") // reset
|
|
} catch (ac: ErrorsReportedException) {
|
|
if(args.printAst1 && resultingProgram!=null) {
|
|
println("\n*********** COMPILER AST *************")
|
|
printProgram(resultingProgram!!)
|
|
println("*********** COMPILER AST END *************\n")
|
|
}
|
|
if (args.printAst2) {
|
|
if(ast==null)
|
|
println("There is no intermediate AST available because of compilation errors.")
|
|
else {
|
|
println("\n*********** INTERMEDIATE AST *************")
|
|
printAst(ast!!, true, ::println)
|
|
println("*********** INTERMEDIATE AST END *************\n")
|
|
}
|
|
}
|
|
if(!ac.message.isNullOrEmpty()) {
|
|
System.out.flush()
|
|
System.err.print("\n\u001b[91m") // bright red
|
|
System.err.println(ac.message)
|
|
System.err.print("\u001b[0m") // reset
|
|
}
|
|
} catch (nsf: NoSuchFileException) {
|
|
System.out.flush()
|
|
System.err.print("\n\u001b[91m") // bright red
|
|
System.err.println("File not found: ${nsf.message}")
|
|
System.err.print("\u001b[0m") // reset
|
|
} catch (ax: AstException) {
|
|
System.out.flush()
|
|
System.err.print("\n\u001b[91m") // bright red
|
|
System.err.println(ax.toString())
|
|
System.err.print("\u001b[0m") // reset
|
|
} catch (x: Exception) {
|
|
print("\n\u001b[91m") // bright red
|
|
println("\n* internal error *")
|
|
print("\u001b[0m") // reset
|
|
System.out.flush()
|
|
throw x
|
|
} catch (x: NotImplementedError) {
|
|
print("\n\u001b[91m") // bright red
|
|
println("\n* internal error: missing feature/code *")
|
|
print("\u001b[0m") // reset
|
|
System.out.flush()
|
|
throw x
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
|
|
internal fun determineProgramLoadAddress(program: Program, options: CompilationOptions, errors: IErrorReporter) {
|
|
val specifiedAddress = program.toplevelModule.loadAddress
|
|
var loadAddress: UInt? = null
|
|
if(specifiedAddress!=null) {
|
|
loadAddress = specifiedAddress.first
|
|
}
|
|
else {
|
|
when(options.output) {
|
|
OutputType.RAW -> { /* no predefined load address */ }
|
|
OutputType.PRG -> {
|
|
if(options.launcher==CbmPrgLauncherType.BASIC) {
|
|
loadAddress = options.compTarget.machine.PROGRAM_LOAD_ADDRESS
|
|
}
|
|
}
|
|
OutputType.XEX -> {
|
|
if(options.launcher!=CbmPrgLauncherType.NONE)
|
|
throw AssemblyError("atari xex output can't contain BASIC launcher")
|
|
loadAddress = options.compTarget.machine.PROGRAM_LOAD_ADDRESS
|
|
}
|
|
}
|
|
}
|
|
|
|
if(options.output==OutputType.PRG && options.launcher==CbmPrgLauncherType.BASIC) {
|
|
val expected = options.compTarget.machine.PROGRAM_LOAD_ADDRESS
|
|
if(loadAddress!=expected) {
|
|
errors.err("BASIC output must have load address ${expected.toHex()}", specifiedAddress?.second ?: program.toplevelModule.position)
|
|
}
|
|
}
|
|
|
|
if(loadAddress==null) {
|
|
errors.err("load address must be specified with these output options", program.toplevelModule.position)
|
|
return
|
|
}
|
|
|
|
options.loadAddress = loadAddress
|
|
}
|
|
|
|
|
|
private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuiltinFunctions {
|
|
lateinit var program: Program
|
|
|
|
override val names = functions.keys
|
|
override val purefunctionNames = functions.filter { it.value.pure }.map { it.key }.toSet()
|
|
|
|
override fun constValue(funcName: String, args: List<Expression>, position: Position): NumericLiteral? {
|
|
val func = BuiltinFunctions[funcName]
|
|
if(func!=null) {
|
|
val exprfunc = constEvaluatorsForBuiltinFuncs[funcName]
|
|
if(exprfunc!=null) {
|
|
return try {
|
|
exprfunc(args, position, program)
|
|
} catch(x: NotConstArgumentException) {
|
|
// const-evaluating the builtin function call failed.
|
|
null
|
|
} catch(x: CannotEvaluateException) {
|
|
// const-evaluating the builtin function call failed.
|
|
null
|
|
}
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
override fun returnType(funcName: String) = builtinFunctionReturnType(funcName)
|
|
}
|
|
|
|
fun parseMainModule(filepath: Path,
|
|
errors: IErrorReporter,
|
|
compTarget: ICompilationTarget,
|
|
sourceDirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
|
|
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
|
|
val program = Program(filepath.nameWithoutExtension, bf, compTarget, compTarget)
|
|
bf.program = program
|
|
|
|
val importer = ModuleImporter(program, compTarget.name, errors, sourceDirs)
|
|
val importedModuleResult = importer.importMainModule(filepath)
|
|
importedModuleResult.onFailure { throw it }
|
|
errors.report()
|
|
|
|
val importedFiles = program.modules.map { it.source }
|
|
.filter { it.isFromFilesystem }
|
|
.map { Path(it.origin) }
|
|
val compilerOptions = determineCompilationOptions(program, compTarget)
|
|
// depending on the machine and compiler options we may have to include some libraries
|
|
for(lib in compTarget.machine.importLibs(compilerOptions, compTarget.name))
|
|
importer.importImplicitLibraryModule(lib)
|
|
|
|
if(compilerOptions.compTarget.name!=VMTarget.NAME && !compilerOptions.experimentalCodegen) {
|
|
importer.importImplicitLibraryModule("math")
|
|
}
|
|
importer.importImplicitLibraryModule("prog8_lib")
|
|
if(program.allBlocks.any { it.options().any { option->option=="verafxmuls" } }) {
|
|
if(compTarget.name==Cx16Target.NAME)
|
|
importer.importImplicitLibraryModule("verafx")
|
|
}
|
|
|
|
if (compilerOptions.launcher == CbmPrgLauncherType.BASIC && compilerOptions.output != OutputType.PRG)
|
|
errors.err("BASIC launcher requires output type PRG", program.toplevelModule.position)
|
|
if(compilerOptions.launcher == CbmPrgLauncherType.BASIC && compTarget.name== AtariTarget.NAME)
|
|
errors.err("atari target cannot use CBM BASIC launcher, use NONE", program.toplevelModule.position)
|
|
|
|
errors.report()
|
|
|
|
return Triple(program, compilerOptions, importedFiles)
|
|
}
|
|
|
|
fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions {
|
|
val toplevelModule = program.toplevelModule
|
|
val outputDirective = (toplevelModule.statements.singleOrNull { it is Directive && it.directive == "%output" } as? Directive)
|
|
val launcherDirective = (toplevelModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" } as? Directive)
|
|
val outputTypeStr = outputDirective?.args?.single()?.name?.uppercase()
|
|
val launcherTypeStr = launcherDirective?.args?.single()?.name?.uppercase()
|
|
val zpoption: String? = (toplevelModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
|
|
as? Directive)?.args?.single()?.name?.uppercase()
|
|
val allOptions = program.modules.flatMap { it.options() }.toSet()
|
|
val floatsEnabled = "enable_floats" in allOptions
|
|
val noSysInit = "no_sysinit" in allOptions
|
|
val zpType: ZeropageType =
|
|
if (zpoption == null)
|
|
if (floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
|
|
else
|
|
try {
|
|
ZeropageType.valueOf(zpoption)
|
|
} catch (x: IllegalArgumentException) {
|
|
ZeropageType.KERNALSAFE
|
|
// error will be printed by the astchecker
|
|
}
|
|
|
|
val zpReserved = toplevelModule.statements
|
|
.asSequence()
|
|
.filter { it is Directive && it.directive == "%zpreserved" }
|
|
.map { (it as Directive).args }
|
|
.filter { it.size==2 && it[0].int!=null && it[1].int!=null }
|
|
.map { it[0].int!!..it[1].int!! }
|
|
.toList()
|
|
|
|
val zpAllowed = toplevelModule.statements
|
|
.asSequence()
|
|
.filter { it is Directive && it.directive == "%zpallowed" }
|
|
.map { (it as Directive).args }
|
|
.filter { it.size==2 && it[0].int!=null && it[1].int!=null }
|
|
.map { it[0].int!!..it[1].int!! }
|
|
.toList()
|
|
|
|
val outputType = if (outputTypeStr == null) {
|
|
if(compTarget is AtariTarget)
|
|
OutputType.XEX
|
|
else
|
|
OutputType.PRG
|
|
} else {
|
|
try {
|
|
OutputType.valueOf(outputTypeStr)
|
|
} catch (x: IllegalArgumentException) {
|
|
// set default value; actual check and error handling of invalid option is handled in the AstChecker later
|
|
OutputType.PRG
|
|
}
|
|
}
|
|
val launcherType = if (launcherTypeStr == null) {
|
|
when(compTarget) {
|
|
is AtariTarget -> CbmPrgLauncherType.NONE
|
|
else -> CbmPrgLauncherType.BASIC
|
|
}
|
|
} else {
|
|
try {
|
|
CbmPrgLauncherType.valueOf(launcherTypeStr)
|
|
} catch (x: IllegalArgumentException) {
|
|
// set default value; actual check and error handling of invalid option is handled in the AstChecker later
|
|
CbmPrgLauncherType.BASIC
|
|
}
|
|
}
|
|
|
|
return CompilationOptions(
|
|
outputType, launcherType,
|
|
zpType, zpReserved, zpAllowed, floatsEnabled, noSysInit,
|
|
compTarget, 0u
|
|
)
|
|
}
|
|
|
|
private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
|
program.preprocessAst(errors, compilerOptions)
|
|
|
|
if(compilerOptions.dumpSymbols) {
|
|
printSymbols(program)
|
|
exitProcess(0)
|
|
}
|
|
|
|
program.checkIdentifiers(errors, compilerOptions)
|
|
errors.report()
|
|
program.charLiteralsToUByteLiterals(compilerOptions.compTarget, errors)
|
|
errors.report()
|
|
program.constantFold(errors, compilerOptions)
|
|
errors.report()
|
|
program.reorderStatements(errors)
|
|
errors.report()
|
|
program.desugaring(errors, compilerOptions)
|
|
errors.report()
|
|
program.changeNotExpressionAndIfComparisonExpr(errors, compilerOptions.compTarget)
|
|
errors.report()
|
|
program.addTypecasts(errors, compilerOptions)
|
|
errors.report()
|
|
program.variousCleanups(errors, compilerOptions)
|
|
errors.report()
|
|
program.checkValid(errors, compilerOptions)
|
|
errors.report()
|
|
program.checkIdentifiers(errors, compilerOptions)
|
|
errors.report()
|
|
}
|
|
|
|
private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, errors: IErrorReporter, functions: IBuiltinFunctions) {
|
|
fun removeUnusedCode(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
|
val remover = UnusedCodeRemover(program, errors, compilerOptions.compTarget)
|
|
remover.visit(program)
|
|
while (errors.noErrors() && remover.applyModifications() > 0) {
|
|
remover.visit(program)
|
|
}
|
|
}
|
|
|
|
removeUnusedCode(program, errors,compilerOptions)
|
|
while (true) {
|
|
// keep optimizing expressions and statements until no more steps remain
|
|
val optsDone1 = program.simplifyExpressions(errors, compilerOptions)
|
|
val optsDone2 = program.optimizeStatements(errors, functions, compilerOptions)
|
|
val optsDone3 = program.inlineSubroutines(compilerOptions)
|
|
program.constantFold(errors, compilerOptions) // because simplified statements and expressions can result in more constants that can be folded away
|
|
if(!errors.noErrors()) {
|
|
errors.report()
|
|
break
|
|
}
|
|
if (optsDone1 + optsDone2 + optsDone3 == 0)
|
|
break
|
|
}
|
|
removeUnusedCode(program, errors, compilerOptions)
|
|
if(errors.noErrors())
|
|
program.constantFold(errors, compilerOptions) // because simplified statements and expressions can result in more constants that can be folded away
|
|
errors.report()
|
|
}
|
|
|
|
private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
|
program.desugaring(errors, compilerOptions)
|
|
program.addTypecasts(errors, compilerOptions)
|
|
errors.report()
|
|
program.variousCleanups(errors, compilerOptions)
|
|
val callGraph = CallGraph(program)
|
|
callGraph.checkRecursiveCalls(errors)
|
|
program.verifyFunctionArgTypes(errors, compilerOptions)
|
|
errors.report()
|
|
program.moveMainBlockAsFirst()
|
|
program.checkValid(errors, compilerOptions) // check if final tree is still valid
|
|
errors.report()
|
|
}
|
|
|
|
private fun createAssemblyAndAssemble(program: PtProgram,
|
|
errors: IErrorReporter,
|
|
compilerOptions: CompilationOptions
|
|
): Boolean {
|
|
|
|
val asmgen = if(compilerOptions.experimentalCodegen)
|
|
prog8.codegen.experimental.ExperiCodeGen()
|
|
else if (compilerOptions.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02))
|
|
prog8.codegen.cpu6502.AsmGen6502(prefixSymbols = true)
|
|
else if (compilerOptions.compTarget.name == VMTarget.NAME)
|
|
VmCodeGen()
|
|
else
|
|
throw NotImplementedError("no code generator for cpu ${compilerOptions.compTarget.machine.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()
|
|
|
|
return if(assembly!=null && errors.noErrors()) {
|
|
assembly.assemble(compilerOptions, errors)
|
|
} else {
|
|
false
|
|
}
|
|
}
|