added 'inline' keyword to force inlining of trivial subroutines

This commit is contained in:
Irmen de Jong 2020-12-26 05:33:00 +01:00
parent c62ff16f8b
commit 170a0183f8
18 changed files with 89 additions and 57 deletions

View File

@ -188,11 +188,11 @@ graphics {
; @(addr) |= ormask[lsb(px) & 7]
; }
asmsub plot(uword plotx @XY, ubyte ploty @A) clobbers (A, X, Y) {
inline asmsub plot(uword plotx @XY, ubyte ploty @A) clobbers (A, X, Y) {
%asm {{
stx internal_plotx
sty internal_plotx+1
jmp internal_plot
stx graphics.internal_plotx
sty graphics.internal_plotx+1
jsr graphics.internal_plot
}}
}

View File

@ -250,21 +250,21 @@ output .text "0000", $00 ; 0-terminated output buffer (to make printing ea
}
asmsub str2ubyte(str string @ AY) clobbers(Y) -> ubyte @A {
inline asmsub str2ubyte(str string @ AY) clobbers(Y) -> ubyte @A {
; -- returns the unsigned byte value of the string number argument in AY
; the number may NOT be preceded by a + sign and may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
%asm {{
jmp str2uword
jsr conv.str2uword
}}
}
asmsub str2byte(str string @ AY) clobbers(Y) -> ubyte @A {
inline asmsub str2byte(str string @ AY) clobbers(Y) -> ubyte @A {
; -- returns the signed byte value of the string number argument in AY
; the number may be preceded by a + or - sign but may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
%asm {{
jmp str2word
jsr conv.str2word
}}
}

View File

@ -141,12 +141,12 @@ gfx2 {
}
}
asmsub next_pixel(ubyte color @A) {
inline asmsub next_pixel(ubyte color @A) {
; -- sets the next pixel byte to the graphics chip.
; for 8 bpp screens this will plot 1 pixel. for 1 bpp screens it will actually plot 8 pixels at once (bitmask).
; For super fast pixel plotting, don't call this subroutine but instead just use the assignment: cx16.VERA_DATA0 = color
%asm {{
sta cx16.VERA_DATA0
rts
}}
}

View File

@ -129,8 +129,11 @@ graphics {
}
}
sub plot(uword plotx, ubyte ploty) {
cx16.FB_cursor_position(plotx, ploty)
cx16.FB_set_pixel(1)
inline asmsub plot(uword plotx @R0, uword ploty @R1) {
%asm {{
jsr cx16.FB_cursor_position
lda #1
jsr cx16.FB_set_pixel
}}
}
}

View File

@ -175,6 +175,7 @@ interface INameScope {
}
}
fun containsDefinedVariables() = statements.any { it is VarDecl && (it !is ParameterVarDecl) }
fun containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"}
fun containsNoCodeNorVars() = !containsCodeOrVars()

View File

@ -225,11 +225,12 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
}
private fun prog8Parser.AsmsubroutineContext.toAst(): Subroutine {
val inline = this.inline()!=null
val subdecl = asmsub_decl().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf()
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes,
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
subdecl.asmClobbers, null, true, statements, toPosition())
subdecl.asmClobbers, null, true, inline, statements, toPosition())
}
private fun prog8Parser.RomsubroutineContext.toAst(): Subroutine {
@ -237,7 +238,8 @@ private fun prog8Parser.RomsubroutineContext.toAst(): Subroutine {
val address = integerliteral().toAst().number.toInt()
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes,
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
subdecl.asmClobbers, address, true, mutableListOf(), toPosition())
subdecl.asmClobbers, address, true, inline = false, statements = mutableListOf(), position = toPosition()
)
}
private class AsmsubDecl(val name: String,
@ -341,11 +343,13 @@ private fun prog8Parser.LabeldefContext.toAst(): Statement =
private fun prog8Parser.SubroutineContext.toAst() : Subroutine {
// non-asm subroutine
val inline = inline()!=null
val returntypes = sub_return_part()?.toAst() ?: emptyList()
return Subroutine(identifier().text,
sub_params()?.toAst() ?: emptyList(),
returntypes,
statement_block()?.toAst() ?: mutableListOf(),
inline,
toPosition())
}

View File

@ -209,6 +209,13 @@ internal class AstChecker(private val program: Program,
if(uniqueNames.size!=subroutine.parameters.size)
err("parameter names must be unique")
if(subroutine.inline) {
if (subroutine.containsDefinedVariables())
err("can't inline a subroutine that defines variables")
if (!subroutine.isAsmSubroutine && subroutine.parameters.isNotEmpty())
err("can't inline a non-asm subroutine that has parameters")
}
super.visit(subroutine)
// user-defined subroutines can only have zero or one return type
@ -222,8 +229,8 @@ internal class AstChecker(private val program: Program,
if (subroutine.amountOfRtsInAsm() == 0) {
if (subroutine.returntypes.isNotEmpty()) {
// for asm subroutines with an address, no statement check is possible.
if (subroutine.asmAddress == null)
err("subroutine has result value(s) and thus must have at least one 'return' or 'goto' in it (or 'rts' / 'jmp' in case of %asm)")
if (subroutine.asmAddress == null && !subroutine.inline)
err("non-inline subroutine has result value(s) and thus must have at least one 'return' or 'goto' in it (or 'rts' / 'jmp' in case of %asm)")
}
}
}

View File

@ -712,11 +712,12 @@ class Subroutine(override val name: String,
val asmClobbers: Set<CpuRegister>,
val asmAddress: Int?,
val isAsmSubroutine: Boolean,
val inline: Boolean,
override var statements: MutableList<Statement>,
override val position: Position) : Statement(), INameScope {
constructor(name: String, parameters: List<SubroutineParameter>, returntypes: List<DataType>, statements: MutableList<Statement>, position: Position)
: this(name, parameters, returntypes, emptyList(), determineReturnRegisters(returntypes), emptySet(), null, false, statements, position)
constructor(name: String, parameters: List<SubroutineParameter>, returntypes: List<DataType>, statements: MutableList<Statement>, inline: Boolean, position: Position)
: this(name, parameters, returntypes, emptyList(), determineReturnRegisters(returntypes), emptySet(), null, false, inline, statements, position)
companion object {
private fun determineReturnRegisters(returntypes: List<DataType>): List<RegisterOrStatusflag> {

View File

@ -111,6 +111,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
val mods = mutableListOf<IAstModification>()
val returnStmt = Return(null, subroutine.position)
if (subroutine.asmAddress == null
&& !subroutine.inline
&& subroutine.statements.isNotEmpty()
&& subroutine.amountOfRtsInAsm() == 0
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return

View File

@ -30,6 +30,7 @@ data class CompilationOptions(val output: OutputType,
val floats: Boolean,
val noSysInit: Boolean) {
var slowCodegenWarnings = false
var optimize = false
}

View File

@ -51,17 +51,18 @@ fun compileProgram(filepath: Path,
// import main module and everything it needs
val (ast, compilationOptions, imported) = parseImports(filepath, errors)
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
compilationOptions.optimize = optimize
programAst = ast
importedFiles = imported
processAst(programAst, errors, compilationOptions)
if (optimize)
if (compilationOptions.optimize)
optimizeAst(programAst, errors)
postprocessAst(programAst, errors, compilationOptions)
// printAst(programAst)
if(writeAssembly)
programName = writeAssembly(programAst, errors, outputDir, optimize, compilationOptions)
programName = writeAssembly(programAst, errors, outputDir, compilationOptions)
}
System.out.flush()
System.err.flush()
@ -218,7 +219,7 @@ private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerO
}
private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir: Path,
optimize: Boolean, compilerOptions: CompilationOptions): String {
compilerOptions: CompilationOptions): String {
// asm generation directly from the Ast,
programAst.processAstBeforeAsmGeneration(errors)
errors.handle()
@ -231,7 +232,7 @@ private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir:
errors,
CompilationTarget.instance.machine.zeropage,
compilerOptions,
outputDir).compileToAssembly(optimize)
outputDir).compileToAssembly()
assembly.assemble(compilerOptions)
errors.handle()
return assembly.name

View File

@ -3,7 +3,7 @@ package prog8.compiler.target
import prog8.compiler.CompilationOptions
internal interface IAssemblyGenerator {
fun compileToAssembly(optimize: Boolean): IAssemblyProgram
fun compileToAssembly(): IAssemblyProgram
}
internal const val generatedLabelPrefix = "_prog8_label_"

View File

@ -51,7 +51,7 @@ internal class AsmGen(private val program: Program,
internal val loopEndLabels = ArrayDeque<String>()
private val blockLevelVarInits = mutableMapOf<Block, MutableSet<VarDecl>>()
override fun compileToAssembly(optimize: Boolean): IAssemblyProgram {
override fun compileToAssembly(): IAssemblyProgram {
assemblyLines.clear()
loopEndLabels.clear()
@ -70,7 +70,7 @@ internal class AsmGen(private val program: Program,
for (line in assemblyLines) { it.println(line) }
}
if(optimize) {
if(options.optimize) {
assemblyLines.clear()
assemblyLines.addAll(outputFile.readLines())
var optimizationsDone = 1
@ -789,9 +789,19 @@ internal class AsmGen(private val program: Program,
private fun translateSubroutine(sub: Subroutine) {
if(sub.inline) {
if(options.optimize)
return // inline subroutines don't exist anymore on their own
else if(sub.amountOfRtsInAsm()==0) {
// make sure the NOT INLINED subroutine actually does an rts at the end
sub.statements.add(Return(null, Position.DUMMY))
}
}
out("")
outputSourceLine(sub)
if(sub.isAsmSubroutine) {
if(sub.asmAddress!=null)
return // already done at the memvars section

View File

@ -5,10 +5,9 @@ import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.ast.statements.SubroutineParameter
import prog8.ast.statements.*
import prog8.compiler.AssemblyError
import prog8.compiler.CompilationOptions
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.CpuType
import prog8.compiler.target.c64.codegen.assignment.*
@ -71,7 +70,20 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
}
}
}
asmgen.out(" jsr $subName")
if(sub.inline && asmgen.options.optimize) {
if(sub.containsDefinedVariables())
throw AssemblyError("can't inline sub with vars")
if(!sub.isAsmSubroutine && sub.parameters.isNotEmpty())
throw AssemblyError("can't inline a non-asm subroutine with parameters")
asmgen.out(" \t; inlined routine follows: ${sub.name} from ${sub.position}")
val statements = sub.statements.filter { it !is ParameterVarDecl && it !is Directive }
statements.forEach { asmgen.translate(it) }
}
else {
asmgen.out(" jsr $subName")
}
if(saveX) {
if(regSaveOnStack)
asmgen.restoreRegisterStack(CpuRegister.X, keepAonReturn)

View File

@ -477,7 +477,7 @@ class TestMemory {
val memexpr = IdentifierReference(listOf("address"), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
return target
}
@ -497,7 +497,7 @@ class TestMemory {
val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, null, false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertTrue(target.isInRegularRAM(target.definingScope()))
}
@ -509,7 +509,7 @@ class TestMemory {
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertTrue(target.isInRegularRAM(target.definingScope()))
}
@ -521,7 +521,7 @@ class TestMemory {
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertFalse(target.isInRegularRAM(target.definingScope()))
}
@ -533,7 +533,7 @@ class TestMemory {
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertTrue(target.isInRegularRAM(target.definingScope()))
}
@ -546,7 +546,7 @@ class TestMemory {
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertTrue(target.isInRegularRAM(target.definingScope()))
}
@ -559,7 +559,7 @@ class TestMemory {
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertFalse(target.isInRegularRAM(target.definingScope()))
}

View File

@ -2,6 +2,8 @@
TODO
====
- why are unused vars not removed in gfx2 module
- document the 'inline' keyword on asmsub and sub declarations + the restriction
- hoist all variable declarations up to the subroutine scope *before* even the constant folding takes place (to avoid undefined symbol errors when referring to a variable from another nested scope in the subroutine)
- make it possible to use cpu opcodes such as 'nop' as variable names by prefixing all asm vars with something such as '_'
- option to load the built-in library files from a directory instead of the embedded ones (for easier library development/debugging)

View File

@ -1,28 +1,16 @@
%import test_stack
%import gfx2
%import textio
%zeropage basicsafe
%option no_sysinit
main {
sub start () {
void cx16.screen_set_mode($80)
cx16.GRAPH_init(0)
cx16.GRAPH_set_colors(13, 6, 6)
cx16.GRAPH_clear()
uword cp
for cp in 0 to 15 {
cx16.r0 = 10+cp*2
cx16.r1 = 10+cp*11
ubyte cc
for cc in "Hello world, 123456789<>-=!#$%"
cx16.GRAPH_put_next_char(cc)
}
txt.lowercase()
txt.print_ub(txt.width())
txt.chrout('\n')
txt.print_ub(txt.height())
txt.chrout('\n')
test_stack.test()
}

View File

@ -244,9 +244,10 @@ literalvalue :
inlineasm : '%asm' INLINEASMBLOCK;
inline: 'inline';
subroutine :
'sub' identifier '(' sub_params? ')' sub_return_part? (statement_block EOL)
inline? 'sub' identifier '(' sub_params? ')' sub_return_part? (statement_block EOL)
;
sub_return_part : '->' sub_returns ;
@ -263,7 +264,7 @@ sub_params : vardecl (',' EOL? vardecl)* ;
sub_returns : datatype (',' EOL? datatype)* ;
asmsubroutine :
'asmsub' asmsub_decl statement_block
inline? 'asmsub' asmsub_decl statement_block
;
romsubroutine :