tweaking multiple assignment targets

This commit is contained in:
Irmen de Jong 2018-11-18 23:26:05 +01:00
parent a2a8a772ec
commit fad74a6ae0
8 changed files with 241 additions and 88 deletions

View File

@ -11,16 +11,24 @@ sub start() {
ubyte v2
float f2
uword address
memory uword memaddr = $c000
uword[2] wordarray
byte b1
v1=foo()
X, Y =c64.GETADR()
v1, v2 =c64.GETADR()
address =c64.MEMBOT(1, 0.w)
address =c64.IOBASE()
A = c64.CHRIN()
X = c64.CHRIN()
v1 = c64.CHRIN()
;v1=foo() ; @todo fix return type value error see sub
A, Y =c64.GETADR() ; ok!
Y, A =c64.GETADR() ; ok!
address = c64flt.GETADRAY() ; ok!
memaddr = c64flt.GETADRAY() ; ok!
wordarray[1] = c64flt.GETADRAY() ; ok!
v1, v2 =c64.GETADR() ; ok!
address =c64.MEMBOT(1, 0.w) ; ok !
address =c64.IOBASE() ; ok!
A = c64.CHRIN() ; ok !
X = c64.CHRIN() ; ok !
v1 = c64.CHRIN() ; ok !
return
}

View File

