prog8/compiler/src/prog8/compiler/Compiler.kt

415 lines
17 KiB
Kotlin
Raw Normal View History

2018-09-15 14:21:05 +00:00
package prog8.compiler
2018-08-16 16:32:05 +00:00
import com.github.michaelbull.result.onFailure
2021-10-28 23:06:01 +00:00
import prog8.ast.AstToSourceTextConverter
import prog8.ast.IBuiltinFunctions
2022-02-10 22:20:19 +00:00
import prog8.ast.IStatementContainer
import prog8.ast.Program
import prog8.ast.base.AstException
import prog8.ast.expressions.Expression
2022-02-10 23:21:40 +00:00
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.Directive
2022-02-10 22:20:19 +00:00
import prog8.ast.statements.VarDecl
import prog8.ast.walk.IAstVisitor
2022-03-10 22:08:41 +00:00
import prog8.code.SymbolTable
import prog8.code.core.*
2022-03-11 19:35:25 +00:00
import prog8.code.target.AtariTarget
import prog8.code.target.C128Target
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.compiler.astprocessing.*
import prog8.optimizer.*
import prog8.parser.ParseError
import java.nio.file.Path
2021-10-10 22:22:04 +00:00
import kotlin.io.path.Path
import kotlin.io.path.nameWithoutExtension
2022-02-06 21:56:17 +00:00
import kotlin.math.round
import kotlin.system.measureTimeMillis
2018-08-16 16:32:05 +00:00
2022-03-07 20:41:12 +00:00
class CompilationResult(val program: Program,
val compilationOptions: CompilationOptions,
val importedFiles: List<Path>)
2021-11-30 00:40:21 +00:00
class CompilerArguments(val filepath: Path,
val optimize: Boolean,
val optimizeFloatExpressions: Boolean,
2022-01-01 15:35:36 +00:00
val dontReinitGlobals: Boolean,
2021-11-30 00:40:21 +00:00
val writeAssembly: Boolean,
val slowCodegenWarnings: Boolean,
val quietAssembler: Boolean,
val asmListfile: Boolean,
val experimentalCodegen: Boolean,
2021-11-30 00:40:21 +00:00
val compilationTarget: String,
val sourceDirs: List<String> = emptyList(),
val outputDir: Path = Path(""),
val errors: IErrorReporter = ErrorReporter())
2021-11-30 00:40:21 +00:00
2022-03-07 20:41:12 +00:00
fun compileProgram(args: CompilerArguments): CompilationResult? {
2021-10-29 22:25:34 +00:00
lateinit var program: Program
lateinit var importedFiles: List<Path>
2021-11-30 00:40:21 +00:00
val optimizeFloatExpr = if(args.optimize) args.optimizeFloatExpressions else false
val compTarget =
2021-11-30 00:40:21 +00:00
when(args.compilationTarget) {
C64Target.NAME -> C64Target()
C128Target.NAME -> C128Target()
Cx16Target.NAME -> Cx16Target()
AtariTarget.NAME -> AtariTarget()
else -> throw IllegalArgumentException("invalid compilation target")
}
var compilationOptions: CompilationOptions
try {
val totalTime = measureTimeMillis {
// import main module and everything it needs
val (programresult, options, imported) = parseImports(args.filepath, args.errors, compTarget, args.sourceDirs)
compilationOptions = options
2022-02-05 13:02:24 +00:00
print("Parsed ${args.filepath}")
ModuleImporter.ansiEraseRestOfLine(true)
with(compilationOptions) {
slowCodegenWarnings = args.slowCodegenWarnings
optimize = args.optimize
optimizeFloatExpressions = optimizeFloatExpr
2022-01-01 15:35:36 +00:00
dontReinitGlobals = args.dontReinitGlobals
asmQuiet = args.quietAssembler
asmListfile = args.asmListfile
experimentalCodegen = args.experimentalCodegen
2022-02-06 16:07:03 +00:00
outputDir = args.outputDir.normalize()
}
2021-10-29 22:25:34 +00:00
program = programresult
importedFiles = imported
2021-11-30 00:40:21 +00:00
processAst(program, args.errors, compilationOptions)
if (compilationOptions.optimize) {
// println("*********** AST RIGHT BEFORE OPTIMIZING *************")
// printProgram(program)
optimizeAst(
2021-10-29 22:25:34 +00:00
program,
compilationOptions,
2021-11-30 00:40:21 +00:00
args.errors,
BuiltinFunctionsFacade(BuiltinFunctions),
compTarget
)
}
2021-11-30 00:40:21 +00:00
postprocessAst(program, args.errors, compilationOptions)
// println("*********** AST BEFORE ASSEMBLYGEN *************")
2021-11-20 21:43:08 +00:00
// printProgram(program)
2021-11-30 00:40:21 +00:00
if (args.writeAssembly) {
if(!createAssemblyAndAssemble(program, args.errors, compilationOptions)) {
System.err.println("Error in codegeneration or assembler")
return null
}
}
}
System.out.flush()
System.err.flush()
2022-02-06 21:56:17 +00:00
val seconds = totalTime/1000.0
println("\nTotal compilation+assemble time: ${round(seconds*100.0)/100.0} sec.")
2022-03-07 20:41:12 +00:00
return CompilationResult(program, compilationOptions, importedFiles)
} catch (px: ParseError) {
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
2022-03-10 22:08:41 +00:00
} catch (ac: ErrorsReportedException) {
2021-10-30 15:05:23 +00:00
if(!ac.message.isNullOrEmpty()) {
System.err.print("\n\u001b[91m") // bright red
2021-10-30 15:05:23 +00:00
System.err.println(ac.message)
System.err.print("\u001b[0m") // reset
}
} catch (nsf: NoSuchFileException) {
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.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
}
2022-03-07 20:41:12 +00:00
return null
}
private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuiltinFunctions {
lateinit var program: Program
override val names = functions.keys
2021-02-09 00:47:05 +00:00
override val purefunctionNames = functions.filter { it.value.pure }.map { it.key }.toSet()
2022-02-10 23:21:40 +00:00
override fun constValue(name: String, args: List<Expression>, position: Position): NumericLiteral? {
val func = BuiltinFunctions[name]
if(func!=null) {
val exprfunc = func.constExpressionFunc
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(name: String, args: MutableList<Expression>) =
builtinFunctionReturnType(name, args, program)
}
fun parseImports(filepath: Path,
errors: IErrorReporter,
compTarget: ICompilationTarget,
sourceDirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
println("Compilation target: ${compTarget.name}")
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
2021-10-29 22:25:34 +00:00
val program = Program(filepath.nameWithoutExtension, bf, compTarget, compTarget)
bf.program = program
2021-10-29 22:25:34 +00:00
val importer = ModuleImporter(program, compTarget.name, errors, sourceDirs)
val importedModuleResult = importer.importModule(filepath)
importedModuleResult.onFailure { throw it }
2021-02-20 16:19:54 +00:00
errors.report()
2021-10-29 22:25:34 +00:00
val importedFiles = program.modules.map { it.source }
.filter { it.isFromFilesystem }
.map { Path(it.origin) }
2021-10-29 22:25:34 +00:00
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.importLibraryModule(lib)
// always import prog8_lib and math
importer.importLibraryModule("math")
importer.importLibraryModule("prog8_lib")
2021-10-30 15:05:23 +00:00
if (compilerOptions.launcher == CbmPrgLauncherType.BASIC && compilerOptions.output != OutputType.PRG)
2021-10-30 15:05:23 +00:00
errors.err("BASIC launcher requires output type PRG", program.toplevelModule.position)
2022-03-11 19:35:25 +00:00
if(compilerOptions.launcher == CbmPrgLauncherType.BASIC && compTarget.name== AtariTarget.NAME)
errors.err("atari target cannot use CBM BASIC launcher, use NONE", program.toplevelModule.position)
2021-02-20 16:19:54 +00:00
errors.report()
2021-10-30 15:05:23 +00:00
2021-10-29 22:25:34 +00:00
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.statements }.filter { it is Directive && it.directive == "%option" }
.flatMap { (it as Directive).args }.toSet()
val floatsEnabled = allOptions.any { it.name == "enable_floats" }
val noSysInit = allOptions.any { it.name == "no_sysinit" }
var 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
}
if (zpType == ZeropageType.FLOATSAFE && compTarget.name == Cx16Target.NAME) {
2021-04-05 22:15:43 +00:00
System.err.println("Warning: zp option floatsafe changed to basicsafe for cx16 target")
zpType = ZeropageType.BASICSAFE
}
val zpReserved = toplevelModule.statements
.asSequence()
.filter { it is Directive && it.directive == "%zpreserved" }
.map { (it as Directive).args }
2022-01-15 12:03:55 +00:00
.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, floatsEnabled, noSysInit,
compTarget
)
}
2021-10-29 22:25:34 +00:00
private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
println("Analyzing code...")
program.preprocessAst(errors, compilerOptions.compTarget)
2022-01-21 22:33:54 +00:00
program.checkIdentifiers(errors, compilerOptions)
2021-02-20 16:19:54 +00:00
errors.report()
program.charLiteralsToUByteLiterals(compilerOptions.compTarget, errors)
errors.report()
2021-10-29 22:25:34 +00:00
program.constantFold(errors, compilerOptions.compTarget)
2021-02-20 16:19:54 +00:00
errors.report()
program.desugaring(errors)
errors.report()
program.reorderStatements(errors, compilerOptions)
2021-02-20 16:19:54 +00:00
errors.report()
program.addTypecasts(errors, compilerOptions)
2021-02-20 16:19:54 +00:00
errors.report()
2022-01-21 22:33:54 +00:00
program.variousCleanups(errors, compilerOptions)
errors.report()
program.checkValid(errors, compilerOptions)
2021-02-20 16:19:54 +00:00
errors.report()
2022-01-21 22:33:54 +00:00
program.checkIdentifiers(errors, compilerOptions)
2021-02-20 16:19:54 +00:00
errors.report()
}
private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget) {
println("Optimizing...")
2021-10-29 22:25:34 +00:00
val remover = UnusedCodeRemover(program, errors, compTarget)
remover.visit(program)
remover.applyModifications()
while (true) {
// keep optimizing expressions and statements until no more steps remain
2022-01-06 21:45:36 +00:00
val optsDone1 = program.simplifyExpressions(errors)
2022-03-11 18:54:30 +00:00
val optsDone2 = program.splitBinaryExpressions(compilerOptions)
2021-10-29 22:25:34 +00:00
val optsDone3 = program.optimizeStatements(errors, functions, compTarget)
program.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away
2021-02-20 16:19:54 +00:00
errors.report()
if (optsDone1 + optsDone2 + optsDone3 == 0)
break
}
2021-02-20 16:19:54 +00:00
errors.report()
}
2021-10-29 22:25:34 +00:00
private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
program.desugaring(errors)
program.addTypecasts(errors, compilerOptions)
2021-02-20 16:19:54 +00:00
errors.report()
2022-01-21 22:33:54 +00:00
program.variousCleanups(errors, compilerOptions)
2021-10-29 22:25:34 +00:00
val callGraph = CallGraph(program)
callGraph.checkRecursiveCalls(errors)
2022-02-27 15:27:02 +00:00
program.verifyFunctionArgTypes(errors)
2021-02-20 16:19:54 +00:00
errors.report()
2021-10-29 22:25:34 +00:00
program.moveMainAndStartToFirst()
program.checkValid(errors, compilerOptions) // check if final tree is still valid
errors.report()
}
private fun createAssemblyAndAssemble(program: Program,
errors: IErrorReporter,
compilerOptions: CompilationOptions
): Boolean {
2022-02-06 17:57:23 +00:00
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
program.processAstBeforeAsmGeneration(compilerOptions, errors)
2021-02-20 16:19:54 +00:00
errors.report()
2022-03-04 22:25:26 +00:00
val symbolTable = SymbolTableMaker().makeFrom(program)
2022-02-10 22:20:19 +00:00
// TODO make removing all VarDecls work, but this needs inferType to be able to get its information from somewhere else as the VarDecl nodes in the Ast,
// or don't use inferType at all anymore and "bake the type information" into the Ast somehow.
// Note: we don't actually *need* to remove the VarDecl nodes, but it is nice as a temporary measure
// to help clean up the code that still depends on them.
// removeAllVardeclsFromAst(program)
// println("*********** AST RIGHT BEFORE ASM GENERATION *************")
// printProgram(program)
val assembly = asmGeneratorFor(program, errors, symbolTable, compilerOptions).compileToAssembly()
errors.report()
2021-10-11 23:45:32 +00:00
return if(assembly!=null && errors.noErrors()) {
2022-03-11 18:54:30 +00:00
assembly.assemble(compilerOptions)
2021-10-11 23:45:32 +00:00
} else {
false
}
}
2022-02-10 22:20:19 +00:00
private fun removeAllVardeclsFromAst(program: Program) {
// remove all VarDecl nodes from the AST.
// code generation doesn't require them anymore, it operates only on the 'variables' collection.
class SearchAndRemove: IAstVisitor {
private val allVars = mutableListOf<VarDecl>()
init {
visit(program)
for (it in allVars) {
require((it.parent as IStatementContainer).statements.remove(it))
}
}
override fun visit(decl: VarDecl) {
allVars.add(decl)
}
}
SearchAndRemove()
}
fun printProgram(program: Program) {
println()
2021-10-29 22:25:34 +00:00
val printer = AstToSourceTextConverter(::print, program)
printer.visit(program)
println()
}
2022-03-04 22:25:26 +00:00
internal fun asmGeneratorFor(program: Program,
errors: IErrorReporter,
symbolTable: SymbolTable,
options: CompilationOptions): IAssemblyGenerator
{
if(options.experimentalCodegen) {
2022-03-10 20:28:35 +00:00
if (options.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02)) {
// TODO for now, only use the new Intermediary Ast for this experimental codegen:
2022-03-12 22:28:17 +00:00
val intermediateAst = IntermediateAstMaker(program, options).transform()
2022-03-11 20:22:16 +00:00
return prog8.codegen.experimental.AsmGen(intermediateAst, errors, symbolTable, options)
2022-03-10 20:28:35 +00:00
}
} else {
if (options.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02))
return prog8.codegen.cpu6502.AsmGen(program, errors, symbolTable, options)
}
throw NotImplementedError("no asm generator for cpu ${options.compTarget.machine.cpu}")
}