translating subroutine calls and returns

This commit is contained in:
Irmen de Jong 2018-09-28 02:26:56 +02:00
parent bf5c2e07a2
commit 1c036c4813
7 changed files with 131 additions and 63 deletions

View File

@ -1,32 +1,18 @@
%option enable_floats
%import c64lib
%import mathlib
%import prog8lib
~ main {
sub start() -> () {
const word yoffset=100
const float height=20.2
word pixely
float yy
float v
yy = 11.0-(v-22.0)
yy = 11.0-(22.0-v)
yy = (v-22.0)-11.0
yy = (22.0-v)-11.0
yy = 11.0/(v/22.0)
yy = 11.0/(22.0/v)
yy = (v/22.0)/11.0
yy = (22.0/v)/11.0
A = calcIt(12345, 99)
;A = 99/5 + lsb(12345)
_vm_write_num(A)
_vm_write_char($8d)
return
}
sub printIt(length: XY, control: A) -> (A) {
return 42 ; length / control
sub calcIt(length: XY, control: A) -> (Y) {
return control/5 +lsb(length)
}
}

View File

@ -202,6 +202,11 @@ interface IAstProcessor {
repeatLoop.statements = repeatLoop.statements.map { it.process(this) }
return repeatLoop
}
fun process(returnStmt: Return): IStatement {
returnStmt.values = returnStmt.values.map { it.process(this) }
return returnStmt
}
}
@ -271,7 +276,7 @@ interface INameScope {
fun subScopes() = statements.asSequence().filter { it is INameScope }.map { it as INameScope }.associate { it.name to it }
fun labelsAndVariables() = statements.asSequence().filter { it is Label || it is VarDecl }
.associate {((it as? Label)?.name ?: (it as? VarDecl)?.name) to it }
.associate {((it as? Label)?.name ?: (it as? VarDecl)?.name)!! to it }
fun lookup(scopedName: List<String>, statement: Node) : IStatement? {
if(scopedName.size>1) {
@ -504,10 +509,7 @@ class Return(var values: List<IExpression>, override val position: Position) : I
values.forEach {it.linkParents(this)}
}
override fun process(processor: IAstProcessor): IStatement {
values = values.map { it.process(processor) }
return this
}
override fun process(processor: IAstProcessor) = processor.process(this)
override fun toString(): String {
return "Return(values: $values, pos=$position)"
@ -1225,6 +1227,9 @@ class FunctionCall(override var target: IdentifierReference,
}
TODO("return type for subroutine with multiple return values $stmt")
}
else if(stmt is Label) {
return null
}
TODO("datatype of functioncall to $stmt")
}
@ -1302,6 +1307,12 @@ data class SubroutineParameter(val name: String,
override fun linkParents(parent: Node) {
this.parent = parent
}
val type = when(register) {
Register.A, Register.X, Register.Y -> DataType.BYTE
Register.AX, Register.AY, Register.XY -> DataType.WORD
null -> DataType.BYTE
}
}
@ -1562,16 +1573,16 @@ private fun prog8Parser.FunctioncallContext.toAst(): FunctionCall {
}
private fun prog8Parser.InlineasmContext.toAst(): IStatement =
private fun prog8Parser.InlineasmContext.toAst() =
InlineAssembly(INLINEASMBLOCK().text, toPosition())
private fun prog8Parser.ReturnstmtContext.toAst() : IStatement {
private fun prog8Parser.ReturnstmtContext.toAst() : Return {
val values = expression_list()
return Return(values?.toAst() ?: emptyList(), toPosition())
}
private fun prog8Parser.UnconditionaljumpContext.toAst(): IStatement {
private fun prog8Parser.UnconditionaljumpContext.toAst(): Jump {
val address = integerliteral()?.toAst()?.number?.toInt()
val identifier =

View File

@ -38,11 +38,6 @@ fun printWarning(msg: String, position: Position, detailInfo: String?=null) {
}
/**
* todo check subroutine call parameters against signature
* todo check subroutine return values against the call's result assignments
*/
class AstChecker(private val namespace: INameScope, private val compilerOptions: CompilationOptions) : IAstProcessor {
private val checkResult: MutableList<AstException> = mutableListOf()
@ -62,12 +57,24 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
// there must be a 'main' block with a 'start' subroutine for the program entry point.
val mainBlock = module.statements.singleOrNull { it is Block && it.name=="main" } as? Block?
val startSub = mainBlock?.subScopes()?.get("start")
val startSub = mainBlock?.subScopes()?.get("start") as? Subroutine
if(startSub==null) {
checkResult.add(SyntaxError("missing program entrypoint ('start' subroutine in 'main' block)", module.position))
} else {
if(startSub.parameters.isNotEmpty() || startSub.returnvalues.isNotEmpty())
checkResult.add(SyntaxError("program entrypoint subroutine can't have parameters and/or return values", module.position))
}
}
override fun process(returnStmt: Return): IStatement {
val expectedReturnValues = (returnStmt.definingScope() as? Subroutine)?.returnvalues ?: emptyList()
if(expectedReturnValues.size != returnStmt.values.size)
checkResult.add(SyntaxError("number of return values doesn't match subroutine return spec", returnStmt.position))
// @todo: check return value types versus sub return spec
return super.process(returnStmt)
}
override fun process(forLoop: ForLoop): IStatement {
if(forLoop.body.isEmpty())
printWarning("for loop body is empty", forLoop.position)
@ -479,6 +486,12 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
val targetStatement = checkFunctionOrLabelExists(functionCall.target, stmtOfExpression)
if(targetStatement!=null)
checkBuiltinFunctionCall(functionCall, functionCall.position)
if(targetStatement is Label && functionCall.arglist.isNotEmpty())
checkResult.add(SyntaxError("cannot use arguments when calling a label", functionCall.position))
// todo check subroutine call parameters against signature
return super.process(functionCall)
}
@ -486,6 +499,12 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
val targetStatement = checkFunctionOrLabelExists(functionCall.target, functionCall)
if(targetStatement!=null)
checkBuiltinFunctionCall(functionCall, functionCall.position)
if(targetStatement is Label && functionCall.arglist.isNotEmpty())
checkResult.add(SyntaxError("cannot use arguments when calling a label", functionCall.position))
// todo check subroutine call parameters against signature
return super.process(functionCall)
}

View File

@ -5,6 +5,7 @@ import prog8.functions.BuiltinFunctionNames
/**
* Checks the validity of all identifiers (no conflicts)
* Also builds a list of all (scoped) symbol definitions
* Also makes sure that subroutine's parameters also become local variable decls in the subroutine's scope.
* Finally, it also makes sure the datatype of all Var decls is set correctly.
*/
@ -65,7 +66,7 @@ class AstIdentifiersChecker : IAstProcessor {
// the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", subroutine.position))
} else {
if(subroutine.parameters.any { BuiltinFunctionNames.contains(it.name) })
if (subroutine.parameters.any { BuiltinFunctionNames.contains(it.name) })
checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position))
val scopedName = subroutine.scopedname
@ -75,6 +76,24 @@ class AstIdentifiersChecker : IAstProcessor {
} else {
symbols[scopedName] = subroutine
}
// check that there are no local variables that redefine the subroutine's parameters
val definedNames = subroutine.labelsAndVariables()
val paramNames = subroutine.parameters.map { it.name }
val definedNamesCorrespondingToParameters = definedNames.filter { paramNames.contains(it.key) }
for(name in definedNamesCorrespondingToParameters) {
if(name.value.position != subroutine.position)
nameError(name.key, name.value.position, subroutine)
}
// inject subroutine params as local variables (if they're not there yet)
subroutine.parameters
.filter { !definedNames.containsKey(it.name) }
.forEach {
val vardecl = VarDecl(VarDeclType.VAR, it.type, null, it.name, null, subroutine.position)
vardecl.linkParents(subroutine)
subroutine.statements.add(0, vardecl)
}
}
return super.process(subroutine)
}