@ -394,7 +394,6 @@ interface INameScope {
}
}
private object ParentSentinel : Node {
override val position = Position("<<sentinel>>", 0, 0, 0)
override var parent: Node = this
@ -1436,7 +1435,7 @@ class InlineAssembly(val assembly: String, override val position: Position) : IS
}
class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?)
data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?)
class AnonymousScope(override var statements: MutableList<IStatement>,
override val position: Position) : INameScope, IStatement {
@ -1592,8 +1591,6 @@ fun prog8Parser.ModuleContext.toAst(name: String) : Module =
Module(name, modulestatement().asSequence().map { it.toAst() }.toMutableList(), toPosition())
/************** Helper extension methods (private) ************/
private fun ParserRuleContext.toPosition() : Position {
val file = Paths.get(this.start.inputStream.sourceName).fileName.toString()
// note: be ware of TAB characters in the source text, they count as 1 column...
@ -2056,3 +2053,28 @@ private fun prog8Parser.RepeatloopContext.toAst(): RepeatLoop {
val scope = AnonymousScope(statements, statement_block()?.toPosition() ?: statement().toPosition())
return RepeatLoop(scope, untilCondition, toPosition())
}
internal fun registerSet(asmReturnvaluesRegisters: Iterable<RegisterOrStatusflag>): Set<Register> {
val resultRegisters = mutableSetOf<Register>()
for(x in asmReturnvaluesRegisters) {
when(x.registerOrPair) {
RegisterOrPair.A -> resultRegisters.add(Register.A)
RegisterOrPair.X -> resultRegisters.add(Register.X)
RegisterOrPair.Y -> resultRegisters.add(Register.Y)
RegisterOrPair.AX -> {
resultRegisters.add(Register.A)
resultRegisters.add(Register.X)
}
RegisterOrPair.AY -> {
resultRegisters.add(Register.A)
resultRegisters.add(Register.Y)
}
RegisterOrPair.XY -> {
resultRegisters.add(Register.X)
resultRegisters.add(Register.Y)
}
}
}
return resultRegisters
}

View File

@ -177,7 +177,6 @@ class AstChecker(private val namespace: INameScope,
return super.process(label)
}
/**
* Check subroutine definition
*/
@ -300,9 +299,32 @@ class AstChecker(private val namespace: INameScope,
/**
* Assignment target must be register, or a variable name
* Also check data type compatibility
* Also check data type compatibility and number of values
*/
override fun process(assignment: Assignment): IStatement {
// assigning from a functioncall COULD return multiple values (from an asm subroutine)
val stmt = (assignment.value as FunctionCall).target.targetStatement(namespace)
if(stmt is Subroutine && stmt.returntypes.size>1) {
if(stmt.isAsmSubroutine) {
if(stmt.returntypes.size != assignment.targets.size)
checkResult.add(ExpressionError("number of return values doesn't match number of assignment targets", assignment.value.position))
else {
if(assignment.targets.all{it.register!=null}) {
val returnRegisters = registerSet(stmt.asmReturnvaluesRegisters)
val targetRegisters = assignment.targets.filter { it.register != null }.map { it.register }.toSet()
if (returnRegisters != targetRegisters)
checkResult.add(ExpressionError("asmsub return registers $returnRegisters don't match assignment target registers", assignment.position))
}
for(thing in stmt.returntypes.zip(assignment.targets)) {
if(thing.second.determineDatatype(namespace, heap, assignment)!=thing.first)
checkResult.add(ExpressionError("return type mismatch for target ${thing.second.shortString()}", assignment.value.position))
}
}
} else
checkResult.add(ExpressionError("only asmsub subroutines can return multiple values", assignment.value.position))
}
var resultingAssignment = assignment
for (target in assignment.targets) {
resultingAssignment = processAssignmentTarget(resultingAssignment, target)
@ -367,31 +389,15 @@ class AstChecker(private val namespace: INameScope,
} else {
val sourceDatatype: DataType? = assignment.value.resultingDatatype(namespace, heap)
if(sourceDatatype==null) {
if(assignment.value is FunctionCall) {
// a functioncall COULD return multiple values (from an asm subroutine), treat that differently
val stmt = (assignment.value as FunctionCall).target.targetStatement(namespace)
if(stmt is Subroutine && stmt.returntypes.size>1) {
if(stmt.isAsmSubroutine) {
if(stmt.returntypes.size != assignment.targets.size)
checkResult.add(ExpressionError("number of return values doesn't match number of assignment targets", assignment.value.position))
else {
for(thing in stmt.returntypes.zip(assignment.targets)) {
if(thing.second.determineDatatype(namespace, heap, assignment)!=thing.first)
checkResult.add(ExpressionError("return type mismatch for target ${thing.second.shortString()}", assignment.value.position))
}
}
} else
checkResult.add(ExpressionError("only asmsub subroutines can return multiple values", assignment.value.position))
}
else
if(assignment.targets.size<=1) {
if (assignment.value is FunctionCall)
checkResult.add(ExpressionError("function call doesn't return a suitable value to use in assignment", assignment.value.position))
else
checkResult.add(ExpressionError("assignment value is invalid or has no proper datatype", assignment.value.position))
}
else
checkResult.add(ExpressionError("assignment value is invalid or has no proper datatype", assignment.value.position))
}
else {
else
checkAssignmentCompatible(targetDatatype, sourceDatatype, assignment.value, assignment.position)
}
}
}
return assignment

View File

@ -792,13 +792,18 @@ private class StatementTranslator(private val prog: IntermediateProgram,
private fun translateSubroutineCall(subroutine: Subroutine, arguments: List<IExpression>, callPosition: Position) {
// evaluate the arguments and assign them into the subroutine's argument variables.
prog.line(callPosition)
for(arg in arguments.zip(subroutine.parameters)) {
translate(arg.first)
val opcode=opcodePopvar(arg.second.type)
prog.instr(opcode, callLabel = subroutine.scopedname+"."+arg.second.name)
if(subroutine.asmReturnvaluesRegisters.isNotEmpty()) {
TODO("call asmsub by loading registers instead")
} else {
prog.line(callPosition)
for (arg in arguments.zip(subroutine.parameters)) {
translate(arg.first)
val opcode = opcodePopvar(arg.second.type)
prog.instr(opcode, callLabel = subroutine.scopedname + "." + arg.second.name)
}
}
prog.instr(Opcode.CALL, callLabel=subroutine.scopedname)
prog.instr(Opcode.CALL, callLabel = subroutine.scopedname)
}
private fun translateBinaryOperator(operator: String, dt: DataType) {
@ -1080,7 +1085,6 @@ private class StatementTranslator(private val prog: IntermediateProgram,
}
}
private fun translate(stmt: VariableInitializationAssignment) {
// this is an assignment to initialize a variable's value in the scope.
// the compiler can perhaps optimize this phase.
@ -1089,48 +1093,16 @@ private class StatementTranslator(private val prog: IntermediateProgram,
}
private fun translate(stmt: Assignment) {
val assignTarget= stmt.singleTarget
prog.line(stmt.position)
translate(stmt.value)
val assignTarget= stmt.singleTarget
if(assignTarget==null) {
// we're dealing with multiple return values
val targetStmt = (stmt.value as? FunctionCall)?.target?.targetStatement(namespace)
if(targetStmt is Subroutine && targetStmt.isAsmSubroutine) {
// we're dealing with the one case where multiple assignment targets are allowed: a call to an asmsub with multiple return values
// for now, we only support multiple return values as long as they're returned in registers as well.
if(targetStmt.asmReturnvaluesRegisters.isEmpty())
throw CompilerException("we only support multiple return values / assignment when the asmsub returns values in registers")
// if the result registers are not assigned in the exact same registers, or in variables, we need some code
if(stmt.targets.all{it.register!=null}) {
val resultRegisters = mutableListOf<Register>()
for(x in targetStmt.asmReturnvaluesRegisters) {
when(x.registerOrPair) {
RegisterOrPair.A -> resultRegisters.add(Register.A)
RegisterOrPair.X -> resultRegisters.add(Register.X)
RegisterOrPair.Y -> resultRegisters.add(Register.Y)
RegisterOrPair.AX -> {
resultRegisters.add(Register.A)
resultRegisters.add(Register.X)
}
RegisterOrPair.AY -> {
resultRegisters.add(Register.A)
resultRegisters.add(Register.Y)
}
RegisterOrPair.XY -> {
resultRegisters.add(Register.X)
resultRegisters.add(Register.Y)
}
}
}
TODO("$resultRegisters")
} else {
TODO("store results from registers ${targetStmt.asmReturnvaluesRegisters}")
}
} else throw CompilerException("can only use multiple assignment targets on an asmsub call")
translateMultiReturnAssignment(stmt)
return
}
prog.line(stmt.position)
translate(stmt.value)
val valueDt = stmt.value.resultingDatatype(namespace, heap)
val targetDt = assignTarget.determineDatatype(namespace, heap, stmt)
if(valueDt!=targetDt) {
@ -1181,11 +1153,90 @@ private class StatementTranslator(private val prog: IntermediateProgram,
translateAugAssignOperator(stmt.aug_op, stmt.value.resultingDatatype(namespace, heap))
}
if(stmt.value is FunctionCall) {
val sub = (stmt.value as FunctionCall).target.targetStatement(namespace)
if(sub is Subroutine && sub.asmReturnvaluesRegisters.isNotEmpty()) {
// the subroutine call returns its values in registers
storeRegisterIntoTarget(sub.asmReturnvaluesRegisters.single(), assignTarget, stmt)
return
}
}
// pop the result value back into the assignment target
val datatype = assignTarget.determineDatatype(namespace, heap, stmt)!!
popValueIntoTarget(assignTarget, datatype)
}
private fun translateMultiReturnAssignment(stmt: Assignment) {
val targetStmt = (stmt.value as? FunctionCall)?.target?.targetStatement(namespace)
if(targetStmt is Subroutine && targetStmt.isAsmSubroutine) {
// we're dealing with the one case where multiple assignment targets are allowed: a call to an asmsub with multiple return values
// for now, we only support multiple return values as long as they're returned in registers as well.
if(targetStmt.asmReturnvaluesRegisters.isEmpty())
throw CompilerException("we only support multiple return values / assignment when the asmsub returns values in registers")
// if the result registers are not assigned in the exact same registers, or in variables, we need some code
if(stmt.targets.all{it.register!=null}) {
val resultRegisters = registerSet(targetStmt.asmReturnvaluesRegisters)
if(stmt.targets.size!=resultRegisters.size)
throw CompilerException("asmsub number of return values doesn't match number of assignment targets ${stmt.position}")
val targetRegs = stmt.targets.filter {it.register!=null}.map{it.register}.toSet()
if(resultRegisters!=targetRegs)
throw CompilerException("asmsub return registers don't match assignment target registers ${stmt.position}")
// output is in registers already, no need to emit any asm code
} else {
// output is in registers but has to be stored somewhere
for(result in targetStmt.asmReturnvaluesRegisters.zip(stmt.targets))
storeRegisterIntoTarget(result.first, result.second, stmt)
}
} else throw CompilerException("can only use multiple assignment targets on an asmsub call")
}
private fun storeRegisterIntoTarget(registerOrStatus: RegisterOrStatusflag, target: AssignTarget, parent: IStatement) {
if(registerOrStatus.statusflag!=null)
return
when(registerOrStatus.registerOrPair){
RegisterOrPair.A -> {
val assignment = Assignment(listOf(target), null, RegisterExpr(Register.A, target.position), target.position)
assignment.linkParents(parent)
translate(assignment)
}
RegisterOrPair.X -> {
val assignment = Assignment(listOf(target), null, RegisterExpr(Register.X, target.position), target.position)
assignment.linkParents(parent)
translate(assignment)
}
RegisterOrPair.Y -> {
val assignment = Assignment(listOf(target), null, RegisterExpr(Register.Y, target.position), target.position)
assignment.linkParents(parent)
translate(assignment)
}
RegisterOrPair.AX -> {
// deal with register pair AX: target = A + X*256
val targetDt = target.determineDatatype(namespace, heap, parent)
if(targetDt!=DataType.UWORD)
throw CompilerException("invalid target datatype for registerpair $targetDt")
prog.instr(Opcode.PUSH_REGAX_WORD)
popValueIntoTarget(target, targetDt)
}
RegisterOrPair.AY -> {
// deal with register pair AY: target = A + Y*256
val targetDt = target.determineDatatype(namespace, heap, parent)
if(targetDt!=DataType.UWORD)
throw CompilerException("invalid target datatype for registerpair $targetDt")
prog.instr(Opcode.PUSH_REGAY_WORD)
popValueIntoTarget(target, targetDt)
}
RegisterOrPair.XY -> {
// deal with register pair XY: target = X + Y*256
val targetDt = target.determineDatatype(namespace, heap, parent)
if(targetDt!=DataType.UWORD)
throw CompilerException("invalid target datatype for registerpair $targetDt")
prog.instr(Opcode.PUSH_REGXY_WORD)
popValueIntoTarget(target, targetDt)
}
}
}
private fun popValueIntoTarget(assignTarget: AssignTarget, datatype: DataType) {
when {
assignTarget.identifier != null -> {

View File

@ -14,6 +14,9 @@ enum class Opcode {
PUSH_VAR_BYTE, // push byte variable (ubyte, byte)
PUSH_VAR_WORD, // push word variable (uword, word)
PUSH_VAR_FLOAT, // push float variable
PUSH_REGAX_WORD, // push registers A/X as a 16-bit word
PUSH_REGAY_WORD, // push registers A/Y as a 16-bit word
PUSH_REGXY_WORD, // push registers X/Y as a 16-bit word
// popping values off the (evaluation) stack, possibly storing them in another location
DISCARD_BYTE, // discard top byte value

View File

@ -459,6 +459,10 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
"""
}
Opcode.PUSH_REGAY_WORD -> {
" sta ${ESTACK_LO.toHex()},x | tya | sta ${ESTACK_HI.toHex()},x | dex "
}
Opcode.READ_INDEXED_VAR_BYTE -> { // @todo is this correct?
"""
ldy ${(ESTACK_LO+1).toHex()},x
@ -998,8 +1002,6 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
"""
private val patterns = listOf(
// -------------- simple conversions ----------------
// ----------- assignment to BYTE VARIABLE ----------------
// var = (u)bytevalue
AsmPattern(listOf(Opcode.PUSH_BYTE, Opcode.POP_VAR_BYTE)) { segment ->
@ -2434,10 +2436,56 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
ldy #>${hexVal(segment[2])}
jsr prog8_lib.copy_float
"""
},
// ---------- some special operations ------------------
// var word = AY register pair
AsmPattern(listOf(Opcode.PUSH_REGAY_WORD, Opcode.POP_VAR_WORD)) { segment ->
"""
sta ${segment[1].callLabel}
sty ${segment[1].callLabel}+1
"""
},
// var word = AX register pair
AsmPattern(listOf(Opcode.PUSH_REGAX_WORD, Opcode.POP_VAR_WORD)) { segment ->
"""
sta ${segment[1].callLabel}
stx ${segment[1].callLabel}+1
"""
},
// var word = XY register pair
AsmPattern(listOf(Opcode.PUSH_REGXY_WORD, Opcode.POP_VAR_WORD)) { segment ->
"""
stx ${segment[1].callLabel}
sty ${segment[1].callLabel}+1
"""
},
// mem word = AY register pair
AsmPattern(listOf(Opcode.PUSH_REGAY_WORD, Opcode.POP_MEM_WORD)) { segment ->
"""
sta ${hexVal(segment[1])}
sty ${hexValPlusOne(segment[1])}
"""
},
// mem word = AX register pair
AsmPattern(listOf(Opcode.PUSH_REGAX_WORD, Opcode.POP_MEM_WORD)) { segment ->
"""
sta ${hexVal(segment[1])}
stx ${hexValPlusOne(segment[1])}
"""
},
// mem word = XY register pair
AsmPattern(listOf(Opcode.PUSH_REGXY_WORD, Opcode.POP_MEM_WORD)) { segment ->
"""
stx ${hexVal(segment[1])}
sty ${hexValPlusOne(segment[1])}
"""
}
// // assignment: floatarray[idxbyte] = float
// // @todo assignment: floatarray[idxbyte] = float
// AsmPattern(listOf(Opcode.PUSH_FLOAT, Opcode.PUSH_BYTE, Opcode.WRITE_INDEXED_VAR_FLOAT)) { segment ->
// val floatConst = getFloatConst(segment[0].arg!!)
// val index = intVal(segment[1]) * Mflpt5.MemorySize
@ -2451,7 +2499,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
// jsr prog8_lib.copy_float
// """
// },
// // assignment: floatarray[idxbyte] = floatvar
// // @todo assignment: floatarray[idxbyte] = floatvar
// AsmPattern(listOf(Opcode.PUSH_VAR_FLOAT, Opcode.PUSH_BYTE, Opcode.WRITE_INDEXED_VAR_FLOAT)) { segment ->
// val index = intVal(segment[1]) * Mflpt5.MemorySize
// """
@ -2464,7 +2512,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
// jsr prog8_lib.copy_float
// """
// },
// // assignment: floatarray[idxbyte] = memfloat
// // @todo assignment: floatarray[idxbyte] = memfloat
// AsmPattern(listOf(Opcode.PUSH_MEM_FLOAT, Opcode.PUSH_BYTE, Opcode.WRITE_INDEXED_VAR_FLOAT)) { segment ->
// val index = intVal(segment[1]) * Mflpt5.MemorySize
// """

View File

@ -762,6 +762,21 @@ class StackVm(private var traceOutputFile: String?) {
checkDt(value, DataType.FLOAT)
evalstack.push(value)
}
Opcode.PUSH_REGAX_WORD -> {
val a=variables["A"]!!.integerValue()
val x=variables["X"]!!.integerValue()
evalstack.push(Value(DataType.UWORD, x*256+a))
}
Opcode.PUSH_REGAY_WORD -> {
val a=variables["A"]!!.integerValue()
val y=variables["Y"]!!.integerValue()
evalstack.push(Value(DataType.UWORD, y*256+a))
}
Opcode.PUSH_REGXY_WORD -> {
val x=variables["X"]!!.integerValue()
val y=variables["Y"]!!.integerValue()
evalstack.push(Value(DataType.UWORD, y*256+x))
}
Opcode.POP_VAR_BYTE -> {
val value = evalstack.pop()
checkDt(value, DataType.UBYTE, DataType.BYTE)

View File

@ -120,18 +120,18 @@ asmsub MOVEF () -> clobbers(A,X) -> () = $bc0f ; copy fac1 to fac2
asmsub FTOMEMXY (mflpt: uword @ XY) -> clobbers(A,Y) -> () = $bbd4 ; store fac1 to memory X/Y as 5-byte mflpt
; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
; (use c64flt.FTOSWRDAY to get A/Y output; lo/hi switched to normal order)
; (tip: use c64flt.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order)
asmsub FTOSWORDYA () -> clobbers(X) -> (ubyte @ Y, ubyte @ A) = $b1aa
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
; (use c64flt.GETADRAY to get A/Y output; lo/hi switched to normal order)
; (tip: use c64flt.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
asmsub GETADR () -> clobbers(X) -> (ubyte @ Y, ubyte @ A) = $b7f7
asmsub QINT () -> clobbers(A,X,Y) -> () = $bc9b ; fac1 -> 4-byte signed integer in 98-101 ($62-$65), with the MSB FIRST.
asmsub AYINT () -> clobbers(A,X,Y) -> () = $b1bf ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
; signed word in Y/A -> float in fac1
; (use c64flt.GIVAYFAY to use A/Y input; lo/hi switched to normal order)
; (tip: use c64flt.GIVAYFAY to use A/Y input; lo/hi switched to normal order)
; there is also c64flt.GIVUAYF - unsigned word in A/Y (lo/hi) to fac1
; there is also c64flt.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST
; there is also c64flt.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST
@ -169,7 +169,7 @@ asmsub ABS () -> clobbers() -> () = $bc58 ; fac1 = ABS(fac1)
asmsub SQR () -> clobbers(A,X,Y) -> () = $bf71 ; fac1 = SQRT(fac1)
asmsub EXP () -> clobbers(A,X,Y) -> () = $bfed ; fac1 = EXP(fac1) (e ** fac1)
asmsub NEGOP () -> clobbers(A) -> () = $bfb4 ; switch the sign of fac1
asmsub RND () -> clobbers(A,X,Y) -> () = $e097 ; fac1 = RND() (use RNDA instead)
asmsub RND () -> clobbers(A,X,Y) -> () = $e097 ; fac1 = RND() (tip: use RNDA instead)
asmsub RNDA (acc: ubyte @ A) -> clobbers(A,X,Y) -> () = $e09a ; fac1 = RND(A)
asmsub COS () -> clobbers(A,X,Y) -> () = $e264 ; fac1 = COS(fac1)
asmsub SIN () -> clobbers(A,X,Y) -> () = $e26b ; fac1 = SIN(fac1)
@ -229,7 +229,7 @@ asmsub CLALL () -> clobbers(A,X) -> () = $FFE7 ; (via 812 ($32C)) close al
asmsub UDTIM () -> clobbers(A,X) -> () = $FFEA ; update the software clock
asmsub SCREEN () -> clobbers() -> (ubyte @ X, ubyte @ Y) = $FFED ; read number of screen rows and columns
asmsub PLOT (dir: ubyte @ Pc, col: ubyte @ Y, row: ubyte @ X) -> clobbers() -> (ubyte @ X, ubyte @ Y) = $FFF0 ; read/set position of cursor on screen
asmsub IOBASE () -> clobbers() -> (ubyte @ X, ubyte @ Y) = $FFF3 ; read base address of I/O devices
asmsub IOBASE () -> clobbers() -> (uword @ XY) = $FFF3 ; read base address of I/O devices
; ---- end of C64 kernal routines ----