Compare commits

...
27 Commits
Author SHA1 Message Date
Irmen de Jong 9d98746501 version 3.2 2020-08-21 18:02:49 +02:00
Irmen de Jong 63b03ba70c fix typecasting 2020-08-21 18:02:01 +02:00
Irmen de Jong 70bab76b36 added plasma example 2020-08-21 17:58:43 +02:00
Irmen de Jong 15d24d4308 adding plasma example 2020-08-21 17:27:18 +02:00
Irmen de Jong 9ec62eb045 fixed lsb(), fixed const value type mismatch, fixed and() const evaluation. 2020-08-21 16:26:40 +02:00
Irmen de Jong 12f841e30d just prints 2020-08-21 09:25:32 +02:00
Irmen de Jong 335599ed22 restored certain memoryread asm gen 2020-08-21 07:44:50 +02:00
Irmen de Jong 0b717f9e76 clear messages about slow expression code generation points 2020-08-21 05:45:39 +02:00
Irmen de Jong e941f6ecca fix asm bug 2020-08-21 04:23:08 +02:00
Irmen de Jong ef7744dbda asm fix 2020-08-21 04:02:10 +02:00
Irmen de Jong c83a61c460 some float asm code added for in-place 2020-08-21 03:06:37 +02:00
Irmen de Jong 335684caf7 don't remove asmsub definitions... 2020-08-21 03:01:07 +02:00
Irmen de Jong 8d6220ce51 added most essential of the new in-place assignment code 2020-08-21 02:17:40 +02:00
Irmen de Jong 39ea5c5f99 fix parse error for <<= and >>= 2020-08-20 23:24:01 +02:00
Irmen de Jong b03597ac13 fixed bug in operand equality comparison, could lead to compiler endless loop 2020-08-20 22:21:26 +02:00
Irmen de Jong 58f323c087 implemented missing memory postincrdecr codegen 2020-08-20 21:48:15 +02:00
Irmen de Jong 513a68584c implemented more optimized prefix expression codegen 2020-08-20 21:42:38 +02:00
Irmen de Jong 88d5c68b32 don't inc/dec a memory mapped register 2020-08-20 21:16:48 +02:00
Irmen de Jong 14f9382cf9 typecheck prefix expressions better 2020-08-20 20:46:28 +02:00
Irmen de Jong cffb582568 added start of optimized in-place assignment code (for prefix expressions) 2020-08-20 18:43:10 +02:00
Irmen de Jong e1812ce16c fix typecast removal error. 2020-08-20 18:07:48 +02:00
Irmen de Jong 7a3163f59a bugfix in direct memory assignment 2020-08-20 17:02:22 +02:00
Irmen de Jong 6f3b2749b0 refactoring assignments codegen 2020-08-20 16:47:43 +02:00
Irmen de Jong c144d4e501 improved warnings about unreachable code 2020-08-20 14:28:17 +02:00
Irmen de Jong edfd9d55ba added sizeof() function 2020-08-20 13:50:28 +02:00
Irmen de Jong 774897260e avoid silent type casts that remove precision (such as float -> word) 2020-08-20 12:49:48 +02:00
Irmen de Jong 65ba91411d improved function arg type checking and error message 2020-08-20 12:38:22 +02:00
39 changed files with 1722 additions and 1232 deletions

No files matched your search