View File

@ -311,17 +311,15 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
translateBinaryOperator(expr.operator)
}
is FunctionCall -> {
expr.arglist.forEach { translate(it) }
val target = expr.target.targetStatement(namespace)
if(target is BuiltinFunctionStatementPlaceholder) {
// call to a builtin function (some will just be an opcode!)
expr.arglist.forEach { translate(it) }
val funcname = expr.target.nameInSource[0]
translateFunctionCall(funcname, expr.arglist)
} else {
when(target) {
is Subroutine -> {
stackvmProg.instr(Opcode.CALL, callLabel = target.scopedname)
}
is Subroutine -> translateSubroutineCall(target, expr.arglist, expr.parent)
else -> TODO("non-builtin-function call to $target")
}
}
@ -369,6 +367,26 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
}
}
private fun translate(stmt: FunctionCallStatement) {
stackvmProg.line(stmt.position)
val targetStmt = stmt.target.targetStatement(namespace)!!
if(targetStmt is BuiltinFunctionStatementPlaceholder) {
stmt.arglist.forEach { translate(it) }
val funcname = stmt.target.nameInSource[0]
translateFunctionCall(funcname, stmt.arglist)
return
}
when(targetStmt) {
is Label ->
stackvmProg.instr(Opcode.CALL, callLabel = targetStmt.scopedname)
is Subroutine ->
translateSubroutineCall(targetStmt, stmt.arglist, stmt)
else ->
throw AstException("invalid call target node type: ${targetStmt::class}")
}
}
private fun translateFunctionCall(funcname: String, args: List<IExpression>) {
// some functions are implemented as vm opcodes
when (funcname) {
@ -398,6 +416,22 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
}
}
fun translateSubroutineCall(subroutine: Subroutine, arguments: List<IExpression>, parent: Node) {
// setup the arguments: simply put them into the register vars
// @todo support other types of parameters beside just registers
for(param in arguments.zip(subroutine.parameters)) {
val assign = Assignment(
AssignTarget(param.second.register, null, param.first.position),
null,
param.first,
param.first.position
)
assign.linkParents(parent)
translate(assign)
}
stackvmProg.instr(Opcode.CALL, callLabel=subroutine.scopedname)
}
private fun translateBinaryOperator(operator: String) {
val opcode = when(operator) {
"+" -> Opcode.ADD
@ -435,25 +469,6 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
stackvmProg.instr(opcode)
}
private fun translate(stmt: FunctionCallStatement) {
stackvmProg.line(stmt.position)
val targetStmt = stmt.target.targetStatement(namespace)!!
if(targetStmt is BuiltinFunctionStatementPlaceholder) {
stmt.arglist.forEach { translate(it) }
val funcname = stmt.target.nameInSource[0]
translateFunctionCall(funcname, stmt.arglist)
return
}
val targetname = when(targetStmt) {
is Label -> targetStmt.scopedname
is Subroutine -> targetStmt.scopedname
else -> throw AstException("invalid call target node type: ${targetStmt::class}")
}
stmt.arglist.forEach { translate(it) }
stackvmProg.instr(Opcode.CALL, callLabel = targetname)
}
private fun createSyscall(funcname: String) {
val function = (
if (funcname.startsWith("_vm_"))
@ -572,8 +587,18 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
}
private fun translate(stmt: Return) {
if(stmt.values.isNotEmpty()) {
TODO("return with value(s) not yet supported: $stmt")
val returnvalues = (stmt.definingScope() as? Subroutine)?.returnvalues ?: emptyList()
for(value in stmt.values.zip(returnvalues)) {
// assign the return values to the proper result registers
// @todo support other things than just result registers
val assign = Assignment(
AssignTarget(value.second.register, null, stmt.position),
null,
value.first,
stmt.position
)
assign.linkParents(stmt.parent)
translate(assign)
}
stackvmProg.line(stmt.position)
stackvmProg.instr(Opcode.RETURN)

View File

@ -144,6 +144,13 @@ class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcesso
// we chose to just print the blocks that aren't used.
if(value is Block)
println("${value.position} Info: block '$localname' is never used")
if(value is VarDecl) {
val scope = value.definingScope() as? Subroutine
if(scope!=null && scope.parameters.any { it.name==localname})
println("${value.position} Info: parameter '$localname' is never used")
else
println("${value.position} Info: variable '$localname' is never used")
}
parentScope.removeStatement(value)
symbolsToRemove.add(name)
optimizationsDone++

View File

@ -1390,7 +1390,8 @@ class StackVm(val traceOutputFile: String?) {
val value = evalstack.pop()
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
if(variable.type!=value.type) throw VmExecutionException("value datatype ${value.type} is not the same as variable datatype ${variable.type}")
if(variable.type!=value.type)
throw VmExecutionException("value datatype ${value.type} is not the same as variable datatype ${variable.type} for var $varname")
variables[varname] = value
}
Opcode.SHL_VAR -> {