+10
View File
@@ -201,6 +201,16 @@ pop_float_fac1 .proc
jmp MOVFM
.pend
pop_float_fac2 .proc
; -- pops float from stack into FAC2
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jmp CONUPK
.pend
pop_float_to_indexed_var .proc
; -- pop the float on the stack, to the memory in the array at A/Y indexed by the byte on stack
sta c64.SCRATCH_ZPWORD1
+1
View File
@@ -52,6 +52,7 @@ multiply_words .proc
; -- multiply two 16-bit words into a 32-bit result (signed and unsigned)
; input: A/Y = first 16-bit number, c64.SCRATCH_ZPWORD1 in ZP = second 16-bit number
; output: multiply_words.result 4-bytes/32-bits product, LSB order (low-to-high)
; clobbers: A
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
+22 -11
View File
@@ -36,13 +36,26 @@ init_system .proc
.pend
read_byte_from_address .proc
read_byte_from_address_on_stack .proc
; -- read the byte from the memory address on the top of the stack, return in A (stack remains unchanged)
lda c64.ESTACK_LO+1,x
ldy c64.ESTACK_HI+1,x
sta (+) +1
sty (+) +2
+ lda $ffff ; modified
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
ldy #0
lda (c64.SCRATCH_ZPWORD2),y
rts
.pend
write_byte_to_address_on_stack .proc
; -- write the byte in A to the memory address on the top of the stack (stack remains unchanged)
ldy c64.ESTACK_LO+1,x
sty c64.SCRATCH_ZPWORD2
ldy c64.ESTACK_HI+1,x
sty c64.SCRATCH_ZPWORD2+1
ldy #0
sta (c64.SCRATCH_ZPWORD2),y
rts
.pend
@@ -330,9 +343,7 @@ mul_word .proc
sta c64.SCRATCH_ZPWORD1+1
lda c64.ESTACK_LO+1,x
ldy c64.ESTACK_HI+1,x
stx c64.SCRATCH_ZPREGX
jsr math.multiply_words
ldx c64.SCRATCH_ZPREGX
lda math.multiply_words.result
sta c64.ESTACK_LO+1,x
lda math.multiply_words.result+1
@@ -2079,7 +2090,7 @@ ror2_array_uw .proc
+ rts
.pend
strcpy .proc
; copy a string (0-terminated) from A/Y to (ZPWORD1)
; it is assumed the target string is large enough.
@@ -2092,8 +2103,8 @@ strcpy .proc
bne -
rts
.pend
func_leftstr .proc
; leftstr(source, target, length) with params on stack
inx
@@ -2175,12 +2186,12 @@ func_substr .proc
lda c64.ESTACK_LO,x ; start
sta c64.SCRATCH_ZPB1
inx
lda c64.ESTACK_LO,x
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD2
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD2+1
inx
lda c64.ESTACK_LO,x
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
+1 -1
View File
@@ -1 +1 @@
3.1
3.2
+2 -2
View File
@@ -314,9 +314,9 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
}
// lookup something from the module.
return when (val stmt = localContext.definingModule().lookup(scopedName, localContext)) {
is Label, is VarDecl, is Block, is Subroutine -> stmt
is Label, is VarDecl, is Block, is Subroutine, is StructDecl -> stmt
null -> null
else -> throw SyntaxError("wrong identifier target for $scopedName: $stmt", stmt.position)
else -> throw SyntaxError("invalid identifier target type", stmt.position)
}
}
}
+3 -2
View File
@@ -167,7 +167,8 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
augassignment()?.let {
// replace A += X with A = A + X
val target = it.assign_target().toAst()
val expression = BinaryExpression(target.toExpression(), it.operator.text.substring(0, 1), it.expression().toAst(), it.expression().toPosition())
val oper = it.operator.text.substringBefore('=')
val expression = BinaryExpression(target.toExpression(), oper, it.expression().toAst(), it.expression().toPosition())
return Assignment(it.assign_target().toAst(), expression, it.toPosition())
}
@@ -301,7 +302,7 @@ private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParamete
when (val name = register.nameInSource.single()) {
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(name)
in Statusflag.names -> statusregister = Statusflag.valueOf(name)
else -> throw FatalAstException("invalid register or status flag in $it")
else -> throw FatalAstException("invalid register or status flag '$name'")
}
}
AsmSubroutineParameter(vardecl.varname.text, datatype, registerorpair, statusregister,
+1 -1
View File
@@ -58,7 +58,7 @@ enum class DataType {
in ByteDatatypes -> 1
in WordDatatypes -> 2
FLOAT -> CompilationTarget.machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> 2
in PassByReferenceDatatypes -> CompilationTarget.machine.POINTER_MEM_SIZE
else -> -9999999
}
}
@@ -43,6 +43,23 @@ sealed class Expression: Node {
(other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource
&& other.arrayspec.index isSameAs arrayspec.index)
}
is DirectMemoryRead -> {
(other is DirectMemoryRead && other.addressExpression isSameAs addressExpression)
}
is TypecastExpression -> {
(other is TypecastExpression && other.implicit==implicit && other.type==type && other.expression isSameAs expression)
}
is AddressOf -> {
(other is AddressOf && other.identifier.nameInSource == identifier.nameInSource)
}
is RangeExpr -> {
(other is RangeExpr && other.from==from && other.to==to && other.step==step)
}
is FunctionCall -> {
(other is FunctionCall && other.target.nameInSource == target.nameInSource
&& other.args.size == args.size
&& other.args.zip(args).all { it.first isSameAs it.second } )
}
else -> other==this
}
}
@@ -277,7 +294,7 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
override fun constValue(program: Program): NumericLiteralValue? {
val cv = expression.constValue(program) ?: return null
return cv.cast(type)
return cv.castNoCheck(type)
// val value = RuntimeValue(cv.type, cv.asNumericValue!!).cast(type)
// return LiteralValue.fromNumber(value.numericValue(), value.type, position).cast(type)
}
@@ -398,7 +415,7 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
operator fun compareTo(other: NumericLiteralValue): Int = number.toDouble().compareTo(other.number.toDouble())
fun cast(targettype: DataType): NumericLiteralValue {
fun castNoCheck(targettype: DataType): NumericLiteralValue {
if(type==targettype)
return this
val numval = number.toDouble()
@@ -567,7 +584,7 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
it
} else {
try {
num.cast(elementType)
num.castNoCheck(elementType)
} catch(x: ExpressionError) {
return null
}
@@ -716,11 +733,10 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
override fun referencesIdentifiers(vararg name: String): Boolean = nameInSource.last() in name
override fun inferType(program: Program): InferredTypes.InferredType {
val targetStmt = targetStatement(program.namespace)
return if(targetStmt is VarDecl) {
InferredTypes.knownFor(targetStmt.datatype)
} else {
InferredTypes.InferredType.unknown()
return when (val targetStmt = targetStatement(program.namespace)) {
is VarDecl -> InferredTypes.knownFor(targetStmt.datatype)
is StructDecl -> InferredTypes.knownFor(DataType.STRUCT)
else -> InferredTypes.InferredType.unknown()
}
}
+48 -98
View File
@@ -304,13 +304,11 @@ internal class AstChecker(private val program: Program,
} else {
// Pass-by-reference datatypes can not occur as parameters to a subroutine directly
// Instead, their reference (address) should be passed (as an UWORD).
// TODO The language has no typed pointers at this time! This should be handled better.
// TODO The language has no (typed) pointers at this time. Should this be handled better?
if(subroutine.parameters.any{it.type in PassByReferenceDatatypes }) {
err("Pass-by-reference types (str, array) cannot occur as a parameter type directly. Instead, use an uword to receive their address, or access the variable from the outer scope directly.")
}
}
visitStatements(subroutine.statements)
}
override fun visit(untilLoop: UntilLoop) {
@@ -362,9 +360,15 @@ internal class AstChecker(private val program: Program,
}
}
if(assignment.value.inferType(program) != assignment.target.inferType(program, assignment))
val targetDt = assignment.target.inferType(program, assignment)
if(assignment.value.inferType(program) != targetDt)
errors.err("assignment value is of different type as the target", assignment.value.position)
if(assignment.value is TypecastExpression) {
if(assignment.isAugmentable && targetDt.istype(DataType.FLOAT))
errors.err("typecasting a float value in-place makes no sense", assignment.value.position)
}
super.visit(assignment)
}
@@ -434,7 +438,9 @@ internal class AstChecker(private val program: Program,
if(variable==null)
errors.err("pointer-of operand must be the name of a heap variable", addressOf.position)
else {
if(variable.datatype !in ArrayDatatypes && variable.datatype != DataType.STR && variable.datatype!=DataType.STRUCT)
if(variable.datatype !in ArrayDatatypes
&& variable.type!=VarDeclType.MEMORY
&& variable.datatype != DataType.STR && variable.datatype!=DataType.STRUCT)
errors.err("invalid pointer-of operand type", addressOf.position)
}
super.visit(addressOf)
@@ -710,12 +716,20 @@ internal class AstChecker(private val program: Program,
}
override fun visit(expr: PrefixExpression) {
val dt = expr.inferType(program).typeOrElse(DataType.STRUCT)
if(expr.operator=="-") {
val dt = expr.inferType(program).typeOrElse(DataType.STRUCT)
if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) {
errors.err("can only take negative of a signed number type", expr.position)
}
}
else if(expr.operator == "not") {
if(dt !in IntegerDatatypes)
errors.err("can only use boolean not on integer types", expr.position)
}
else if(expr.operator == "~") {
if(dt !in IntegerDatatypes)
errors.err("can only use bitwise invert on integer types", expr.position)
}
super.visit(expr)
}
@@ -758,7 +772,7 @@ internal class AstChecker(private val program: Program,
errors.err("bitwise operator can only be used on integer operands", expr.right.position)
}
"<<", ">>" -> {
// for now, bit-shifts can only shift by a constant number
// for now, bit-shifts can only shift by a constant number TODO remove this restriction
val constRight = expr.right.constValue(program)
if(constRight==null)
errors.err("bit-shift can only be done by a constant number (for now)", expr.right.position)
@@ -863,8 +877,9 @@ internal class AstChecker(private val program: Program,
}
val error = VerifyFunctionArgTypes.checkTypes(functionCallStatement, functionCallStatement.definingScope(), program)
if(error!=null)
errors.err(error, functionCallStatement.args.first().position)
if(error!=null) {
errors.err(error, functionCallStatement.args.firstOrNull()?.position ?: functionCallStatement.position)
}
super.visit(functionCallStatement)
}
@@ -874,79 +889,38 @@ internal class AstChecker(private val program: Program,
errors.err("cannot use arguments when calling a label", position)
if(target is BuiltinFunctionStatementPlaceholder) {
// it's a call to a builtin function.
val func = BuiltinFunctions.getValue(target.name)
if(args.size!=func.parameters.size)
errors.err("invalid number of arguments", position)
else {
val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD
for (arg in args.withIndex().zip(func.parameters)) {
val argDt=arg.first.value.inferType(program)
if (argDt.isKnown
&& !(argDt.typeOrElse(DataType.STRUCT) isAssignableTo arg.second.possibleDatatypes)
&& (argDt.typeOrElse(DataType.STRUCT) != DataType.UWORD || arg.second.possibleDatatypes.intersect(paramTypesForAddressOf).isEmpty())) {
errors.err("builtin function '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.possibleDatatypes}", position)
}
if(target.name=="swap") {
// swap() is a bit weird because this one is translated into a operations directly, instead of being a function call
val dt1 = args[0].inferType(program)
val dt2 = args[1].inferType(program)
if (dt1 != dt2)
errors.err("swap requires 2 args of identical type", position)
else if (args[0].constValue(program) != null || args[1].constValue(program) != null)
errors.err("swap requires 2 variables, not constant value(s)", position)
else if(args[0] isSameAs args[1])
errors.err("swap should have 2 different args", position)
else if(dt1.typeOrElse(DataType.STRUCT) !in NumericDatatypes)
errors.err("swap requires args of numerical type", position)
}
else if(target.name=="all" || target.name=="any") {
if((args[0] as? AddressOf)?.identifier?.targetVarDecl(program.namespace)?.datatype == DataType.STR) {
errors.err("any/all on a string is useless (is always true unless the string is empty)", position)
}
if(target.name=="swap") {
// swap() is a bit weird because this one is translated into a operations directly, instead of being a function call
val dt1 = args[0].inferType(program)
val dt2 = args[1].inferType(program)
if (dt1 != dt2)
errors.err("swap requires 2 args of identical type", position)
else if (args[0].constValue(program) != null || args[1].constValue(program) != null)
errors.err("swap requires 2 variables, not constant value(s)", position)
else if(args[0] isSameAs args[1])
errors.err("swap should have 2 different args", position)
else if(dt1.typeOrElse(DataType.STRUCT) !in NumericDatatypes)
errors.err("swap requires args of numerical type", position)
}
else if(target.name=="all" || target.name=="any") {
if((args[0] as? AddressOf)?.identifier?.targetVarDecl(program.namespace)?.datatype == DataType.STR) {
errors.err("any/all on a string is useless (is always true unless the string is empty)", position)
}
if(args[0].inferType(program).typeOrElse(DataType.STR) == DataType.STR) {
errors.err("any/all on a string is useless (is always true unless the string is empty)", position)
}
if(args[0].inferType(program).typeOrElse(DataType.STR) == DataType.STR) {
errors.err("any/all on a string is useless (is always true unless the string is empty)", position)
}
}
} else if(target is Subroutine) {
if(target.regXasResult())
errors.warn("subroutine call return value in X register is discarded and replaced by 0", position)
if(args.size!=target.parameters.size)
errors.err("invalid number of arguments", position)
else {
if(target.isAsmSubroutine) {
for (arg in args.withIndex().zip(target.parameters)) {
val argIDt = arg.first.value.inferType(program)
if(!argIDt.isKnown) {
if (!argIDt.isKnown)
return
}
val argDt=argIDt.typeOrElse(DataType.STRUCT)
if(!(argDt isAssignableTo arg.second.type)) {
// for asm subroutines having STR param it's okay to provide a UWORD (address value)
if(!(target.isAsmSubroutine && arg.second.type == DataType.STR && argDt == DataType.UWORD))
errors.err("subroutine '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.type}", position)
}
if(target.isAsmSubroutine) {
if (target.asmParameterRegisters[arg.first.index].registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.XY, RegisterOrPair.X)) {
if (arg.first.value !is NumericLiteralValue && arg.first.value !is IdentifierReference)
errors.warn("calling a subroutine that expects X as a parameter is problematic. If you see a compiler error/crash about this later, try to change this call", position)
}
// check if the argument types match the register(pairs)
val asmParamReg = target.asmParameterRegisters[arg.first.index]
if(asmParamReg.statusflag!=null) {
if(argDt !in ByteDatatypes)
errors.err("subroutine '${target.name}' argument ${arg.first.index + 1} must be byte type for statusflag", position)
} else if(asmParamReg.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
if(argDt !in ByteDatatypes)
errors.err("subroutine '${target.name}' argument ${arg.first.index + 1} must be byte type for single register", position)
} else if(asmParamReg.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
if(argDt !in WordDatatypes + IterableDatatypes)
errors.err("subroutine '${target.name}' argument ${arg.first.index + 1} must be word type for register pair", position)
}
}
if (target.asmParameterRegisters[arg.first.index].registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.XY, RegisterOrPair.X)
&& arg.first.value !is NumericLiteralValue && arg.first.value !is IdentifierReference)
errors.warn("calling a subroutine that expects X as a parameter is problematic. If you see a compiler error/crash about this later, try to change this call", position)
}
}
}
@@ -1067,30 +1041,6 @@ internal class AstChecker(private val program: Program,
}
}
override fun visit(scope: AnonymousScope) {
visitStatements(scope.statements)
}
private fun visitStatements(statements: List<Statement>) {
for((index, stmt) in statements.withIndex()) {
if(index < statements.lastIndex && statements[index+1] !is Subroutine) {
when {
stmt is FunctionCallStatement && stmt.target.nameInSource.last() == "exit" -> {
errors.warn("unreachable code, preceding exit call will never return", statements[index + 1].position)
}
stmt is Return && statements[index + 1] !is Subroutine -> {
errors.warn("unreachable code, preceding return statement", statements[index + 1].position)
}
stmt is Jump && statements[index + 1] !is Subroutine -> {
errors.warn("unreachable code, preceding jump statement", statements[index + 1].position)
}
}
}
stmt.accept(this)
}
}
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? {
val targetStatement = target.targetStatement(program.namespace)
if(targetStatement is Label || targetStatement is Subroutine || targetStatement is BuiltinFunctionStatementPlaceholder)
@@ -1265,7 +1215,7 @@ internal class AstChecker(private val program: Program,
is AddressOf -> it.identifier.heapId(program.namespace)
is TypecastExpression -> {
val constVal = it.expression.constValue(program)
constVal?.cast(it.type)?.number?.toInt() ?: -9999999
constVal?.castNoCheck(it.type)?.number?.toInt() ?: -9999999
}
else -> -9999999
}
@@ -47,18 +47,6 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
return noModifications
}
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") {
// lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte"
val typecast = TypecastExpression(functionCall.args.single(), DataType.UBYTE, false, functionCall.position)
return listOf(IAstModification.ReplaceNode(
functionCall, typecast, parent
))
}
return noModifications
}
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
// is it a struct variable? then define all its struct members as mangled names,
// and include the original decl as well.
@@ -52,7 +52,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
assignment))
} else {
fun castLiteral(cvalue: NumericLiteralValue): List<IAstModification.ReplaceNode> =
listOf(IAstModification.ReplaceNode(cvalue, cvalue.cast(targettype), cvalue.parent))
listOf(IAstModification.ReplaceNode(cvalue, cvalue.castNoCheck(targettype), cvalue.parent))
val cvalue = assignment.value.constValue(program)
if(cvalue!=null) {
val number = cvalue.number.toDouble()
@@ -109,14 +109,16 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
AddressOf(arg.second.value as IdentifierReference, arg.second.value.position),
call as Node)
} else if(arg.second.value is NumericLiteralValue) {
try {
val castedValue = (arg.second.value as NumericLiteralValue).cast(requiredType)
modifications += IAstModification.ReplaceNode(
call.args[arg.second.index],
castedValue,
call as Node)
} catch (x: ExpressionError) {
// no cast possible
if(argtype.isAssignableTo(requiredType)) {
try {
val castedValue = (arg.second.value as NumericLiteralValue).castNoCheck(requiredType)
modifications += IAstModification.ReplaceNode(
call.args[arg.second.index],
castedValue,
call as Node)
} catch (x: ExpressionError) {
// cast failed
}
}
}
}
@@ -161,7 +163,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
// make sure the memory address is an uword
val dt = memread.addressExpression.inferType(program)
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
val typecast = (memread.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)
val typecast = (memread.addressExpression as? NumericLiteralValue)?.castNoCheck(DataType.UWORD)
?: TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
return listOf(IAstModification.ReplaceNode(memread.addressExpression, typecast, memread))
}
@@ -172,7 +174,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
// make sure the memory address is an uword
val dt = memwrite.addressExpression.inferType(program)
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
val typecast = (memwrite.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)
val typecast = (memwrite.addressExpression as? NumericLiteralValue)?.castNoCheck(DataType.UWORD)
?: TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
return listOf(IAstModification.ReplaceNode(memwrite.addressExpression, typecast, memwrite))
}
@@ -189,7 +191,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
if (returnValue.inferType(program).istype(subReturnType))
return noModifications
if (returnValue is NumericLiteralValue) {
returnStmt.value = returnValue.cast(subroutine.returntypes.single())
returnStmt.value = returnValue.castNoCheck(subroutine.returntypes.single())
} else {
return listOf(IAstModification.ReplaceNode(
returnValue,
@@ -34,7 +34,7 @@ internal class VariousCleanups: AstWalker() {
override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
if(typecast.expression is NumericLiteralValue) {
val value = (typecast.expression as NumericLiteralValue).cast(typecast.type)
val value = (typecast.expression as NumericLiteralValue).castNoCheck(typecast.type)
return listOf(IAstModification.ReplaceNode(typecast, value, parent))
}
@@ -29,24 +29,32 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
fun checkTypes(call: IFunctionCall, scope: INameScope, program: Program): String? {
val argtypes = call.args.map { it.inferType(program).typeOrElse(DataType.STRUCT) }
val target = call.target.targetStatement(scope)
when (target) {
is Subroutine -> {
val paramtypes = target.parameters.map { it.type }
val mismatch = argtypes.zip(paramtypes).indexOfFirst { it.first != it.second}
if(mismatch>=0)
return "argument ${mismatch+1} type mismatch"
}
is BuiltinFunctionStatementPlaceholder -> {
val func = BuiltinFunctions.getValue(target.name)
val paramtypes = func.parameters.map { it.possibleDatatypes }
for (x in argtypes.zip(paramtypes).withIndex()) {
if (x.value.first !in x.value.second)
return "argument ${x.index+1} type mismatch"
}
}
else -> {
if (target is Subroutine) {
// asmsub types are not checked specifically at this time
if(call.args.size != target.parameters.size)
return "invalid number of arguments"
val paramtypes = target.parameters.map { it.type }
val mismatch = argtypes.zip(paramtypes).indexOfFirst { it.first != it.second}
if(mismatch>=0) {
val actual = argtypes[mismatch].toString()
val expected = paramtypes[mismatch].toString()
return "argument ${mismatch + 1} type mismatch, was: $actual expected: $expected"
}
}
else if (target is BuiltinFunctionStatementPlaceholder) {
val func = BuiltinFunctions.getValue(target.name)
if(call.args.size != func.parameters.size)
return "invalid number of arguments"
val paramtypes = func.parameters.map { it.possibleDatatypes }
for (x in argtypes.zip(paramtypes).withIndex()) {
if (x.value.first !in x.value.second) {
val actual = x.value.first.toString()
val expected = x.value.second.toString()
return "argument ${x.index + 1} type mismatch, was: $actual expected: $expected"
}
}
}
return null
}
}
@@ -139,15 +139,6 @@ open class Return(var value: Expression?, override val position: Position) : Sta
}
}
class ReturnFromIrq(override val position: Position) : Return(null, position) {
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun toString(): String {
return "ReturnFromIrq(pos=$position)"
}
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
}
class Break(override val position: Position) : Statement() {
override lateinit var parent: Node
@@ -337,32 +328,50 @@ open class Assignment(var target: AssignTarget, var value: Expression, override
return("Assignment(target: $target, value: $value, pos=$position)")
}
val isInplace: Boolean
/**
* Is the assigment value an expression that references the assignment target itself?
* The expression can be a BinaryExpression, PrefixExpression or TypecastExpression (possibly with one sub-cast).
*/
val isAugmentable: Boolean
get() {
val binExpr = value as? BinaryExpression
if(binExpr!=null) {
if(binExpr.left isSameAs target)
return true // A = A <operator> 5
if(binExpr.right !is BinaryExpression && binExpr.left isSameAs target)
return true // A = A <operator> v
if(binExpr.operator in associativeOperators) {
if (binExpr.right isSameAs target)
if (binExpr.left !is BinaryExpression && binExpr.right isSameAs target)
return true // A = v <associative-operator> A
val leftBinExpr = binExpr.left as? BinaryExpression
if(leftBinExpr?.operator == binExpr.operator) {
// one of these?
// A = (A <associative-operator> x) <same-operator> y
// A = (x <associative-operator> A) <same-operator> y
return leftBinExpr.left isSameAs target || leftBinExpr.right isSameAs target
// A = (x <associative-operator> y) <same-operator> A
return leftBinExpr.left isSameAs target || leftBinExpr.right isSameAs target || binExpr.right isSameAs target
}
val rightBinExpr = binExpr.right as? BinaryExpression
if(rightBinExpr?.operator == binExpr.operator) {
// one of these?
// A = y <associative-operator> (A <same-operator> x)
// A = y <associative-operator> (x <same-operator> y)
return rightBinExpr.left isSameAs target || rightBinExpr.right isSameAs target
// A = A <associative-operator> (x <same-operator> y)
return rightBinExpr.left isSameAs target || rightBinExpr.right isSameAs target || binExpr.left isSameAs target
}
}
}
val prefixExpr = value as? PrefixExpression
if(prefixExpr!=null)
return prefixExpr.expression isSameAs target
val castExpr = value as? TypecastExpression
if(castExpr!=null) {
val subCast = castExpr.expression as? TypecastExpression
return if(subCast!=null) subCast.expression isSameAs target else castExpr.expression isSameAs target
}
return false
}
}
@@ -624,7 +633,6 @@ class Subroutine(override val name: String,
override var statements: MutableList<Statement>,
override val position: Position) : Statement(), INameScope {
var keepAlways: Boolean = false
override lateinit var parent: Node
val scopedname: String by lazy { makeScopedName(name) }
@@ -89,7 +89,14 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
}
}
else if(sourceDt in PassByReferenceDatatypes) {
// Note: for various reasons (most importantly, code simplicity), the code generator assumes/requires
// that the types of assignment values and their target are the same,
// and that the types of both operands of a binaryexpression node are the same.
// So, it is not easily possible to remove the typecasts that are there to make these conditions true.
if(sourceDt in PassByReferenceDatatypes) {
if(typecast.type==DataType.UWORD) {
return listOf(IAstModification.ReplaceNode(
typecast,
+2 -1
View File
@@ -169,9 +169,10 @@ private fun optimizeAst(programAst: Program, errors: ErrorReporter) {
break
}
val remover = UnusedCodeRemover()
val remover = UnusedCodeRemover(errors)
remover.visit(programAst)
remover.applyModifications()
errors.handle()
}
private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
@@ -8,6 +8,7 @@ interface IMachineDefinition {
val FLOAT_MAX_NEGATIVE: Double
val FLOAT_MAX_POSITIVE: Double
val FLOAT_MEM_SIZE: Int
val POINTER_MEM_SIZE: Int
val opcodeNames: Set<String>
@@ -14,9 +14,9 @@ class AssemblyProgram(override val name: String, outputDir: Path) : IAssemblyPro
private val viceMonListFile = outputDir.resolve("$name.vice-mon-list")
override fun assemble(options: CompilationOptions) {
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", "-Wno-strict-bool", "-Wno-shadow", "-Werror", "-Wno-error=long-branch",
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
"--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor")
val outFile = when (options.output) {
@@ -14,6 +14,7 @@ object C64MachineDefinition: IMachineDefinition {
override val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
override val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
override val FLOAT_MEM_SIZE = 5
override val POINTER_MEM_SIZE = 2
const val BASIC_LOAD_ADDRESS = 0x0801
const val RAW_LOAD_ADDRESS = 0xc000
@@ -636,7 +636,11 @@ internal class AsmGen(private val program: Program,
is BranchStatement -> translate(stmt)
is IfStatement -> translate(stmt)
is ForLoop -> forloopsAsmGen.translate(stmt)
is Break -> out(" jmp ${loopEndLabels.peek()}")
is Break -> {
if(loopEndLabels.isEmpty())
throw AssemblyError("break statement out of context ${stmt.position}")
out(" jmp ${loopEndLabels.peek()}")
}
is WhileLoop -> translate(stmt)
is RepeatLoop -> translate(stmt)
is UntilLoop -> translate(stmt)
@@ -1004,7 +1008,6 @@ $counterVar .byte 0""")
val indexName = asmIdentifierName(index)
out(" lda $indexName")
}
// TODO optimize more cases
else -> {
expressionsAsmGen.translateExpression(index)
out(" inx | lda $ESTACK_LO_HEX,x")
@@ -1021,36 +1024,6 @@ $counterVar .byte 0""")
internal fun translateFunctionCall(functionCall: FunctionCall) =
functioncallAsmGen.translateFunctionCall(functionCall)
internal fun assignFromEvalResult(target: AssignTarget) =
assignmentAsmGen.assignFromEvalResult(target)
fun assignFromByteConstant(target: AssignTarget, value: Short) =
assignmentAsmGen.assignFromByteConstant(target, value)
fun assignFromWordConstant(target: AssignTarget, value: Int) =
assignmentAsmGen.assignFromWordConstant(target, value)
fun assignFromFloatConstant(target: AssignTarget, value: Double) =
assignmentAsmGen.assignFromFloatConstant(target, value)
fun assignFromByteVariable(target: AssignTarget, variable: IdentifierReference) =
assignmentAsmGen.assignFromByteVariable(target, variable)
fun assignFromWordVariable(target: AssignTarget, variable: IdentifierReference) =
assignmentAsmGen.assignFromWordVariable(target, variable)
fun assignFromAddressOf(target: AssignTarget, variable: IdentifierReference) =
assignmentAsmGen.assignFromAddressOf(target, variable)
fun assignFromFloatVariable(target: AssignTarget, variable: IdentifierReference) =
assignmentAsmGen.assignFromFloatVariable(target, variable)
fun assignFromRegister(target: AssignTarget, register: CpuRegister) =
assignmentAsmGen.assignFromRegister(target, register)
fun assignFromMemoryByte(target: AssignTarget, address: Int?, identifier: IdentifierReference?) =
assignmentAsmGen.assignFromMemoryByte(target, address, identifier)
fun assignToRegister(reg: CpuRegister, value: Short?, identifier: IdentifierReference?) =
internal fun assignToRegister(reg: CpuRegister, value: Short?, identifier: IdentifierReference?) =
assignmentAsmGen.assignToRegister(reg, value, identifier)
}
@@ -3,10 +3,7 @@ package prog8.compiler.target.c64.codegen
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.VarDecl
import prog8.ast.statements.*
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
@@ -17,34 +14,49 @@ import prog8.compiler.toHex
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen) {
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, asmgen)
internal fun translate(assign: Assignment) {
if(assign.isInplace)
translateNormalAssignment(assign) // TODO generate better code here for in-place assignments
else
translateNormalAssignment(assign)
when {
assign.value is NumericLiteralValue -> translateConstantValueAssignment(assign)
assign.value is IdentifierReference -> translateVariableAssignment(assign)
assign.isAugmentable -> augmentableAsmGen.translate(assign)
else -> translateOtherAssignment(assign)
}
}
// old code-generation below:
// eventually, all of this should have been replaced by newer more optimized code.
private fun translateNormalAssignment(assign: Assignment) {
internal fun assignToRegister(reg: CpuRegister, value: Short?, identifier: IdentifierReference?) {
if(value!=null) {
asmgen.out(" ld${reg.toString().toLowerCase()} #${value.toHex()}")
} else if(identifier!=null) {
val name = asmgen.asmIdentifierName(identifier)
asmgen.out(" ld${reg.toString().toLowerCase()} $name")
}
}
private fun translateVariableAssignment(assign: Assignment) {
val identifier = assign.value as IdentifierReference
when (val type = assign.target.inferType(program, assign).typeOrElse(DataType.STRUCT)) {
DataType.UBYTE, DataType.BYTE -> assignFromByteVariable(assign.target, identifier)
DataType.UWORD, DataType.WORD -> assignFromWordVariable(assign.target, identifier)
DataType.FLOAT -> assignFromFloatVariable(assign.target, identifier)
in PassByReferenceDatatypes -> assignFromAddressOf(assign.target, identifier)
else -> throw AssemblyError("unsupported assignment target type $type")
}
}
private fun translateConstantValueAssignment(assign: Assignment) {
val numVal = assign.value as NumericLiteralValue
when (numVal.type) {
DataType.UBYTE, DataType.BYTE -> assignFromByteConstant(assign.target, numVal.number.toShort())
DataType.UWORD, DataType.WORD -> assignFromWordConstant(assign.target, numVal.number.toInt())
DataType.FLOAT -> assignFromFloatConstant(assign.target, numVal.number.toDouble())
else -> throw AssemblyError("weird numval type")
}
}
internal fun translateOtherAssignment(assign: Assignment) {
when (assign.value) {
is NumericLiteralValue -> {
val numVal = assign.value as NumericLiteralValue
when (numVal.type) {
DataType.UBYTE, DataType.BYTE -> assignFromByteConstant(assign.target, numVal.number.toShort())
DataType.UWORD, DataType.WORD -> assignFromWordConstant(assign.target, numVal.number.toInt())
DataType.FLOAT -> assignFromFloatConstant(assign.target, numVal.number.toDouble())
else -> throw AssemblyError("weird numval type")
}
}
is IdentifierReference -> {
when (val type = assign.target.inferType(program, assign).typeOrElse(DataType.STRUCT)) {
DataType.UBYTE, DataType.BYTE -> assignFromByteVariable(assign.target, assign.value as IdentifierReference)
DataType.UWORD, DataType.WORD -> assignFromWordVariable(assign.target, assign.value as IdentifierReference)
DataType.FLOAT -> assignFromFloatVariable(assign.target, assign.value as IdentifierReference)
else -> throw AssemblyError("unsupported assignment target type $type")
}
}
is AddressOf -> {
val identifier = (assign.value as AddressOf).identifier
assignFromAddressOf(assign.target, identifier)
@@ -60,22 +72,21 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
assignFromMemoryByte(assign.target, null, read.addressExpression as IdentifierReference)
}
else -> {
throw AssemblyError("missing asm gen for memread assignment into ${assign.target}")
asmgen.translateExpression(read.addressExpression)
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | inx")
assignFromRegister(assign.target, CpuRegister.A)
}
}
}
is PrefixExpression -> {
// TODO optimize common cases
asmgen.translateExpression(assign.value as PrefixExpression)
assignFromEvalResult(assign.target)
}
is BinaryExpression -> {
// TODO optimize common cases
asmgen.translateExpression(assign.value as BinaryExpression)
assignFromEvalResult(assign.target)
}
is ArrayIndexedExpression -> {
// TODO optimize common cases
val arrayExpr = assign.value as ArrayIndexedExpression
val arrayDt = arrayExpr.identifier.inferType(program).typeOrElse(DataType.STRUCT)
val index = arrayExpr.arrayspec.index
@@ -102,10 +113,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
is TypecastExpression -> {
val cast = assign.value as TypecastExpression
val sourceType = cast.expression.inferType(program)
val targetType = assign.target.inferType(program, assign)
if (sourceType.isKnown && targetType.isKnown &&
(sourceType.typeOrElse(DataType.STRUCT) in ByteDatatypes && targetType.typeOrElse(DataType.STRUCT) in ByteDatatypes) ||
(sourceType.typeOrElse(DataType.STRUCT) in WordDatatypes && targetType.typeOrElse(DataType.STRUCT) in WordDatatypes)) {
if (sourceType.isKnown &&
(sourceType.typeOrElse(DataType.STRUCT) in ByteDatatypes && cast.type in ByteDatatypes) ||
(sourceType.typeOrElse(DataType.STRUCT) in WordDatatypes && cast.type in WordDatatypes)) {
// no need for a type cast
assign.value = cast.expression
translate(assign)
@@ -120,11 +130,14 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array assignment $assign")
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values ${assign.value.position}")
else -> throw AssemblyError("assignment value type ${assign.value} should have been handled elsewhere")
}
}
internal fun assignFromEvalResult(target: AssignTarget) {
private fun assignFromEvalResult(target: AssignTarget) {
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
val targetMemory = target.memoryAddress
when {
targetIdent != null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
@@ -151,14 +164,14 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
else -> throw AssemblyError("weird target variable type $targetDt")
}
}
target.memoryAddress != null -> {
asmgen.out(" inx | ldy $ESTACK_LO_HEX,x")
storeRegisterInMemoryAddress(CpuRegister.Y, target.memoryAddress)
targetMemory != null -> {
asmgen.out(" inx")
storeByteViaRegisterAInMemoryAddress("$ESTACK_LO_HEX,x", targetMemory)
}
target.arrayindexed != null -> {
val arrayDt = target.arrayindexed!!.identifier.inferType(program).typeOrElse(DataType.STRUCT)
val arrayVarName = asmgen.asmIdentifierName(target.arrayindexed!!.identifier)
asmgen.translateExpression(target.arrayindexed!!.arrayspec.index)
targetArrayIdx != null -> {
val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT)
val arrayVarName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
asmgen.translateExpression(targetArrayIdx.arrayspec.index)
asmgen.out(" inx | lda $ESTACK_LO_HEX,x")
popAndWriteArrayvalueWithIndexA(arrayDt, arrayVarName)
}
@@ -166,7 +179,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
internal fun assignFromAddressOf(target: AssignTarget, name: IdentifierReference) {
private fun assignFromAddressOf(target: AssignTarget, name: IdentifierReference) {
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
val struct = name.memberOfStruct(program.namespace)
@@ -204,7 +217,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
internal fun assignFromWordVariable(target: AssignTarget, variable: IdentifierReference) {
private fun assignFromWordVariable(target: AssignTarget, variable: IdentifierReference) {
val sourceName = asmgen.asmIdentifierName(variable)
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
@@ -234,7 +247,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
internal fun assignFromFloatVariable(target: AssignTarget, variable: IdentifierReference) {
private fun assignFromFloatVariable(target: AssignTarget, variable: IdentifierReference) {
val sourceName = asmgen.asmIdentifierName(variable)
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
@@ -265,10 +278,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
internal fun assignFromByteVariable(target: AssignTarget, variable: IdentifierReference) {
private fun assignFromByteVariable(target: AssignTarget, variable: IdentifierReference) {
val sourceName = asmgen.asmIdentifierName(variable)
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
val targetMemory = target.memoryAddress
when {
targetIdent != null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
@@ -277,6 +291,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
sta $targetName
""")
}
targetMemory != null -> {
storeByteViaRegisterAInMemoryAddress(sourceName, targetMemory)
}
targetArrayIdx != null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
@@ -286,34 +303,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.out(" inx | lda $ESTACK_LO_HEX,x")
popAndWriteArrayvalueWithIndexA(arrayDt, targetName)
}
target.memoryAddress != null -> {
val addressExpr = target.memoryAddress.addressExpression
val addressLv = addressExpr as? NumericLiteralValue
when {
addressLv != null -> asmgen.out(" lda $sourceName | sta ${addressLv.number.toHex()}")
addressExpr is IdentifierReference -> {
val targetName = asmgen.asmIdentifierName(addressExpr)
asmgen.out(" lda $sourceName | sta $targetName")
}
else -> {
asmgen.translateExpression(addressExpr)
asmgen.out("""
inx
lda $ESTACK_LO_HEX,x
ldy $ESTACK_HI_HEX,x
sta (+) +1
sty (+) +2
lda $sourceName
+ sta ${'$'}ffff ; modified
""")
}
}
}
else -> throw AssemblyError("no asm gen for assign bytevar to $target")
}
}
internal fun assignFromRegister(target: AssignTarget, register: CpuRegister) {
private fun assignFromRegister(target: AssignTarget, register: CpuRegister) {
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
when {
@@ -372,57 +366,80 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
private fun storeRegisterInMemoryAddress(register: CpuRegister, memoryAddress: DirectMemoryWrite) {
private fun storeByteViaRegisterAInMemoryAddress(ldaInstructionArg: String, memoryAddress: DirectMemoryWrite) {
val addressExpr = memoryAddress.addressExpression
val addressLv = addressExpr as? NumericLiteralValue
val registerName = register.name.toLowerCase()
when {
addressLv != null -> asmgen.out(" st$registerName ${addressLv.number.toHex()}")
addressLv != null -> {
asmgen.out(" lda $ldaInstructionArg | sta ${addressLv.number.toHex()}")
}
addressExpr is IdentifierReference -> {
val targetName = asmgen.asmIdentifierName(addressExpr)
when (register) {
CpuRegister.A -> asmgen.out("""
ldy $targetName
sty (+) +1
ldy $targetName+1
sty (+) +2
+ sta ${'$'}ffff ; modified""")
CpuRegister.X -> asmgen.out("""
ldy $targetName
sty (+) +1
ldy $targetName+1
sty (+) +2
+ stx ${'$'}ffff ; modified""")
CpuRegister.Y -> asmgen.out("""
lda $targetName
sta (+) +1
lda $targetName+1
sta (+) +2
+ sty ${'$'}ffff ; modified""")
}
val pointerVarName = asmgen.asmIdentifierName(addressExpr)
asmgen.out("""
lda $pointerVarName
sta ${C64Zeropage.SCRATCH_W2}
lda $pointerVarName+1
sta ${C64Zeropage.SCRATCH_W2+1}
lda $ldaInstructionArg
ldy #0
sta (${C64Zeropage.SCRATCH_W2}),y""")
}
else -> {
asmgen.saveRegister(register)
asmgen.translateExpression(addressExpr)
asmgen.restoreRegister(register)
when (register) {
CpuRegister.A -> asmgen.out(" tay")
CpuRegister.X -> throw AssemblyError("can't use X register here")
CpuRegister.Y -> {}
}
asmgen.out("""
inx
lda $ESTACK_LO_HEX,x
sta (+) +1
lda $ESTACK_HI_HEX,x
sta (+) +2
+ sty ${'$'}ffff ; modified
""")
inx
lda $ESTACK_LO_HEX,x
sta ${C64Zeropage.SCRATCH_W2}
lda $ESTACK_HI_HEX,x
sta ${C64Zeropage.SCRATCH_W2+1}
lda $ldaInstructionArg
ldy #0
sta (${C64Zeropage.SCRATCH_W2}),y""")
}
}
}
internal fun assignFromWordConstant(target: AssignTarget, word: Int) {
private fun storeRegisterInMemoryAddress(register: CpuRegister, memoryAddress: DirectMemoryWrite) {
// this is optimized for register A.
val addressExpr = memoryAddress.addressExpression
val addressLv = addressExpr as? NumericLiteralValue
val registerName = register.name.toLowerCase()
when {
addressLv != null -> {
asmgen.out(" st$registerName ${addressLv.number.toHex()}")
}
addressExpr is IdentifierReference -> {
val targetName = asmgen.asmIdentifierName(addressExpr)
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> asmgen.out(" txa")
CpuRegister.Y -> asmgen.out(" tya")
}
asmgen.out("""
ldy $targetName
sty ${C64Zeropage.SCRATCH_W1}
ldy $targetName+1
sty ${C64Zeropage.SCRATCH_W1+1}
ldy #0
sta (${C64Zeropage.SCRATCH_W1}),y""")
}
else -> {
asmgen.saveRegister(register)
asmgen.translateExpression(addressExpr)
asmgen.restoreRegister(CpuRegister.A)
asmgen.out("""
inx
ldy $ESTACK_LO_HEX,x
sty ${C64Zeropage.SCRATCH_W1}
ldy $ESTACK_HI_HEX,x
sty ${C64Zeropage.SCRATCH_W1+1}
ldy #0
sta (${C64Zeropage.SCRATCH_W1}),y""")
}
}
}
private fun assignFromWordConstant(target: AssignTarget, word: Int) {
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
when {
@@ -450,7 +467,6 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
targetArrayIdx != null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
// TODO optimize common cases
asmgen.translateExpression(index)
asmgen.out("""
inx
@@ -467,22 +483,21 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
internal fun assignFromByteConstant(target: AssignTarget, byte: Short) {
private fun assignFromByteConstant(target: AssignTarget, byte: Short) {
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
val targetMemory = target.memoryAddress
when {
targetIdent != null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out(" lda #${byte.toHex()} | sta $targetName ")
}
target.memoryAddress != null -> {
asmgen.out(" ldy #${byte.toHex()}")
storeRegisterInMemoryAddress(CpuRegister.Y, target.memoryAddress)
targetMemory != null -> {
storeByteViaRegisterAInMemoryAddress("#${byte.toHex()}", targetMemory)
}
targetArrayIdx != null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
// TODO optimize common cases
asmgen.translateExpression(index)
asmgen.out("""
inx
@@ -495,7 +510,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
internal fun assignFromFloatConstant(target: AssignTarget, float: Double) {
private fun assignFromFloatConstant(target: AssignTarget, float: Double) {
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
if (float == 0.0) {
@@ -594,9 +609,10 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
internal fun assignFromMemoryByte(target: AssignTarget, address: Int?, identifier: IdentifierReference?) {
private fun assignFromMemoryByte(target: AssignTarget, address: Int?, identifier: IdentifierReference?) {
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
val targetMemory = target.memoryAddress
if (address != null) {
when {
targetIdent != null -> {
@@ -606,9 +622,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
sta $targetName
""")
}
target.memoryAddress != null -> {
asmgen.out(" ldy ${address.toHex()}")
storeRegisterInMemoryAddress(CpuRegister.Y, target.memoryAddress)
targetMemory != null -> {
storeByteViaRegisterAInMemoryAddress(address.toHex(), targetMemory)
}
targetArrayIdx != null -> {
val index = targetArrayIdx.arrayspec.index
@@ -624,15 +639,15 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out("""
lda $sourceName
sta (+) + 1
sta ${C64Zeropage.SCRATCH_W1}
lda $sourceName+1
sta (+) + 2
+ lda ${'$'}ffff\t; modified
sta ${C64Zeropage.SCRATCH_W1+1}
ldy #0
lda (${C64Zeropage.SCRATCH_W1}),y
sta $targetName""")
}
target.memoryAddress != null -> {
asmgen.out(" ldy $sourceName")
storeRegisterInMemoryAddress(CpuRegister.Y, target.memoryAddress)
targetMemory != null -> {
storeByteViaRegisterAInMemoryAddress(sourceName, targetMemory)
}
targetArrayIdx != null -> {
val index = targetArrayIdx.arrayspec.index
@@ -663,13 +678,4 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
throw AssemblyError("weird array type")
}
}
fun assignToRegister(reg: CpuRegister, value: Short?, identifier: IdentifierReference?) {
if(value!=null) {
asmgen.out(" ld${reg.toString().toLowerCase()} #${value.toHex()}")
} else if(identifier!=null) {
val name = asmgen.asmIdentifierName(identifier)
asmgen.out(" ld${reg.toString().toLowerCase()} $name")
}
}
}
File diff suppressed because it is too large. Load diff
@@ -35,6 +35,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
when (functionName) {
"msb" -> funcMsb(fcall)
"lsb" -> funcLsb(fcall)
"mkword" -> funcMkword(fcall, func)
"abs" -> funcAbs(fcall, func)
"swap" -> funcSwap(fcall)
@@ -584,7 +585,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
throw AssemblyError("msb required word argument")
if (arg is NumericLiteralValue)
throw AssemblyError("should have been const-folded")
throw AssemblyError("msb(const) should have been const-folded away")
if (arg is IdentifierReference) {
val sourceName = asmgen.asmIdentifierName(arg)
asmgen.out(" lda $sourceName+1 | sta $ESTACK_LO_HEX,x | dex")
@@ -594,6 +595,21 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
}
private fun funcLsb(fcall: IFunctionCall) {
val arg = fcall.args.single()
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
throw AssemblyError("lsb required word argument")
if (arg is NumericLiteralValue)
throw AssemblyError("lsb(const) should have been const-folded away")
if (arg is IdentifierReference) {
val sourceName = asmgen.asmIdentifierName(arg)
asmgen.out(" lda $sourceName | sta $ESTACK_LO_HEX,x | dex")
} else {
asmgen.translateExpression(arg)
// just ignore any high-byte
}
}
private fun outputPushAddressAndLenghtOfArray(arg: Expression) {
arg as IdentifierReference
val identifierName = asmgen.asmIdentifierName(arg)
@@ -140,16 +140,17 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
val sourceName = asmgen.asmIdentifierName(expr.addressExpression as IdentifierReference)
asmgen.out("""
lda $sourceName
sta (+) +1
sta ${C64MachineDefinition.C64Zeropage.SCRATCH_W1}
lda $sourceName+1
sta (+) +2
+ lda ${'$'}ffff ; modified
sta ${C64MachineDefinition.C64Zeropage.SCRATCH_W1+1}
ldy #0
lda (${C64MachineDefinition.C64Zeropage.SCRATCH_W1}),y
sta $ESTACK_LO_HEX,x
dex""")
}
else -> {
translateExpression(expr.addressExpression)
asmgen.out(" jsr prog8_lib.read_byte_from_address")
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack")
asmgen.out(" sta $ESTACK_LO_PLUS1_HEX,x")
}
}
@@ -5,6 +5,7 @@ import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.Subroutine
import prog8.ast.statements.SubroutineParameter
import prog8.compiler.AssemblyError
@@ -107,48 +108,9 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
val paramVar = parameter.value
val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".")
val target = AssignTarget(IdentifierReference(scopedParamVar, sub.position), null, null, sub.position)
target.linkParents(value.parent)
when (value) {
is NumericLiteralValue -> {
// optimize when the argument is a constant literal
when(parameter.value.type) {
in ByteDatatypes -> asmgen.assignFromByteConstant(target, value.number.toShort())
in WordDatatypes -> asmgen.assignFromWordConstant(target, value.number.toInt())
DataType.FLOAT -> asmgen.assignFromFloatConstant(target, value.number.toDouble())
else -> throw AssemblyError("weird parameter datatype")
}
}
is IdentifierReference -> {
// optimize when the argument is a variable
when (parameter.value.type) {
in ByteDatatypes -> asmgen.assignFromByteVariable(target, value)
in WordDatatypes -> asmgen.assignFromWordVariable(target, value)
DataType.FLOAT -> asmgen.assignFromFloatVariable(target, value)
in PassByReferenceDatatypes -> asmgen.assignFromAddressOf(target, value)
else -> throw AssemblyError("weird parameter datatype")
}
}
is DirectMemoryRead -> {
when(value.addressExpression) {
is NumericLiteralValue -> {
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
asmgen.assignFromMemoryByte(target, address, null)
}
is IdentifierReference -> {
asmgen.assignFromMemoryByte(target, null, value.addressExpression as IdentifierReference)
}
else -> {
asmgen.translateExpression(value.addressExpression)
asmgen.out(" jsr prog8_lib.read_byte_from_address | inx")
asmgen.assignFromRegister(target, CpuRegister.A)
}
}
}
else -> {
asmgen.translateExpression(value)
asmgen.assignFromEvalResult(target)
}
}
val assign = Assignment(target, value, value.position)
assign.linkParents(value.parent)
asmgen.translate(assign)
}
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
@@ -1,744 +0,0 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.VarDecl
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.toHex
// OLD inplace-assignment code.
// should really come up with a more compact way to generate this kind of code...
/***
internal class InplaceAssignmentAsmGen(private val program: Program, private val errors: ErrorReporter, private val asmgen: AsmGen) {
internal fun translate(assign: Assignment) {
if (assign.aug_op == null)
translateNormalAssignment(assign)
else
translateInplaceAssignment(assign)
}
private fun translateInplaceAssignment(assign: Assignment) {
require(assign.aug_op != null)
when {
assign.target.identifier != null -> {
if (inplaceAssignToIdentifier(assign))
return
}
assign.target.memoryAddress != null -> {
if (inplaceAssignToMemoryByte(assign))
return
}
assign.target.arrayindexed != null -> {
if (inplaceAssignToArrayOrString(assign))
return
}
}
// TODO this is the slow FALLBACK, eventually we don't want to have to use it anymore:
errors.warn("using suboptimal in-place assignment code (this should still be optimized)", assign.position)
val normalAssignment = assign.asDesugaredNonaugmented()
return translateNormalAssignment(normalAssignment)
}
private fun inplaceAssignToArrayOrString(assign: Assignment): Boolean {
val targetArray = assign.target.arrayindexed!!
val arrayName = targetArray.identifier
val arrayIndex = targetArray.arrayspec.index
val targetName = asmgen.asmIdentifierName(arrayName)
val arrayDt = arrayName.targetVarDecl(program.namespace)!!.datatype
val constValue = assign.value.constValue(program)?.number
if (constValue != null) {
// constant value to set in array
val hexValue = constValue.toHex()
if (assign.aug_op == "setvalue") {
when (arrayDt) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> {
if (arrayIndex is NumericLiteralValue)
asmgen.out(" ldy #${arrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoY(targetArray)
asmgen.out(" lda #$hexValue | sta $targetName,y")
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
if (arrayIndex is NumericLiteralValue)
asmgen.out(" lda #${arrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoA(targetArray)
asmgen.out("""
asl a
tay
lda #<$hexValue
sta $targetName,y
lda #>$hexValue
sta $targetName+1,y
""")
}
DataType.ARRAY_F -> {
assignFromFloatConstant(assign.target, constValue.toDouble())
}
else -> throw AssemblyError("assignment to array: invalid array dt $arrayDt")
}
} else {
TODO("aug assignment to element in array/string")
}
return true
}
// non-const value.
// !!! DON'T FORGET : CAN BE AUGMENTED ASSIGNMENT !!!
when (assign.value) {
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(assign.value as IdentifierReference)
when(arrayDt) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> {
asmgen.out(" lda $sourceName")
if (arrayIndex is NumericLiteralValue)
asmgen.out(" ldy #${arrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoY(targetArray)
asmgen.out(" sta $targetName,y")
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
if (arrayIndex is NumericLiteralValue)
asmgen.out(" lda #${arrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoA(targetArray)
asmgen.out("""
asl a
tay
lda $sourceName
sta $targetName,y
lda $sourceName+1
sta $targetName+1,y
""")
}
DataType.ARRAY_F -> return false // TODO optimize instead of fallback?
else -> throw AssemblyError("assignment to array: invalid array dt $arrayDt")
}
return true
}
is AddressOf -> {
TODO("assign address into array $assign")
}
is DirectMemoryRead -> {
TODO("assign memory read into array $assign")
}
is ArrayIndexedExpression -> {
if(assign.aug_op != "setvalue")
return false // we don't put effort into optimizing anything beside simple assignment
val valueArrayExpr = assign.value as ArrayIndexedExpression
val valueArrayIndex = valueArrayExpr.arrayspec.index
val valueVariablename = asmgen.asmIdentifierName(valueArrayExpr.identifier)
// val valueDt = valueArrayExpr.identifier.inferType(program).typeOrElse(DataType.STRUCT)
when(arrayDt) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.STR -> {
if (valueArrayIndex is NumericLiteralValue)
asmgen.out(" ldy #${valueArrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoY(valueArrayExpr)
asmgen.out(" lda $valueVariablename,y")
if (arrayIndex is NumericLiteralValue)
asmgen.out(" ldy #${arrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoY(targetArray)
asmgen.out(" sta $targetName,y")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
if (valueArrayIndex is NumericLiteralValue)
asmgen.out(" ldy #2*${valueArrayIndex.number.toHex()}")
else {
asmgen.translateArrayIndexIntoA(valueArrayExpr)
asmgen.out(" asl a | tay")
}
asmgen.out("""
lda $valueVariablename,y
pha
lda $valueVariablename+1,y
pha
""")
if (arrayIndex is NumericLiteralValue)
asmgen.out(" ldy #2*${arrayIndex.number.toHex()}")
else {
asmgen.translateArrayIndexIntoA(targetArray)
asmgen.out(" asl a | tay")
}
asmgen.out("""
pla
sta $targetName+1,y
pla
sta $targetName,y
""")
return true
}
DataType.ARRAY_F -> {
if (valueArrayIndex is NumericLiteralValue)
asmgen.out(" ldy #5*${valueArrayIndex.number.toHex()}")
else {
asmgen.translateArrayIndexIntoA(valueArrayExpr)
asmgen.out("""
sta ${C64Zeropage.SCRATCH_REG}
asl a
asl a
clc
adc ${C64Zeropage.SCRATCH_REG}
tay
""")
}
asmgen.out("""
lda $valueVariablename,y
pha
lda $valueVariablename+1,y
pha
lda $valueVariablename+2,y
pha
lda $valueVariablename+3,y
pha
lda $valueVariablename+4,y
pha
""")
if (arrayIndex is NumericLiteralValue)
asmgen.out(" ldy #5*${arrayIndex.number.toHex()}")
else {
asmgen.translateArrayIndexIntoA(targetArray)
asmgen.out("""
sta ${C64Zeropage.SCRATCH_REG}
asl a
asl a
clc
adc ${C64Zeropage.SCRATCH_REG}
tay
""")
}
asmgen.out("""
pla
sta $targetName+4,y
pla
sta $targetName+3,y
pla
sta $targetName+2,y
pla
sta $targetName+1,y
pla
sta $targetName,y
""")
return true
}
else -> throw AssemblyError("assignment to array: invalid array dt")
}
return true
}
else -> {
fallbackAssignment(assign)
return true
}
}
return false
}
private fun inplaceAssignToMemoryByte(assign: Assignment): Boolean {
val address = assign.target.memoryAddress?.addressExpression?.constValue(program)?.number
?: return inplaceAssignToNonConstMemoryByte(assign)
val hexAddr = address.toHex()
val constValue = assign.value.constValue(program)
if (constValue != null) {
val hexValue = constValue.number.toHex()
when (assign.aug_op) {
"setvalue" -> asmgen.out(" lda #$hexValue | sta $hexAddr")
"+=" -> asmgen.out(" lda $hexAddr | clc | adc #$hexValue | sta $hexAddr")
"-=" -> asmgen.out(" lda $hexAddr | sec | sbc #$hexValue | sta $hexAddr")
"/=" -> TODO("membyte /= const $hexValue")
"*=" -> TODO("membyte *= const $hexValue")
"&=" -> asmgen.out(" lda $hexAddr | and #$hexValue | sta $hexAddr")
"|=" -> asmgen.out(" lda $hexAddr | ora #$hexValue | sta $hexAddr")
"^=" -> asmgen.out(" lda $hexAddr | eor #$hexValue | sta $hexAddr")
"%=" -> TODO("membyte %= const $hexValue")
"<<=" -> throw AssemblyError("<<= should have been replaced by lsl()")
">>=" -> throw AssemblyError("<<= should have been replaced by lsr()")
else -> throw AssemblyError("invalid aug_op ${assign.aug_op}")
}
return true
}
// non-const value.
when (assign.value) {
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(assign.value as IdentifierReference)
when(assign.aug_op) {
"setvalue" -> asmgen.out(" lda $sourceName | sta $hexAddr")
else -> TODO("membyte aug.assign variable $assign")
}
return true
}
is DirectMemoryRead -> {
val memory = (assign.value as DirectMemoryRead).addressExpression.constValue(program)!!.number.toHex()
when(assign.aug_op) {
"setvalue" -> asmgen.out(" lda $memory | sta $hexAddr")
else -> TODO("membyte aug.assign memread $assign")
}
return true
}
is ArrayIndexedExpression -> {
TODO("membyte = array value $assign")
}
is AddressOf -> throw AssemblyError("can't assign address to byte")
else -> {
fallbackAssignment(assign)
return true
}
}
return false
}
private fun inplaceAssignToNonConstMemoryByte(assign: Assignment): Boolean {
// target address is not constant, so evaluate it from the stack
asmgen.translateExpression(assign.target.memoryAddress!!.addressExpression)
asmgen.out("""
inx
lda $ESTACK_LO_HEX,x
sta ${C64Zeropage.SCRATCH_W1}
lda $ESTACK_HI_HEX,x
sta ${C64Zeropage.SCRATCH_W1}+1
""")
val constValue = assign.value.constValue(program)
if (constValue != null) {
val hexValue = constValue.number.toHex()
asmgen.out(" ldy #0")
when (assign.aug_op) {
"setvalue" -> asmgen.out(" lda #$hexValue | sta (${C64Zeropage.SCRATCH_W1}),y")
"+=" -> asmgen.out(" lda (${C64Zeropage.SCRATCH_W1}),y | clc | adc #$hexValue | sta (${C64Zeropage.SCRATCH_W1}),y")
"-=" -> asmgen.out(" lda (${C64Zeropage.SCRATCH_W1}),y | sec | sbc #$hexValue | sta (${C64Zeropage.SCRATCH_W1}),y")
"/=" -> TODO("membyte /= const $hexValue")
"*=" -> TODO("membyte *= const $hexValue")
"&=" -> asmgen.out(" lda (${C64Zeropage.SCRATCH_W1}),y | and #$hexValue | sta (${C64Zeropage.SCRATCH_W1}),y")
"|=" -> asmgen.out(" lda (${C64Zeropage.SCRATCH_W1}),y | ora #$hexValue | sta (${C64Zeropage.SCRATCH_W1}),y")
"^=" -> asmgen.out(" lda (${C64Zeropage.SCRATCH_W1}),y | eor #$hexValue | sta (${C64Zeropage.SCRATCH_W1}),y")
"%=" -> TODO("membyte %= const $hexValue")
"<<=" -> throw AssemblyError("<<= should have been replaced by lsl()")
">>=" -> throw AssemblyError("<<= should have been replaced by lsr()")
else -> throw AssemblyError("invalid aug_op ${assign.aug_op}")
}
return true
}
// non-const value.
// !!! DON'T FORGET : CAN BE AUGMENTED ASSIGNMENT !!!
when (assign.value) {
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(assign.value as IdentifierReference)
TODO("membyte = variable $assign")
}
is DirectMemoryRead -> {
TODO("membyte = memread $assign")
}
is ArrayIndexedExpression -> {
if (assign.aug_op == "setvalue") {
val arrayExpr = assign.value as ArrayIndexedExpression
val arrayIndex = arrayExpr.arrayspec.index
val variablename = asmgen.asmIdentifierName(arrayExpr.identifier)
val arrayDt = arrayExpr.identifier.inferType(program).typeOrElse(DataType.STRUCT)
if (arrayDt != DataType.ARRAY_B && arrayDt != DataType.ARRAY_UB && arrayDt != DataType.STR)
throw AssemblyError("assign to memory byte: expected byte array or string source")
if (arrayIndex is NumericLiteralValue)
asmgen.out(" ldy #${arrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoY(arrayExpr)
asmgen.out("""
lda $variablename,y
ldy #0
sta (${C64Zeropage.SCRATCH_W1}),y
""")
} else {
// TODO optimize more augmented assignment cases
val normalAssign = assign.asDesugaredNonaugmented()
asmgen.translateExpression(normalAssign.value)
assignFromEvalResult(normalAssign.target)
}
return true
}
is AddressOf -> throw AssemblyError("can't assign memory address to memory byte")
else -> {
fallbackAssignment(assign)
return true
}
}
return false // TODO optimized
}
private fun inplaceAssignToIdentifier(assign: Assignment): Boolean {
val targetType = assign.target.inferType(program, assign)
val constNumber = assign.value.constValue(program)?.number
val targetName = asmgen.asmIdentifierName(assign.target.identifier!!)
when (targetType.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE, DataType.BYTE -> {
// (u)byte assignment
if (constNumber != null) {
val hexValue = constNumber.toHex()
when (assign.aug_op) {
"setvalue" -> asmgen.out(" lda #$hexValue | sta $targetName")
"+=" -> asmgen.out(" lda $targetName | clc | adc #$hexValue | sta $targetName")
"-=" -> asmgen.out(" lda $targetName | sec | sbc #$hexValue | sta $targetName")
"/=" -> TODO("variable /= const $hexValue")
"*=" -> TODO("variable *= const $hexValue")
"&=" -> asmgen.out(" lda $targetName | and #$hexValue | sta $targetName")
"|=" -> asmgen.out(" lda $targetName | ora #$hexValue | sta $targetName")
"^=" -> asmgen.out(" lda $targetName | eor #$hexValue | sta $targetName")
"%=" -> TODO("variable %= const $hexValue")
"<<=" -> throw AssemblyError("<<= should have been replaced by lsl()")
">>=" -> throw AssemblyError("<<= should have been replaced by lsr()")
else -> throw AssemblyError("invalid aug_op ${assign.aug_op}")
}
return true
}
// non-const (u)byte value
// !!! DON'T FORGET : CAN BE AUGMENTED ASSIGNMENT !!!
when (assign.value) {
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(assign.value as IdentifierReference)
when (assign.aug_op) {
"setvalue" -> asmgen.out(" lda $sourceName | sta $targetName")
"+=" -> asmgen.out(" lda $targetName | clc | adc $sourceName | sta $targetName")
"-=" -> asmgen.out(" lda $targetName | sec | sbc $sourceName | sta $targetName")
"/=" -> TODO("variable /= variable")
"*=" -> TODO("variable *= variable")
"&=" -> asmgen.out(" lda $targetName | and $sourceName | sta $targetName")
"|=" -> asmgen.out(" lda $targetName | ora $sourceName | sta $targetName")
"^=" -> asmgen.out(" lda $targetName | eor $sourceName | sta $targetName")
"%=" -> TODO("variable %= variable")
"<<=" -> throw AssemblyError("<<= should have been replaced by lsl()")
">>=" -> throw AssemblyError("<<= should have been replaced by lsr()")
else -> throw AssemblyError("invalid aug_op ${assign.aug_op}")
}
return true
}
is DirectMemoryRead -> {
TODO("variable = memory read $assign")
}
is ArrayIndexedExpression -> {
if (assign.aug_op == "setvalue") {
val arrayExpr = assign.value as ArrayIndexedExpression
val arrayIndex = arrayExpr.arrayspec.index
val variablename = asmgen.asmIdentifierName(arrayExpr.identifier)
val arrayDt = arrayExpr.identifier.inferType(program).typeOrElse(DataType.STRUCT)
if (arrayDt != DataType.ARRAY_B && arrayDt != DataType.ARRAY_UB && arrayDt != DataType.STR)
throw AssemblyError("assign to identifier: expected byte array or string source")
if (arrayIndex is NumericLiteralValue)
asmgen.out(" ldy #${arrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoY(arrayExpr)
asmgen.out(" lda $variablename,y | sta $targetName")
} else {
// TODO optimize more augmented assignment cases
val normalAssign = assign.asDesugaredNonaugmented()
asmgen.translateExpression(normalAssign.value)
assignFromEvalResult(normalAssign.target)
}
return true
}
else -> {
fallbackAssignment(assign)
return true
}
}
}
DataType.UWORD, DataType.WORD -> {
if (constNumber != null) {
val hexNumber = constNumber.toHex()
when (assign.aug_op) {
"setvalue" -> {
asmgen.out("""
lda #<$hexNumber
sta $targetName
lda #>$hexNumber
sta $targetName+1
""")
}
"+=" -> {
asmgen.out("""
lda $targetName
clc
adc #<$hexNumber
sta $targetName
lda $targetName+1
adc #>$hexNumber
sta $targetName+1
""")
}
"-=" -> {
asmgen.out("""
lda $targetName
sec
sbc #<$hexNumber
sta $targetName
lda $targetName+1
sbc #>$hexNumber
sta $targetName+1
""")
}
else -> TODO("variable aug.assign ${assign.aug_op} const $hexNumber")
}
return true
}
// non-const value
// !!! DON'T FORGET : CAN BE AUGMENTED ASSIGNMENT !!!
when (assign.value) {
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(assign.value as IdentifierReference)
when (assign.aug_op) {
"setvalue" -> {
asmgen.out("""
lda $sourceName
sta $targetName
lda $sourceName+1
sta $targetName+1
""")
}
"+=" -> {
asmgen.out("""
lda $targetName
clc
adc $sourceName
sta $targetName
lda $targetName+1
adc $sourceName+1
sta $targetName+1
""")
return true
}
"-=" -> {
asmgen.out("""
lda $targetName
sec
sbc $sourceName
sta $targetName
lda $targetName+1
sbc $sourceName+1
sta $targetName+1
""")
return true
}
else -> {
TODO("variable aug.assign variable")
}
}
return true
}
is DirectMemoryRead -> throw AssemblyError("expected a typecast for assigning memory read byte to word")
is AddressOf -> {
val name = asmgen.asmIdentifierName((assign.value as AddressOf).identifier)
asmgen.out(" lda #<$name | sta $targetName | lda #>$name | sta $targetName+1")
return true
}
is ArrayIndexedExpression -> {
if (assign.aug_op == "setvalue") {
val arrayExpr = assign.value as ArrayIndexedExpression
val arrayIndex = arrayExpr.arrayspec.index
val variablename = asmgen.asmIdentifierName(arrayExpr.identifier)
val arrayDt = arrayExpr.identifier.inferType(program).typeOrElse(DataType.STRUCT)
if (arrayDt != DataType.ARRAY_W && arrayDt != DataType.ARRAY_UW)
throw AssemblyError("assign to identifier: expected word array source")
if (arrayIndex is NumericLiteralValue)
asmgen.out(" lda #${arrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoA(arrayExpr)
asmgen.out("""
asl a
tay
lda $variablename,y
sta $targetName
lda $variablename+1,y
sta $targetName+1
""")
} else {
// TODO optimize more augmented assignment cases
val normalAssign = assign.asDesugaredNonaugmented()
asmgen.translateExpression(normalAssign.value)
assignFromEvalResult(normalAssign.target)
}
return true
}
else -> {
fallbackAssignment(assign)
return true
}
}
}
DataType.FLOAT -> {
if (constNumber != null) {
// assign a constant
val floatConst = asmgen.getFloatConst(constNumber.toDouble())
when (assign.aug_op) {
"setvalue" -> assignFromFloatConstant(assign.target, constNumber.toDouble())
"+=" -> {
if (constNumber == 0.5) {
asmgen.out("""
lda #<$targetName
ldy #>$targetName
jsr c64flt.MOVFM
jsr c64flt.FADDH
stx c64.SCRATCH_ZPREGX
ldx #<$targetName
ldy #>$targetName
jsr c64flt.MOVMF
ldx c64.SCRATCH_ZPREGX
""")
} else {
asmgen.out("""
lda #<$targetName
ldy #>$targetName
jsr c64flt.MOVFM
lda #<$floatConst
ldy #>$floatConst
jsr c64flt.FADD
stx c64.SCRATCH_ZPREGX
ldx #<$targetName
ldy #>$targetName
jsr c64flt.MOVMF
ldx c64.SCRATCH_ZPREGX
""")
}
return true
}
"-=" -> {
asmgen.out("""
lda #<$floatConst
ldy #>$floatConst
jsr c64flt.MOVFM
lda #<$targetName
ldy #>$targetName
jsr c64flt.FSUB
stx c64.SCRATCH_ZPREGX
ldx #<$targetName
ldy #>$targetName
jsr c64flt.MOVMF
ldx c64.SCRATCH_ZPREGX
""")
return true
}
else -> TODO("float const value aug.assign $assign")
}
return true
}
// non-const float value.
// !!! DON'T FORGET : CAN BE AUGMENTED ASSIGNMENT !!!
when (assign.value) {
is IdentifierReference -> {
when (assign.aug_op) {
"setvalue" -> assignFromFloatVariable(assign.target, assign.value as IdentifierReference)
"+=" -> return false // TODO optimized float += variable
"-=" -> return false // TODO optimized float -= variable
else -> TODO("float non-const value aug.assign $assign")
}
return true
}
is ArrayIndexedExpression -> {
when(assign.aug_op) {
"setvalue" -> {
val arrayExpr = assign.value as ArrayIndexedExpression
val arrayIndex = arrayExpr.arrayspec.index
val variablename = asmgen.asmIdentifierName(arrayExpr.identifier)
val arrayDt = arrayExpr.identifier.inferType(program).typeOrElse(DataType.STRUCT)
if (arrayDt != DataType.ARRAY_F)
throw AssemblyError("assign to identifier: expected float array source")
if (arrayIndex is NumericLiteralValue)
asmgen.out(" lda #${arrayIndex.number.toHex()}")
else
asmgen.translateArrayIndexIntoA(arrayExpr)
asmgen.out("""
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1
tay
lda $variablename,y
sta $targetName
lda $variablename+1,y
sta $targetName+1
lda $variablename+2,y
sta $targetName+2
lda $variablename+3,y
sta $targetName+3
lda $variablename+4,y
sta $targetName+4
""")
}
else -> TODO("float $assign")
}
return true
}
else -> {
fallbackAssignment(assign)
return true
}
}
}
DataType.STR -> {
val identifier = assign.value as? IdentifierReference
?: throw AssemblyError("string value assignment expects identifier value")
val sourceName = asmgen.asmIdentifierName(identifier)
asmgen.out("""
lda #<$targetName
sta ${C64Zeropage.SCRATCH_W1}
lda #>$targetName
sta ${C64Zeropage.SCRATCH_W1+1}
lda #<$sourceName
ldy #>$sourceName
jsr prog8_lib.strcpy
""")
return true
}
else -> throw AssemblyError("assignment to identifier: invalid target datatype: $targetType")
}
return false
}
private fun fallbackAssignment(assign: Assignment) {
if (assign.aug_op != "setvalue") {
/* stack-based evaluation of the expression is required */
val normalAssign = assign.asDesugaredNonaugmented()
asmgen.translateExpression(normalAssign.value)
assignFromEvalResult(normalAssign.target)
} else {
when (assign.value) {
is FunctionCall -> {
// TODO is there a way to avoid function return value being passed via the stack?
// for instance, 1 byte return value always in A, etc
val normalAssign = assign.asDesugaredNonaugmented()
asmgen.translateExpression(normalAssign.value)
assignFromEvalResult(normalAssign.target)
}
else -> {
/* stack-based evaluation of the expression is required */
val normalAssign = assign.asDesugaredNonaugmented()
asmgen.translateExpression(normalAssign.value)
assignFromEvalResult(normalAssign.target)
}
}
}
}
}
***/
@@ -7,6 +7,8 @@ import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.PostIncrDecr
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.toHex
@@ -53,7 +55,20 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
else
asmgen.out("+\tdec ${'$'}ffff\t; modified")
}
else -> throw AssemblyError("weird target type $targetMemory")
else -> {
asmgen.translateExpression(addressExpr)
asmgen.out("""
inx
lda $ESTACK_LO_HEX,x
sta (+) + 1
lda $ESTACK_HI_HEX,x
sta (+) + 2
""")
if(incr)
asmgen.out("+\tinc ${'$'}ffff\t; modified")
else
asmgen.out("+\tdec ${'$'}ffff\t; modified")
}
}
}
targetArrayIdx!=null -> {
@@ -3,6 +3,8 @@ package prog8.functions
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.StructDecl
import prog8.ast.statements.VarDecl
import prog8.compiler.CompilerException
import kotlin.math.*
@@ -35,6 +37,7 @@ val BuiltinFunctions = mapOf(
"sum" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
"abs" to FSignature(true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
"len" to FSignature(true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
"sizeof" to FSignature(true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinSizeof),
// normal functions follow:
"sgn" to FSignature(true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
"sin" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
@@ -240,6 +243,42 @@ private fun builtinAbs(args: List<Expression>, position: Position, program: Prog
}
}
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
// 1 arg, type = anything, result type = ubyte
if(args.size!=1)
throw SyntaxError("sizeof requires one argument", position)
if(args[0] !is IdentifierReference)
throw SyntaxError("sizeof argument should be an identifier", position)
val dt = args[0].inferType(program)
if(dt.isKnown) {
val target = (args[0] as IdentifierReference).targetStatement(program.namespace)
?: throw CannotEvaluateException("sizeof", "no target")
fun structSize(target: StructDecl) =
NumericLiteralValue(DataType.UBYTE, target.statements.map { (it as VarDecl).datatype.memorySize() }.sum(), position)
return when {
dt.typeOrElse(DataType.STRUCT) in ArrayDatatypes -> {
val length = (target as VarDecl).arraysize!!.size() ?: throw CannotEvaluateException("sizeof", "unknown array size")
val elementDt = ArrayElementTypes.getValue(dt.typeOrElse(DataType.STRUCT))
numericLiteral(elementDt.memorySize() * length, position)
}
dt.istype(DataType.STRUCT) -> {
when (target) {
is VarDecl -> structSize(target.struct!!)
is StructDecl -> structSize(target)
else -> throw CompilerException("weird struct type $target")
}
}
dt.istype(DataType.STR) -> throw SyntaxError("sizeof str is undefined, did you mean len?", position)
else -> NumericLiteralValue(DataType.UBYTE, dt.typeOrElse(DataType.STRUCT).memorySize(), position)
}
} else {
throw SyntaxError("sizeof invalid argument type", position)
}
}
private fun builtinStrlen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("strlen requires one argument", position)
@@ -254,9 +293,6 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
// note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE.
if(args.size!=1)
throw SyntaxError("len requires one argument", position)
val constArg = args[0].constValue(program)
if(constArg!=null)
throw SyntaxError("len of weird argument ${args[0]}", position)
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program.namespace)
var arraySize = directMemVar?.arraysize?.size()
@@ -265,18 +301,12 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
if(args[0] is ArrayLiteralValue)
return NumericLiteralValue.optimalInteger((args[0] as ArrayLiteralValue).value.size, position)
if(args[0] !is IdentifierReference)
throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position)
throw SyntaxError("len argument should be an identifier", position)
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)
?: throw CannotEvaluateException("len", "no target vardecl")
return when(target.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
arraySize = target.arraysize?.size()
if(arraySize==null)
throw CannotEvaluateException("len", "arraysize unknown")
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
}
DataType.ARRAY_F -> {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F -> {
arraySize = target.arraysize?.size()
if(arraySize==null)
throw CannotEvaluateException("len", "arraysize unknown")
@@ -286,7 +316,8 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
val refLv = target.value as StringLiteralValue
NumericLiteralValue.optimalInteger(refLv.value.length, args[0].position)
}
in NumericDatatypes -> throw SyntaxError("len of weird argument ${args[0]}", position)
DataType.STRUCT -> throw SyntaxError("cannot use len on struct, did you mean sizeof?", args[0].position)
in NumericDatatypes -> throw SyntaxError("cannot use len on numeric value, did you mean sizeof?", args[0].position)
else -> throw CompilerException("weird datatype")
}
}
@@ -381,8 +412,8 @@ private fun numericLiteral(value: Number, position: Position): NumericLiteralVal
floatNum
return when(tweakedValue) {
is Int -> NumericLiteralValue.optimalNumeric(value.toInt(), position)
is Short -> NumericLiteralValue.optimalNumeric(value.toInt(), position)
is Int -> NumericLiteralValue.optimalInteger(value.toInt(), position)
is Short -> NumericLiteralValue.optimalInteger(value.toInt(), position)
is Byte -> NumericLiteralValue(DataType.UBYTE, value.toShort(), position)
is Double -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position)
is Float -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position)
@@ -132,11 +132,11 @@ class ConstExprEvaluator {
private fun bitwiseand(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
if(left.type== DataType.UBYTE) {
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toShort(), left.position)
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() and (right.number.toInt() and 255)).toShort(), left.position)
}
} else if(left.type== DataType.UWORD) {
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UWORD, left.number.toInt() or right.number.toInt(), left.position)
return NumericLiteralValue(DataType.UWORD, left.number.toInt() and right.number.toInt(), left.position)
}
}
throw ExpressionError("cannot calculate $left & $right", left.position)
@@ -163,7 +163,7 @@ class ConstExprEvaluator {
val error = "cannot add $left and $right"
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() + right.number.toInt(), left.position)
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() + right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position)
}
@@ -180,7 +180,7 @@ class ConstExprEvaluator {
val error = "cannot subtract $left and $right"
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() - right.number.toInt(), left.position)
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() - right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position)
}
@@ -197,7 +197,7 @@ class ConstExprEvaluator {
val error = "cannot multiply ${left.type} and ${right.type}"
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() * right.number.toInt(), left.position)
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() * right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position)
}
@@ -220,7 +220,7 @@ class ConstExprEvaluator {
in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position)
val result: Int = left.number.toInt() / right.number.toInt()
NumericLiteralValue.optimalNumeric(result, left.position)
NumericLiteralValue.optimalInteger(result, left.position)
}
DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
@@ -171,7 +171,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
if(declValue!=null && decl.type==VarDeclType.VAR
&& declValue is NumericLiteralValue && !declValue.inferType(program).istype(decl.datatype)) {
// cast the numeric literal to the appropriate datatype of the variable
return listOf(IAstModification.ReplaceNode(decl.value!!, declValue.cast(decl.datatype), decl))
return listOf(IAstModification.ReplaceNode(decl.value!!, declValue.castNoCheck(decl.datatype), decl))
}
return noModifications
@@ -203,7 +203,7 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
"-" -> when (subexpr.type) {
in IntegerDatatypes -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue.optimalNumeric(-subexpr.number.toInt(), subexpr.position),
NumericLiteralValue.optimalInteger(-subexpr.number.toInt(), subexpr.position),
parent))
}
DataType.FLOAT -> {
@@ -216,7 +216,7 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
"~" -> when (subexpr.type) {
in IntegerDatatypes -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue.optimalNumeric(subexpr.number.toInt().inv(), subexpr.position),
NumericLiteralValue.optimalInteger(subexpr.number.toInt().inv(), subexpr.position),
parent))
}
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
@@ -273,11 +273,8 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
// const fold when both operands are a const
if(leftconst != null && rightconst != null) {
val evaluator = ConstExprEvaluator()
return listOf(IAstModification.ReplaceNode(
expr,
evaluator.evaluate(leftconst, expr.operator, rightconst),
parent
))
val result = evaluator.evaluate(leftconst, expr.operator, rightconst)
return listOf(IAstModification.ReplaceNode(expr, result, parent))
}
return noModifications
@@ -323,13 +320,13 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
val newFrom: NumericLiteralValue
val newTo: NumericLiteralValue
try {
newFrom = rangeFrom.cast(targetDt)
newTo = rangeTo.cast(targetDt)
newFrom = rangeFrom.castNoCheck(targetDt)
newTo = rangeTo.castNoCheck(targetDt)
} catch (x: ExpressionError) {
return range
}
val newStep: Expression = try {
stepLiteral?.cast(targetDt)?: range.step
stepLiteral?.castNoCheck(targetDt)?: range.step
} catch(ee: ExpressionError) {
range.step
}
@@ -379,6 +376,18 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
return noModifications
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val numval = decl.value as? NumericLiteralValue
if(decl.type== VarDeclType.CONST && numval!=null) {
val valueDt = numval.inferType(program)
if(!valueDt.istype(decl.datatype)) {
val adjustedVal = numval.castNoCheck(decl.datatype)
return listOf(IAstModification.ReplaceNode(numval, adjustedVal, decl))
}
}
return noModifications
}
private class ShuffleOperands(val expr: BinaryExpression,
val exprOperator: String?,
val subExpr: BinaryExpression,
@@ -29,20 +29,23 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
// try to statically convert a literal value into one of the desired type
val literal = typecast.expression as? NumericLiteralValue
if (literal != null) {
val newLiteral = literal.cast(typecast.type)
val newLiteral = literal.castNoCheck(typecast.type)
if (newLiteral !== literal)
mods += IAstModification.ReplaceNode(typecast.expression, newLiteral, typecast)
}
// remove redundant nested typecasts:
// if the typecast casts a value to the same type, remove the cast.
// if the typecast contains another typecast, remove the inner typecast.
// remove redundant nested typecasts
val subTypecast = typecast.expression as? TypecastExpression
if (subTypecast != null) {
mods += IAstModification.ReplaceNode(typecast.expression, subTypecast.expression, typecast)
// remove the sub-typecast if its datatype is larger than the outer typecast
if(subTypecast.type largerThan typecast.type) {
mods += IAstModification.ReplaceNode(typecast.expression, subTypecast.expression, typecast)
}
} else {
if (typecast.expression.inferType(program).istype(typecast.type))
if (typecast.expression.inferType(program).istype(typecast.type)) {
// remove duplicate cast
mods += IAstModification.ReplaceNode(typecast, typecast.expression, parent)
}
}
return mods
@@ -136,7 +136,7 @@ internal class StatementOptimizer(private val program: Program,
val subroutine = functionCallStatement.target.targetSubroutine(program.namespace)
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is ReturnFromIrq || first is Return)
if(first is Return)
return listOf(IAstModification.Remove(functionCallStatement, parent))
}
@@ -368,11 +368,12 @@ internal class StatementOptimizer(private val program: Program,
}
}
}
if(binExpr.right isSameAs assignment.target) {
if(binExpr.operator in setOf("+", "*", "&", "|")) {
// associative operator, swap the operands so that the assignment target is first (left)
if(binExpr.operator in associativeOperators && binExpr.right isSameAs assignment.target) {
// associative operator, swap the operands so that the assignment target is first (left)
// unless the other operand is the same in which case we don't swap (endless loop!)
if (!(binExpr.left isSameAs binExpr.right))
return listOf(IAstModification.SwapOperands(binExpr))
}
}
}
@@ -405,8 +406,8 @@ internal class StatementOptimizer(private val program: Program,
if (cv == 0.0) {
return listOf(IAstModification.Remove(assignment, parent))
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
// replace by several INCs (a bit less when dealing with memory targets)
if (vardeclDt != VarDeclType.MEMORY && cv in 1.0..4.0) {
// replace by several INCs if it's not a memory address (inc on a memory mapped register doesn't work very well)
val incs = AnonymousScope(mutableListOf(), assignment.position)
repeat(cv.toInt()) {
incs.statements.add(PostIncrDecr(assignment.target, "++", assignment.position))
@@ -419,8 +420,8 @@ internal class StatementOptimizer(private val program: Program,
if (cv == 0.0) {
return listOf(IAstModification.Remove(assignment, parent))
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
// replace by several DECs (a bit less when dealing with memory targets)
if (vardeclDt != VarDeclType.MEMORY && cv in 1.0..4.0) {
// replace by several DECs if it's not a memory address (dec on a memory mapped register doesn't work very well)
val decs = AnonymousScope(mutableListOf(), assignment.position)
repeat(cv.toInt()) {
decs.statements.add(PostIncrDecr(assignment.target, "--", assignment.position))
@@ -1,16 +1,14 @@
package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.ErrorReporter
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.Block
import prog8.ast.statements.*
/*
TODO: remove unreachable code after return and exit()
*/
internal class UnusedCodeRemover: AstWalker() {
internal class UnusedCodeRemover(private val errors: ErrorReporter): AstWalker() {
override fun before(program: Program, parent: Node): Iterable<IAstModification> {
val callgraph = CallGraph(program)
@@ -20,8 +18,9 @@ internal class UnusedCodeRemover: AstWalker() {
val entrypoint = program.entrypoint()
program.modules.forEach {
callgraph.forAllSubroutines(it) { sub ->
if (sub !== entrypoint && !sub.keepAlways && (callgraph.calledBy[sub].isNullOrEmpty() || (sub.containsNoCodeNorVars() && !sub.isAsmSubroutine)))
if (sub !== entrypoint && !sub.isAsmSubroutine && (callgraph.calledBy[sub].isNullOrEmpty() || sub.containsNoCodeNorVars())) {
removals.add(IAstModification.Remove(sub, sub.definingScope() as Node))
}
}
}
@@ -38,4 +37,33 @@ internal class UnusedCodeRemover: AstWalker() {
return removals
}
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
reportUnreachable(breakStmt, parent as INameScope)
return emptyList()
}
override fun before(jump: Jump, parent: Node): Iterable<IAstModification> {
reportUnreachable(jump, parent as INameScope)
return emptyList()
}
override fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> {
reportUnreachable(returnStmt, parent as INameScope)
return emptyList()
}
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource.last() == "exit")
reportUnreachable(functionCallStatement, parent as INameScope)
return emptyList()
}
private fun reportUnreachable(stmt: Statement, parent: INameScope) {
when(val next = parent.nextSibling(stmt)) {
null, is Label, is Directive, is VarDecl, is InlineAssembly, is Subroutine, is StructDecl -> {}
else -> errors.warn("unreachable code", next.position)
}
}
}
+7 -1
View File
@@ -724,11 +724,17 @@ reverse(array)
len(x)
Number of values in the array value x, or the number of characters in a string (excluding the size or 0-byte).
Note: this can be different from the number of *bytes* in memory if the datatype isn't a byte.
Note: this can be different from the number of *bytes* in memory if the datatype isn't a byte. See sizeof().
Note: lengths of strings and arrays are determined at compile-time! If your program modifies the actual
length of the string during execution, the value of len(string) may no longer be correct!
(use strlen function if you want to dynamically determine the length)
sizeof(name)
Number of bytes that the object 'name' occupies in memory. This is a constant determined by the data type of
the object. For instance, for a variable of type uword, the sizeof is 2.
For an 10 element array of floats, it is 50 (on the C-64, where a float is 5 bytes).
Note: usually you will be interested in the number of elements in an array, use len() for that.
strlen(str)
Number of bytes in the string. This value is determined during runtime and counts upto
the first terminating 0 byte in the string, regardless of the size of the string during compilation time.
Binary file not shown.
Binary file not shown.
+101
View File
@@ -0,0 +1,101 @@
%import c64lib
;/*****************************************************************************\
;** plasma test program for cc65. **
;** **
;** (w)2001 by groepaz/hitmen **
;** **
;** Cleanup and porting by Ullrich von Bassewitz. **
;** Converted to prog8 by Irmen de Jong **
;** **
;\*****************************************************************************/
main {
const uword SCREEN1 = $E000
const uword SCREEN2 = $E400
const uword CHARSET = $E800
const ubyte PAGE1 = ((SCREEN1 >> 6) & $F0) | ((CHARSET >> 10) & $0E)
const ubyte PAGE2 = ((SCREEN2 >> 6) & $F0) | ((CHARSET >> 10) & $0E)
sub start() {
c64.COLOR = 1
c64scr.print("creating charset...\n")
makechar()
ubyte block = c64.CIA2PRA
; ubyte v = c64.VMCSB
c64.CIA2PRA = (block & $FC) | (lsb(SCREEN1 >> 14) ^ $03)
repeat {
doplasma(SCREEN1)
c64.VMCSB = PAGE1
doplasma(SCREEN2)
c64.VMCSB = PAGE2
}
; restore screen (if you want)
;c64.VMCSB = v
;c64.CIA2PRA = block
;c64scr.print("done!\n")
}
; several variables outside of doplasma to make them retain their value
ubyte c1A
ubyte c1B
ubyte c2A
ubyte c2B
sub doplasma(uword screen) {
ubyte[40] xbuf
ubyte[25] ybuf
ubyte c1a = c1A
ubyte c1b = c1B
ubyte c2a = c2A
ubyte c2b = c2B
ubyte @zp x
ubyte @zp y
for y in 0 to 24 {
ybuf[y] = sin8u(c1a) + sin8u(c1b)
c1a += 4
c1b += 9
}
c1A += 3
c1B -= 5
for x in 0 to 39 {
xbuf[x] = sin8u(c2a) + sin8u(c2b)
c2a += 3
c2b += 7
}
c2A += 2
c2B -= 3
for y in 0 to 24 {
for x in 0 to 39 {
@(screen) = xbuf[x] + ybuf[y]
screen++
}
}
}
sub makechar() {
ubyte[8] bittab = [ $01, $02, $04, $08, $10, $20, $40, $80 ]
ubyte c
for c in 0 to 255 {
ubyte @zp s = sin8u(c)
ubyte i
for i in 0 to 7 {
ubyte b=0
ubyte @zp ii
for ii in 0 to 7 {
; use 16 bit rng for a bit more randomness instead of the 8-bit rng
if lsb(rndw()) > s {
b |= bittab[ii]
}
}
@(CHARSET + i + c*8.w) = b
}
}
}
}
-30
View File
@@ -6,36 +6,6 @@
main {
sub start() {
uword count=0
count=0
repeat 10 {
count++
}
c64scr.print_uw(count)
c64.CHROUT('\n')
count=0
repeat 255 {
count++
}
c64scr.print_uw(count)
c64.CHROUT('\n')
count=0
repeat 256 {
count++
}
c64scr.print_uw(count)
c64.CHROUT('\n')
count=0
repeat 40000 {
count++
}
c64scr.print_uw(count)
c64.CHROUT('\n')
}
}
+2 -2
View File
@@ -46,7 +46,7 @@ turtle {
uword xx = xpos as uword
c64.SPXY[0] = lsb(xx) + 12
c64.MSIGX = msb(xx) > 0
c64.SPXY[1] = lsb(ypos) + 40
c64.SPXY[1] = ypos as ubyte + 40
}
sub pos(float x, float y) {
@@ -66,7 +66,7 @@ turtle {
ypos -= flen * cos(angle)
update_turtle_sprite()
if pendown {
graphics.line(sx as uword, lsb(sy), xpos as uword, lsb(ypos))
graphics.line(sx as uword, sy as ubyte, xpos as uword, ypos as ubyte)
}
}