mirror of
https://github.com/irmen/prog8.git
synced 2025-06-20 06:23:34 +00:00
Compare commits
82 Commits
Author | SHA1 | Date | |
---|---|---|---|
e426fc0922 | |||
5d4bfffc7e | |||
207cdaf7a4 | |||
7315b581ce | |||
38efaae7b2 | |||
469e042216 | |||
0f1a4b9d8f | |||
7303c00296 | |||
fc55b34d84 | |||
6f67fc0e02 | |||
562d722ad5 | |||
144c1ba3a6 | |||
06b032af91 | |||
3603140114 | |||
e094785cbd | |||
e7408224ac | |||
e67c05c274 | |||
b22804efaf | |||
890f55f91a | |||
cc5fc0b892 | |||
5efe2b027a | |||
5b6569d0f9 | |||
0eda7ac498 | |||
a5ef353484 | |||
67a36d8d31 | |||
7cc3cc3990 | |||
dc0edc4c2b | |||
71d2f091e5 | |||
c2f062a391 | |||
224f490455 | |||
5b35232ab4 | |||
6d6db70e42 | |||
6830e15b4e | |||
3f07cad35d | |||
e951340033 | |||
db8912a735 | |||
0e297731a3 | |||
f20c4f98ac | |||
05e60cc7c0 | |||
55b4469767 | |||
f15516e478 | |||
17ceadbadf | |||
8c25b2b316 | |||
8b1ae404a3 | |||
13534cd4a9 | |||
abfb345503 | |||
42ae935496 | |||
434515d957 | |||
094f7803b7 | |||
b0c7bad391 | |||
e9a4a905ef | |||
7b6cd0cfbe | |||
b718b12083 | |||
cfa7258ff4 | |||
b70e0a0870 | |||
da8eb464b8 | |||
8f9d1cfa30 | |||
585009ac5c | |||
30ee65fd14 | |||
76428b16f0 | |||
0d7b14e2d8 | |||
a9d19d02b3 | |||
adcbe55307 | |||
aa99a7df64 | |||
00afa1ce52 | |||
e94bf4c63c | |||
ec5adffdc2 | |||
733c17ad3a | |||
53b0b562e6 | |||
fabae6e970 | |||
a9f9c40d8a | |||
6fc89607d3 | |||
2340760f53 | |||
39d6d2857e | |||
7b722a0001 | |||
e7682119e0 | |||
af6be44676 | |||
5a8f97a0b6 | |||
0d4dd385b8 | |||
94f0f3e966 | |||
43e31765e5 | |||
7c1bdfe713 |
@ -78,6 +78,9 @@ It's handy to have an emulator (or a real machine perhaps!) to run the programs
|
||||
of the [Vice emulator](http://vice-emu.sourceforge.net/) for the C64 target,
|
||||
and the [x16emu emulator](https://github.com/commanderx16/x16-emulator) for the CommanderX16 target.
|
||||
|
||||
**Syntax highlighting:** for a few different editors, syntax highlighting definition files are provided.
|
||||
Look in the [syntax-files](https://github.com/irmen/prog8/tree/master/syntax-files) directory in the github repository to find them.
|
||||
|
||||
|
||||
Example code
|
||||
------------
|
||||
|
@ -1,8 +1,7 @@
|
||||
package prog8
|
||||
package prog8.code
|
||||
|
||||
/**
|
||||
* By convention, the right side of an `Either` is used to hold successful values.
|
||||
*
|
||||
*/
|
||||
sealed class Either<out L, out R> {
|
||||
|
@ -169,6 +169,7 @@ open class StNode(val name: String,
|
||||
|
||||
class StStaticVariable(name: String,
|
||||
val dt: DataType,
|
||||
val bss: Boolean,
|
||||
val onetimeInitializationNumericValue: Double?, // regular (every-run-time) initialization is done via regular assignments
|
||||
val onetimeInitializationStringValue: StString?,
|
||||
val onetimeInitializationArrayValue: StArray?,
|
||||
@ -177,10 +178,19 @@ class StStaticVariable(name: String,
|
||||
position: Position) : StNode(name, StNodeType.STATICVAR, position) {
|
||||
|
||||
init {
|
||||
if(bss) {
|
||||
require(onetimeInitializationNumericValue==null)
|
||||
require(onetimeInitializationStringValue==null)
|
||||
require(onetimeInitializationArrayValue.isNullOrEmpty())
|
||||
} else {
|
||||
require(onetimeInitializationNumericValue!=null ||
|
||||
onetimeInitializationStringValue!=null ||
|
||||
onetimeInitializationArrayValue!=null)
|
||||
}
|
||||
if(length!=null) {
|
||||
require(onetimeInitializationNumericValue == null)
|
||||
if(onetimeInitializationArrayValue!=null)
|
||||
require(length == onetimeInitializationArrayValue.size)
|
||||
require(onetimeInitializationArrayValue.isEmpty() ||onetimeInitializationArrayValue.size==length)
|
||||
}
|
||||
if(onetimeInitializationNumericValue!=null)
|
||||
require(dt in NumericDatatypes)
|
||||
@ -193,7 +203,7 @@ class StStaticVariable(name: String,
|
||||
}
|
||||
|
||||
override fun printProperties() {
|
||||
print("$name dt=$dt zpw=$zpwish")
|
||||
print("$name dt=$dt zpw=$zpwish bss=$bss")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ class PtNodeGroup : PtNode(Position.DUMMY) {
|
||||
}
|
||||
|
||||
|
||||
abstract class PtNamedNode(val name: String, position: Position): PtNode(position) {
|
||||
sealed class PtNamedNode(val name: String, position: Position): PtNode(position) {
|
||||
val scopedName: List<String> by lazy {
|
||||
var namedParent: PtNode = this.parent
|
||||
if(namedParent is PtProgram)
|
||||
@ -96,8 +96,13 @@ class PtBlock(name: String,
|
||||
}
|
||||
|
||||
|
||||
class PtInlineAssembly(val assembly: String, position: Position) : PtNode(position) {
|
||||
class PtInlineAssembly(val assembly: String, val isIR: Boolean, position: Position) : PtNode(position) {
|
||||
override fun printProperties() {}
|
||||
|
||||
init {
|
||||
require(!assembly.startsWith('\n') && !assembly.startsWith('\r')) { "inline assembly should be trimmed" }
|
||||
require(!assembly.endsWith('\n') && !assembly.endsWith('\r')) { "inline assembly should be trimmed" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -106,7 +106,7 @@ class AsmGen(internal val program: Program,
|
||||
DataType.BYTE -> listOf("cx16", "r9sL")
|
||||
DataType.UWORD -> listOf("cx16", "r9")
|
||||
DataType.WORD -> listOf("cx16", "r9s")
|
||||
DataType.FLOAT -> listOf("floats", "tempvar_swap_float") // defined in floats.p8
|
||||
DataType.FLOAT -> TODO("no temporary float var available")
|
||||
else -> throw FatalAstException("invalid dt $dt")
|
||||
}
|
||||
}
|
||||
@ -909,8 +909,7 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
|
||||
private fun translate(asm: InlineAssembly) {
|
||||
val assembly = asm.assembly.trimEnd().trimStart('\r', '\n')
|
||||
assemblyLines.add(assembly)
|
||||
assemblyLines.add(asm.assembly.trimEnd().trimStart('\r', '\n'))
|
||||
}
|
||||
|
||||
internal fun returnRegisterOfFunction(it: IdentifierReference): RegisterOrPair {
|
||||
|
@ -43,7 +43,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
"abs" -> funcAbs(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"any", "all" -> funcAnyAll(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"sgn" -> funcSgn(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"rnd", "rndw" -> funcRnd(func, resultToStack, resultRegister, sscope)
|
||||
"sqrt16" -> funcSqrt16(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"rol" -> funcRol(fcall)
|
||||
"rol2" -> funcRol2(fcall)
|
||||
@ -687,28 +686,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcRnd(func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
|
||||
when(func.name) {
|
||||
"rnd" -> {
|
||||
if(resultToStack)
|
||||
asmgen.out(" jsr prog8_lib.func_rnd_stack")
|
||||
else {
|
||||
asmgen.out(" jsr math.randbyte")
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, asmgen), CpuRegister.A)
|
||||
}
|
||||
}
|
||||
"rndw" -> {
|
||||
if(resultToStack)
|
||||
asmgen.out(" jsr prog8_lib.func_rndw_stack")
|
||||
else {
|
||||
asmgen.out(" jsr math.randword")
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, asmgen), RegisterOrPair.AY)
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("wrong func")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcPokeW(fcall: IFunctionCall) {
|
||||
when(val addrExpr = fcall.args[0]) {
|
||||
is NumericLiteral -> {
|
||||
|
@ -53,7 +53,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
(sub.parameters.size==1 && sub.parameters[0].type in IntegerDatatypes)
|
||||
|| (sub.parameters.size==2 && sub.parameters[0].type in ByteDatatypes && sub.parameters[1].type in ByteDatatypes)
|
||||
|
||||
internal fun translateFunctionCall(call: IFunctionCall, isExpression: Boolean) {
|
||||
internal fun translateFunctionCall(call: IFunctionCall, isExpression: Boolean) { // TODO remove isExpression unused parameter
|
||||
// Output only the code to set up the parameters and perform the actual call
|
||||
// NOTE: does NOT output the code to deal with the result values!
|
||||
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
|
||||
|
@ -287,18 +287,24 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
is PrefixExpression -> {
|
||||
// first assign the value to the target then apply the operator in place on the target.
|
||||
translateNormalAssignment(AsmAssignment(
|
||||
AsmAssignSource.fromAstSource(value.expression, program, asmgen),
|
||||
assign.target,
|
||||
false, program.memsizer, assign.position
|
||||
))
|
||||
val target = virtualRegsToVariables(assign.target)
|
||||
when(value.operator) {
|
||||
"+" -> {}
|
||||
"-" -> augmentableAsmGen.inplaceNegate(target, target.datatype)
|
||||
"~" -> augmentableAsmGen.inplaceInvert(target, target.datatype)
|
||||
else -> throw AssemblyError("invalid prefix operator")
|
||||
if(assign.target.array==null) {
|
||||
// First assign the value to the target then apply the operator in place on the target.
|
||||
// This saves a temporary variable
|
||||
translateNormalAssignment(
|
||||
AsmAssignment(
|
||||
AsmAssignSource.fromAstSource(value.expression, program, asmgen),
|
||||
assign.target,
|
||||
false, program.memsizer, assign.position
|
||||
)
|
||||
)
|
||||
when (value.operator) {
|
||||
"+" -> {}
|
||||
"-" -> augmentableAsmGen.inplaceNegate(assign)
|
||||
"~" -> augmentableAsmGen.inplaceInvert(assign)
|
||||
else -> throw AssemblyError("invalid prefix operator")
|
||||
}
|
||||
} else {
|
||||
assignPrefixedExpressionToArrayElt(assign)
|
||||
}
|
||||
}
|
||||
is ContainmentCheck -> {
|
||||
@ -317,6 +323,28 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignPrefixedExpressionToArrayElt(assign: AsmAssignment) {
|
||||
require(assign.source.expression is PrefixExpression)
|
||||
if(assign.source.datatype==DataType.FLOAT) {
|
||||
// floatarray[x] = -value ... just use FAC1 to calculate the expression into and then store that back into the array.
|
||||
assignExpressionToRegister(assign.source.expression, RegisterOrPair.FAC1, true)
|
||||
assignFAC1float(assign.target)
|
||||
} else {
|
||||
// array[x] = -value ... use a tempvar then store that back into the array.
|
||||
val tempvar = asmgen.getTempVarName(assign.target.datatype).joinToString(".")
|
||||
val assignToTempvar = AsmAssignment(assign.source,
|
||||
AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, assign.target.datatype, assign.target.scope, variableAsmName=tempvar, origAstTarget = assign.target.origAstTarget),
|
||||
false, program.memsizer, assign.position)
|
||||
asmgen.translateNormalAssignment(assignToTempvar)
|
||||
when(assign.target.datatype) {
|
||||
in ByteDatatypes -> assignVariableByte(assign.target, tempvar)
|
||||
in WordDatatypes -> assignVariableWord(assign.target, tempvar)
|
||||
DataType.FLOAT -> assignVariableFloat(assign.target, tempvar)
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignVirtualRegister(target: AsmAssignTarget, register: RegisterOrPair) {
|
||||
when(target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
@ -852,7 +880,8 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
fun assignViaExprEval(addressExpression: Expression) {
|
||||
asmgen.assignExpressionToVariable(addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
|
||||
assignRegisterByte(target, CpuRegister.A)
|
||||
asmgen.out(" ldy #0")
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
}
|
||||
|
||||
when (value.addressExpression) {
|
||||
@ -1972,18 +2001,10 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
|
||||
internal fun assignRegisterByte(target: AsmAssignTarget, register: CpuRegister) {
|
||||
// we make an exception in the type check for assigning something to a cx16 virtual register, or a register pair
|
||||
// these will be correctly typecasted from a byte to a word value
|
||||
if(target.register !in Cx16VirtualRegisters &&
|
||||
target.register!=RegisterOrPair.AX && target.register!=RegisterOrPair.AY && target.register!=RegisterOrPair.XY) {
|
||||
if(target.kind== TargetStorageKind.VARIABLE) {
|
||||
val parts = target.asmVarname.split('.')
|
||||
if (parts.size != 2 || parts[0] != "cx16")
|
||||
require(target.datatype in ByteDatatypes)
|
||||
} else {
|
||||
require(target.datatype in ByteDatatypes)
|
||||
}
|
||||
}
|
||||
// we make an exception in the type check for assigning something to a register pair AX, AY or XY
|
||||
// these will be correctly typecasted from a byte to a word value here
|
||||
if(target.register !in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY))
|
||||
require(target.datatype in ByteDatatypes)
|
||||
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
|
@ -21,13 +21,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
when (val value = assign.source.expression!!) {
|
||||
is PrefixExpression -> {
|
||||
// A = -A , A = +A, A = ~A, A = not A
|
||||
val target = assignmentAsmGen.virtualRegsToVariables(assign.target)
|
||||
val itype = value.inferType(program)
|
||||
val type = itype.getOrElse { throw AssemblyError("unknown dt") }
|
||||
when (value.operator) {
|
||||
"+" -> {}
|
||||
"-" -> inplaceNegate(target, type)
|
||||
"~" -> inplaceInvert(target, type)
|
||||
"-" -> inplaceNegate(assign)
|
||||
"~" -> inplaceInvert(assign)
|
||||
else -> throw AssemblyError("invalid prefix operator")
|
||||
}
|
||||
}
|
||||
@ -1796,8 +1793,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
internal fun inplaceInvert(target: AsmAssignTarget, dt: DataType) {
|
||||
when (dt) {
|
||||
internal fun inplaceInvert(assign: AsmAssignment) {
|
||||
val target = assign.target
|
||||
when (assign.target.datatype) {
|
||||
DataType.UBYTE -> {
|
||||
when (target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
@ -1840,7 +1838,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for byte stack invert")
|
||||
else -> throw AssemblyError("no asm gen for in-place invert ubyte for ${target.kind}")
|
||||
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
|
||||
else -> throw AssemblyError("weird target")
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
@ -1864,15 +1863,17 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for word stack invert")
|
||||
else -> throw AssemblyError("no asm gen for in-place invert uword for ${target.kind}")
|
||||
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
|
||||
else -> throw AssemblyError("weird target")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invert of invalid type")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun inplaceNegate(target: AsmAssignTarget, dt: DataType) {
|
||||
when (dt) {
|
||||
internal fun inplaceNegate(assign: AsmAssignment) {
|
||||
val target = assign.target
|
||||
when (assign.target.datatype) {
|
||||
DataType.BYTE -> {
|
||||
when (target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
@ -1896,9 +1897,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
else -> throw AssemblyError("invalid reg dt for byte negate")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't in-place negate")
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for byte stack negate")
|
||||
else -> throw AssemblyError("no asm gen for in-place negate byte")
|
||||
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
|
||||
else -> throw AssemblyError("weird target")
|
||||
}
|
||||
}
|
||||
DataType.WORD -> {
|
||||
@ -1955,12 +1957,21 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
else -> throw AssemblyError("invalid reg dt for word neg")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for word stack negate")
|
||||
else -> throw AssemblyError("no asm gen for in-place negate word")
|
||||
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
|
||||
else -> throw AssemblyError("weird target")
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
when (target.kind) {
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(target.register!!) {
|
||||
RegisterOrPair.FAC1 -> asmgen.out(" jsr floats.NEGOP")
|
||||
RegisterOrPair.FAC2 -> asmgen.out(" jsr floats.MOVFA | jsr floats.NEGOP | jsr floats.MOVEF")
|
||||
else -> throw AssemblyError("invalid float register")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
// simply flip the sign bit in the float
|
||||
asmgen.out("""
|
||||
@ -1970,10 +1981,11 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
""")
|
||||
}
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for float stack negate")
|
||||
else -> throw AssemblyError("weird target kind for inplace negate float ${target.kind}")
|
||||
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
|
||||
else -> throw AssemblyError("weird target for in-place float negation")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("negate of invalid type $dt")
|
||||
else -> throw AssemblyError("negate of invalid type")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,16 +3,12 @@ package prog8.codegen.intermediate
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.AssemblyError
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.core.SignedDatatypes
|
||||
import prog8.intermediate.IRCodeChunk
|
||||
import prog8.intermediate.IRInstruction
|
||||
import prog8.intermediate.Opcode
|
||||
import prog8.intermediate.VmDataType
|
||||
import prog8.intermediate.*
|
||||
|
||||
internal class AssignmentGen(private val codeGen: IRCodeGen, private val expressionEval: ExpressionGen) {
|
||||
|
||||
internal fun translate(assignment: PtAssignment): IRCodeChunk {
|
||||
internal fun translate(assignment: PtAssignment): IRCodeChunks {
|
||||
if(assignment.target.children.single() is PtMachineRegister)
|
||||
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
|
||||
|
||||
@ -22,7 +18,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
translateRegularAssign(assignment)
|
||||
}
|
||||
|
||||
private fun translateInplaceAssign(assignment: PtAssignment): IRCodeChunk {
|
||||
private fun translateInplaceAssign(assignment: PtAssignment): IRCodeChunks {
|
||||
val ident = assignment.target.identifier
|
||||
val memory = assignment.target.memory
|
||||
val array = assignment.target.array
|
||||
@ -48,23 +44,23 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
address: Int,
|
||||
value: PtExpression,
|
||||
origAssign: PtAssignment
|
||||
): IRCodeChunk {
|
||||
val vmDt = codeGen.vmType(value.type)
|
||||
val code = IRCodeChunk(origAssign.position)
|
||||
): IRCodeChunks {
|
||||
val vmDt = codeGen.irType(value.type)
|
||||
when(value) {
|
||||
is PtIdentifier -> return code // do nothing, x=x null assignment.
|
||||
is PtMachineRegister -> return code // do nothing, reg=reg null assignment
|
||||
is PtPrefix -> return inplacePrefix(value.operator, vmDt, address, null, value.position)
|
||||
is PtIdentifier -> return emptyList() // do nothing, x=x null assignment.
|
||||
is PtMachineRegister -> return emptyList() // do nothing, reg=reg null assignment
|
||||
is PtPrefix -> return inplacePrefix(value.operator, vmDt, address, null)
|
||||
is PtBinaryExpression -> return inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, address, null, origAssign)
|
||||
is PtMemoryByte -> {
|
||||
return if (!codeGen.options.compTarget.machine.isIOAddress(address.toUInt()))
|
||||
code // do nothing, mem=mem null assignment.
|
||||
emptyList() // do nothing, mem=mem null assignment.
|
||||
else {
|
||||
// read and write a (i/o) memory location to itself.
|
||||
val tempReg = codeGen.vmRegisters.nextFree()
|
||||
val tempReg = codeGen.registers.nextFree()
|
||||
val code = IRCodeChunk(null, null)
|
||||
code += IRInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, value = address)
|
||||
code += IRInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, value = address)
|
||||
code
|
||||
listOf(code)
|
||||
}
|
||||
}
|
||||
else -> return fallbackAssign(origAssign)
|
||||
@ -75,25 +71,26 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
symbol: String,
|
||||
value: PtExpression,
|
||||
origAssign: PtAssignment
|
||||
): IRCodeChunk {
|
||||
val vmDt = codeGen.vmType(value.type)
|
||||
val code = IRCodeChunk(origAssign.position)
|
||||
when(value) {
|
||||
is PtIdentifier -> return code // do nothing, x=x null assignment.
|
||||
is PtMachineRegister -> return code // do nothing, reg=reg null assignment
|
||||
is PtPrefix -> return inplacePrefix(value.operator, vmDt, null, symbol, value.position)
|
||||
is PtBinaryExpression -> return inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, null, symbol, origAssign)
|
||||
): IRCodeChunks {
|
||||
val vmDt = codeGen.irType(value.type)
|
||||
return when(value) {
|
||||
is PtIdentifier -> emptyList() // do nothing, x=x null assignment.
|
||||
is PtMachineRegister -> emptyList() // do nothing, reg=reg null assignment
|
||||
is PtPrefix -> inplacePrefix(value.operator, vmDt, null, symbol)
|
||||
is PtBinaryExpression -> inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, null, symbol, origAssign)
|
||||
is PtMemoryByte -> {
|
||||
val tempReg = codeGen.vmRegisters.nextFree()
|
||||
val code = IRCodeChunk(null, null)
|
||||
val tempReg = codeGen.registers.nextFree()
|
||||
code += IRInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, labelSymbol = symbol)
|
||||
code += IRInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, labelSymbol = symbol)
|
||||
return code
|
||||
listOf(code)
|
||||
}
|
||||
else -> return fallbackAssign(origAssign)
|
||||
|
||||
else -> fallbackAssign(origAssign)
|
||||
}
|
||||
}
|
||||
|
||||
private fun fallbackAssign(origAssign: PtAssignment): IRCodeChunk {
|
||||
private fun fallbackAssign(origAssign: PtAssignment): IRCodeChunks {
|
||||
if (codeGen.options.slowCodegenWarnings)
|
||||
codeGen.errors.warn("indirect code for in-place assignment", origAssign.position)
|
||||
return translateRegularAssign(origAssign)
|
||||
@ -102,12 +99,12 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
private fun inplaceBinexpr(
|
||||
operator: String,
|
||||
operand: PtExpression,
|
||||
vmDt: VmDataType,
|
||||
vmDt: IRDataType,
|
||||
signed: Boolean,
|
||||
knownAddress: Int?,
|
||||
symbol: String?,
|
||||
origAssign: PtAssignment
|
||||
): IRCodeChunk {
|
||||
): IRCodeChunks {
|
||||
if(knownAddress!=null) {
|
||||
when (operator) {
|
||||
"+" -> return expressionEval.operatorPlusInplace(knownAddress, null, vmDt, operand)
|
||||
@ -139,8 +136,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
return fallbackAssign(origAssign)
|
||||
}
|
||||
|
||||
private fun inplacePrefix(operator: String, vmDt: VmDataType, knownAddress: Int?, addressSymbol: String?, position: Position): IRCodeChunk {
|
||||
val code= IRCodeChunk(position)
|
||||
private fun inplacePrefix(operator: String, vmDt: IRDataType, knownAddress: Int?, addressSymbol: String?): IRCodeChunks {
|
||||
val code= IRCodeChunk(null, null)
|
||||
when(operator) {
|
||||
"+" -> { }
|
||||
"-" -> {
|
||||
@ -150,8 +147,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
IRInstruction(Opcode.NEGM, vmDt, labelSymbol = addressSymbol)
|
||||
}
|
||||
"~" -> {
|
||||
val regMask = codeGen.vmRegisters.nextFree()
|
||||
val mask = if(vmDt==VmDataType.BYTE) 0x00ff else 0xffff
|
||||
val regMask = codeGen.registers.nextFree()
|
||||
val mask = if(vmDt==IRDataType.BYTE) 0x00ff else 0xffff
|
||||
code += IRInstruction(Opcode.LOAD, vmDt, reg1=regMask, value = mask)
|
||||
code += if(knownAddress!=null)
|
||||
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, value = knownAddress)
|
||||
@ -160,45 +157,46 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
}
|
||||
else -> throw AssemblyError("weird prefix operator")
|
||||
}
|
||||
return code
|
||||
return listOf(code)
|
||||
}
|
||||
|
||||
private fun translateRegularAssign(assignment: PtAssignment): IRCodeChunk {
|
||||
private fun translateRegularAssign(assignment: PtAssignment): IRCodeChunks {
|
||||
// note: assigning array and string values is done via an explicit memcopy/stringcopy function call.
|
||||
val ident = assignment.target.identifier
|
||||
val memory = assignment.target.memory
|
||||
val array = assignment.target.array
|
||||
val vmDt = codeGen.vmType(assignment.value.type)
|
||||
|
||||
val code = IRCodeChunk(assignment.position)
|
||||
val vmDt = codeGen.irType(assignment.value.type)
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
var resultRegister = -1
|
||||
var resultFpRegister = -1
|
||||
val zero = codeGen.isZero(assignment.value)
|
||||
if(!zero) {
|
||||
// calculate the assignment value
|
||||
if (vmDt == VmDataType.FLOAT) {
|
||||
resultFpRegister = codeGen.vmRegisters.nextFreeFloat()
|
||||
code += expressionEval.translateExpression(assignment.value, -1, resultFpRegister)
|
||||
if (vmDt == IRDataType.FLOAT) {
|
||||
resultFpRegister = codeGen.registers.nextFreeFloat()
|
||||
result += expressionEval.translateExpression(assignment.value, -1, resultFpRegister)
|
||||
} else {
|
||||
resultRegister = if (assignment.value is PtMachineRegister) {
|
||||
(assignment.value as PtMachineRegister).register
|
||||
} else {
|
||||
val reg = codeGen.vmRegisters.nextFree()
|
||||
code += expressionEval.translateExpression(assignment.value, reg, -1)
|
||||
val reg = codeGen.registers.nextFree()
|
||||
result += expressionEval.translateExpression(assignment.value, reg, -1)
|
||||
reg
|
||||
}
|
||||
}
|
||||
}
|
||||
if(ident!=null) {
|
||||
val symbol = ident.targetName.joinToString(".")
|
||||
code += if(zero) {
|
||||
val instruction = if(zero) {
|
||||
IRInstruction(Opcode.STOREZM, vmDt, labelSymbol = symbol)
|
||||
} else {
|
||||
if (vmDt == VmDataType.FLOAT)
|
||||
if (vmDt == IRDataType.FLOAT)
|
||||
IRInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, labelSymbol = symbol)
|
||||
else
|
||||
IRInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, labelSymbol = symbol)
|
||||
}
|
||||
result += IRCodeChunk(null, null).also { it += instruction }
|
||||
return result
|
||||
}
|
||||
else if(array!=null) {
|
||||
val variable = array.variable.targetName.joinToString(".")
|
||||
@ -210,85 +208,91 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
throw AssemblyError("non-array var indexing requires bytes dt")
|
||||
if(array.index.type!=DataType.UBYTE)
|
||||
throw AssemblyError("non-array var indexing requires bytes index")
|
||||
val idxReg = codeGen.vmRegisters.nextFree()
|
||||
code += expressionEval.translateExpression(array.index, idxReg, -1)
|
||||
val idxReg = codeGen.registers.nextFree()
|
||||
result += expressionEval.translateExpression(array.index, idxReg, -1)
|
||||
val code = IRCodeChunk(null, null)
|
||||
if(zero) {
|
||||
// there's no STOREZIX instruction
|
||||
resultRegister = codeGen.vmRegisters.nextFree()
|
||||
resultRegister = codeGen.registers.nextFree()
|
||||
code += IRInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, value=0)
|
||||
}
|
||||
code += IRInstruction(Opcode.STOREIX, vmDt, reg1=resultRegister, reg2=idxReg, labelSymbol = variable)
|
||||
return code
|
||||
result += code
|
||||
return result
|
||||
}
|
||||
|
||||
val fixedIndex = constIntValue(array.index)
|
||||
if(zero) {
|
||||
if(fixedIndex!=null) {
|
||||
val offset = fixedIndex*itemsize
|
||||
code += IRInstruction(Opcode.STOREZM, vmDt, labelSymbol = "$variable+$offset")
|
||||
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, vmDt, labelSymbol = "$variable+$offset") }
|
||||
result += chunk
|
||||
} else {
|
||||
val indexReg = codeGen.vmRegisters.nextFree()
|
||||
code += loadIndexReg(array, itemsize, indexReg, array.position)
|
||||
code += IRInstruction(Opcode.STOREZX, vmDt, reg1=indexReg, labelSymbol = variable)
|
||||
val indexReg = codeGen.registers.nextFree()
|
||||
result += loadIndexReg(array, itemsize, indexReg)
|
||||
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZX, vmDt, reg1=indexReg, labelSymbol = variable) }
|
||||
}
|
||||
} else {
|
||||
if(vmDt== VmDataType.FLOAT) {
|
||||
if(vmDt== IRDataType.FLOAT) {
|
||||
if(fixedIndex!=null) {
|
||||
val offset = fixedIndex*itemsize
|
||||
code += IRInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, labelSymbol = "$variable+$offset")
|
||||
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, labelSymbol = "$variable+$offset") }
|
||||
result += chunk
|
||||
} else {
|
||||
val indexReg = codeGen.vmRegisters.nextFree()
|
||||
code += loadIndexReg(array, itemsize, indexReg, array.position)
|
||||
code += IRInstruction(Opcode.STOREX, vmDt, reg1 = resultRegister, reg2=indexReg, labelSymbol = variable)
|
||||
val indexReg = codeGen.registers.nextFree()
|
||||
result += loadIndexReg(array, itemsize, indexReg)
|
||||
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREX, vmDt, reg1 = indexReg, fpReg1 = resultFpRegister, labelSymbol = variable) }
|
||||
}
|
||||
} else {
|
||||
if(fixedIndex!=null) {
|
||||
val offset = fixedIndex*itemsize
|
||||
code += IRInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, labelSymbol = "$variable+$offset")
|
||||
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, labelSymbol = "$variable+$offset") }
|
||||
result += chunk
|
||||
} else {
|
||||
val indexReg = codeGen.vmRegisters.nextFree()
|
||||
code += loadIndexReg(array, itemsize, indexReg, array.position)
|
||||
code += IRInstruction(Opcode.STOREX, vmDt, reg1 = resultRegister, reg2=indexReg, labelSymbol = variable)
|
||||
val indexReg = codeGen.registers.nextFree()
|
||||
result += loadIndexReg(array, itemsize, indexReg)
|
||||
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREX, vmDt, reg1 = resultRegister, reg2=indexReg, labelSymbol = variable) }
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
else if(memory!=null) {
|
||||
require(vmDt== VmDataType.BYTE)
|
||||
require(vmDt== IRDataType.BYTE)
|
||||
if(zero) {
|
||||
if(memory.address is PtNumber) {
|
||||
code += IRInstruction(Opcode.STOREZM, vmDt, value=(memory.address as PtNumber).number.toInt())
|
||||
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, vmDt, value=(memory.address as PtNumber).number.toInt()) }
|
||||
result += chunk
|
||||
} else {
|
||||
val addressReg = codeGen.vmRegisters.nextFree()
|
||||
code += expressionEval.translateExpression(memory.address, addressReg, -1)
|
||||
code += IRInstruction(Opcode.STOREZI, vmDt, reg1=addressReg)
|
||||
val addressReg = codeGen.registers.nextFree()
|
||||
result += expressionEval.translateExpression(memory.address, addressReg, -1)
|
||||
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZI, vmDt, reg1=addressReg) }
|
||||
}
|
||||
} else {
|
||||
if(memory.address is PtNumber) {
|
||||
code += IRInstruction(Opcode.STOREM, vmDt, reg1=resultRegister, value=(memory.address as PtNumber).number.toInt())
|
||||
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, vmDt, reg1=resultRegister, value=(memory.address as PtNumber).number.toInt()) }
|
||||
result += chunk
|
||||
} else {
|
||||
val addressReg = codeGen.vmRegisters.nextFree()
|
||||
code += expressionEval.translateExpression(memory.address, addressReg, -1)
|
||||
code += IRInstruction(Opcode.STOREI, vmDt, reg1=resultRegister, reg2=addressReg)
|
||||
val addressReg = codeGen.registers.nextFree()
|
||||
result += expressionEval.translateExpression(memory.address, addressReg, -1)
|
||||
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREI, vmDt, reg1=resultRegister, reg2=addressReg) }
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
else
|
||||
throw AssemblyError("weird assigntarget")
|
||||
return code
|
||||
}
|
||||
|
||||
private fun loadIndexReg(array: PtArrayIndexer, itemsize: Int, indexReg: Int, position: Position): IRCodeChunk {
|
||||
val code = IRCodeChunk(position)
|
||||
if(itemsize==1) {
|
||||
code += expressionEval.translateExpression(array.index, indexReg, -1)
|
||||
}
|
||||
else {
|
||||
private fun loadIndexReg(array: PtArrayIndexer, itemsize: Int, indexReg: Int): IRCodeChunks {
|
||||
return if(itemsize==1) {
|
||||
expressionEval.translateExpression(array.index, indexReg, -1)
|
||||
} else {
|
||||
val mult = PtBinaryExpression("*", DataType.UBYTE, array.position)
|
||||
mult.children += array.index
|
||||
mult.children += PtNumber(DataType.UBYTE, itemsize.toDouble(), array.position)
|
||||
code += expressionEval.translateExpression(mult, indexReg, -1)
|
||||
expressionEval.translateExpression(mult, indexReg, -1)
|
||||
}
|
||||
return code
|
||||
}
|
||||
}
|
@ -4,13 +4,12 @@ import prog8.code.StStaticVariable
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.AssemblyError
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Position
|
||||
import prog8.intermediate.*
|
||||
|
||||
|
||||
internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGen: ExpressionGen) {
|
||||
|
||||
fun translate(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
|
||||
fun translate(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||
return when(call.name) {
|
||||
"any" -> funcAny(call, resultRegister)
|
||||
"all" -> funcAll(call, resultRegister)
|
||||
@ -25,9 +24,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
"rsave",
|
||||
"rsavex",
|
||||
"rrestore",
|
||||
"rrestorex" -> IRCodeChunk(call.position) // vm doesn't have registers to save/restore
|
||||
"rnd" -> funcRnd(resultRegister, call.position)
|
||||
"rndw" -> funcRndw(resultRegister, call.position)
|
||||
"rrestorex" -> emptyList() // vm doesn't have registers to save/restore
|
||||
"callfar" -> throw AssemblyError("callfar() is for cx16 target only")
|
||||
"callrom" -> throw AssemblyError("callrom() is for cx16 target only")
|
||||
"msb" -> funcMsb(call, resultRegister)
|
||||
@ -37,7 +34,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
"peekw" -> funcPeekW(call, resultRegister)
|
||||
"poke" -> funcPoke(call)
|
||||
"pokew" -> funcPokeW(call)
|
||||
"pokemon" -> IRCodeChunk(call.position)
|
||||
"pokemon" -> emptyList()
|
||||
"mkword" -> funcMkword(call, resultRegister)
|
||||
"sort" -> funcSort(call)
|
||||
"reverse" -> funcReverse(call)
|
||||
@ -49,20 +46,21 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcCmp(call: PtBuiltinFunctionCall): IRCodeChunk {
|
||||
val code = IRCodeChunk(call.position)
|
||||
val leftRegister = codeGen.vmRegisters.nextFree()
|
||||
val rightRegister = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args[0], leftRegister, -1)
|
||||
code += exprGen.translateExpression(call.args[1], rightRegister, -1)
|
||||
code += IRInstruction(Opcode.CMP, codeGen.vmType(call.args[0].type), reg1=leftRegister, reg2=rightRegister)
|
||||
return code
|
||||
private fun funcCmp(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||
val leftRegister = codeGen.registers.nextFree()
|
||||
val rightRegister = codeGen.registers.nextFree()
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
result += exprGen.translateExpression(call.args[0], leftRegister, -1)
|
||||
result += exprGen.translateExpression(call.args[1], rightRegister, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.CMP, codeGen.irType(call.args[0].type), reg1=leftRegister, reg2=rightRegister)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun funcAny(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
|
||||
private fun funcAny(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
||||
val code = IRCodeChunk(call.position)
|
||||
val syscall =
|
||||
when (array.dt) {
|
||||
DataType.ARRAY_UB,
|
||||
@ -72,15 +70,18 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
DataType.ARRAY_F -> IMSyscall.ANY_FLOAT
|
||||
else -> throw IllegalArgumentException("weird type")
|
||||
}
|
||||
code += exprGen.translateExpression(call.args[0], 0, -1)
|
||||
code += IRInstruction(Opcode.LOAD, VmDataType.BYTE, reg1 = 1, value = array.length)
|
||||
code += IRInstruction(Opcode.SYSCALL, value=syscall.ordinal)
|
||||
if (resultRegister != 0)
|
||||
code += IRInstruction(Opcode.LOADR, VmDataType.BYTE, reg1 = resultRegister, reg2 = 0)
|
||||
return code
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
|
||||
it += IRInstruction(Opcode.SYSCALL, value = syscall.number)
|
||||
if(resultRegister!=0)
|
||||
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1 = resultRegister, reg2 = 0)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun funcAll(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
|
||||
private fun funcAll(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
||||
val syscall =
|
||||
@ -92,98 +93,116 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
DataType.ARRAY_F -> IMSyscall.ALL_FLOAT
|
||||
else -> throw IllegalArgumentException("weird type")
|
||||
}
|
||||
val code = IRCodeChunk(call.position)
|
||||
code += exprGen.translateExpression(call.args[0], 0, -1)
|
||||
code += IRInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length)
|
||||
code += IRInstruction(Opcode.SYSCALL, value=syscall.ordinal)
|
||||
if(resultRegister!=0)
|
||||
code += IRInstruction(Opcode.LOADR, VmDataType.BYTE, reg1=resultRegister, reg2=0)
|
||||
return code
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
|
||||
it += IRInstruction(Opcode.SYSCALL, value = syscall.number)
|
||||
if(resultRegister!=0)
|
||||
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1 = resultRegister, reg2 = 0)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun funcAbs(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
|
||||
val code = IRCodeChunk(call.position)
|
||||
private fun funcAbs(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||
val sourceDt = call.args.single().type
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
if(sourceDt!=DataType.UWORD) {
|
||||
code += exprGen.translateExpression(call.args[0], resultRegister, -1)
|
||||
result += exprGen.translateExpression(call.args[0], resultRegister, -1)
|
||||
when (sourceDt) {
|
||||
DataType.UBYTE -> {
|
||||
code += IRInstruction(Opcode.EXT, VmDataType.BYTE, reg1=resultRegister)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1 = resultRegister)
|
||||
}
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
val notNegativeLabel = codeGen.createLabelName()
|
||||
val compareReg = codeGen.vmRegisters.nextFree()
|
||||
code += IRInstruction(Opcode.LOADR, VmDataType.BYTE, reg1=compareReg, reg2=resultRegister)
|
||||
code += IRInstruction(Opcode.AND, VmDataType.BYTE, reg1=compareReg, value=0x80)
|
||||
code += IRInstruction(Opcode.BZ, VmDataType.BYTE, reg1=compareReg, labelSymbol = notNegativeLabel)
|
||||
code += IRInstruction(Opcode.NEG, VmDataType.BYTE, reg1=resultRegister)
|
||||
code += IRInstruction(Opcode.EXT, VmDataType.BYTE, reg1=resultRegister)
|
||||
code += IRCodeLabel(notNegativeLabel)
|
||||
val compareReg = codeGen.registers.nextFree()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=compareReg, reg2=resultRegister)
|
||||
it += IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=compareReg, value=0x80)
|
||||
it += IRInstruction(Opcode.BZ, IRDataType.BYTE, reg1=compareReg, labelSymbol = notNegativeLabel)
|
||||
it += IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1=resultRegister)
|
||||
it += IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=resultRegister)
|
||||
}
|
||||
result += IRCodeChunk(notNegativeLabel, null)
|
||||
}
|
||||
DataType.WORD -> {
|
||||
val notNegativeLabel = codeGen.createLabelName()
|
||||
val compareReg = codeGen.vmRegisters.nextFree()
|
||||
code += IRInstruction(Opcode.LOADR, VmDataType.WORD, reg1=compareReg, reg2=resultRegister)
|
||||
code += IRInstruction(Opcode.AND, VmDataType.WORD, reg1=compareReg, value=0x8000)
|
||||
code += IRInstruction(Opcode.BZ, VmDataType.WORD, reg1=compareReg, labelSymbol = notNegativeLabel)
|
||||
code += IRInstruction(Opcode.NEG, VmDataType.WORD, reg1=resultRegister)
|
||||
code += IRCodeLabel(notNegativeLabel)
|
||||
val compareReg = codeGen.registers.nextFree()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADR, IRDataType.WORD, reg1=compareReg, reg2=resultRegister)
|
||||
it += IRInstruction(Opcode.AND, IRDataType.WORD, reg1=compareReg, value=0x8000)
|
||||
it += IRInstruction(Opcode.BZ, IRDataType.WORD, reg1=compareReg, labelSymbol = notNegativeLabel)
|
||||
it += IRInstruction(Opcode.NEG, IRDataType.WORD, reg1=resultRegister)
|
||||
}
|
||||
result += IRCodeChunk(notNegativeLabel, null)
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
return code
|
||||
return result
|
||||
}
|
||||
|
||||
private fun funcSgn(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
|
||||
val code = IRCodeChunk(call.position)
|
||||
val reg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args.single(), reg, -1)
|
||||
code += IRInstruction(Opcode.SGN, codeGen.vmType(call.type), reg1=resultRegister, reg2=reg)
|
||||
return code
|
||||
private fun funcSgn(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||
val reg = codeGen.registers.nextFree()
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
result += exprGen.translateExpression(call.args.single(), reg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.SGN, codeGen.irType(call.type), reg1 = resultRegister, reg2 = reg)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun funcSqrt16(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
|
||||
val code = IRCodeChunk(call.position)
|
||||
val reg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args.single(), reg, -1)
|
||||
code += IRInstruction(Opcode.SQRT, VmDataType.WORD, reg1=resultRegister, reg2=reg)
|
||||
return code
|
||||
private fun funcSqrt16(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||
val reg = codeGen.registers.nextFree()
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
result += exprGen.translateExpression(call.args.single(), reg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.SQRT, IRDataType.WORD, reg1=resultRegister, reg2=reg)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun funcPop(call: PtBuiltinFunctionCall): IRCodeChunk {
|
||||
val code = IRCodeChunk(call.position)
|
||||
val reg = codeGen.vmRegisters.nextFree()
|
||||
code += IRInstruction(Opcode.POP, VmDataType.BYTE, reg1=reg)
|
||||
code += assignRegisterTo(call.args.single(), reg)
|
||||
return code
|
||||
private fun funcPop(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||
val code = IRCodeChunk(null, null)
|
||||
val reg = codeGen.registers.nextFree()
|
||||
code += IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=reg)
|
||||
val result = mutableListOf<IRCodeChunkBase>(code)
|
||||
result += assignRegisterTo(call.args.single(), reg)
|
||||
return result
|
||||
}
|
||||
|
||||
private fun funcPopw(call: PtBuiltinFunctionCall): IRCodeChunk {
|
||||
val code = IRCodeChunk(call.position)
|
||||
val reg = codeGen.vmRegisters.nextFree()
|
||||
code += IRInstruction(Opcode.POP, VmDataType.WORD, reg1=reg)
|
||||
code += assignRegisterTo(call.args.single(), reg)
|
||||
return code
|
||||
private fun funcPopw(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||
val code = IRCodeChunk(null, null)
|
||||
val reg = codeGen.registers.nextFree()
|
||||
code += IRInstruction(Opcode.POP, IRDataType.WORD, reg1=reg)
|
||||
val result = mutableListOf<IRCodeChunkBase>(code)
|
||||
result += assignRegisterTo(call.args.single(), reg)
|
||||
return result
|
||||
}
|
||||
|
||||
private fun funcPush(call: PtBuiltinFunctionCall): IRCodeChunk {
|
||||
val code = IRCodeChunk(call.position)
|
||||
val reg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args.single(), reg, -1)
|
||||
code += IRInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=reg)
|
||||
return code
|
||||
private fun funcPush(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val reg = codeGen.registers.nextFree()
|
||||
result += exprGen.translateExpression(call.args.single(), reg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=reg)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun funcPushw(call: PtBuiltinFunctionCall): IRCodeChunk {
|
||||
val code = IRCodeChunk(call.position)
|
||||
val reg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args.single(), reg, -1)
|
||||
code += IRInstruction(Opcode.PUSH, VmDataType.WORD, reg1=reg)
|
||||
return code
|
||||
private fun funcPushw(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val reg = codeGen.registers.nextFree()
|
||||
result += exprGen.translateExpression(call.args.single(), reg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.PUSH, IRDataType.WORD, reg1 = reg)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun funcReverse(call: PtBuiltinFunctionCall): IRCodeChunk {
|
||||
private fun funcReverse(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
||||
val syscall =
|
||||
@ -193,14 +212,16 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
DataType.ARRAY_F -> IMSyscall.REVERSE_FLOATS
|
||||
else -> throw IllegalArgumentException("weird type to reverse")
|
||||
}
|
||||
val code = IRCodeChunk(call.position)
|
||||
code += exprGen.translateExpression(call.args[0], 0, -1)
|
||||
code += IRInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length)
|
||||
code += IRInstruction(Opcode.SYSCALL, value=syscall.ordinal)
|
||||
return code
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
|
||||
it += IRInstruction(Opcode.SYSCALL, value = syscall.number)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun funcSort(call: PtBuiltinFunctionCall): IRCodeChunk {
|
||||
private fun funcSort(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
||||
val syscall =
|
||||
@ -213,153 +234,171 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
DataType.ARRAY_F -> throw IllegalArgumentException("sorting a floating point array is not supported")
|
||||
else -> throw IllegalArgumentException("weird type to sort")
|
||||
}
|
||||
val code = IRCodeChunk(call.position)
|
||||
code += exprGen.translateExpression(call.args[0], 0, -1)
|
||||
code += IRInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length)
|
||||
code += IRInstruction(Opcode.SYSCALL, value=syscall.ordinal)
|
||||
return code
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
|
||||
it += IRInstruction(Opcode.SYSCALL, value = syscall.number)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun funcMkword(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
|
||||
val msbReg = codeGen.vmRegisters.nextFree()
|
||||
val code = IRCodeChunk(call.position)
|
||||
code += exprGen.translateExpression(call.args[0], msbReg, -1)
|
||||
code += exprGen.translateExpression(call.args[1], resultRegister, -1)
|
||||
code += IRInstruction(Opcode.CONCAT, VmDataType.BYTE, reg1=resultRegister, reg2=msbReg)
|
||||
return code
|
||||
private fun funcMkword(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||
val msbReg = codeGen.registers.nextFree()
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
result += exprGen.translateExpression(call.args[0], msbReg, -1)
|
||||
result += exprGen.translateExpression(call.args[1], resultRegister, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1 = resultRegister, reg2 = msbReg)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun funcPokeW(call: PtBuiltinFunctionCall): IRCodeChunk {
|
||||
val code = IRCodeChunk(call.position)
|
||||
private fun funcPokeW(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
if(codeGen.isZero(call.args[1])) {
|
||||
if (call.args[0] is PtNumber) {
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
code += IRInstruction(Opcode.STOREZM, VmDataType.WORD, value = address)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREZM, IRDataType.WORD, value = address)
|
||||
}
|
||||
} else {
|
||||
val addressReg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args[0], addressReg, -1)
|
||||
code += IRInstruction(Opcode.STOREZI, VmDataType.WORD, reg2 = addressReg)
|
||||
val addressReg = codeGen.registers.nextFree()
|
||||
result += exprGen.translateExpression(call.args[0], addressReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREZI, IRDataType.WORD, reg2 = addressReg)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val valueReg = codeGen.vmRegisters.nextFree()
|
||||
val valueReg = codeGen.registers.nextFree()
|
||||
if (call.args[0] is PtNumber) {
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
code += exprGen.translateExpression(call.args[1], valueReg, -1)
|
||||
code += IRInstruction(Opcode.STOREM, VmDataType.WORD, reg1 = valueReg, value = address)
|
||||
result += exprGen.translateExpression(call.args[1], valueReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1 = valueReg, value = address)
|
||||
}
|
||||
} else {
|
||||
val addressReg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args[0], addressReg, -1)
|
||||
code += exprGen.translateExpression(call.args[1], valueReg, -1)
|
||||
code += IRInstruction(Opcode.STOREI, VmDataType.WORD, reg1 = valueReg, reg2 = addressReg)
|
||||
val addressReg = codeGen.registers.nextFree()
|
||||
result += exprGen.translateExpression(call.args[0], addressReg, -1)
|
||||
result += exprGen.translateExpression(call.args[1], valueReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREI, IRDataType.WORD, reg1 = valueReg, reg2 = addressReg)
|
||||
}
|
||||
}
|
||||
}
|
||||
return code
|
||||
return result
|
||||
}
|
||||
|
||||
private fun funcPoke(call: PtBuiltinFunctionCall): IRCodeChunk {
|
||||
val code = IRCodeChunk(call.position)
|
||||
private fun funcPoke(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
if(codeGen.isZero(call.args[1])) {
|
||||
if (call.args[0] is PtNumber) {
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
code += IRInstruction(Opcode.STOREZM, VmDataType.BYTE, value = address)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, value = address)
|
||||
}
|
||||
} else {
|
||||
val addressReg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args[0], addressReg, -1)
|
||||
code += IRInstruction(Opcode.STOREZI, VmDataType.BYTE, reg2 = addressReg)
|
||||
val addressReg = codeGen.registers.nextFree()
|
||||
result += exprGen.translateExpression(call.args[0], addressReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg2 = addressReg)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val valueReg = codeGen.vmRegisters.nextFree()
|
||||
val valueReg = codeGen.registers.nextFree()
|
||||
if (call.args[0] is PtNumber) {
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
code += exprGen.translateExpression(call.args[1], valueReg, -1)
|
||||
code += IRInstruction(Opcode.STOREM, VmDataType.BYTE, reg1 = valueReg, value = address)
|
||||
result += exprGen.translateExpression(call.args[1], valueReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = valueReg, value = address)
|
||||
}
|
||||
} else {
|
||||
val addressReg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args[0], addressReg, -1)
|
||||
code += exprGen.translateExpression(call.args[1], valueReg, -1)
|
||||
code += IRInstruction(Opcode.STOREI, VmDataType.BYTE, reg1 = valueReg, reg2 = addressReg)
|
||||
val addressReg = codeGen.registers.nextFree()
|
||||
result += exprGen.translateExpression(call.args[0], addressReg, -1)
|
||||
result += exprGen.translateExpression(call.args[1], valueReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueReg, reg2 = addressReg)
|
||||
}
|
||||
}
|
||||
}
|
||||
return code
|
||||
return result
|
||||
}
|
||||
|
||||
private fun funcPeekW(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
|
||||
val code = IRCodeChunk(call.position)
|
||||
private fun funcPeekW(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
if(call.args[0] is PtNumber) {
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
code += IRInstruction(Opcode.LOADM, VmDataType.WORD, reg1 = resultRegister, value = address)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1 = resultRegister, value = address)
|
||||
}
|
||||
} else {
|
||||
val addressReg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args.single(), addressReg, -1)
|
||||
code += IRInstruction(Opcode.LOADI, VmDataType.WORD, reg1 = resultRegister, reg2 = addressReg)
|
||||
val addressReg = codeGen.registers.nextFree()
|
||||
result += exprGen.translateExpression(call.args.single(), addressReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = resultRegister, reg2 = addressReg)
|
||||
}
|
||||
}
|
||||
return code
|
||||
return result
|
||||
}
|
||||
|
||||
private fun funcPeek(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
|
||||
val code = IRCodeChunk(call.position)
|
||||
private fun funcPeek(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
if(call.args[0] is PtNumber) {
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
code += IRInstruction(Opcode.LOADM, VmDataType.BYTE, reg1 = resultRegister, value = address)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = resultRegister, value = address)
|
||||
}
|
||||
} else {
|
||||
val addressReg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args.single(), addressReg, -1)
|
||||
code += IRInstruction(Opcode.LOADI, VmDataType.BYTE, reg1 = resultRegister, reg2 = addressReg)
|
||||
val addressReg = codeGen.registers.nextFree()
|
||||
result += exprGen.translateExpression(call.args.single(), addressReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1 = resultRegister, reg2 = addressReg)
|
||||
}
|
||||
}
|
||||
return code
|
||||
return result
|
||||
}
|
||||
|
||||
private fun funcRnd(resultRegister: Int, position: Position): IRCodeChunk {
|
||||
val code = IRCodeChunk(position)
|
||||
code += IRInstruction(Opcode.RND, VmDataType.BYTE, reg1=resultRegister)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcRndw(resultRegister: Int, position: Position): IRCodeChunk {
|
||||
val code = IRCodeChunk(position)
|
||||
code += IRInstruction(Opcode.RND, VmDataType.WORD, reg1=resultRegister)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcMemory(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
|
||||
private fun funcMemory(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||
val name = (call.args[0] as PtString).value
|
||||
val code = IRCodeChunk(call.position)
|
||||
code += IRInstruction(Opcode.LOAD, VmDataType.WORD, reg1=resultRegister, labelSymbol = "prog8_slabs.prog8_memoryslab_$name")
|
||||
return code
|
||||
val code = IRCodeChunk(null, null)
|
||||
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultRegister, labelSymbol = "prog8_slabs.prog8_memoryslab_$name")
|
||||
return listOf(code)
|
||||
}
|
||||
|
||||
private fun funcLsb(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
|
||||
val code = IRCodeChunk(call.position)
|
||||
code += exprGen.translateExpression(call.args.single(), resultRegister, -1)
|
||||
private fun funcLsb(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||
return exprGen.translateExpression(call.args.single(), resultRegister, -1)
|
||||
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcMsb(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
|
||||
val code = IRCodeChunk(call.position)
|
||||
code += exprGen.translateExpression(call.args.single(), resultRegister, -1)
|
||||
code += IRInstruction(Opcode.MSIG, VmDataType.BYTE, reg1 = resultRegister, reg2=resultRegister)
|
||||
private fun funcMsb(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
result += exprGen.translateExpression(call.args.single(), resultRegister, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.MSIG, IRDataType.BYTE, reg1 = resultRegister, reg2 = resultRegister)
|
||||
}
|
||||
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
|
||||
return code
|
||||
return result
|
||||
}
|
||||
|
||||
private fun funcRolRor(opcode: Opcode, call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
|
||||
val vmDt = codeGen.vmType(call.args[0].type)
|
||||
val code = IRCodeChunk(call.position)
|
||||
code += exprGen.translateExpression(call.args[0], resultRegister, -1)
|
||||
code += IRInstruction(opcode, vmDt, reg1=resultRegister)
|
||||
code += assignRegisterTo(call.args[0], resultRegister)
|
||||
return code
|
||||
private fun funcRolRor(opcode: Opcode, call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||
val vmDt = codeGen.irType(call.args[0].type)
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
result += exprGen.translateExpression(call.args[0], resultRegister, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(opcode, vmDt, reg1 = resultRegister)
|
||||
}
|
||||
result += assignRegisterTo(call.args[0], resultRegister)
|
||||
return result
|
||||
}
|
||||
|
||||
private fun assignRegisterTo(target: PtExpression, register: Int): IRCodeChunk {
|
||||
val code = IRCodeChunk(target.position)
|
||||
private fun assignRegisterTo(target: PtExpression, register: Int): IRCodeChunks {
|
||||
val assignment = PtAssignment(target.position)
|
||||
val assignTarget = PtAssignTarget(target.position)
|
||||
assignTarget.children.add(target)
|
||||
assignment.children.add(assignTarget)
|
||||
assignment.children.add(PtMachineRegister(register, target.type, target.position))
|
||||
code += codeGen.translateNode(assignment)
|
||||
return code
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
result += codeGen.translateNode(assignment)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -5,25 +5,112 @@ import prog8.intermediate.*
|
||||
internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
fun optimize() {
|
||||
irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub ->
|
||||
sub.chunks.forEach { chunk ->
|
||||
removeEmptyChunks(sub)
|
||||
joinChunks(sub)
|
||||
sub.chunks.withIndex().forEach { (index, chunk1) ->
|
||||
// we don't optimize Inline Asm chunks here.
|
||||
if(chunk is IRCodeChunk) {
|
||||
val chunk2 = if(index<sub.chunks.size-1) sub.chunks[index+1] else null
|
||||
if(chunk1 is IRCodeChunk) {
|
||||
do {
|
||||
val indexedInstructions = chunk.lines.withIndex()
|
||||
.filter { it.value is IRInstruction }
|
||||
.map { IndexedValue(it.index, it.value as IRInstruction) }
|
||||
val changed = removeNops(chunk, indexedInstructions)
|
||||
|| removeDoubleLoadsAndStores(chunk, indexedInstructions) // TODO not yet implemented
|
||||
|| removeUselessArithmetic(chunk, indexedInstructions)
|
||||
|| removeWeirdBranches(chunk, indexedInstructions)
|
||||
|| removeDoubleSecClc(chunk, indexedInstructions)
|
||||
|| cleanupPushPop(chunk, indexedInstructions)
|
||||
val indexedInstructions = chunk1.instructions.withIndex()
|
||||
.map { IndexedValue(it.index, it.value) }
|
||||
val changed = removeNops(chunk1, indexedInstructions)
|
||||
|| removeDoubleLoadsAndStores(chunk1, indexedInstructions) // TODO not yet implemented
|
||||
|| removeUselessArithmetic(chunk1, indexedInstructions)
|
||||
|| removeWeirdBranches(chunk1, chunk2, indexedInstructions)
|
||||
|| removeDoubleSecClc(chunk1, indexedInstructions)
|
||||
|| cleanupPushPop(chunk1, indexedInstructions)
|
||||
// TODO other optimizations:
|
||||
// more complex optimizations such as unused registers
|
||||
} while (changed)
|
||||
}
|
||||
}
|
||||
removeEmptyChunks(sub)
|
||||
}
|
||||
|
||||
irprog.linkChunks() // re-link
|
||||
}
|
||||
|
||||
private fun removeEmptyChunks(sub: IRSubroutine) {
|
||||
if(sub.chunks.isEmpty())
|
||||
return
|
||||
|
||||
/*
|
||||
Empty Code chunk with label ->
|
||||
If next chunk has no label -> move label to next chunk, remove original
|
||||
If next chunk has label -> label name should be the same, remove original. Otherwise FOR NOW leave it in place. (TODO: consolidate labels into 1)
|
||||
If is last chunk -> keep chunk in place because of the label.
|
||||
Empty Code chunk without label ->
|
||||
should not have been generated! ERROR.
|
||||
*/
|
||||
|
||||
|
||||
val relabelChunks = mutableListOf<Pair<Int, String>>()
|
||||
val removeChunks = mutableListOf<Int>()
|
||||
|
||||
sub.chunks.withIndex().forEach { (index, chunk) ->
|
||||
if(chunk is IRCodeChunk && chunk.instructions.isEmpty()) {
|
||||
if(chunk.label==null) {
|
||||
removeChunks += index
|
||||
} else {
|
||||
if (index < sub.chunks.size - 1) {
|
||||
val nextchunk = sub.chunks[index + 1]
|
||||
if (nextchunk.label == null) {
|
||||
// can transplant label to next chunk and remove this empty one.
|
||||
relabelChunks += Pair(index + 1, chunk.label!!)
|
||||
removeChunks += index
|
||||
} else {
|
||||
if (chunk.label == nextchunk.label)
|
||||
removeChunks += index
|
||||
else {
|
||||
// TODO: consolidate labels on same chunk
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
relabelChunks.forEach { (index, label) ->
|
||||
val chunk = IRCodeChunk(label, null)
|
||||
chunk.instructions += sub.chunks[index].instructions
|
||||
sub.chunks[index] = chunk
|
||||
}
|
||||
removeChunks.reversed().forEach { index -> sub.chunks.removeAt(index) }
|
||||
}
|
||||
|
||||
private fun joinChunks(sub: IRSubroutine) {
|
||||
// Subroutine contains a list of chunks. Some can be joined into one.
|
||||
|
||||
if(sub.chunks.isEmpty())
|
||||
return
|
||||
|
||||
fun mayJoin(previous: IRCodeChunkBase, chunk: IRCodeChunkBase): Boolean {
|
||||
if(chunk.label!=null)
|
||||
return false
|
||||
if(previous is IRCodeChunk && chunk is IRCodeChunk) {
|
||||
// if the previous chunk doesn't end in a jump or a return, flow continues into the next chunk
|
||||
val lastInstruction = previous.instructions.lastOrNull()
|
||||
if(lastInstruction!=null)
|
||||
return lastInstruction.opcode !in OpcodesThatJump
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
val chunks = mutableListOf<IRCodeChunkBase>()
|
||||
chunks += sub.chunks[0]
|
||||
for(ix in 1 until sub.chunks.size) {
|
||||
val lastChunk = chunks.last()
|
||||
if(mayJoin(lastChunk, sub.chunks[ix])) {
|
||||
lastChunk.instructions += sub.chunks[ix].instructions
|
||||
lastChunk.next = sub.chunks[ix].next
|
||||
}
|
||||
else
|
||||
chunks += sub.chunks[ix]
|
||||
}
|
||||
sub.chunks.clear()
|
||||
sub.chunks += chunks
|
||||
}
|
||||
|
||||
private fun cleanupPushPop(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
@ -31,15 +118,15 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
var changed = false
|
||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||
if(ins.opcode== Opcode.PUSH) {
|
||||
if(idx < chunk.lines.size-1) {
|
||||
val insAfter = chunk.lines[idx+1] as? IRInstruction
|
||||
if(idx < chunk.instructions.size-1) {
|
||||
val insAfter = chunk.instructions[idx+1] as? IRInstruction
|
||||
if(insAfter!=null && insAfter.opcode == Opcode.POP) {
|
||||
if(ins.reg1==insAfter.reg1) {
|
||||
chunk.lines.removeAt(idx)
|
||||
chunk.lines.removeAt(idx)
|
||||
chunk.instructions.removeAt(idx)
|
||||
chunk.instructions.removeAt(idx)
|
||||
} else {
|
||||
chunk.lines[idx] = IRInstruction(Opcode.LOADR, ins.type, reg1=insAfter.reg1, reg2=ins.reg1)
|
||||
chunk.lines.removeAt(idx+1)
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.LOADR, ins.type, reg1=insAfter.reg1, reg2=ins.reg1)
|
||||
chunk.instructions.removeAt(idx+1)
|
||||
}
|
||||
changed = true
|
||||
}
|
||||
@ -55,18 +142,18 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
var changed = false
|
||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||
if(ins.opcode== Opcode.SEC || ins.opcode== Opcode.CLC) {
|
||||
if(idx < chunk.lines.size-1) {
|
||||
val insAfter = chunk.lines[idx+1] as? IRInstruction
|
||||
if(idx < chunk.instructions.size-1) {
|
||||
val insAfter = chunk.instructions[idx+1] as? IRInstruction
|
||||
if(insAfter?.opcode == ins.opcode) {
|
||||
chunk.lines.removeAt(idx)
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
else if(ins.opcode== Opcode.SEC && insAfter?.opcode== Opcode.CLC) {
|
||||
chunk.lines.removeAt(idx)
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
else if(ins.opcode== Opcode.CLC && insAfter?.opcode== Opcode.SEC) {
|
||||
chunk.lines.removeAt(idx)
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
@ -75,19 +162,24 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
return changed
|
||||
}
|
||||
|
||||
private fun removeWeirdBranches(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
// jump/branch to label immediately below
|
||||
private fun removeWeirdBranches(chunk: IRCodeChunk, nextChunk: IRCodeChunkBase?, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
var changed = false
|
||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||
val labelSymbol = ins.labelSymbol
|
||||
|
||||
// remove jump/branch to label immediately below (= next chunk if it has that label)
|
||||
if(ins.opcode== Opcode.JUMP && labelSymbol!=null) {
|
||||
// if jumping to label immediately following this
|
||||
if(idx < chunk.lines.size-1) {
|
||||
val label = chunk.lines[idx+1] as? IRCodeLabel
|
||||
if(label?.name == labelSymbol) {
|
||||
chunk.lines.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
if(idx==chunk.instructions.size-1 && ins.branchTarget===nextChunk) {
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
// remove useless RETURN
|
||||
if(ins.opcode == Opcode.RETURN && idx>0) {
|
||||
val previous = chunk.instructions[idx-1] as? IRInstruction
|
||||
if(previous?.opcode in OpcodesThatJump) {
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -101,47 +193,47 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
when (ins.opcode) {
|
||||
Opcode.DIV, Opcode.DIVS, Opcode.MUL, Opcode.MOD -> {
|
||||
if (ins.value == 1) {
|
||||
chunk.lines.removeAt(idx)
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
Opcode.ADD, Opcode.SUB -> {
|
||||
if (ins.value == 1) {
|
||||
chunk.lines[idx] = IRInstruction(
|
||||
chunk.instructions[idx] = IRInstruction(
|
||||
if (ins.opcode == Opcode.ADD) Opcode.INC else Opcode.DEC,
|
||||
ins.type,
|
||||
ins.reg1
|
||||
)
|
||||
changed = true
|
||||
} else if (ins.value == 0) {
|
||||
chunk.lines.removeAt(idx)
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
Opcode.AND -> {
|
||||
if (ins.value == 0) {
|
||||
chunk.lines[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = 0)
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = 0)
|
||||
changed = true
|
||||
} else if (ins.value == 255 && ins.type == VmDataType.BYTE) {
|
||||
chunk.lines.removeAt(idx)
|
||||
} else if (ins.value == 255 && ins.type == IRDataType.BYTE) {
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
} else if (ins.value == 65535 && ins.type == VmDataType.WORD) {
|
||||
chunk.lines.removeAt(idx)
|
||||
} else if (ins.value == 65535 && ins.type == IRDataType.WORD) {
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
Opcode.OR -> {
|
||||
if (ins.value == 0) {
|
||||
chunk.lines.removeAt(idx)
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
} else if ((ins.value == 255 && ins.type == VmDataType.BYTE) || (ins.value == 65535 && ins.type == VmDataType.WORD)) {
|
||||
chunk.lines[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = ins.value)
|
||||
} else if ((ins.value == 255 && ins.type == IRDataType.BYTE) || (ins.value == 65535 && ins.type == IRDataType.WORD)) {
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = ins.value)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
Opcode.XOR -> {
|
||||
if (ins.value == 0) {
|
||||
chunk.lines.removeAt(idx)
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
@ -156,7 +248,7 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||
if (ins.opcode == Opcode.NOP) {
|
||||
changed = true
|
||||
chunk.lines.removeAt(idx)
|
||||
chunk.instructions.removeAt(idx)
|
||||
}
|
||||
}
|
||||
return changed
|
||||
|
@ -0,0 +1,103 @@
|
||||
package prog8.codegen.intermediate
|
||||
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.code.core.SourceCode.Companion.libraryFilePrefix
|
||||
import prog8.intermediate.*
|
||||
|
||||
|
||||
internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val errors: IErrorReporter) {
|
||||
fun optimize(): Int {
|
||||
var numRemoved = removeSimpleUnlinked() + removeUnreachable()
|
||||
|
||||
// remove empty subs
|
||||
irprog.blocks.forEach { block ->
|
||||
block.subroutines.reversed().forEach { sub ->
|
||||
if(sub.isEmpty()) {
|
||||
if(!sub.position.file.startsWith(libraryFilePrefix))
|
||||
errors.warn("unused subroutine ${sub.name}", sub.position)
|
||||
block.subroutines.remove(sub)
|
||||
numRemoved++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove empty blocks
|
||||
irprog.blocks.reversed().forEach { block ->
|
||||
if(block.isEmpty()) {
|
||||
irprog.blocks.remove(block)
|
||||
numRemoved++
|
||||
}
|
||||
}
|
||||
|
||||
return numRemoved
|
||||
}
|
||||
|
||||
private fun removeUnreachable(): Int {
|
||||
val reachable = mutableSetOf(irprog.blocks.single { it.name=="main" }.subroutines.single { it.name=="main.start" }.chunks.first())
|
||||
|
||||
fun grow() {
|
||||
val new = mutableSetOf<IRCodeChunkBase>()
|
||||
reachable.forEach {
|
||||
it.next?.let { next -> new += next }
|
||||
it.instructions.forEach { instr -> instr.branchTarget?.let { target -> new += target} }
|
||||
}
|
||||
reachable += new
|
||||
}
|
||||
|
||||
var previousCount = reachable.size
|
||||
while(true) {
|
||||
grow()
|
||||
if(reachable.size<=previousCount)
|
||||
break
|
||||
previousCount = reachable.size
|
||||
}
|
||||
|
||||
return removeUnlinkedChunks(reachable)
|
||||
}
|
||||
|
||||
private fun removeSimpleUnlinked(): Int {
|
||||
val linkedChunks = mutableSetOf<IRCodeChunkBase>()
|
||||
|
||||
irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub ->
|
||||
sub.chunks.forEach { chunk ->
|
||||
chunk.next?.let { next -> linkedChunks += next }
|
||||
chunk.instructions.forEach { it.branchTarget?.let { target -> linkedChunks += target } }
|
||||
if (chunk.label == "main.start")
|
||||
linkedChunks += chunk
|
||||
}
|
||||
}
|
||||
|
||||
return removeUnlinkedChunks(linkedChunks)
|
||||
}
|
||||
|
||||
private fun removeUnlinkedChunks(
|
||||
linkedChunks: MutableSet<IRCodeChunkBase>
|
||||
): Int {
|
||||
var numRemoved = 0
|
||||
irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub ->
|
||||
sub.chunks.withIndex().reversed().forEach { (index, chunk) ->
|
||||
if (chunk !in linkedChunks) {
|
||||
if (chunk === sub.chunks[0]) {
|
||||
when(chunk) {
|
||||
is IRCodeChunk -> {
|
||||
if (chunk.isNotEmpty()) {
|
||||
// don't remove the first chunk of the sub itself because it has to have the name of the sub as label
|
||||
chunk.instructions.clear()
|
||||
numRemoved++
|
||||
}
|
||||
}
|
||||
is IRInlineAsmChunk, is IRInlineBinaryChunk -> {
|
||||
sub.chunks[index] = IRCodeChunk(chunk.label, chunk.next)
|
||||
numRemoved++
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sub.chunks.removeAt(index)
|
||||
numRemoved++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return numRemoved
|
||||
}
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
package prog8.codegen.intermediate
|
||||
|
||||
import prog8.code.core.AssemblyError
|
||||
import prog8.intermediate.SyscallRegisterBase
|
||||
|
||||
internal class RegisterPool {
|
||||
private var firstFree: Int=3 // integer registers 0,1,2 are reserved
|
||||
private var firstFreeFloat: Int=0
|
||||
// reserve 0,1,2 for return values of subroutine calls and syscalls
|
||||
private var firstFree: Int=3
|
||||
private var firstFreeFloat: Int=3
|
||||
|
||||
fun peekNext() = firstFree
|
||||
fun peekNextFloat() = firstFreeFloat
|
||||
@ -12,7 +14,7 @@ internal class RegisterPool {
|
||||
fun nextFree(): Int {
|
||||
val result = firstFree
|
||||
firstFree++
|
||||
if(firstFree>65535)
|
||||
if(firstFree >= SyscallRegisterBase)
|
||||
throw AssemblyError("out of virtual registers (int)")
|
||||
return result
|
||||
}
|
||||
@ -20,7 +22,7 @@ internal class RegisterPool {
|
||||
fun nextFreeFloat(): Int {
|
||||
val result = firstFreeFloat
|
||||
firstFreeFloat++
|
||||
if(firstFreeFloat>65535)
|
||||
if(firstFreeFloat >= SyscallRegisterBase)
|
||||
throw AssemblyError("out of virtual registers (fp)")
|
||||
return result
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package prog8.vm.codegen
|
||||
package prog8.codegen.vm
|
||||
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.ast.PtProgram
|
||||
@ -7,10 +7,8 @@ import prog8.code.core.IAssemblyGenerator
|
||||
import prog8.code.core.IAssemblyProgram
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.codegen.intermediate.IRCodeGen
|
||||
import prog8.intermediate.IRFileReader
|
||||
import prog8.intermediate.IRFileWriter
|
||||
import prog8.intermediate.IRProgram
|
||||
import java.nio.file.Path
|
||||
|
||||
class VmCodeGen(private val program: PtProgram,
|
||||
private val symbolTable: SymbolTable,
|
||||
@ -25,13 +23,6 @@ class VmCodeGen(private val program: PtProgram,
|
||||
// no need to check options.keepIR, as the VM file format *is* the IR file.
|
||||
return VmAssemblyProgram(irProgram.name, irProgram)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun compileIR(irFile: Path): IAssemblyProgram {
|
||||
val irProgram = IRFileReader().read(irFile)
|
||||
return VmAssemblyProgram(irProgram.name, irProgram)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,13 +6,11 @@ import prog8.codegen.intermediate.IRPeepholeOptimizer
|
||||
import prog8.intermediate.*
|
||||
|
||||
class TestIRPeepholeOpt: FunSpec({
|
||||
fun makeIRProgram(lines: List<IRCodeLine>): IRProgram {
|
||||
fun makeIRProgram(chunks: List<IRCodeChunkBase>): IRProgram {
|
||||
require(chunks.first().label=="main.start")
|
||||
val block = IRBlock("main", null, IRBlock.BlockAlignment.NONE, Position.DUMMY)
|
||||
val sub = IRSubroutine("main.start", emptyList(), null, Position.DUMMY)
|
||||
val chunk = IRCodeChunk(Position.DUMMY)
|
||||
for(line in lines)
|
||||
chunk += line
|
||||
sub += chunk
|
||||
chunks.forEach { sub += it }
|
||||
block += sub
|
||||
val target = VMTarget()
|
||||
val options = CompilationOptions(
|
||||
@ -27,44 +25,60 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
)
|
||||
val prog = IRProgram("test", IRSymbolTable(null), options, target)
|
||||
prog.addBlock(block)
|
||||
prog.linkChunks()
|
||||
prog.validate()
|
||||
return prog
|
||||
}
|
||||
|
||||
fun IRProgram.lines(): List<IRCodeLine> = this.blocks.flatMap { it.subroutines }.flatMap { it.chunks }.flatMap { it.lines }
|
||||
fun makeIRProgram(instructions: List<IRInstruction>): IRProgram {
|
||||
val chunk = IRCodeChunk("main.start", null)
|
||||
instructions.forEach { chunk += it }
|
||||
return makeIRProgram(listOf(chunk))
|
||||
}
|
||||
|
||||
fun IRProgram.chunks(): List<IRCodeChunkBase> = this.blocks.flatMap { it.subroutines }.flatMap { it.chunks }
|
||||
|
||||
test("remove nops") {
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRInstruction(Opcode.JUMP, labelSymbol = "dummy"),
|
||||
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, value=42),
|
||||
IRInstruction(Opcode.NOP),
|
||||
IRInstruction(Opcode.NOP)
|
||||
))
|
||||
irProg.lines().size shouldBe 3
|
||||
irProg.chunks().single().instructions.size shouldBe 3
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize()
|
||||
irProg.lines().size shouldBe 1
|
||||
irProg.chunks().single().instructions.size shouldBe 1
|
||||
}
|
||||
|
||||
test("remove jmp to label below") {
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRInstruction(Opcode.JUMP, labelSymbol = "label"), // removed
|
||||
IRCodeLabel("label"),
|
||||
IRInstruction(Opcode.JUMP, labelSymbol = "label2"), // removed
|
||||
IRInstruction(Opcode.NOP), // removed
|
||||
IRCodeLabel("label2"),
|
||||
IRInstruction(Opcode.JUMP, labelSymbol = "label3"),
|
||||
IRInstruction(Opcode.INC, VmDataType.BYTE, reg1=1),
|
||||
IRCodeLabel("label3")
|
||||
))
|
||||
irProg.lines().size shouldBe 8
|
||||
val c1 = IRCodeChunk("main.start", null)
|
||||
c1 += IRInstruction(Opcode.JUMP, labelSymbol = "label") // removed, but chunk stays because of label
|
||||
val c2 = IRCodeChunk("label", null)
|
||||
c2 += IRInstruction(Opcode.JUMP, labelSymbol = "label2") // removed, but chunk stays because of label
|
||||
c2 += IRInstruction(Opcode.NOP) // removed
|
||||
val c3 = IRCodeChunk("label2", null)
|
||||
c3 += IRInstruction(Opcode.JUMP, labelSymbol = "label3")
|
||||
c3 += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=1)
|
||||
val c4 = IRCodeChunk("label3", null)
|
||||
val irProg = makeIRProgram(listOf(c1, c2, c3, c4))
|
||||
|
||||
irProg.chunks().size shouldBe 4
|
||||
irProg.chunks().flatMap { it.instructions }.size shouldBe 5
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize()
|
||||
val lines = irProg.lines()
|
||||
lines.size shouldBe 5
|
||||
(lines[0] as IRCodeLabel).name shouldBe "label"
|
||||
(lines[1] as IRCodeLabel).name shouldBe "label2"
|
||||
(lines[2] as IRInstruction).opcode shouldBe Opcode.JUMP
|
||||
(lines[3] as IRInstruction).opcode shouldBe Opcode.INC
|
||||
(lines[4] as IRCodeLabel).name shouldBe "label3"
|
||||
irProg.chunks().size shouldBe 4
|
||||
irProg.chunks()[0].label shouldBe "main.start"
|
||||
irProg.chunks()[1].label shouldBe "label"
|
||||
irProg.chunks()[2].label shouldBe "label2"
|
||||
irProg.chunks()[3].label shouldBe "label3"
|
||||
irProg.chunks()[0].isEmpty() shouldBe true
|
||||
irProg.chunks()[1].isEmpty() shouldBe true
|
||||
irProg.chunks()[2].isEmpty() shouldBe false
|
||||
irProg.chunks()[3].isEmpty() shouldBe true
|
||||
val instr = irProg.chunks().flatMap { it.instructions }
|
||||
instr.size shouldBe 2
|
||||
instr[0].opcode shouldBe Opcode.JUMP
|
||||
instr[1].opcode shouldBe Opcode.INC
|
||||
}
|
||||
|
||||
test("remove double sec/clc") {
|
||||
@ -76,102 +90,100 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
IRInstruction(Opcode.CLC),
|
||||
IRInstruction(Opcode.CLC)
|
||||
))
|
||||
irProg.lines().size shouldBe 6
|
||||
irProg.chunks().single().instructions.size shouldBe 6
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize()
|
||||
val lines = irProg.lines()
|
||||
lines.size shouldBe 1
|
||||
(lines[0] as IRInstruction).opcode shouldBe Opcode.CLC
|
||||
val instr = irProg.chunks().single().instructions
|
||||
instr.size shouldBe 1
|
||||
instr[0].opcode shouldBe Opcode.CLC
|
||||
}
|
||||
|
||||
test("push followed by pop") {
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=42),
|
||||
IRInstruction(Opcode.POP, VmDataType.BYTE, reg1=42),
|
||||
IRInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=99),
|
||||
IRInstruction(Opcode.POP, VmDataType.BYTE, reg1=222)
|
||||
IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=42),
|
||||
IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=42),
|
||||
IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=99),
|
||||
IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=222)
|
||||
))
|
||||
irProg.lines().size shouldBe 4
|
||||
irProg.chunks().single().instructions.size shouldBe 4
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize()
|
||||
val lines = irProg.lines()
|
||||
lines.size shouldBe 1
|
||||
(lines[0] as IRInstruction).opcode shouldBe Opcode.LOADR
|
||||
(lines[0] as IRInstruction).reg1 shouldBe 222
|
||||
(lines[0] as IRInstruction).reg2 shouldBe 99
|
||||
val instr = irProg.chunks().single().instructions
|
||||
instr.size shouldBe 1
|
||||
instr[0].opcode shouldBe Opcode.LOADR
|
||||
instr[0].reg1 shouldBe 222
|
||||
instr[0].reg2 shouldBe 99
|
||||
}
|
||||
|
||||
test("remove useless div/mul, add/sub") {
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRInstruction(Opcode.DIV, VmDataType.BYTE, reg1=42, value = 1),
|
||||
IRInstruction(Opcode.DIVS, VmDataType.BYTE, reg1=42, value = 1),
|
||||
IRInstruction(Opcode.MUL, VmDataType.BYTE, reg1=42, value = 1),
|
||||
IRInstruction(Opcode.MOD, VmDataType.BYTE, reg1=42, value = 1),
|
||||
IRInstruction(Opcode.DIV, VmDataType.BYTE, reg1=42, value = 2),
|
||||
IRInstruction(Opcode.DIVS, VmDataType.BYTE, reg1=42, value = 2),
|
||||
IRInstruction(Opcode.MUL, VmDataType.BYTE, reg1=42, value = 2),
|
||||
IRInstruction(Opcode.MOD, VmDataType.BYTE, reg1=42, value = 2),
|
||||
IRInstruction(Opcode.ADD, VmDataType.BYTE, reg1=42, value = 0),
|
||||
IRInstruction(Opcode.SUB, VmDataType.BYTE, reg1=42, value = 0)
|
||||
IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=42, value = 1),
|
||||
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=42, value = 1),
|
||||
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=42, value = 1),
|
||||
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=42, value = 1),
|
||||
IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=42, value = 2),
|
||||
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=42, value = 2),
|
||||
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=42, value = 2),
|
||||
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=42, value = 2),
|
||||
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=42, value = 0),
|
||||
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, value = 0)
|
||||
))
|
||||
irProg.lines().size shouldBe 10
|
||||
irProg.chunks().single().instructions.size shouldBe 10
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize()
|
||||
val lines = irProg.lines()
|
||||
lines.size shouldBe 4
|
||||
irProg.chunks().single().instructions.size shouldBe 4
|
||||
}
|
||||
|
||||
test("replace add/sub 1 by inc/dec") {
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRInstruction(Opcode.ADD, VmDataType.BYTE, reg1=42, value = 1),
|
||||
IRInstruction(Opcode.SUB, VmDataType.BYTE, reg1=42, value = 1)
|
||||
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=42, value = 1),
|
||||
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, value = 1)
|
||||
))
|
||||
irProg.lines().size shouldBe 2
|
||||
irProg.chunks().single().instructions.size shouldBe 2
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize()
|
||||
val lines = irProg.lines()
|
||||
lines.size shouldBe 2
|
||||
(lines[0] as IRInstruction).opcode shouldBe Opcode.INC
|
||||
(lines[1] as IRInstruction).opcode shouldBe Opcode.DEC
|
||||
val instr = irProg.chunks().single().instructions
|
||||
instr.size shouldBe 2
|
||||
instr[0].opcode shouldBe Opcode.INC
|
||||
instr[1].opcode shouldBe Opcode.DEC
|
||||
}
|
||||
|
||||
test("remove useless and/or/xor") {
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRInstruction(Opcode.AND, VmDataType.BYTE, reg1=42, value = 255),
|
||||
IRInstruction(Opcode.AND, VmDataType.WORD, reg1=42, value = 65535),
|
||||
IRInstruction(Opcode.OR, VmDataType.BYTE, reg1=42, value = 0),
|
||||
IRInstruction(Opcode.XOR, VmDataType.BYTE, reg1=42, value = 0),
|
||||
IRInstruction(Opcode.AND, VmDataType.BYTE, reg1=42, value = 200),
|
||||
IRInstruction(Opcode.AND, VmDataType.WORD, reg1=42, value = 60000),
|
||||
IRInstruction(Opcode.OR, VmDataType.BYTE, reg1=42, value = 1),
|
||||
IRInstruction(Opcode.XOR, VmDataType.BYTE, reg1=42, value = 1)
|
||||
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, value = 255),
|
||||
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, value = 65535),
|
||||
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, value = 0),
|
||||
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, value = 0),
|
||||
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, value = 200),
|
||||
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, value = 60000),
|
||||
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, value = 1),
|
||||
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, value = 1)
|
||||
))
|
||||
irProg.lines().size shouldBe 8
|
||||
irProg.chunks().single().instructions.size shouldBe 8
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize()
|
||||
val lines = irProg.lines()
|
||||
lines.size shouldBe 4
|
||||
irProg.chunks().single().instructions.size shouldBe 4
|
||||
}
|
||||
|
||||
test("replace and/or/xor by constant number") {
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRInstruction(Opcode.AND, VmDataType.BYTE, reg1=42, value = 0),
|
||||
IRInstruction(Opcode.AND, VmDataType.WORD, reg1=42, value = 0),
|
||||
IRInstruction(Opcode.OR, VmDataType.BYTE, reg1=42, value = 255),
|
||||
IRInstruction(Opcode.OR, VmDataType.WORD, reg1=42, value = 65535)
|
||||
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, value = 0),
|
||||
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, value = 0),
|
||||
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, value = 255),
|
||||
IRInstruction(Opcode.OR, IRDataType.WORD, reg1=42, value = 65535)
|
||||
))
|
||||
irProg.lines().size shouldBe 4
|
||||
irProg.chunks().single().instructions.size shouldBe 4
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize()
|
||||
val lines = irProg.lines()
|
||||
lines.size shouldBe 4
|
||||
(lines[0] as IRInstruction).opcode shouldBe Opcode.LOAD
|
||||
(lines[1] as IRInstruction).opcode shouldBe Opcode.LOAD
|
||||
(lines[2] as IRInstruction).opcode shouldBe Opcode.LOAD
|
||||
(lines[3] as IRInstruction).opcode shouldBe Opcode.LOAD
|
||||
(lines[0] as IRInstruction).value shouldBe 0
|
||||
(lines[1] as IRInstruction).value shouldBe 0
|
||||
(lines[2] as IRInstruction).value shouldBe 255
|
||||
(lines[3] as IRInstruction).value shouldBe 65535
|
||||
val instr = irProg.chunks().single().instructions
|
||||
instr.size shouldBe 4
|
||||
instr[0].opcode shouldBe Opcode.LOAD
|
||||
instr[1].opcode shouldBe Opcode.LOAD
|
||||
instr[2].opcode shouldBe Opcode.LOAD
|
||||
instr[3].opcode shouldBe Opcode.LOAD
|
||||
instr[0].value shouldBe 0
|
||||
instr[1].value shouldBe 0
|
||||
instr[2].value shouldBe 255
|
||||
instr[3].value shouldBe 65535
|
||||
}
|
||||
})
|
@ -20,7 +20,9 @@ import kotlin.math.pow
|
||||
|
||||
// TODO add more peephole expression optimizations? Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
|
||||
|
||||
class ExpressionSimplifier(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() {
|
||||
class ExpressionSimplifier(private val program: Program,
|
||||
private val errors: IErrorReporter,
|
||||
private val compTarget: ICompilationTarget) : AstWalker() {
|
||||
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
|
||||
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
|
||||
|
||||
@ -586,11 +588,13 @@ class ExpressionSimplifier(private val program: Program, private val compTarget:
|
||||
when (val targetDt = targetIDt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
if (amount >= 8) {
|
||||
errors.warn("shift always results in 0", expr.position)
|
||||
return NumericLiteral(targetDt, 0.0, expr.position)
|
||||
}
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
if (amount >= 16) {
|
||||
errors.warn("shift always results in 0", expr.position)
|
||||
return NumericLiteral(targetDt, 0.0, expr.position)
|
||||
}
|
||||
else if(amount==8) {
|
||||
@ -625,6 +629,7 @@ class ExpressionSimplifier(private val program: Program, private val compTarget:
|
||||
when (idt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.UBYTE -> {
|
||||
if (amount >= 8) {
|
||||
errors.warn("shift always results in 0", expr.position)
|
||||
return NumericLiteral.optimalInteger(0, expr.position)
|
||||
}
|
||||
}
|
||||
@ -636,6 +641,7 @@ class ExpressionSimplifier(private val program: Program, private val compTarget:
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if (amount >= 16) {
|
||||
errors.warn("shift always results in 0", expr.position)
|
||||
return NumericLiteral.optimalInteger(0, expr.position)
|
||||
}
|
||||
else if(amount==8) {
|
||||
|
@ -60,8 +60,8 @@ fun Program.inlineSubroutines(): Int {
|
||||
return inliner.applyModifications()
|
||||
}
|
||||
|
||||
fun Program.simplifyExpressions(target: ICompilationTarget) : Int {
|
||||
val opti = ExpressionSimplifier(this, target)
|
||||
fun Program.simplifyExpressions(errors: IErrorReporter, target: ICompilationTarget) : Int {
|
||||
val opti = ExpressionSimplifier(this, errors, target)
|
||||
opti.visit(this)
|
||||
return opti.applyModifications()
|
||||
}
|
||||
|
@ -38,6 +38,8 @@ class Inliner(val program: Program): AstWalker() {
|
||||
is Return -> {
|
||||
if(stmt.value is NumericLiteral)
|
||||
true
|
||||
else if(stmt.value==null)
|
||||
true
|
||||
else if (stmt.value is IdentifierReference) {
|
||||
makeFullyScoped(stmt.value as IdentifierReference)
|
||||
true
|
||||
|
@ -97,11 +97,13 @@ class StatementOptimizer(private val program: Program,
|
||||
if(constvalue!=null) {
|
||||
return if(constvalue.asBooleanValue){
|
||||
// always true -> keep only if-part
|
||||
errors.warn("condition is always true", ifElse.condition.position)
|
||||
if(!ifElse.definingModule.isLibrary)
|
||||
errors.warn("condition is always true", ifElse.condition.position)
|
||||
listOf(IAstModification.ReplaceNode(ifElse, ifElse.truepart, parent))
|
||||
} else {
|
||||
// always false -> keep only else-part
|
||||
errors.warn("condition is always false", ifElse.condition.position)
|
||||
if(!ifElse.definingModule.isLibrary)
|
||||
errors.warn("condition is always false", ifElse.condition.position)
|
||||
listOf(IAstModification.ReplaceNode(ifElse, ifElse.elsepart, parent))
|
||||
}
|
||||
}
|
||||
@ -291,9 +293,9 @@ class StatementOptimizer(private val program: Program,
|
||||
val bexpr=assignment.value as? BinaryExpression
|
||||
if(bexpr!=null) {
|
||||
val rightCv = bexpr.right.constValue(program)?.number
|
||||
if(bexpr.operator=="-" && rightCv==null) {
|
||||
if(bexpr.right isSameAs assignment.target) {
|
||||
// X = value - X --> X = -X ; X += value (to avoid need of stack-evaluation)
|
||||
if(bexpr.operator=="-" && rightCv==null && targetIDt.isInteger) {
|
||||
if(bexpr.right.isSimple && bexpr.right isSameAs assignment.target) {
|
||||
// X = value - X --> X = -X ; X += value (to avoid need of stack-evaluation, for integers)
|
||||
val negation = PrefixExpression("-", bexpr.right.copy(), bexpr.position)
|
||||
val addValue = Assignment(assignment.target.copy(), BinaryExpression(bexpr.right, "+", bexpr.left, bexpr.position), AssignmentOrigin.OPTIMIZER, assignment.position)
|
||||
return listOf(
|
||||
|
@ -31,6 +31,7 @@ dependencies {
|
||||
implementation project(':codeOptimizers')
|
||||
implementation project(':compilerAst')
|
||||
implementation project(':codeGenCpu6502')
|
||||
implementation project(':codeGenIntermediate')
|
||||
implementation project(':codeGenExperimental')
|
||||
implementation project(':virtualmachine')
|
||||
implementation 'org.antlr:antlr4-runtime:4.10.1'
|
||||
|
@ -21,6 +21,7 @@
|
||||
<orderEntry type="module" module-name="codeOptimizers" />
|
||||
<orderEntry type="module" module-name="codeGenCpu6502" />
|
||||
<orderEntry type="module" module-name="codeGenExperimental" />
|
||||
<orderEntry type="module" module-name="codeGenIntermediate" />
|
||||
<orderEntry type="module" module-name="virtualmachine" />
|
||||
</component>
|
||||
</module>
|
||||
</module>
|
@ -56,7 +56,8 @@ romsub $af4b = ROUND() clobbers(A,X,Y) ; round fac1
|
||||
romsub $af4e = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
|
||||
romsub $af51 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
||||
romsub $af54 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
||||
romsub $af57 = RND_0() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator NOTE: special cx16 setup required, use RND() stub instead!!
|
||||
romsub $af57 = RND_0() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
|
||||
romsub $af57 = RND() clobbers(A,X,Y) ; alias for RND_0
|
||||
romsub $af5a = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac2
|
||||
romsub $af5d = ROMUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in current bank in A/Y into fac2
|
||||
romsub $af60 = MOVFRM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1 (use MOVFM instead)
|
||||
@ -151,6 +152,17 @@ asmsub FREADUY (ubyte value @Y) {
|
||||
|
||||
&uword AYINT_facmo = $66 ; $66/$67 contain result of AYINT
|
||||
|
||||
sub rndf() -> float {
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #1
|
||||
jsr RND_0
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
%asminclude "library:c128/floats.asm"
|
||||
%asminclude "library:c64/floats_funcs.asm"
|
||||
|
||||
|
@ -171,6 +171,18 @@ asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
||||
|
||||
&uword AYINT_facmo = $64 ; $64/$65 contain result of AYINT
|
||||
|
||||
sub rndf() -> float {
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #1
|
||||
jsr FREADSA
|
||||
jsr RND ; rng into fac1
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
%asminclude "library:c64/floats.asm"
|
||||
%asminclude "library:c64/floats_funcs.asm"
|
||||
|
||||
|
@ -37,14 +37,19 @@ cx16diskio {
|
||||
asmsub vload(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
|
||||
; -- like the basic command VLOAD "filename",device,bank,address
|
||||
; loads a file into Vera's video memory in the given bank:address, returns success in A
|
||||
; the file has to have the usual 2 byte header (which will be skipped)
|
||||
%asm {{
|
||||
; -- load a file into video ram
|
||||
clc
|
||||
internal_vload:
|
||||
phx
|
||||
pha
|
||||
tya
|
||||
tax
|
||||
lda #1
|
||||
ldy #0
|
||||
bcc +
|
||||
ldy #%00000010 ; headerless load mode
|
||||
bne ++
|
||||
+ ldy #0 ; normal load mode
|
||||
+ lda #1
|
||||
jsr c64.SETLFS
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
@ -71,6 +76,15 @@ cx16diskio {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub vload_raw(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
|
||||
; -- like the basic command BVLOAD "filename",device,bank,address
|
||||
; loads a file into Vera's video memory in the given bank:address, returns success in A
|
||||
; the file is read fully including the first two bytes.
|
||||
%asm {{
|
||||
sec
|
||||
jmp vload.internal_vload
|
||||
}}
|
||||
}
|
||||
|
||||
; replacement function that makes use of fast block read capability of the X16
|
||||
; use this in place of regular diskio.f_read()
|
||||
@ -97,7 +111,7 @@ cx16diskio {
|
||||
size = 255
|
||||
if num_bytes<size
|
||||
size = num_bytes
|
||||
size = cx16.macptr(lsb(size), bufferpointer)
|
||||
size = cx16.macptr(lsb(size), bufferpointer, false)
|
||||
if_cs
|
||||
goto byte_read_loop ; macptr block read not supported, do fallback loop
|
||||
diskio.list_blocks += size
|
||||
|
@ -57,7 +57,8 @@ romsub $fe4b = ROUND() clobbers(A,X,Y) ; round fac1
|
||||
romsub $fe4e = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
|
||||
romsub $fe51 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
||||
romsub $fe54 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
||||
romsub $fe57 = RND_0() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator NOTE: special cx16 setup required, use RND() stub instead!!
|
||||
romsub $fe57 = RND_0() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator NOTE: incompatible with C64's RND routine
|
||||
romsub $fe57 = RND() clobbers(A,X,Y) ; alias for RND_0
|
||||
romsub $fe5a = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac2
|
||||
romsub $fe5d = ROMUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in current bank in A/Y into fac2
|
||||
romsub $fe60 = MOVFRM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1 (use MOVFM instead)
|
||||
@ -147,19 +148,21 @@ asmsub FREADUY (ubyte value @Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub RND() clobbers(A,X,Y) {
|
||||
%asm {{
|
||||
lda #0
|
||||
php
|
||||
jsr cx16.entropy_get
|
||||
plp
|
||||
jmp RND_0
|
||||
}}
|
||||
}
|
||||
|
||||
&uword AYINT_facmo = $c6 ; $c6/$c7 contain result of AYINT
|
||||
|
||||
sub rndf() -> float {
|
||||
%asm {{
|
||||
phx
|
||||
lda #1
|
||||
jsr RND_0
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
%asminclude "library:c64/floats.asm"
|
||||
%asminclude "library:c64/floats_funcs.asm"
|
||||
|
||||
|
||||
}
|
||||
|
@ -366,7 +366,7 @@ _done
|
||||
position2(x,y,true)
|
||||
set_both_strides(13) ; 160 increment = 1 line in 640 px 4c mode
|
||||
color &= 3
|
||||
color <<= gfx2.plot.shift4c[lsb(x) & 3]
|
||||
color <<= gfx2.plot.shift4c[lsb(x) & 3] ; TODO with lookup table
|
||||
ubyte @shared mask = gfx2.plot.mask4c[lsb(x) & 3]
|
||||
repeat lheight {
|
||||
%asm {{
|
||||
@ -561,7 +561,11 @@ _done
|
||||
and #1
|
||||
}}
|
||||
if_nz {
|
||||
cx16.r0L = lsb(x) & 7 ; xbits
|
||||
%asm {{
|
||||
lda x
|
||||
and #7
|
||||
pha ; xbits
|
||||
}}
|
||||
x /= 8
|
||||
x += y*(320/8)
|
||||
%asm {{
|
||||
@ -571,7 +575,7 @@ _done
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda x
|
||||
sta cx16.VERA_ADDR_L
|
||||
ldy cx16.r0L ; xbits
|
||||
ply ; xbits
|
||||
lda bits,y
|
||||
ldy color
|
||||
beq +
|
||||
@ -608,7 +612,11 @@ _done
|
||||
and #1
|
||||
}}
|
||||
if_nz {
|
||||
cx16.r0L = lsb(x) & 7 ; xbits
|
||||
%asm {{
|
||||
lda x
|
||||
and #7
|
||||
pha ; xbits
|
||||
}}
|
||||
x /= 8
|
||||
x += y*(640/8)
|
||||
%asm {{
|
||||
@ -618,7 +626,7 @@ _done
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda x
|
||||
sta cx16.VERA_ADDR_L
|
||||
ldy cx16.r0L ; xbits
|
||||
ply ; xbits
|
||||
lda bits,y
|
||||
ldy color
|
||||
beq +
|
||||
@ -635,7 +643,7 @@ _done
|
||||
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
|
||||
cx16.r2L = lsb(x) & 3 ; xbits
|
||||
color &= 3
|
||||
color <<= shift4c[cx16.r2L]
|
||||
color <<= shift4c[cx16.r2L] ; TODO with lookup table
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
lda cx16.r1L
|
||||
@ -654,6 +662,93 @@ _done
|
||||
}
|
||||
}
|
||||
|
||||
sub pget(uword @zp x, uword y) -> ubyte {
|
||||
when active_mode {
|
||||
1 -> {
|
||||
; lores monochrome
|
||||
%asm {{
|
||||
lda x
|
||||
and #7
|
||||
pha ; xbits
|
||||
}}
|
||||
x /= 8
|
||||
x += y*(320/8)
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
stz cx16.VERA_ADDR_H
|
||||
lda x+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda x
|
||||
sta cx16.VERA_ADDR_L
|
||||
ply ; xbits
|
||||
lda plot.bits,y
|
||||
and cx16.VERA_DATA0
|
||||
beq +
|
||||
lda #1
|
||||
+
|
||||
}}
|
||||
}
|
||||
; TODO mode 2 and 3
|
||||
4 -> {
|
||||
; lores 256c
|
||||
void addr_mul_24_for_lores_256c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
lda cx16.r1
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda cx16.r0+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda cx16.r0
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.VERA_DATA0
|
||||
}}
|
||||
}
|
||||
5 -> {
|
||||
; hires monochrome
|
||||
%asm {{
|
||||
lda x
|
||||
and #7
|
||||
pha ; xbits
|
||||
}}
|
||||
x /= 8
|
||||
x += y*(640/8)
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
stz cx16.VERA_ADDR_H
|
||||
lda x+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda x
|
||||
sta cx16.VERA_ADDR_L
|
||||
ply ; xbits
|
||||
lda plot.bits,y
|
||||
and cx16.VERA_DATA0
|
||||
beq +
|
||||
lda #1
|
||||
+
|
||||
}}
|
||||
}
|
||||
6 -> {
|
||||
; hires 4c
|
||||
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
lda cx16.r1L
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda cx16.r0H
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda cx16.r0L
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.VERA_DATA0
|
||||
sta cx16.r0L
|
||||
}}
|
||||
cx16.r1L = lsb(x) & 3
|
||||
cx16.r0L >>= gfx2.plot.shift4c[cx16.r1L] ; TODO with lookup table
|
||||
return cx16.r0L & 3
|
||||
}
|
||||
else -> return 0
|
||||
}
|
||||
}
|
||||
|
||||
sub position(uword @zp x, uword y) {
|
||||
ubyte bank
|
||||
when active_mode {
|
||||
|
@ -26,7 +26,7 @@ palette {
|
||||
}
|
||||
|
||||
sub set_rgb(uword palette_words_ptr, uword num_colors) {
|
||||
; 1 word per color entry (in little endian format as layed out in video memory, so $gb0r)
|
||||
; 1 word per color entry (in little endian format as layed out in video memory, so $gb;$0r)
|
||||
vera_palette_ptr = $fa00
|
||||
repeat num_colors*2 {
|
||||
cx16.vpoke(1, vera_palette_ptr, @(palette_words_ptr))
|
||||
|
@ -363,7 +363,7 @@ romsub $fed5 = console_set_paging_message(uword msgptr @R0) clobbers(A,X,Y)
|
||||
romsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y
|
||||
romsub $fecc = monitor() clobbers(A,X,Y)
|
||||
|
||||
romsub $ff44 = macptr(ubyte length @A, uword buffer @XY) clobbers(A) -> ubyte @Pc, uword @XY
|
||||
romsub $ff44 = macptr(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
|
||||
romsub $ff47 = enter_basic(ubyte cold_or_warm @Pc) clobbers(A,X,Y)
|
||||
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y)
|
||||
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time()
|
||||
@ -393,9 +393,10 @@ asmsub kbdbuf_clear() {
|
||||
asmsub mouse_config2(ubyte shape @A) clobbers (A, X, Y) {
|
||||
; -- convenience wrapper function that handles the screen resolution for mouse_config() for you
|
||||
%asm {{
|
||||
pha ; save shape
|
||||
sec
|
||||
jsr cx16.screen_mode ; set current screen mode and res in A, X, Y
|
||||
lda #1
|
||||
pla ; get shape back
|
||||
jmp cx16.mouse_config
|
||||
}}
|
||||
}
|
||||
|
@ -221,13 +221,18 @@ sub ceil(float value) -> float {
|
||||
}}
|
||||
}
|
||||
|
||||
sub rndf() -> float {
|
||||
sub rndseedf(float seed) {
|
||||
if seed>0
|
||||
seed = -seed ; make sure fp seed is always negative
|
||||
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #1
|
||||
jsr FREADSA
|
||||
jsr RND ; rng into fac1
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
stx floats_store_reg
|
||||
lda #<seed
|
||||
ldy #>seed
|
||||
jsr MOVFM ; load float into fac1
|
||||
lda #-1
|
||||
jsr floats.RND
|
||||
ldx floats_store_reg
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
@ -229,60 +229,32 @@ _divisor .word 0
|
||||
.pend
|
||||
|
||||
|
||||
randseed .proc
|
||||
; -- reset the random seeds for the byte and word random generators
|
||||
; arguments: uword seed in A/Y clobbers A
|
||||
; (default starting values are: A=$2c Y=$9e)
|
||||
sta randword._seed
|
||||
sty randword._seed+1
|
||||
clc
|
||||
adc #14
|
||||
sta randbyte._seed
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
randbyte .proc
|
||||
; -- 8 bit pseudo random number generator into A (by just reusing randword)
|
||||
jmp randword
|
||||
.pend
|
||||
|
||||
randword .proc
|
||||
; -- 16 bit pseudo random number generator into AY
|
||||
|
||||
; rand64k ;Factors of 65535: 3 5 17 257
|
||||
lda sr1+1
|
||||
asl a
|
||||
asl a
|
||||
eor sr1+1
|
||||
asl a
|
||||
eor sr1+1
|
||||
asl a
|
||||
asl a
|
||||
eor sr1+1
|
||||
asl a
|
||||
rol sr1 ;shift this left, "random" bit comes from low
|
||||
rol sr1+1
|
||||
; rand32k ;Factors of 32767: 7 31 151 are independent and can be combined
|
||||
lda sr2+1
|
||||
asl a
|
||||
eor sr2+1
|
||||
asl a
|
||||
asl a
|
||||
ror sr2 ;shift this right, random bit comes from high - nicer when eor with sr1
|
||||
rol sr2+1
|
||||
lda sr1+1 ;can be left out
|
||||
eor sr2+1 ;if you dont use
|
||||
tay ;y as suggested
|
||||
lda sr1 ;mix up lowbytes of SR1
|
||||
eor sr2 ;and SR2 to combine both
|
||||
; default seed = $00c2 $1137
|
||||
; routine from https://codebase64.org/doku.php?id=base:x_abc_random_number_generator_8_16_bit
|
||||
inc x1
|
||||
clc
|
||||
x1=*+1
|
||||
lda #$00 ;x1
|
||||
c1=*+1
|
||||
eor #$c2 ;c1
|
||||
a1=*+1
|
||||
eor #$11 ;a1
|
||||
sta a1
|
||||
b1=*+1
|
||||
adc #$37 ;b1
|
||||
sta b1
|
||||
lsr a
|
||||
eor a1
|
||||
adc c1
|
||||
sta c1
|
||||
ldy b1
|
||||
rts
|
||||
|
||||
sr1 .word $a55a
|
||||
sr2 .word $7653
|
||||
|
||||
.pend
|
||||
|
||||
randbyte = randword ; -- 8 bit pseudo random number generator into A (by just reusing randword)
|
||||
|
||||
|
||||
; ----------- optimized multiplications (stack) : ---------
|
||||
stack_mul_byte_3 .proc
|
||||
|
@ -71,4 +71,28 @@ _sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub rnd() -> ubyte @A {
|
||||
%asm {{
|
||||
jmp math.randbyte
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub rndw() -> uword @AY {
|
||||
%asm {{
|
||||
jmp math.randword
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub rndseed(uword seed1 @AY, uword seed2 @R0) clobbers(A,Y) {
|
||||
; -- set new pseudo RNG's seed values. Defaults are: $00c2, $1137
|
||||
%asm {{
|
||||
sta math.randword.x1
|
||||
sty math.randword.c1
|
||||
lda cx16.r0L
|
||||
sta math.randword.a1
|
||||
lda cx16.r0H
|
||||
sta math.randword.b1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
@ -244,24 +244,6 @@ func_sqrt16_into_A .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_rnd_stack .proc
|
||||
; -- put a random ubyte on the estack
|
||||
jsr math.randbyte
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_rndw_stack .proc
|
||||
; -- put a random uword on the estack
|
||||
jsr math.randword
|
||||
sta P8ESTACK_LO,x
|
||||
tya
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
func_sort_ub .proc
|
||||
; 8bit unsigned sort
|
||||
|
@ -195,8 +195,8 @@ sub str2uword(str string) -> uword {
|
||||
; -- returns the unsigned word value of the string number argument in AY
|
||||
; the number may NOT be preceded by a + sign and may NOT contain spaces
|
||||
; (any non-digit character will terminate the number string that is parsed)
|
||||
%asm {{
|
||||
loadm.w r0,conv.str2uword.string
|
||||
%ir {{
|
||||
loadm.w r65500,conv.str2uword.string
|
||||
syscall 11
|
||||
return
|
||||
}}
|
||||
@ -206,8 +206,8 @@ sub str2word(str string) -> word {
|
||||
; -- returns the signed word value of the string number argument in AY
|
||||
; the number may be preceded by a + or - sign but may NOT contain spaces
|
||||
; (any non-digit character will terminate the number string that is parsed)
|
||||
%asm {{
|
||||
loadm.w r0,conv.str2word.string
|
||||
%ir {{
|
||||
loadm.w r65500,conv.str2word.string
|
||||
syscall 12
|
||||
return
|
||||
}}
|
||||
|
@ -9,15 +9,15 @@ floats {
|
||||
|
||||
sub print_f(float value) {
|
||||
; ---- prints the floating point value (without a newline).
|
||||
%asm {{
|
||||
loadm.f fr0,floats.print_f.value
|
||||
%ir {{
|
||||
loadm.f fr65500,floats.print_f.value
|
||||
syscall 25
|
||||
return
|
||||
}}
|
||||
}
|
||||
|
||||
sub pow(float value, float power) -> float {
|
||||
%asm {{
|
||||
%ir {{
|
||||
loadm.f fr0,floats.pow.value
|
||||
loadm.f fr1,floats.pow.power
|
||||
fpow.f fr0,fr1
|
||||
@ -26,7 +26,7 @@ sub pow(float value, float power) -> float {
|
||||
}
|
||||
|
||||
sub fabs(float value) -> float {
|
||||
%asm {{
|
||||
%ir {{
|
||||
loadm.f fr0,floats.fabs.value
|
||||
fabs.f fr0,fr0
|
||||
return
|
||||
@ -34,7 +34,7 @@ sub fabs(float value) -> float {
|
||||
}
|
||||
|
||||
sub sin(float angle) -> float {
|
||||
%asm {{
|
||||
%ir {{
|
||||
loadm.f fr0,floats.sin.angle
|
||||
fsin.f fr0,fr0
|
||||
return
|
||||
@ -42,7 +42,7 @@ sub sin(float angle) -> float {
|
||||
}
|
||||
|
||||
sub cos(float angle) -> float {
|
||||
%asm {{
|
||||
%ir {{
|
||||
loadm.f fr0,floats.cos.angle
|
||||
fcos.f fr0,fr0
|
||||
return
|
||||
@ -50,7 +50,7 @@ sub cos(float angle) -> float {
|
||||
}
|
||||
|
||||
sub tan(float value) -> float {
|
||||
%asm {{
|
||||
%ir {{
|
||||
loadm.f fr0,floats.tan.value
|
||||
ftan.f fr0,fr0
|
||||
return
|
||||
@ -58,7 +58,7 @@ sub tan(float value) -> float {
|
||||
}
|
||||
|
||||
sub atan(float value) -> float {
|
||||
%asm {{
|
||||
%ir {{
|
||||
loadm.f fr0,floats.atan.value
|
||||
fatan.f fr0,fr0
|
||||
return
|
||||
@ -66,7 +66,7 @@ sub atan(float value) -> float {
|
||||
}
|
||||
|
||||
sub ln(float value) -> float {
|
||||
%asm {{
|
||||
%ir {{
|
||||
loadm.f fr0,floats.ln.value
|
||||
fln.f fr0,fr0
|
||||
return
|
||||
@ -74,7 +74,7 @@ sub ln(float value) -> float {
|
||||
}
|
||||
|
||||
sub log2(float value) -> float {
|
||||
%asm {{
|
||||
%ir {{
|
||||
loadm.f fr0,floats.log2.value
|
||||
flog.f fr0,fr0
|
||||
return
|
||||
@ -82,7 +82,7 @@ sub log2(float value) -> float {
|
||||
}
|
||||
|
||||
sub sqrt(float value) -> float {
|
||||
%asm {{
|
||||
%ir {{
|
||||
loadm.f fr0,floats.sqrt.value
|
||||
sqrt.f fr0,fr0
|
||||
return
|
||||
@ -100,7 +100,7 @@ sub deg(float angle) -> float {
|
||||
}
|
||||
|
||||
sub round(float value) -> float {
|
||||
%asm {{
|
||||
%ir {{
|
||||
loadm.f fr0,floats.round.value
|
||||
fround.f fr0,fr0
|
||||
return
|
||||
@ -108,7 +108,7 @@ sub round(float value) -> float {
|
||||
}
|
||||
|
||||
sub floor(float value) -> float {
|
||||
%asm {{
|
||||
%ir {{
|
||||
loadm.f fr0,floats.floor.value
|
||||
ffloor.f fr0,fr0
|
||||
return
|
||||
@ -117,7 +117,7 @@ sub floor(float value) -> float {
|
||||
|
||||
sub ceil(float value) -> float {
|
||||
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
|
||||
%asm {{
|
||||
%ir {{
|
||||
loadm.f fr0,floats.ceil.value
|
||||
fceil.f fr0,fr0
|
||||
return
|
||||
@ -125,9 +125,17 @@ sub ceil(float value) -> float {
|
||||
}
|
||||
|
||||
sub rndf() -> float {
|
||||
%asm {{
|
||||
rnd.f fr0
|
||||
%ir {{
|
||||
syscall 35
|
||||
return
|
||||
}}
|
||||
}
|
||||
|
||||
sub rndseedf(float seed) {
|
||||
%ir {{
|
||||
loadm.f fr65500,floats.rndseedf.seed
|
||||
syscall 32
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -159,4 +159,27 @@ math {
|
||||
return costab[radians] as byte
|
||||
}
|
||||
|
||||
sub rnd() -> ubyte {
|
||||
%ir {{
|
||||
syscall 33
|
||||
return
|
||||
}}
|
||||
}
|
||||
|
||||
sub rndw() -> uword {
|
||||
%ir {{
|
||||
syscall 34
|
||||
return
|
||||
}}
|
||||
}
|
||||
|
||||
sub rndseed(uword seed1, uword seed2) {
|
||||
; -- reset the pseudo RNG's seed values. Defaults are: $a55a, $7653.
|
||||
%ir {{
|
||||
loadm.w r65500,math.rndseed.seed1
|
||||
loadm.w r65501,math.rndseed.seed2
|
||||
syscall 31
|
||||
return
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
@ -1,53 +1,6 @@
|
||||
; Internal library routines - always included by the compiler
|
||||
|
||||
%import textio
|
||||
|
||||
prog8_lib {
|
||||
%option force_output
|
||||
|
||||
sub string_contains(ubyte needle, str haystack) -> ubyte {
|
||||
repeat {
|
||||
if @(haystack)==0
|
||||
return false
|
||||
if @(haystack)==needle
|
||||
return true
|
||||
haystack++
|
||||
}
|
||||
}
|
||||
|
||||
sub bytearray_contains(ubyte needle, uword haystack_ptr, ubyte num_elements) -> ubyte {
|
||||
haystack_ptr--
|
||||
while num_elements {
|
||||
if haystack_ptr[num_elements]==needle
|
||||
return true
|
||||
num_elements--
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
sub wordarray_contains(ubyte needle, uword haystack_ptr, ubyte num_elements) -> ubyte {
|
||||
haystack_ptr += (num_elements-1) * 2
|
||||
while num_elements {
|
||||
if peekw(haystack_ptr)==needle
|
||||
return true
|
||||
haystack_ptr -= 2
|
||||
num_elements--
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
sub string_compare(str st1, str st2) -> byte {
|
||||
; Compares two strings for sorting.
|
||||
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
|
||||
; Note that you can also directly compare strings and string values with eachother using
|
||||
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
|
||||
%asm {{
|
||||
loadm.w r0,prog8_lib.string_compare.st1
|
||||
loadm.w r1,prog8_lib.string_compare.st2
|
||||
syscall 29
|
||||
return
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -83,7 +83,12 @@ string {
|
||||
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
|
||||
; Note that you can also directly compare strings and string values with eachother using
|
||||
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
|
||||
return prog8_lib.string_compare(st1, st2)
|
||||
%ir {{
|
||||
loadm.w r65500,string.compare.st1
|
||||
loadm.w r65501,string.compare.st2
|
||||
syscall 29
|
||||
return
|
||||
}}
|
||||
}
|
||||
|
||||
sub lower(str st) -> ubyte {
|
||||
|
@ -7,22 +7,22 @@ sys {
|
||||
|
||||
sub reset_system() {
|
||||
; Soft-reset the system back to initial power-on Basic prompt.
|
||||
%asm {{
|
||||
%ir {{
|
||||
syscall 0
|
||||
}}
|
||||
}
|
||||
|
||||
sub wait(uword jiffies) {
|
||||
; --- wait approximately the given number of jiffies (1/60th seconds)
|
||||
%asm {{
|
||||
loadm.w r0,sys.wait.jiffies
|
||||
%ir {{
|
||||
loadm.w r65500,sys.wait.jiffies
|
||||
syscall 13
|
||||
}}
|
||||
}
|
||||
|
||||
sub waitvsync() {
|
||||
; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
|
||||
%asm {{
|
||||
%ir {{
|
||||
syscall 14
|
||||
}}
|
||||
}
|
||||
@ -61,52 +61,52 @@ sys {
|
||||
|
||||
sub exit(ubyte returnvalue) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
%asm {{
|
||||
loadm.b r0,sys.exit.returnvalue
|
||||
%ir {{
|
||||
loadm.b r65500,sys.exit.returnvalue
|
||||
syscall 1
|
||||
}}
|
||||
}
|
||||
|
||||
sub set_carry() {
|
||||
%asm {{
|
||||
%ir {{
|
||||
sec
|
||||
}}
|
||||
}
|
||||
|
||||
sub clear_carry() {
|
||||
%asm {{
|
||||
%ir {{
|
||||
clc
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
sub gfx_enable(ubyte mode) {
|
||||
%asm {{
|
||||
loadm.b r0,sys.gfx_enable.mode
|
||||
%ir {{
|
||||
loadm.b r65500,sys.gfx_enable.mode
|
||||
syscall 8
|
||||
}}
|
||||
}
|
||||
|
||||
sub gfx_clear(ubyte color) {
|
||||
%asm {{
|
||||
loadm.b r0,sys.gfx_clear.color
|
||||
%ir {{
|
||||
loadm.b r65500,sys.gfx_clear.color
|
||||
syscall 9
|
||||
}}
|
||||
}
|
||||
|
||||
sub gfx_plot(uword xx, uword yy, ubyte color) {
|
||||
%asm {{
|
||||
loadm.w r0,sys.gfx_plot.xx
|
||||
loadm.w r1,sys.gfx_plot.yy
|
||||
loadm.b r2,sys.gfx_plot.color
|
||||
%ir {{
|
||||
loadm.w r65500,sys.gfx_plot.xx
|
||||
loadm.w r65501,sys.gfx_plot.yy
|
||||
loadm.b r65502,sys.gfx_plot.color
|
||||
syscall 10
|
||||
}}
|
||||
}
|
||||
|
||||
sub gfx_getpixel(uword xx, uword yy) -> ubyte {
|
||||
%asm {{
|
||||
loadm.w r0,sys.gfx_getpixel.xx
|
||||
loadm.w r1,sys.gfx_getpixel.yy
|
||||
%ir {{
|
||||
loadm.w r65500,sys.gfx_getpixel.xx
|
||||
loadm.w r65501,sys.gfx_getpixel.yy
|
||||
syscall 30
|
||||
return
|
||||
}}
|
||||
|
@ -6,8 +6,8 @@ txt {
|
||||
|
||||
sub clear_screen() {
|
||||
str @shared sequence = "\x1b[2J\x1B[H"
|
||||
%asm {{
|
||||
load.w r0,txt.clear_screen.sequence
|
||||
%ir {{
|
||||
load.w r65500,txt.clear_screen.sequence
|
||||
syscall 3
|
||||
}}
|
||||
}
|
||||
@ -29,15 +29,15 @@ sub uppercase() {
|
||||
}
|
||||
|
||||
sub chrout(ubyte char) {
|
||||
%asm {{
|
||||
loadm.b r0,txt.chrout.char
|
||||
%ir {{
|
||||
loadm.b r65500,txt.chrout.char
|
||||
syscall 2
|
||||
}}
|
||||
}
|
||||
|
||||
sub print (str text) {
|
||||
%asm {{
|
||||
loadm.w r0,txt.print.text
|
||||
%ir {{
|
||||
loadm.w r65500,txt.print.text
|
||||
syscall 3
|
||||
}}
|
||||
}
|
||||
@ -113,8 +113,8 @@ sub print_w (word value) {
|
||||
sub input_chars (uword buffer) -> ubyte {
|
||||
; ---- Input a string (max. 80 chars) from the keyboard. Returns length of input. (string is terminated with a 0 byte as well)
|
||||
; It assumes the keyboard is selected as I/O channel!
|
||||
%asm {{
|
||||
loadm.w r0,txt.input_chars.buffer
|
||||
%ir {{
|
||||
loadm.w r65500,txt.input_chars.buffer
|
||||
syscall 6
|
||||
return
|
||||
}}
|
||||
|
@ -1 +1 @@
|
||||
8.6.2
|
||||
8.7
|
||||
|
@ -14,10 +14,10 @@ import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.*
|
||||
import prog8.codegen.vm.VmCodeGen
|
||||
import prog8.compiler.astprocessing.*
|
||||
import prog8.optimizer.*
|
||||
import prog8.parser.ParseError
|
||||
import prog8.vm.codegen.VmCodeGen
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.nameWithoutExtension
|
||||
@ -361,7 +361,7 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
|
||||
remover.applyModifications()
|
||||
while (true) {
|
||||
// keep optimizing expressions and statements until no more steps remain
|
||||
val optsDone1 = program.simplifyExpressions(compTarget)
|
||||
val optsDone1 = program.simplifyExpressions(errors, compTarget)
|
||||
val optsDone2 = program.splitBinaryExpressions(compilerOptions)
|
||||
val optsDone3 = program.optimizeStatements(errors, functions, compTarget)
|
||||
val optsDone4 = program.inlineSubroutines()
|
||||
@ -394,7 +394,7 @@ private fun createAssemblyAndAssemble(program: Program,
|
||||
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
|
||||
program.processAstBeforeAsmGeneration(compilerOptions, errors)
|
||||
errors.report()
|
||||
val symbolTable = SymbolTableMaker().makeFrom(program)
|
||||
val symbolTable = SymbolTableMaker().makeFrom(program, compilerOptions)
|
||||
|
||||
// TODO make removing all VarDecls work, but this needs inferType to be able to get its information from somewhere else as the VarDecl nodes in the Ast,
|
||||
// or don't use inferType at all anymore and "bake the type information" into the Ast somehow.
|
||||
|
@ -7,7 +7,6 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.compiler.BuiltinFunctions
|
||||
import prog8.compiler.InplaceModifyingBuiltinFunctions
|
||||
import prog8.compiler.builtinFunctionReturnType
|
||||
@ -252,16 +251,8 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(inlineAssembly: InlineAssembly) {
|
||||
val assembly = inlineAssembly.assembly
|
||||
if(compilerOptions.compTarget.name!=VMTarget.NAME) {
|
||||
if (" rti" in assembly || "\trti" in assembly || " rts" in assembly || "\trts" in assembly ||
|
||||
" jmp" in assembly || "\tjmp" in assembly || " bra" in assembly || "\tbra" in assembly
|
||||
)
|
||||
count++
|
||||
} else {
|
||||
if(" return" in assembly || "\treturn" in assembly || " jump" in assembly || "\tjump" in assembly)
|
||||
count++
|
||||
}
|
||||
if(inlineAssembly.hasReturnOrRts(compilerOptions.compTarget))
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
@ -319,12 +310,12 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
else if(param.second.registerOrPair in arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
||||
if (param.first.type != DataType.UWORD && param.first.type != DataType.WORD
|
||||
&& param.first.type != DataType.STR && param.first.type !in ArrayDatatypes && param.first.type != DataType.FLOAT)
|
||||
&& param.first.type != DataType.STR && param.first.type !in ArrayDatatypes)
|
||||
err("parameter '${param.first.name}' should be (u)word (an address) or str")
|
||||
}
|
||||
else if(param.second.statusflag!=null) {
|
||||
if (param.first.type != DataType.UBYTE)
|
||||
err("parameter '${param.first.name}' should be ubyte")
|
||||
if (param.first.type != DataType.UBYTE && param.first.type != DataType.BOOL)
|
||||
err("parameter '${param.first.name}' should be bool or ubyte")
|
||||
}
|
||||
}
|
||||
subroutine.returntypes.zip(subroutine.asmReturnvaluesRegisters).forEachIndexed { index, pair ->
|
||||
@ -334,12 +325,12 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
else if(pair.second.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
||||
if (pair.first != DataType.UWORD && pair.first != DataType.WORD
|
||||
&& pair.first != DataType.STR && pair.first !in ArrayDatatypes && pair.first != DataType.FLOAT)
|
||||
&& pair.first != DataType.STR && pair.first !in ArrayDatatypes)
|
||||
err("return type #${index + 1} should be (u)word/address")
|
||||
}
|
||||
else if(pair.second.statusflag!=null) {
|
||||
if (pair.first != DataType.UBYTE)
|
||||
err("return type #${index + 1} should be ubyte")
|
||||
if (pair.first != DataType.UBYTE && pair.first != DataType.BOOL)
|
||||
err("return type #${index + 1} should be bool or ubyte")
|
||||
}
|
||||
}
|
||||
|
||||
@ -1005,7 +996,7 @@ internal class AstChecker(private val program: Program,
|
||||
// It's not (yet) possible to handle these multiple return values because assignments
|
||||
// are only to a single unique target at the same time.
|
||||
// EXCEPTION:
|
||||
// if the asmsub returns multiple values and one of them is via a status register bit,
|
||||
// if the asmsub returns multiple values and one of them is via a status register bit (such as carry),
|
||||
// it *is* possible to handle them by just actually assigning the register value and
|
||||
// dealing with the status bit as just being that, the status bit after the call.
|
||||
val (returnRegisters, _) = stmt.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }
|
||||
|
@ -12,7 +12,6 @@ import prog8.ast.statements.VarDeclOrigin
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.VMTarget
|
||||
|
||||
|
||||
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||
@ -170,15 +169,8 @@ internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolea
|
||||
}
|
||||
|
||||
internal fun Subroutine.hasRtsInAsm(compTarget: ICompilationTarget): Boolean {
|
||||
val instructions =
|
||||
if(compTarget.name == VMTarget.NAME)
|
||||
listOf(" return", "\treturn", " jump", "\tjump")
|
||||
else
|
||||
listOf(" rti", "\trti", " rts", "\trts", " jmp", "\tjmp", " bra", "\tbra")
|
||||
return statements
|
||||
.asSequence()
|
||||
.filterIsInstance<InlineAssembly>()
|
||||
.any {
|
||||
instructions.any { instr->instr in it.assembly }
|
||||
}
|
||||
.any { it.hasReturnOrRts(compTarget) }
|
||||
}
|
@ -150,6 +150,13 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
override fun visit(functionCallStatement: FunctionCallStatement) = visitFunctionCall(functionCallStatement)
|
||||
|
||||
private fun visitFunctionCall(call: IFunctionCall) {
|
||||
if(call.target.nameInSource==listOf("rnd") || call.target.nameInSource==listOf("rndw")) {
|
||||
val target = call.target.targetStatement(program)
|
||||
if(target==null) {
|
||||
errors.err("rnd() and rndw() builtin functions have been moved into the math module", call.position)
|
||||
return
|
||||
}
|
||||
}
|
||||
when (val target = call.target.targetStatement(program)) {
|
||||
is Subroutine -> {
|
||||
val expectedNumberOfArgs: Int = target.parameters.size
|
||||
|
@ -18,9 +18,8 @@ internal class AstOnetimeTransforms(private val program: Program, private val op
|
||||
|
||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(parent !is VarDecl) {
|
||||
// TODO move this / remove this, and make the codegen better instead.
|
||||
// If the expression is pointervar[idx] where pointervar is uword and not a real array,
|
||||
// replace it by a @(pointervar+idx) expression.
|
||||
if(options.compTarget.name == VMTarget.NAME)
|
||||
return noModifications // vm codegen deals correctly with all cases
|
||||
// Don't replace the initializer value in a vardecl - this will be moved to a separate
|
||||
// assignment statement soon in after(VarDecl)
|
||||
return replacePointerVarIndexWithMemreadOrMemwrite(arrayIndexedExpression, parent)
|
||||
@ -29,15 +28,16 @@ internal class AstOnetimeTransforms(private val program: Program, private val op
|
||||
}
|
||||
|
||||
private fun replacePointerVarIndexWithMemreadOrMemwrite(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(options.compTarget.name==VMTarget.NAME)
|
||||
return noModifications // vm codegen deals correctly with all cases
|
||||
|
||||
// note: The CodeDesugarer already does something similar, but that is meant ONLY to take
|
||||
// into account the case where the index value is a word type.
|
||||
// The replacement here is to fix missing cases in the 6502 codegen.
|
||||
// TODO make the 6502 codegen better so this work around can be removed
|
||||
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
|
||||
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
|
||||
if(parent is AssignTarget) {
|
||||
val assignment = parent.parent as? Assignment
|
||||
if(assignment?.value is NumericLiteral || assignment?.value is IdentifierReference) {
|
||||
// ONLY for a constant assignment, or direct variable assignment, the codegen contains correct optimized code.
|
||||
// the codegen contains correct optimized code ONLY for a constant assignment, or direct variable assignment.
|
||||
return noModifications
|
||||
}
|
||||
// Other cases aren't covered correctly by the 6502 codegen, and there are a LOT of cases.
|
||||
|
@ -135,15 +135,20 @@ internal class BeforeAsmAstChanger(val program: Program,
|
||||
val mods = mutableListOf<IAstModification>()
|
||||
|
||||
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernal routine.
|
||||
// and if an assembly block doesn't contain a rts/rti, and some other situations.
|
||||
// and if an assembly block doesn't contain a rts/rti.
|
||||
if (!subroutine.isAsmSubroutine) {
|
||||
if(subroutine.statements.isEmpty() ||
|
||||
(!subroutine.hasRtsInAsm(options.compTarget)
|
||||
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
|
||||
&& subroutine.statements.last() !is Subroutine
|
||||
&& subroutine.statements.last() !is Return)) {
|
||||
if(subroutine.isEmpty()) {
|
||||
val returnStmt = Return(null, subroutine.position)
|
||||
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
||||
} else {
|
||||
val last = subroutine.statements.last()
|
||||
if((last !is InlineAssembly || !last.hasReturnOrRts(options.compTarget)) && last !is Return) {
|
||||
val lastStatement = subroutine.statements.reversed().firstOrNull { it !is Subroutine }
|
||||
if(lastStatement !is Return) {
|
||||
val returnStmt = Return(null, subroutine.position)
|
||||
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,8 +172,18 @@ internal class BeforeAsmAstChanger(val program: Program,
|
||||
if (subroutine.isAsmSubroutine && subroutine.asmAddress==null && !subroutine.hasRtsInAsm(options.compTarget)) {
|
||||
// make sure the NOT INLINED asm subroutine actually has a rts at the end
|
||||
// (non-asm routines get a Return statement as needed, above)
|
||||
val instruction = if(options.compTarget.name==VMTarget.NAME) " return\n" else " rts\n"
|
||||
mods += IAstModification.InsertLast(InlineAssembly(instruction, Position.DUMMY), subroutine)
|
||||
mods += if(options.compTarget.name==VMTarget.NAME)
|
||||
IAstModification.InsertLast(InlineAssembly(" return\n", true, Position.DUMMY), subroutine)
|
||||
else
|
||||
IAstModification.InsertLast(InlineAssembly(" rts\n", false, Position.DUMMY), subroutine)
|
||||
}
|
||||
}
|
||||
|
||||
if(subroutine.isNotEmpty() && subroutine.statements.last() is Return) {
|
||||
// maybe the last return can be removed because there is a fall-through prevention above it
|
||||
val lastStatementBefore = subroutine.statements.reversed().drop(1).firstOrNull { it !is Subroutine }
|
||||
if(lastStatementBefore is Return) {
|
||||
mods += IAstModification.Remove(subroutine.statements.last(), subroutine)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,4 +129,18 @@ internal class BeforeAsmTypecastCleaner(val program: Program,
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(expr.operator=="<<" || expr.operator==">>") {
|
||||
val shifts = expr.right.constValue(program)
|
||||
if(shifts!=null) {
|
||||
val dt = expr.left.inferType(program)
|
||||
if(dt.istype(DataType.UBYTE) && shifts.number>=8.0)
|
||||
errors.warn("shift always results in 0", expr.position)
|
||||
if(dt.istype(DataType.UWORD) && shifts.number>=16.0)
|
||||
errors.warn("shift always results in 0", expr.position)
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
@ -10,8 +10,7 @@ import prog8.code.core.IErrorReporter
|
||||
import prog8.code.core.Position
|
||||
|
||||
|
||||
internal class CodeDesugarer(val program: Program,
|
||||
private val errors: IErrorReporter) : AstWalker() {
|
||||
internal class CodeDesugarer(val program: Program, private val errors: IErrorReporter) : AstWalker() {
|
||||
|
||||
// Some more code shuffling to simplify the Ast that the codegenerator has to process.
|
||||
// Several changes have already been done by the StatementReorderer !
|
||||
@ -23,6 +22,7 @@ internal class CodeDesugarer(val program: Program,
|
||||
// - replace while and do-until loops by just jumps.
|
||||
// - replace peek() and poke() by direct memory accesses.
|
||||
// - repeat-forever loops replaced by label+jump.
|
||||
// - pointer[word] replaced by @(pointer+word)
|
||||
|
||||
|
||||
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
|
||||
@ -135,4 +135,32 @@ _after:
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||
// replace pointervar[word] by @(pointervar+word) to avoid the
|
||||
// "array indexing is limited to byte size 0..255" error for pointervariables.
|
||||
val indexExpr = arrayIndexedExpression.indexer.indexExpr
|
||||
val indexerDt = indexExpr.inferType(program)
|
||||
if(indexerDt.isWords) {
|
||||
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)!!
|
||||
if(arrayVar.datatype==DataType.UWORD) {
|
||||
val add: Expression =
|
||||
if(indexExpr.constValue(program)?.number==0.0)
|
||||
arrayIndexedExpression.arrayvar.copy()
|
||||
else
|
||||
BinaryExpression(arrayIndexedExpression.arrayvar.copy(), "+", indexExpr, arrayIndexedExpression.position)
|
||||
return if(parent is AssignTarget) {
|
||||
// assignment to array
|
||||
val memwrite = DirectMemoryWrite(add, arrayIndexedExpression.position)
|
||||
val newtarget = AssignTarget(null, null, memwrite, arrayIndexedExpression.position)
|
||||
listOf(IAstModification.ReplaceNode(parent, newtarget, parent.parent))
|
||||
} else {
|
||||
// read from array
|
||||
val memread = DirectMemoryRead(add, arrayIndexedExpression.position)
|
||||
listOf(IAstModification.ReplaceNode(arrayIndexedExpression, memread, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
@ -197,7 +197,7 @@ class IntermediateAstMaker(val program: Program) {
|
||||
"%asminclude" -> {
|
||||
val result = loadAsmIncludeFile(directive.args[0].str!!, directive.definingModule.source)
|
||||
val assembly = result.getOrElse { throw it }
|
||||
PtInlineAssembly(assembly, directive.position)
|
||||
PtInlineAssembly(assembly.trimEnd().trimStart('\r', '\n'), false, directive.position)
|
||||
}
|
||||
else -> {
|
||||
// other directives don't output any code (but could end up in option flags somewhere else)
|
||||
@ -251,8 +251,10 @@ class IntermediateAstMaker(val program: Program) {
|
||||
return ifelse
|
||||
}
|
||||
|
||||
private fun transform(srcNode: InlineAssembly): PtInlineAssembly =
|
||||
PtInlineAssembly(srcNode.assembly, srcNode.position)
|
||||
private fun transform(srcNode: InlineAssembly): PtInlineAssembly {
|
||||
val assembly = srcNode.assembly.trimEnd().trimStart('\r', '\n')
|
||||
return PtInlineAssembly(assembly, srcNode.isIR, srcNode.position)
|
||||
}
|
||||
|
||||
private fun transform(srcJump: Jump): PtJump {
|
||||
val identifier = if(srcJump.identifier!=null) transform(srcJump.identifier!!) else null
|
||||
@ -306,13 +308,26 @@ class IntermediateAstMaker(val program: Program) {
|
||||
sub.parameters.forEach { it.first.parent=sub }
|
||||
|
||||
if(srcSub.asmAddress==null) {
|
||||
var combinedAsm = ""
|
||||
for (asm in srcSub.statements)
|
||||
combinedAsm += (asm as InlineAssembly).assembly + "\n"
|
||||
if(combinedAsm.isNotEmpty())
|
||||
sub.add(PtInlineAssembly(combinedAsm, srcSub.statements[0].position))
|
||||
else
|
||||
sub.add(PtInlineAssembly("", srcSub.position))
|
||||
var combinedTrueAsm = ""
|
||||
var combinedIrAsm = ""
|
||||
for (asm in srcSub.statements) {
|
||||
asm as InlineAssembly
|
||||
if(asm.isIR)
|
||||
combinedIrAsm += asm.assembly + "\n"
|
||||
else
|
||||
combinedTrueAsm += asm.assembly + "\n"
|
||||
}
|
||||
|
||||
if(combinedTrueAsm.isNotEmpty()) {
|
||||
combinedTrueAsm = combinedTrueAsm.trimEnd().trimStart('\r', '\n')
|
||||
sub.add(PtInlineAssembly(combinedTrueAsm, false, srcSub.statements[0].position))
|
||||
}
|
||||
if(combinedIrAsm.isNotEmpty()) {
|
||||
combinedIrAsm = combinedIrAsm.trimEnd().trimStart('\r', '\n')
|
||||
sub.add(PtInlineAssembly(combinedIrAsm, true, srcSub.statements[0].position))
|
||||
}
|
||||
if(combinedIrAsm.isEmpty() && combinedTrueAsm.isEmpty())
|
||||
sub.add(PtInlineAssembly("", true, srcSub.position))
|
||||
}
|
||||
|
||||
return sub
|
||||
|
@ -1,13 +1,11 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.*
|
||||
import prog8.compiler.BuiltinFunctions
|
||||
|
||||
internal class StatementReorderer(val program: Program,
|
||||
val errors: IErrorReporter,
|
||||
@ -195,54 +193,6 @@ internal class StatementReorderer(val program: Program,
|
||||
&& maySwapOperandOrder(expr))
|
||||
return listOf(IAstModification.SwapOperands(expr))
|
||||
|
||||
// when using a simple bit shift and assigning it to a variable of a different type,
|
||||
// try to make the bit shifting 'wide enough' to fall into the variable's type.
|
||||
// with this, for instance, uword x = 1 << 10 will result in 1024 rather than 0 (the ubyte result).
|
||||
if(expr.operator=="<<" || expr.operator==">>") {
|
||||
val leftDt = expr.left.inferType(program)
|
||||
when (parent) {
|
||||
is Assignment -> {
|
||||
val targetDt = parent.target.inferType(program)
|
||||
if(leftDt != targetDt) {
|
||||
val cast = TypecastExpression(expr.left, targetDt.getOr(DataType.UNDEFINED), true, parent.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
}
|
||||
}
|
||||
is VarDecl -> {
|
||||
if(leftDt isnot parent.datatype) {
|
||||
val cast = TypecastExpression(expr.left, parent.datatype, true, parent.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
}
|
||||
}
|
||||
is IFunctionCall -> {
|
||||
val argnum = parent.args.indexOf(expr)
|
||||
when (val callee = parent.target.targetStatement(program)) {
|
||||
is Subroutine -> {
|
||||
val paramType = callee.parameters[argnum].type
|
||||
if(leftDt isAssignableTo paramType) {
|
||||
val (replaced, cast) = expr.left.typecastTo(paramType, leftDt.getOr(DataType.UNDEFINED), true)
|
||||
if(replaced)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
}
|
||||
}
|
||||
is BuiltinFunctionPlaceholder -> {
|
||||
val func = BuiltinFunctions.getValue(callee.name)
|
||||
val paramTypes = func.parameters[argnum].possibleDatatypes
|
||||
for(type in paramTypes) {
|
||||
if(leftDt isAssignableTo type) {
|
||||
val (replaced, cast) = expr.left.typecastTo(type, leftDt.getOr(DataType.UNDEFINED), true)
|
||||
if(replaced)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw FatalAstException("weird callee")
|
||||
}
|
||||
}
|
||||
else -> return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,8 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.*
|
||||
import prog8.code.core.ArrayDatatypes
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Position
|
||||
import java.util.*
|
||||
|
||||
@ -14,10 +16,12 @@ internal class SymbolTableMaker: IAstVisitor {
|
||||
|
||||
private val st = SymbolTable()
|
||||
private val scopestack = Stack<StNode>()
|
||||
private var dontReinitGlobals = false
|
||||
|
||||
fun makeFrom(program: Program): SymbolTable {
|
||||
fun makeFrom(program: Program, options: CompilationOptions): SymbolTable {
|
||||
scopestack.clear()
|
||||
st.children.clear()
|
||||
dontReinitGlobals = options.dontReinitGlobals
|
||||
this.visit(program)
|
||||
program.builtinFunctions.names.forEach {
|
||||
val node = StNode(it, StNodeType.BUILTINFUNC, Position.DUMMY)
|
||||
@ -38,7 +42,7 @@ internal class SymbolTableMaker: IAstVisitor {
|
||||
override fun visit(subroutine: Subroutine) {
|
||||
if(subroutine.asmAddress!=null) {
|
||||
val parameters = subroutine.parameters.zip(subroutine.asmParameterRegisters).map { StRomSubParameter(it.second, it.first.type) }
|
||||
val node = StRomSub(subroutine.name, subroutine.asmAddress!!, parameters, subroutine.asmParameterRegisters, subroutine.position)
|
||||
val node = StRomSub(subroutine.name, subroutine.asmAddress!!, parameters, subroutine.asmReturnvaluesRegisters, subroutine.position)
|
||||
scopestack.peek().add(node)
|
||||
// st.origAstLinks[subroutine] = node
|
||||
} else {
|
||||
@ -57,7 +61,7 @@ internal class SymbolTableMaker: IAstVisitor {
|
||||
val node =
|
||||
when(decl.type) {
|
||||
VarDeclType.VAR -> {
|
||||
val initialNumeric = (decl.value as? NumericLiteral)?.number
|
||||
var initialNumeric = (decl.value as? NumericLiteral)?.number
|
||||
val initialStringLit = decl.value as? StringLiteral
|
||||
val initialString = if(initialStringLit==null) null else Pair(initialStringLit.value, initialStringLit.encoding)
|
||||
val initialArrayLit = decl.value as? ArrayLiteral
|
||||
@ -71,7 +75,15 @@ internal class SymbolTableMaker: IAstVisitor {
|
||||
initialStringLit.value.length+1 // include the terminating 0-byte
|
||||
else
|
||||
null
|
||||
StStaticVariable(decl.name, decl.datatype, initialNumeric, initialString, initialArray, numElements, decl.zeropage, decl.position)
|
||||
val bss = if(decl.datatype==DataType.STR)
|
||||
false
|
||||
else if(decl.isArray)
|
||||
initialArray.isNullOrEmpty()
|
||||
else {
|
||||
if(dontReinitGlobals) initialNumeric = initialNumeric ?: 0.0
|
||||
initialNumeric == null
|
||||
}
|
||||
StStaticVariable(decl.name, decl.datatype, bss, initialNumeric, initialString, initialArray, numElements, decl.zeropage, decl.position)
|
||||
}
|
||||
VarDeclType.CONST -> StConstant(decl.name, decl.datatype, (decl.value as NumericLiteral).number, decl.position)
|
||||
VarDeclType.MEMORY -> {
|
||||
@ -105,15 +117,15 @@ internal class SymbolTableMaker: IAstVisitor {
|
||||
// st.origAstLinks[label] = node
|
||||
}
|
||||
|
||||
override fun visit(fcall: BuiltinFunctionCall) {
|
||||
if(fcall.name=="memory") {
|
||||
override fun visit(bfc: BuiltinFunctionCall) {
|
||||
if(bfc.name=="memory") {
|
||||
// memory slab allocations are a builtin functioncall in the program, but end up named as well in the symboltable
|
||||
val name = (fcall.args[0] as StringLiteral).value
|
||||
val name = (bfc.args[0] as StringLiteral).value
|
||||
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"}
|
||||
val size = (fcall.args[1] as NumericLiteral).number.toUInt()
|
||||
val align = (fcall.args[2] as NumericLiteral).number.toUInt()
|
||||
st.add(StMemorySlab("prog8_memoryslab_$name", size, align, fcall.position))
|
||||
val size = (bfc.args[1] as NumericLiteral).number.toUInt()
|
||||
val align = (bfc.args[2] as NumericLiteral).number.toUInt()
|
||||
st.add(StMemorySlab("prog8_memoryslab_$name", size, align, bfc.position))
|
||||
}
|
||||
super.visit(fcall)
|
||||
super.visit(bfc)
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import prog8.ast.Program
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.core.ByteDatatypes
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.code.core.Position
|
||||
@ -82,7 +83,9 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
|
||||
if(mismatch>=0) {
|
||||
val actual = argtypes[mismatch]
|
||||
val expected = consideredParamTypes[mismatch]
|
||||
return if(expected==DataType.BOOL && actual==DataType.UBYTE && call.args[mismatch].constValue(program)?.number in setOf(0.0, 1.0))
|
||||
return if(actual==DataType.BOOL && expected in ByteDatatypes)
|
||||
null // a bool is just 1 or 0.
|
||||
else if(expected==DataType.BOOL && actual==DataType.UBYTE && call.args[mismatch].constValue(program)?.number in setOf(0.0, 1.0))
|
||||
null // specifying a 1 or 0 as a BOOL is okay
|
||||
else
|
||||
Pair("argument ${mismatch + 1} type mismatch, was: $actual expected: $expected", call.args[mismatch].position)
|
||||
@ -91,6 +94,10 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
|
||||
if(target.asmReturnvaluesRegisters.size>1) {
|
||||
// multiple return values will NOT work inside an expression.
|
||||
// they MIGHT work in a regular assignment or just a function call statement.
|
||||
// EXCEPTION:
|
||||
// if the asmsub returns multiple values and one of them is via a status register bit (such as carry),
|
||||
// it *is* possible to handle them by just actually assigning the register value and
|
||||
// dealing with the status bit as just being that, the status bit after the call.
|
||||
val parent = if(call is Statement) call.parent else if(call is Expression) call.parent else null
|
||||
if (call !is FunctionCallStatement) {
|
||||
val checkParent =
|
||||
@ -99,7 +106,10 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
|
||||
else
|
||||
parent
|
||||
if (checkParent !is Assignment && checkParent !is VarDecl) {
|
||||
return Pair("can't use subroutine call that returns multiple return values here", call.position)
|
||||
val (returnRegisters, _) = target.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }
|
||||
if (returnRegisters.size>1) {
|
||||
return Pair("can't use subroutine call that returns multiple return values here", call.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,18 +44,18 @@ class TestBuiltinFunctions: FunSpec({
|
||||
conv.returns.reg shouldBe RegisterOrPair.A
|
||||
}
|
||||
|
||||
test("not-pure func with fixed type") {
|
||||
val func = BuiltinFunctions.getValue("rnd")
|
||||
func.name shouldBe "rnd"
|
||||
func.parameters.size shouldBe 0
|
||||
test("not-pure func with varying result value type") {
|
||||
val func = BuiltinFunctions.getValue("cmp")
|
||||
func.name shouldBe "cmp"
|
||||
func.parameters.size shouldBe 2
|
||||
func.pure shouldBe false
|
||||
func.returnType shouldBe DataType.UBYTE
|
||||
func.returnType shouldBe null
|
||||
|
||||
val conv = func.callConvention(emptyList())
|
||||
conv.params.size shouldBe 0
|
||||
conv.returns.dt shouldBe DataType.UBYTE
|
||||
val conv = func.callConvention(listOf(DataType.UWORD, DataType.UWORD))
|
||||
conv.params.size shouldBe 2
|
||||
conv.returns.dt shouldBe null
|
||||
conv.returns.floatFac1 shouldBe false
|
||||
conv.returns.reg shouldBe RegisterOrPair.A
|
||||
conv.returns.reg shouldBe null
|
||||
}
|
||||
|
||||
test("func without return type") {
|
||||
|
@ -8,7 +8,6 @@ import prog8.compiler.CompilationResult
|
||||
import prog8.compiler.CompilerArguments
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8tests.helpers.*
|
||||
import prog8tests.helpers.compileText
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.absolute
|
||||
import kotlin.io.path.exists
|
||||
|
@ -87,7 +87,7 @@ class TestCompilerOptionSourcedirs: FunSpec({
|
||||
|
||||
test("testFilePathOutsideWorkingDirRelativeTo1stInSourcedirs") {
|
||||
val filepath = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
||||
val sourcedirs = listOf("${fixturesDir}")
|
||||
val sourcedirs = listOf("$fixturesDir")
|
||||
compileFile(filepath.fileName, sourcedirs) shouldNotBe null
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,9 @@ class TestLaunchEmu: FunSpec({
|
||||
<OPTIONS>
|
||||
</OPTIONS>
|
||||
|
||||
<ASMSYMBOLS>
|
||||
</ASMSYMBOLS>
|
||||
|
||||
<VARIABLES>
|
||||
</VARIABLES>
|
||||
|
||||
|
@ -258,8 +258,8 @@ class TestSubroutines: FunSpec({
|
||||
sub start() {
|
||||
label()
|
||||
label(1)
|
||||
void rnd()
|
||||
void rnd(1)
|
||||
void cmp(22,44)
|
||||
void cmp(11)
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
@ -78,10 +78,10 @@ private fun makeSt(): SymbolTable {
|
||||
block1.add(sub12)
|
||||
block1.add(StConstant("c1", DataType.UWORD, 12345.0, Position.DUMMY))
|
||||
block1.add(StConstant("blockc", DataType.UWORD, 999.0, Position.DUMMY))
|
||||
sub11.add(StStaticVariable("v1", DataType.BYTE, null, null, null, null, ZeropageWish.DONTCARE, Position.DUMMY))
|
||||
sub11.add(StStaticVariable("v2", DataType.BYTE, null, null, null, null, ZeropageWish.DONTCARE, Position.DUMMY))
|
||||
sub12.add(StStaticVariable("v1", DataType.BYTE, null, null, null, null, ZeropageWish.DONTCARE, Position.DUMMY))
|
||||
sub12.add(StStaticVariable("v2", DataType.BYTE, null, null, null, null, ZeropageWish.DONTCARE, Position.DUMMY))
|
||||
sub11.add(StStaticVariable("v1", DataType.BYTE, true, null, null, null, null, ZeropageWish.DONTCARE, Position.DUMMY))
|
||||
sub11.add(StStaticVariable("v2", DataType.BYTE, true, null, null, null, null, ZeropageWish.DONTCARE, Position.DUMMY))
|
||||
sub12.add(StStaticVariable("v1", DataType.BYTE, true, null, null, null, null, ZeropageWish.DONTCARE, Position.DUMMY))
|
||||
sub12.add(StStaticVariable("v2", DataType.BYTE, true, null, null, null, null, ZeropageWish.DONTCARE, Position.DUMMY))
|
||||
|
||||
val block2 = StNode("block2", StNodeType.BLOCK, Position.DUMMY)
|
||||
val sub21 = StNode("sub1", StNodeType.SUBROUTINE, Position.DUMMY)
|
||||
|
@ -919,4 +919,19 @@ main {
|
||||
}"""
|
||||
compileText(C64Target(), true, text, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
test("memory reads byte into word variable") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
uword @shared ww
|
||||
uword address = $1000
|
||||
ww = @(address+100)
|
||||
ww = @(address+1000)
|
||||
cx16.r0 = @(address+100)
|
||||
cx16.r0 = @(address+1000)
|
||||
}
|
||||
}"""
|
||||
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
})
|
||||
|
@ -2,6 +2,7 @@
|
||||
%import floats
|
||||
%import string
|
||||
%import syslib
|
||||
%import math
|
||||
%import test_stack
|
||||
%zeropage basicsafe
|
||||
|
||||
@ -281,17 +282,17 @@ main {
|
||||
txt.nl()
|
||||
|
||||
|
||||
ub = rnd()
|
||||
ub = math.rnd()
|
||||
txt.print_ub(ub)
|
||||
txt.nl()
|
||||
ub = zero+rnd()*1+zero
|
||||
ub = zero+math.rnd()*1+zero
|
||||
txt.print_ub(ub)
|
||||
txt.nl()
|
||||
|
||||
uw = rndw()
|
||||
uw = math.rndw()
|
||||
txt.print_uw(uw)
|
||||
txt.nl()
|
||||
uw = zero+rndw()*1+zero
|
||||
uw = zero+math.rndw()*1+zero
|
||||
txt.print_uw(uw)
|
||||
txt.nl()
|
||||
|
||||
|
@ -10,14 +10,13 @@ import prog8.ast.statements.InlineAssembly
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.compiler.printProgram
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
class TestVarious: FunSpec({
|
||||
test("symbol names in inline assembly blocks") {
|
||||
val names1 = InlineAssembly("""
|
||||
|
||||
""", Position.DUMMY).names
|
||||
""", false, Position.DUMMY).names
|
||||
names1 shouldBe emptySet()
|
||||
|
||||
val names2 = InlineAssembly("""
|
||||
@ -29,7 +28,7 @@ label2:
|
||||
; also not these
|
||||
;; ...or these
|
||||
// valid words 123456
|
||||
""", Position.DUMMY).names
|
||||
""", false, Position.DUMMY).names
|
||||
|
||||
names2 shouldBe setOf("label", "lda", "sta", "ea", "value", "label2", "othervalue", "valid", "words")
|
||||
}
|
||||
@ -100,7 +99,6 @@ main {
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target(), optimize=false, src, writeAssembly=true)!!
|
||||
printProgram(result.program)
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 6
|
||||
val name1 = stmts[0] as VarDecl
|
||||
@ -114,5 +112,22 @@ main {
|
||||
(name2.targetVarDecl(result.program)!!.value as StringLiteral).value shouldBe "xx1xx2"
|
||||
(rept2.targetVarDecl(result.program)!!.value as StringLiteral).value shouldBe "xyzxyzxyzxyz"
|
||||
}
|
||||
|
||||
test("pointervariable indexing allowed with >255") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
uword pointer = ${'$'}2000
|
||||
@(pointer+${'$'}1000) = 123
|
||||
ubyte @shared ub = @(pointer+${'$'}1000)
|
||||
pointer[${'$'}1000] = 99
|
||||
ub = pointer[${'$'}1000]
|
||||
uword index = ${'$'}1000
|
||||
pointer[index] = 55
|
||||
ub = pointer[index]
|
||||
}
|
||||
}"""
|
||||
compileText(C64Target(), optimize=false, src, writeAssembly=false) shouldNotBe null
|
||||
}
|
||||
})
|
||||
|
||||
|
93
compiler/test/codegeneration/TestArrayInplaceAssign.kt
Normal file
93
compiler/test/codegeneration/TestArrayInplaceAssign.kt
Normal file
@ -0,0 +1,93 @@
|
||||
package prog8tests.codegeneration
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
class TestArrayInplaceAssign: FunSpec({
|
||||
test("assign prefix var to array should compile fine and is not split into inplace array modification") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
byte[5] array
|
||||
byte bb
|
||||
array[1] = -bb
|
||||
}
|
||||
}
|
||||
"""
|
||||
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
|
||||
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
test("array in-place negation (integer types)") {
|
||||
val text = """
|
||||
main {
|
||||
byte[10] foo
|
||||
ubyte[10] foou
|
||||
word[10] foow
|
||||
uword[10] foowu
|
||||
|
||||
sub start() {
|
||||
foo[1] = 42
|
||||
foo[1] = -foo[1]
|
||||
|
||||
foow[1] = 4242
|
||||
foow[1] = -foow[1]
|
||||
}
|
||||
}"""
|
||||
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
|
||||
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
test("array in-place negation (float type) vm target") {
|
||||
val text = """
|
||||
%import floats
|
||||
|
||||
main {
|
||||
float[10] flt
|
||||
|
||||
sub start() {
|
||||
flt[1] = 42.42
|
||||
flt[1] = -flt[1]
|
||||
}
|
||||
}"""
|
||||
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
// TODO implement this in 6502 codegen and re-enable test
|
||||
xtest("array in-place negation (float type) 6502 target") {
|
||||
val text = """
|
||||
%import floats
|
||||
|
||||
main {
|
||||
float[10] flt
|
||||
|
||||
sub start() {
|
||||
flt[1] = 42.42
|
||||
flt[1] = -flt[1]
|
||||
}
|
||||
}"""
|
||||
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
test("array in-place invert") {
|
||||
val text = """
|
||||
main {
|
||||
ubyte[10] foo
|
||||
uword[10] foow
|
||||
|
||||
sub start() {
|
||||
foo[1] = 42
|
||||
foo[1] = ~foo[1]
|
||||
|
||||
foow[1] = 4242
|
||||
foow[1] = ~foow[1]
|
||||
}
|
||||
}"""
|
||||
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
|
||||
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
})
|
||||
|
@ -71,7 +71,7 @@ class TestAsmGenSymbols: StringSpec({
|
||||
val errors = ErrorReporterForTests()
|
||||
val options = CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, true, C64Target(), 999u)
|
||||
options.compTarget.machine.zeropage = C64Zeropage(options)
|
||||
val st = SymbolTableMaker().makeFrom(program)
|
||||
val st = SymbolTableMaker().makeFrom(program, options)
|
||||
return AsmGen(program, st, options, errors)
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,7 @@ class PathsHelpersTests: FunSpec({
|
||||
|
||||
test("on existing directory") {
|
||||
shouldThrow<java.lang.AssertionError> {
|
||||
assumeNotExists("${fixturesDir}")
|
||||
assumeNotExists("$fixturesDir")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -157,13 +157,13 @@ class PathsHelpersTests: FunSpec({
|
||||
context("WithStringAndStringArgs") {
|
||||
test("on non-existing path") {
|
||||
shouldThrow<AssertionError> {
|
||||
assumeDirectory("${fixturesDir}", "i_do_not_exist")
|
||||
assumeDirectory("$fixturesDir", "i_do_not_exist")
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing file") {
|
||||
shouldThrow<AssertionError> {
|
||||
assumeDirectory("${fixturesDir}", "ast_simple_main.p8")
|
||||
assumeDirectory("$fixturesDir", "ast_simple_main.p8")
|
||||
}
|
||||
}
|
||||
|
||||
@ -178,13 +178,13 @@ class PathsHelpersTests: FunSpec({
|
||||
context("WithStringAndPathArgs") {
|
||||
test("on non-existing path") {
|
||||
shouldThrow<AssertionError> {
|
||||
assumeDirectory("${fixturesDir}", Path("i_do_not_exist"))
|
||||
assumeDirectory("$fixturesDir", Path("i_do_not_exist"))
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing file") {
|
||||
shouldThrow<AssertionError> {
|
||||
assumeDirectory("${fixturesDir}", Path("ast_simple_main.p8"))
|
||||
assumeDirectory("$fixturesDir", Path("ast_simple_main.p8"))
|
||||
}
|
||||
}
|
||||
|
||||
@ -240,7 +240,7 @@ class PathsHelpersTests: FunSpec({
|
||||
|
||||
test("on directory") {
|
||||
shouldThrow<AssertionError> {
|
||||
assumeReadableFile("${fixturesDir}")
|
||||
assumeReadableFile("$fixturesDir")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -289,7 +289,7 @@ class PathsHelpersTests: FunSpec({
|
||||
context("WithStringAndStringArgs") {
|
||||
test("on non-existing path") {
|
||||
shouldThrow<java.lang.AssertionError> {
|
||||
assumeReadableFile("${fixturesDir}", "i_do_not_exist")
|
||||
assumeReadableFile("$fixturesDir", "i_do_not_exist")
|
||||
}
|
||||
}
|
||||
|
||||
@ -301,7 +301,7 @@ class PathsHelpersTests: FunSpec({
|
||||
|
||||
test("on directory") {
|
||||
shouldThrow<AssertionError> {
|
||||
assumeReadableFile("${fixturesDir}", "..")
|
||||
assumeReadableFile("$fixturesDir", "..")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,13 @@
|
||||
package prog8tests.vm
|
||||
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import prog8.ast.expressions.BuiltinFunctionCall
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.vm.VmRunner
|
||||
@ -67,8 +70,12 @@ main {
|
||||
}
|
||||
}"""
|
||||
val target = VMTarget()
|
||||
val result = compileText(target, true, src, writeAssembly = true)!!
|
||||
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
|
||||
var result = compileText(target, false, src, writeAssembly = true)!!
|
||||
var virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
|
||||
VmRunner().runProgram(virtfile.readText())
|
||||
|
||||
result = compileText(target, true, src, writeAssembly = true)!!
|
||||
virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
|
||||
VmRunner().runProgram(virtfile.readText())
|
||||
}
|
||||
|
||||
@ -79,8 +86,34 @@ main {
|
||||
uword i
|
||||
uword k
|
||||
|
||||
while k <= 10 {
|
||||
k++
|
||||
repeat {
|
||||
mylabel0:
|
||||
goto mylabel0
|
||||
}
|
||||
|
||||
while cx16.r0 {
|
||||
mylabel1:
|
||||
goto mylabel1
|
||||
}
|
||||
|
||||
do {
|
||||
mylabel2:
|
||||
goto mylabel2
|
||||
} until cx16.r0
|
||||
|
||||
repeat cx16.r0 {
|
||||
mylabel3:
|
||||
goto mylabel3
|
||||
}
|
||||
|
||||
for cx16.r0L in 0 to 2 {
|
||||
mylabel4:
|
||||
goto mylabel4
|
||||
}
|
||||
|
||||
for cx16.r0L in cx16.r1L to cx16.r2L {
|
||||
mylabel5:
|
||||
goto mylabel5
|
||||
}
|
||||
|
||||
mylabel_outside:
|
||||
@ -106,10 +139,11 @@ mylabel_inside:
|
||||
}
|
||||
}"""
|
||||
|
||||
val target1 = C64Target()
|
||||
compileText(target1, false, src, writeAssembly = false) shouldNotBe null
|
||||
|
||||
val target = VMTarget()
|
||||
val result = compileText(target, true, src, writeAssembly = true)!!
|
||||
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
|
||||
VmRunner().runProgram(virtfile.readText())
|
||||
compileText(target, false, src, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
test("case sensitive symbols") {
|
||||
@ -160,4 +194,52 @@ main {
|
||||
vm.memory.getUB(3) shouldBe 43u
|
||||
}
|
||||
}
|
||||
|
||||
test("memory mapped var as for loop counter") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
for cx16.r0 in 0 to 10 {
|
||||
cx16.r1++
|
||||
}
|
||||
}
|
||||
}"""
|
||||
val othertarget = Cx16Target()
|
||||
compileText(othertarget, true, src, writeAssembly = true, keepIR=true) shouldNotBe null
|
||||
|
||||
val target = VMTarget()
|
||||
val result = compileText(target, false, src, writeAssembly = true)!!
|
||||
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
|
||||
VmRunner().runAndTestProgram(virtfile.readText()) { vm ->
|
||||
vm.stepCount shouldBe 49
|
||||
}
|
||||
}
|
||||
|
||||
test("asmsub for virtual target") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
void test(42)
|
||||
}
|
||||
|
||||
asmsub test(ubyte xx @A) -> ubyte @Y {
|
||||
%asm {{
|
||||
lda #99
|
||||
tay
|
||||
rts
|
||||
return
|
||||
}}
|
||||
}
|
||||
}"""
|
||||
val othertarget = Cx16Target()
|
||||
compileText(othertarget, true, src, writeAssembly = true, keepIR=true) shouldNotBe null
|
||||
|
||||
val target = VMTarget()
|
||||
val result = compileText(target, false, src, writeAssembly = true)!!
|
||||
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
|
||||
val exc = shouldThrow<Exception> {
|
||||
VmRunner().runProgram(virtfile.readText())
|
||||
}
|
||||
exc.message shouldContain("does not support non-IR asmsubs")
|
||||
}
|
||||
})
|
@ -40,6 +40,7 @@ internal fun Prog8ANTLRParser.BlockContext.toAst(isInLibrary: Boolean) : Block {
|
||||
it.subroutinedeclaration()!=null -> it.subroutinedeclaration().toAst()
|
||||
it.directive()!=null -> it.directive().toAst()
|
||||
it.inlineasm()!=null -> it.inlineasm().toAst()
|
||||
it.inlineir()!=null -> it.inlineir().toAst()
|
||||
it.labeldef()!=null -> it.labeldef().toAst()
|
||||
else -> throw FatalAstException("weird block node $it")
|
||||
}
|
||||
@ -125,6 +126,9 @@ private fun Prog8ANTLRParser.StatementContext.toAst() : Statement {
|
||||
val asm = inlineasm()?.toAst()
|
||||
if(asm!=null) return asm
|
||||
|
||||
val ir = inlineir()?.toAst()
|
||||
if(ir!=null) return ir
|
||||
|
||||
val branchstmt = branch_stmt()?.toAst()
|
||||
if(branchstmt!=null) return branchstmt
|
||||
|
||||
@ -252,7 +256,12 @@ private fun Prog8ANTLRParser.FunctioncallContext.toAst(): FunctionCallExpression
|
||||
|
||||
private fun Prog8ANTLRParser.InlineasmContext.toAst(): InlineAssembly {
|
||||
val text = INLINEASMBLOCK().text
|
||||
return InlineAssembly(text.substring(2, text.length-2), toPosition())
|
||||
return InlineAssembly(text.substring(2, text.length-2), false, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.InlineirContext.toAst(): InlineAssembly {
|
||||
val text = INLINEASMBLOCK().text
|
||||
return InlineAssembly(text.substring(2, text.length-2), true, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.ReturnstmtContext.toAst() : Return {
|
||||
|
@ -7,6 +7,7 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.VMTarget
|
||||
|
||||
|
||||
interface INamedStatement {
|
||||
@ -616,19 +617,28 @@ class FunctionCallStatement(override var target: IdentifierReference,
|
||||
override fun toString() = "FunctionCallStatement(target=$target, pos=$position)"
|
||||
}
|
||||
|
||||
class InlineAssembly(val assembly: String, override val position: Position) : Statement() {
|
||||
class InlineAssembly(val assembly: String, val isIR: Boolean, override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
override fun copy() = InlineAssembly(assembly, position)
|
||||
override fun copy() = InlineAssembly(assembly, isIR, position)
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
fun hasReturnOrRts(target: ICompilationTarget): Boolean {
|
||||
return if(target.name!= VMTarget.NAME) {
|
||||
" rti" in assembly || "\trti" in assembly || " rts" in assembly || "\trts" in assembly ||
|
||||
" jmp" in assembly || "\tjmp" in assembly || " bra" in assembly || "\tbra" in assembly
|
||||
} else {
|
||||
" return" in assembly || "\treturn" in assembly || " jump" in assembly || "\tjump" in assembly || " jumpa" in assembly || "\tjumpa" in assembly
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val names: Set<String> by lazy {
|
||||
// A cache of all the words (identifiers) present in this block of assembly code
|
||||
|
@ -119,8 +119,6 @@ private val functionSignatures: List<FSignature> = listOf(
|
||||
FSignature("rsavex" , false, emptyList(), null),
|
||||
FSignature("rrestore" , false, emptyList(), null),
|
||||
FSignature("rrestorex" , false, emptyList(), null),
|
||||
FSignature("rnd" , false, emptyList(), DataType.UBYTE),
|
||||
FSignature("rndw" , false, emptyList(), DataType.UWORD),
|
||||
FSignature("memory" , true, listOf(FParam("name", arrayOf(DataType.STR)), FParam("size", arrayOf(DataType.UWORD)), FParam("alignment", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||
FSignature("callfar" , false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), null),
|
||||
FSignature("callrom" , false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), null),
|
||||
|
@ -68,7 +68,7 @@ It contains all of the program's code and data and has a certain file format tha
|
||||
allows it to be loaded directly on the target system. Prog8 currently has no built-in
|
||||
support for programs that exceed 64 Kb of memory, nor for multi-part loaders.
|
||||
|
||||
For the Commodore-64, most programs will have a tiny BASIC launcher that does a SYS into the generated machine code.
|
||||
For the Commodore 64, most programs will have a tiny BASIC launcher that does a SYS into the generated machine code.
|
||||
This way the user can load it as any other program and simply RUN it to start. (This is a regular ".prg" program).
|
||||
Prog8 can create those, but it is also possible to output plain binary programs
|
||||
that can be loaded into memory anywhere.
|
||||
@ -95,7 +95,7 @@ For normal use the compiler can be invoked with the command:
|
||||
|
||||
By default, assembly code is generated and written to ``sourcefile.asm``.
|
||||
It is then (automatically) fed to the `64tass <https://sourceforge.net/projects/tass64/>`_ assembler tool
|
||||
that creastes the final runnable program.
|
||||
that creates the final runnable program.
|
||||
|
||||
|
||||
Command line options
|
||||
@ -138,7 +138,7 @@ One or more .p8 module files
|
||||
|
||||
``-noreinit``
|
||||
Don't create code to reinitialize the global (block level) variables on every run of the program.
|
||||
Also means that all such variables are no longer placed in the zero page.
|
||||
Also means that all such variables are no longer placed in the zeropage.
|
||||
Sometimes the program will be a lot shorter when using this, but sometimes the opposite happens.
|
||||
When using this option, it is no longer be possible to run the program correctly more than once!
|
||||
*Experimental feature*: still has some problems!
|
||||
@ -182,7 +182,7 @@ One or more .p8 module files
|
||||
``-esa <address>``
|
||||
Override the base address of the evaluation stack. Has to be page-aligned.
|
||||
You can specify an integer or hexadecimal address.
|
||||
When not compiling for the CommanderX16 target, the location of the 16 virtual registers cx16.r0..r15
|
||||
When not compiling for the Commander X16 target, the location of the 16 virtual registers cx16.r0..r15
|
||||
is changed accordingly (to keep them in the same memory space as the evaluation stack).
|
||||
|
||||
|
||||
@ -210,26 +210,26 @@ the ``srcdirs`` command line option.
|
||||
|
||||
.. _debugging:
|
||||
|
||||
Debugging (with Vice)
|
||||
Debugging (with VICE)
|
||||
---------------------
|
||||
|
||||
There's support for using the monitor and debugging capabilities of the rather excellent
|
||||
`Vice emulator <http://vice-emu.sourceforge.net/>`_.
|
||||
`VICE emulator <http://vice-emu.sourceforge.net/>`_.
|
||||
|
||||
The ``%breakpoint`` directive (see :ref:`directives`) in the source code instructs the compiler to put
|
||||
a *breakpoint* at that position. Some systems use a BRK instruction for this, but
|
||||
this will usually halt the machine altogether instead of just suspending execution.
|
||||
Prog8 issues a NOP instruction instead and creates a 'virtual' breakpoint at this position.
|
||||
All breakpoints are then written to a file called "programname.vice-mon-list",
|
||||
which is meant to be used by the Vice emulator.
|
||||
It contains a series of commands for Vice's monitor, including source labels and the breakpoint settings.
|
||||
which is meant to be used by the VICE emulator.
|
||||
It contains a series of commands for VICE's monitor, including source labels and the breakpoint settings.
|
||||
If you use the emulator autostart feature of the compiler, it will take care of this for you.
|
||||
If you launch Vice manually, you'll have to use a command line option to load this file:
|
||||
If you launch VICE manually, you'll have to use a command line option to load this file:
|
||||
|
||||
``$ x64 -moncommands programname.vice-mon-list``
|
||||
|
||||
Vice will then use the label names in memory disassembly, and will activate any breakpoints as well.
|
||||
If your running program hits one of the breakpoints, Vice will halt execution and drop you into the monitor.
|
||||
VICE will then use the label names in memory disassembly, and will activate any breakpoints as well.
|
||||
If your running program hits one of the breakpoints, VICE will halt execution and drop you into the monitor.
|
||||
|
||||
|
||||
Troubleshooting
|
||||
|
@ -15,7 +15,7 @@ This is a compiled programming language targeting the 8-bit
|
||||
`6510 <https://en.wikipedia.org/wiki/MOS_Technology_6510>`_ /
|
||||
`65c02 <https://en.wikipedia.org/wiki/MOS_Technology_65C02>`_ microprocessors.
|
||||
This CPU is from the late 1970's and early 1980's and was used in many home computers from that era,
|
||||
such as the `Commodore-64 <https://en.wikipedia.org/wiki/Commodore_64>`_.
|
||||
such as the `Commodore 64 <https://en.wikipedia.org/wiki/Commodore_64>`_.
|
||||
The language aims to provide many conveniences over raw assembly code (even when using a macro assembler),
|
||||
while still being low level enough to create high performance programs.
|
||||
You can compile programs for various machines with this CPU:
|
||||
@ -69,12 +69,12 @@ Language features
|
||||
- Nested subroutines can access variables from outer scopes to avoids the overhead to pass everything via parameters
|
||||
- Variable data types include signed and unsigned bytes and words, arrays, strings.
|
||||
- Floating point math also supported if the target system provides floating point library routines (C64 and Cx16 both do).
|
||||
- Strings can contain escaped characters but also many symbols directly if they have a petscii equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \\, {, } and | are also accepted and converted to the closest petscii equivalents.
|
||||
- Strings can contain escaped characters but also many symbols directly if they have a PETSCII equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \\, {, } and | are also accepted and converted to the closest PETSCII equivalents.
|
||||
- High-level code optimizations, such as const-folding, expression and statement simplifications/rewriting.
|
||||
- Many built-in functions, such as ``sin``, ``cos``, ``rnd``, ``abs``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``sort`` and ``reverse``
|
||||
- Many built-in functions, such as ``sin``, ``cos``, ``abs``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``sort`` and ``reverse``
|
||||
- Programs can be run multiple times without reloading because of automatic variable (re)initializations.
|
||||
- Supports the sixteen 'virtual' 16-bit registers R0 .. R15 from the Commander X16, also on the other machines.
|
||||
- If you only use standard kernal and core prog8 library routines, it is possible to compile the *exact same program* for different machines (just change the compilation target flag)!
|
||||
- If you only use standard Kernal and core prog8 library routines, it is possible to compile the *exact same program* for different machines (just change the compilation target flag)!
|
||||
|
||||
|
||||
Code example
|
||||
@ -83,6 +83,7 @@ Code example
|
||||
Here is a hello world program::
|
||||
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
@ -139,11 +140,11 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
}
|
||||
|
||||
|
||||
when compiled an ran on a C-64 you get this:
|
||||
when compiled an ran on a C64 you get this:
|
||||
|
||||
.. image:: _static/primes_example.png
|
||||
:align: center
|
||||
:alt: result when run on C-64
|
||||
:alt: result when run on C64
|
||||
|
||||
when the exact same program is compiled for the Commander X16 target, and run on the emulator, you get this:
|
||||
|
||||
@ -169,19 +170,23 @@ Required additional tools
|
||||
It's very easy to compile yourself.
|
||||
A recent precompiled .exe (only for Windows) can be obtained from my `clone <https://github.com/irmen/64tass/releases>`_ of this project.
|
||||
*You need at least version 1.55.2257 of this assembler to correctly use the breakpoints feature.*
|
||||
It's possible to use older versions, but it is very likely that the automatic Vice breakpoints won't work with them.
|
||||
It's possible to use older versions, but it is very likely that the automatic VICE breakpoints won't work with them.
|
||||
|
||||
A **Java runtime (jre or jdk), version 11 or newer** is required to run the prog8 compiler itself.
|
||||
If you're scared of Oracle's licensing terms, most Linux distributions ship OpenJDK in their packages repository instead.
|
||||
For Windows it's possible to get that as well; check out `AdoptOpenJDK <https://adoptopenjdk.net/>`_ .
|
||||
For MacOS you can use the Homebrew system to install a recent version of OpenJDK.
|
||||
|
||||
Finally: an **emulator** (or a real machine ofcourse) to test and run your programs on.
|
||||
In C64 mode, the compiler assumes the presence of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
|
||||
If you're targeting the CommanderX16 instead, there's a choice of the official `x16emu <https://github.com/commanderx16/x16-emulator>`_
|
||||
Finally: an **emulator** (or a real machine of course) to test and run your programs on.
|
||||
In C64 mode, the compiler assumes the presence of the `VICE emulator <http://vice-emu.sourceforge.net/>`_.
|
||||
If you're targeting the Commander X16 instead, there's a choice of the official `x16emu <https://github.com/commanderx16/x16-emulator>`_
|
||||
and the unofficial `box16 <https://github.com/indigodarkwolf/box16>`_ (you can select which one you want to launch
|
||||
using the ``-emu`` or ``-emu2`` command line options)
|
||||
|
||||
**Syntax highlighting:** for a few different editors, syntax highlighting definition files are provided.
|
||||
Look in the `syntax-files <https://github.com/irmen/prog8/tree/master/syntax-files>`_ directory in the github repository to find them.
|
||||
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
@ -29,7 +29,7 @@ of these library modules automatically as required.
|
||||
syslib
|
||||
------
|
||||
The "system library" for your target machine. It contains many system-specific definitions such
|
||||
as ROM/kernal subroutine definitions, memory location constants, and utility subroutines.
|
||||
as ROM/Kernal subroutine definitions, memory location constants, and utility subroutines.
|
||||
|
||||
Depending on the compilation target, other routines may also be available in here specific to that target.
|
||||
Best is to check the source code of the correct syslib module.
|
||||
@ -45,8 +45,8 @@ sys (part of syslib)
|
||||
system when the program is running.
|
||||
The following return values are currently defined:
|
||||
|
||||
- 16 = compiled for CommanderX16 with 65C02 CPU
|
||||
- 64 = compiled for Commodore-64 with 6502/6510 CPU
|
||||
- 16 = compiled for Commander X16 with 65C02 CPU
|
||||
- 64 = compiled for Commodore 64 with 6502/6510 CPU
|
||||
|
||||
``exit(returncode)``
|
||||
Immediately stops the program and exits it, with the returncode in the A register.
|
||||
@ -105,7 +105,7 @@ sys (part of syslib)
|
||||
note: a more accurate way to do this is by using a raster irq handler instead.
|
||||
|
||||
``reset_system()``
|
||||
Soft-reset the system back to initial power-on Basic prompt.
|
||||
Soft-reset the system back to initial power-on BASIC prompt.
|
||||
(called automatically by Prog8 when the main subroutine returns and the program is not using basicsafe zeropage option)
|
||||
|
||||
|
||||
@ -140,7 +140,7 @@ Provides several routines that deal with disk drive I/O, such as:
|
||||
- delete and rename files on the disk
|
||||
- send arbitrary CbmDos command to disk drive
|
||||
|
||||
On the Commander X16 it tries to use that machine's fast kernal loading routines if possible.
|
||||
On the Commander X16 it tries to use that machine's fast Kernal loading routines if possible.
|
||||
|
||||
|
||||
string
|
||||
@ -150,7 +150,7 @@ Provides string manipulation routines.
|
||||
``length(str) -> ubyte length``
|
||||
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.
|
||||
Don't confuse this with ``len`` and ``sizeof``
|
||||
Don't confuse this with ``len`` and ``sizeof``!
|
||||
|
||||
``left(source, length, target)``
|
||||
Copies the left side of the source string of the given length to target string.
|
||||
@ -176,8 +176,8 @@ Provides string manipulation routines.
|
||||
and the index in the string. Or 0+carry bit clear if the character was not found.
|
||||
|
||||
``compare(string1, string2) -> ubyte result``
|
||||
Returns -1, 0 or 1 depeding on wether string1 sorts before, equal or after string2.
|
||||
Note that you can also directly compare strings and string values with eachother
|
||||
Returns -1, 0 or 1 depending on whether string1 sorts before, equal or after string2.
|
||||
Note that you can also directly compare strings and string values with each other
|
||||
using ``==``, ``<`` etcetera (it will use string.compare for you under water automatically).
|
||||
|
||||
``copy(from, to) -> ubyte length``
|
||||
@ -186,10 +186,10 @@ Provides string manipulation routines.
|
||||
but this function is useful if you're dealing with addresses for instance.
|
||||
|
||||
``lower(string)``
|
||||
Lowercases the petscii-string in place.
|
||||
Lowercases the PETSCII-string in place.
|
||||
|
||||
``upper(string)``
|
||||
Uppercases the petscii-string in place.
|
||||
Uppercases the PETSCII-string in place.
|
||||
|
||||
``lowerchar(char)``
|
||||
Returns lowercased character.
|
||||
@ -210,7 +210,7 @@ Provides string manipulation routines.
|
||||
|
||||
floats
|
||||
------
|
||||
Provides definitions for the ROM/kernal subroutines and utility routines dealing with floating
|
||||
Provides definitions for the ROM/Kernal subroutines and utility routines dealing with floating
|
||||
point variables. This includes ``print_f``, the routine used to print floating point numbers,
|
||||
``fabs`` to get the absolute value of a floating point number, and a dozen or so floating point
|
||||
math routines.
|
||||
@ -257,7 +257,10 @@ tan(x)
|
||||
Tangent.
|
||||
|
||||
rndf()
|
||||
returns a pseudo-random float between 0.0 and 1.0
|
||||
returns the next random float between 0.0 and 1.0 from the Pseudo RNG sequence.
|
||||
|
||||
rndseedf(seed)
|
||||
Sets a new seed for the float pseudo-RNG sequence. Use a negative non-zero number as seed value.
|
||||
|
||||
|
||||
graphics
|
||||
@ -276,12 +279,22 @@ Use the ``gfx2`` library if you want full-screen graphics or non-monochrome draw
|
||||
|
||||
math
|
||||
----
|
||||
Low level math routines. You should not normally have to bother with this directly.
|
||||
The compiler needs it to implement most of the math operations in your programs.
|
||||
Low-level integer math routines (which you usually don't have to bother with directly, but they are used by the compiler internally).
|
||||
Pseudo-Random number generators (byte and word).
|
||||
Various 8-bit integer trig functions that use lookup tables to quickly calculate sine and cosines.
|
||||
Usually a custom lookup table is the way to go if your application needs these,
|
||||
but perhaps the provided ones can be of service too.
|
||||
|
||||
However there's a bunch of 8-bit integer trig functions in here too that use lookup tables
|
||||
to quickly calculate sine and cosines. Usually a custom lookup table is the way to go if your
|
||||
application needs this, but perhaps the provided ones can be of service too:
|
||||
|
||||
rnd()
|
||||
Returns next random byte 0-255 from the pseudo-RNG sequence.
|
||||
|
||||
rndw()
|
||||
Returns next random word 0-65535 from the pseudo-RNG sequence.
|
||||
|
||||
rndseed(uword seed1, uword seed2)
|
||||
Sets a new seed for the pseudo-RNG sequence (both rnd and rndw). The seed consists of two words.
|
||||
Do not use zeros for the seed!
|
||||
|
||||
sin8u(x)
|
||||
Fast 8-bit ubyte sine of angle 0..255, result is in range 0..255
|
||||
@ -320,7 +333,7 @@ and allows you to print it anywhere on the screen.
|
||||
|
||||
prog8_lib
|
||||
---------
|
||||
Low level language support. You should not normally have to bother with this directly.
|
||||
Low-level language support. You should not normally have to bother with this directly.
|
||||
The compiler needs it for various built-in system routines.
|
||||
|
||||
|
||||
@ -330,7 +343,7 @@ Full-screen multicolor bitmap graphics routines, available on the Cx16 machine o
|
||||
|
||||
- multiple full-screen resolutions: 640 * 480 monochrome, and 320 * 240 monochrome and 256 colors
|
||||
- clearing screen, switching screen mode, also back to text mode is possible.
|
||||
- drawing individual pixels
|
||||
- drawing and reading individual pixels
|
||||
- drawing lines, rectangles, filled rectangles, circles, discs
|
||||
- drawing text inside the bitmap
|
||||
- in monochrome mode, it's possible to use a stippled drawing pattern to simulate a shade of gray.
|
||||
@ -339,15 +352,16 @@ Full-screen multicolor bitmap graphics routines, available on the Cx16 machine o
|
||||
palette (cx16 only)
|
||||
--------------------
|
||||
Available for the Cx16 target. Various routines to set the display color palette.
|
||||
There are also a few better looking Commodore-64 color palettes available here,
|
||||
There are also a few better looking Commodore 64 color palettes available here,
|
||||
because the Commander X16's default colors for this (the first 16 colors) are too saturated
|
||||
and are quite different than how they looked on a VIC-II chip in a C-64.
|
||||
and are quite different than how they looked on a VIC-II chip in a C64.
|
||||
|
||||
|
||||
cx16diskio (cx16 only)
|
||||
-----------------------
|
||||
Available for the Cx16 target. Contains extensions to the load and load_raw routines from the regular
|
||||
diskio module, to deal with loading of potentially large files in to banked ram (HiRam).
|
||||
Routines to directly load data into video ram are also present (vload and vload_raw).
|
||||
Also contains a helper function to calculate the file size of a loaded file (although that is truncated
|
||||
to 16 bits, 64Kb)
|
||||
|
||||
|
@ -17,15 +17,15 @@ CPU
|
||||
Memory Map
|
||||
----------
|
||||
|
||||
Zero page
|
||||
zeropage
|
||||
=========
|
||||
#. *Absolute requirement:* Provide three times 2 consecutive bytes (i.e. three 16-bit pointers) in the Zero page that are free to use at all times.
|
||||
#. Provide list of any additional free Zero page locations for a normal running system (basic + kernal enabled)
|
||||
#. Provide list of any additional free Zero page locations when basic is off, but floating point routines should still work
|
||||
#. Provide list of any additional free Zero page locations when only the kernal remains enabled
|
||||
#. *Absolute requirement:* Provide three times 2 consecutive bytes (i.e. three 16-bit pointers) in the zeropage that are free to use at all times.
|
||||
#. Provide list of any additional free zeropage locations for a normal running system (BASIC + Kernal enabled)
|
||||
#. Provide list of any additional free zeropage locations when BASIC is off, but floating point routines should still work
|
||||
#. Provide list of any additional free zeropage locations when only the Kernal remains enabled
|
||||
|
||||
Only the three 16-bit pointers are absolutely required to be able to use prog8 on the system.
|
||||
But more known available Zero page locations mean smaller and faster programs.
|
||||
But more known available zeropage locations mean smaller and faster programs.
|
||||
|
||||
|
||||
RAM, ROM, I/O
|
||||
@ -40,7 +40,7 @@ RAM, ROM, I/O
|
||||
|
||||
Character encodings
|
||||
-------------------
|
||||
#. if not Petscii or CBM screencodes: provide the primary character encoding table that the system uses (i.e. how is text represented in memory)
|
||||
#. if not PETSCII or CBM screencodes: provide the primary character encoding table that the system uses (i.e. how is text represented in memory)
|
||||
#. provide alternate character encodings (if any)
|
||||
#. what are the system's standard character screen dimensions?
|
||||
#. is there a screen character matrix directly accessible in Ram? What's it address? Same for color attributes if any.
|
||||
@ -50,7 +50,7 @@ ROM routines
|
||||
------------
|
||||
#. provide a list of the core ROM routines on the system, with names, addresses, and call signatures.
|
||||
|
||||
Ideally there are at least some routines to manipulate the screen and get some user input(clear, print text, print numbers, input strings from the keyboard)
|
||||
Ideally there are at least some routines to manipulate the screen and get some user input (clear, print text, print numbers, input strings from the keyboard)
|
||||
Routines to initialize the system to a sane state and to do a warm reset are useful too.
|
||||
The more the merrier.
|
||||
|
||||
@ -74,7 +74,7 @@ the new target system.
|
||||
|
||||
There are several other support libraries that you may want to port (``diskio``, ``graphics`` to name a few).
|
||||
|
||||
Also ofcourse if there are unique things available on the new target system, don't hesitate to provide
|
||||
Also of course if there are unique things available on the new target system, don't hesitate to provide
|
||||
extensions to the ``syslib`` or perhaps a new special custom library altogether.
|
||||
|
||||
|
||||
|
Binary file not shown.
@ -1,12 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.2" width="215.9mm" height="210mm" viewBox="0 0 21590 21000" preserveAspectRatio="xMidYMid" fill-rule="evenodd" stroke-width="28.222" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" xmlns:ooo="http://xml.openoffice.org/svg/export" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:presentation="http://sun.com/xmlns/staroffice/presentation" xmlns:smil="http://www.w3.org/2001/SMIL20/" xmlns:anim="urn:oasis:names:tc:opendocument:xmlns:animation:1.0" xml:space="preserve">
|
||||
<svg version="1.2" width="215.9mm" height="235mm" viewBox="0 0 21590 23500" preserveAspectRatio="xMidYMid" fill-rule="evenodd" stroke-width="28.222" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" xmlns:ooo="http://xml.openoffice.org/svg/export" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:presentation="http://sun.com/xmlns/staroffice/presentation" xmlns:smil="http://www.w3.org/2001/SMIL20/" xmlns:anim="urn:oasis:names:tc:opendocument:xmlns:animation:1.0" xml:space="preserve">
|
||||
<defs class="ClipPathGroup">
|
||||
<clipPath id="presentation_clip_path" clipPathUnits="userSpaceOnUse">
|
||||
<rect x="0" y="0" width="21590" height="21000"/>
|
||||
<rect x="0" y="0" width="21590" height="23500"/>
|
||||
</clipPath>
|
||||
<clipPath id="presentation_clip_path_shrink" clipPathUnits="userSpaceOnUse">
|
||||
<rect x="21" y="21" width="21547" height="20958"/>
|
||||
<rect x="21" y="23" width="21547" height="23453"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<defs>
|
||||
@ -17,6 +17,7 @@
|
||||
<glyph unicode="p" horiz-adv-x="927" d="M 375,141 L 375,-426 190,-426 190,1120 375,1120 375,977 C 406,1032 447,1075 498,1104 549,1133 607,1147 674,1147 809,1147 916,1095 993,990 1070,885 1108,740 1108,555 1108,373 1069,230 992,127 915,23 809,-29 674,-29 606,-29 547,-14 496,15 445,44 404,86 375,141 Z M 915,559 C 915,702 893,809 848,882 803,955 736,991 647,991 558,991 490,955 444,882 398,809 375,701 375,559 375,418 398,310 444,237 490,164 558,127 647,127 736,127 803,163 848,236 893,309 915,416 915,559 Z"/>
|
||||
<glyph unicode="o" horiz-adv-x="980" d="M 616,991 C 523,991 452,955 404,882 356,809 332,702 332,559 332,417 356,310 404,237 452,164 523,127 616,127 710,127 781,164 829,237 877,310 901,417 901,559 901,702 877,809 829,882 781,955 710,991 616,991 Z M 616,1147 C 771,1147 890,1097 973,996 1055,895 1096,750 1096,559 1096,368 1055,222 973,122 891,21 772,-29 616,-29 461,-29 342,21 260,122 178,222 137,368 137,559 137,750 178,895 260,996 342,1097 461,1147 616,1147 Z"/>
|
||||
<glyph unicode="l" horiz-adv-x="874" d="M 639,406 C 639,323 654,261 685,219 715,177 760,156 819,156 L 1034,156 1034,0 801,0 C 691,0 606,35 546,106 485,177 455,277 455,406 L 455,1423 160,1423 160,1567 639,1567 639,406 Z"/>
|
||||
<glyph unicode="i" horiz-adv-x="953" d="M 256,1120 L 727,1120 727,143 1092,143 1092,0 178,0 178,143 543,143 543,977 256,977 256,1120 Z M 543,1556 L 727,1556 727,1323 543,1323 543,1556 Z"/>
|
||||
<glyph unicode="h" horiz-adv-x="874" d="M 1051,694 L 1051,0 866,0 866,694 C 866,795 848,869 813,916 778,963 722,987 647,987 561,987 495,957 449,896 402,835 379,747 379,633 L 379,0 195,0 195,1556 379,1556 379,952 C 412,1016 456,1065 512,1098 568,1131 634,1147 711,1147 825,1147 910,1110 967,1035 1023,960 1051,846 1051,694 Z"/>
|
||||
<glyph unicode="g" horiz-adv-x="953" d="M 858,569 C 858,707 836,812 791,884 746,955 680,991 594,991 504,991 435,955 388,884 341,812 317,707 317,569 317,431 341,326 389,254 436,181 505,145 596,145 681,145 746,181 791,254 836,327 858,432 858,569 Z M 1042,72 C 1042,-96 1002,-223 923,-310 844,-397 727,-440 573,-440 522,-440 469,-435 414,-426 359,-417 303,-403 248,-385 L 248,-203 C 313,-234 373,-256 426,-271 479,-286 528,-293 573,-293 672,-293 745,-266 790,-212 835,-158 858,-72 858,45 L 858,53 858,178 C 829,115 789,69 738,38 687,7 626,-8 553,-8 422,-8 318,44 240,149 162,254 123,394 123,569 123,745 162,885 240,990 318,1095 422,1147 553,1147 625,1147 686,1133 736,1104 786,1075 827,1031 858,971 L 858,1116 1042,1116 1042,72 Z"/>
|
||||
<glyph unicode="e" horiz-adv-x="1006" d="M 1112,606 L 1112,516 315,516 315,510 C 315,388 347,294 411,227 474,160 564,127 680,127 739,127 800,136 864,155 928,174 996,202 1069,240 L 1069,57 C 999,28 932,7 867,-8 802,-22 739,-29 678,-29 504,-29 368,23 270,128 172,232 123,376 123,559 123,738 171,880 267,987 363,1094 491,1147 651,1147 794,1147 906,1099 989,1002 1071,905 1112,773 1112,606 Z M 928,659 C 925,767 900,850 852,906 803,963 734,991 643,991 554,991 481,962 424,903 367,844 333,763 322,659 L 928,659 Z"/>
|
||||
@ -118,7 +119,7 @@
|
||||
</font>
|
||||
</defs>
|
||||
<defs class="TextShapeIndex">
|
||||
<g ooo:slide="id1" ooo:id-list="id3 id4 id5 id6 id7 id8 id9 id10 id11 id12 id13 id14 id15 id16 id17 id18 id19 id20 id21 id22 id23 id24 id25 id26 id27 id28 id29 id30 id31 id32 id33 id34 id35 id36 id37 id38 id39 id40 id41 id42 id43 id44 id45 id46"/>
|
||||
<g ooo:slide="id1" ooo:id-list="id3 id4 id5 id6 id7 id8 id9 id10 id11 id12 id13 id14 id15 id16 id17 id18 id19 id20 id21 id22 id23 id24 id25 id26 id27 id28 id29 id30 id31 id32 id33 id34 id35 id36 id37 id38 id39 id40 id41 id42 id43 id44 id45 id46 id47 id48 id49"/>
|
||||
</defs>
|
||||
<defs class="EmbeddedBulletChars">
|
||||
<g id="bullet-char-template-57356" transform="scale(0.00048828125,-0.00048828125)">
|
||||
@ -437,55 +438,77 @@
|
||||
<path fill="rgb(52,101,164)" stroke="none" d="M 15108,18332 L 14968,18611 14829,18332 15108,18332 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g class="com.sun.star.drawing.ConnectorShape">
|
||||
<g id="id40">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="17473" y="14222" width="3305" height="1212"/>
|
||||
<path fill="rgb(255,166,166)" stroke="none" d="M 19125,15406 L 17500,15406 17500,14249 20750,14249 20750,15406 19125,15406 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="round" d="M 19125,15406 L 17500,15406 17500,14249 20750,14249 20750,15406 19125,15406 Z"/>
|
||||
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="18050" y="14709"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Generate </tspan></tspan><tspan class="TextPosition" x="18109" y="15289"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">VM code</tspan></tspan></tspan></text>
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="18809" y="17375" width="281" height="1028"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="miter" stroke-linecap="round" d="M 18869,17402 L 18869,17902 18949,17902 18949,18142"/>
|
||||
<path fill="rgb(52,101,164)" stroke="none" d="M 19089,18123 L 18949,18402 18810,18123 19089,18123 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.ConnectorShape">
|
||||
<g id="id41">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="18934" y="15625" width="281" height="778"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="miter" stroke-linecap="round" d="M 19119,15652 L 19119,16027 19074,16027 19074,16142"/>
|
||||
<path fill="rgb(52,101,164)" stroke="none" d="M 19214,16123 L 19074,16402 18935,16123 19214,16123 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.ConnectorShape">
|
||||
<g id="id42">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="14827" y="12362" width="2076" height="1889"/>
|
||||
<path fill="none" stroke="rgb(183,179,202)" stroke-width="53" stroke-linejoin="miter" stroke-linecap="round" d="M 16875,12389 L 16875,13319 14967,13319 14967,13990"/>
|
||||
<path fill="rgb(183,179,202)" stroke="none" d="M 15107,13971 L 14967,14250 14828,13971 15107,13971 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.ConnectorShape">
|
||||
<g id="id43">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="16848" y="12362" width="2418" height="1889"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="miter" stroke-linecap="round" d="M 16875,12389 L 16875,13319 19125,13319 19125,13990"/>
|
||||
<path fill="rgb(52,101,164)" stroke="none" d="M 19265,13971 L 19125,14250 18986,13971 19265,13971 Z"/>
|
||||
<g id="id42">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="16848" y="12362" width="2195" height="1652"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="miter" stroke-linecap="round" d="M 16875,12389 L 16875,13213 18902,13213 18902,13753"/>
|
||||
<path fill="rgb(52,101,164)" stroke="none" d="M 19042,13734 L 18902,14013 18763,13734 19042,13734 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="TextShape">
|
||||
<g id="id44">
|
||||
<g id="id43">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="13473" y="13084" width="2778" height="1167"/>
|
||||
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Liberation Serif, serif" font-size="388px" font-style="italic" font-weight="400"><tspan class="TextPosition" x="13723" y="13556"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">(future)</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id44">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="17125" y="18375" width="3647" height="1806"/>
|
||||
<path fill="rgb(255,166,166)" stroke="none" d="M 17729,20153 L 17729,20153 C 17628,20153 17528,20113 17441,20036 17353,19959 17280,19848 17229,19715 17179,19582 17152,19431 17152,19278 L 17152,19278 C 17152,19124 17179,18973 17229,18840 17280,18707 17353,18596 17441,18519 17528,18442 17628,18402 17729,18402 L 20166,18402 20167,18402 C 20268,18402 20368,18442 20455,18519 20543,18596 20616,18707 20667,18840 20717,18973 20744,19124 20744,19278 L 20744,19278 20744,19278 C 20744,19431 20717,19582 20667,19715 20616,19848 20543,19959 20455,20036 20368,20113 20268,20153 20167,20153 L 17729,20153 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="round" d="M 17729,20153 L 17729,20153 C 17628,20153 17528,20113 17441,20036 17353,19959 17280,19848 17229,19715 17179,19582 17152,19431 17152,19278 L 17152,19278 C 17152,19124 17179,18973 17229,18840 17280,18707 17353,18596 17441,18519 17528,18442 17628,18402 17729,18402 L 20166,18402 20167,18402 C 20268,18402 20368,18442 20455,18519 20543,18596 20616,18707 20667,18840 20717,18973 20744,19124 20744,19278 L 20744,19278 20744,19278 C 20744,19431 20717,19582 20667,19715 20616,19848 20543,19959 20455,20036 20368,20113 20268,20153 20167,20153 L 17729,20153 Z"/>
|
||||
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="423px" font-weight="400"><tspan class="TextPosition" x="18277" y="18927"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Run in </tspan></tspan><tspan class="TextPosition" x="18266" y="19423"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">virtual </tspan></tspan><tspan class="TextPosition" x="18074" y="19919"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">machine</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id45">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="17375" y="16375" width="3397" height="1806"/>
|
||||
<path fill="rgb(255,166,166)" stroke="none" d="M 17938,18153 L 17939,18153 C 17845,18153 17752,18113 17670,18036 17589,17959 17521,17848 17474,17715 17427,17582 17402,17431 17402,17278 L 17402,17278 C 17402,17124 17427,16973 17474,16840 17521,16707 17589,16596 17670,16519 17752,16442 17845,16402 17939,16402 L 20207,16402 20207,16402 C 20301,16402 20394,16442 20476,16519 20557,16596 20625,16707 20672,16840 20719,16973 20744,17124 20744,17278 L 20744,17278 20744,17278 C 20744,17431 20719,17582 20672,17715 20625,17848 20557,17959 20476,18036 20394,18113 20301,18153 20207,18153 L 17938,18153 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="round" d="M 17938,18153 L 17939,18153 C 17845,18153 17752,18113 17670,18036 17589,17959 17521,17848 17474,17715 17427,17582 17402,17431 17402,17278 L 17402,17278 C 17402,17124 17427,16973 17474,16840 17521,16707 17589,16596 17670,16519 17752,16442 17845,16402 17939,16402 L 20207,16402 20207,16402 C 20301,16402 20394,16442 20476,16519 20557,16596 20625,16707 20672,16840 20719,16973 20744,17124 20744,17278 L 20744,17278 20744,17278 C 20744,17431 20719,17582 20672,17715 20625,17848 20557,17959 20476,18036 20394,18113 20301,18153 20207,18153 L 17938,18153 Z"/>
|
||||
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="423px" font-weight="400"><tspan class="TextPosition" x="18402" y="16927"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Run in </tspan></tspan><tspan class="TextPosition" x="18391" y="17423"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">virtual </tspan></tspan><tspan class="TextPosition" x="18199" y="17919"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">machine</tspan></tspan></tspan></text>
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="17217" y="15973" width="3305" height="1457"/>
|
||||
<path fill="rgb(255,166,166)" stroke="none" d="M 18869,17402 L 17244,17402 17244,16000 20494,16000 20494,17402 18869,17402 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="round" d="M 18869,17402 L 17244,17402 17244,16000 20494,16000 20494,17402 18869,17402 Z"/>
|
||||
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="17781" y="16582"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Build VM </tspan></tspan><tspan class="TextPosition" x="17836" y="17162"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">program</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id46">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="17467" y="14223" width="3305" height="1457"/>
|
||||
<path fill="rgb(255,166,166)" stroke="none" d="M 19119,15652 L 17494,15652 17494,14250 20744,14250 20744,15652 19119,15652 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="round" d="M 19119,15652 L 17494,15652 17494,14250 20744,14250 20744,15652 19119,15652 Z"/>
|
||||
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="18031" y="14832"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Build VM </tspan></tspan><tspan class="TextPosition" x="18086" y="15412"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">program</tspan></tspan></tspan></text>
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="17307" y="14011" width="3189" height="1393"/>
|
||||
<path fill="rgb(255,245,206)" stroke="none" d="M 17308,14012 L 20494,14012 20494,15129 C 19232,15120 19275,15348 18136,15402 17729,15369 17580,15344 17308,15306 L 17308,14012 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 17308,14012 L 20494,14012 20494,15129 C 19232,15120 19275,15348 18136,15402 17729,15369 17580,15344 17308,15306 L 17308,14012 Z"/>
|
||||
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Bitstream Vera Sans Mono, monospace" font-size="370px" font-weight="400"><tspan class="TextPosition" x="17801" y="14698"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">hello.p8ir</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.ConnectorShape">
|
||||
<g id="id47">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="18729" y="15293" width="281" height="708"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="miter" stroke-linecap="round" d="M 18902,15320 L 18902,15688 18869,15688 18869,15740"/>
|
||||
<path fill="rgb(52,101,164)" stroke="none" d="M 19009,15721 L 18869,16000 18730,15721 19009,15721 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id48">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="13125" y="20625" width="3647" height="1806"/>
|
||||
<path fill="rgb(255,166,166)" stroke="none" d="M 13729,22403 L 13729,22403 C 13628,22403 13528,22363 13441,22286 13353,22209 13280,22098 13229,21965 13179,21832 13152,21681 13152,21528 L 13152,21528 C 13152,21374 13179,21223 13229,21090 13280,20957 13353,20846 13441,20769 13528,20692 13628,20652 13729,20652 L 16166,20652 16167,20652 C 16268,20652 16368,20692 16455,20769 16543,20846 16616,20957 16667,21090 16717,21223 16744,21374 16744,21528 L 16744,21528 16744,21528 C 16744,21681 16717,21832 16667,21965 16616,22098 16543,22209 16455,22286 16368,22363 16268,22403 16167,22403 L 13729,22403 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="round" d="M 13729,22403 L 13729,22403 C 13628,22403 13528,22363 13441,22286 13353,22209 13280,22098 13229,21965 13179,21832 13152,21681 13152,21528 L 13152,21528 C 13152,21374 13179,21223 13229,21090 13280,20957 13353,20846 13441,20769 13528,20692 13628,20652 13729,20652 L 16166,20652 16167,20652 C 16268,20652 16368,20692 16455,20769 16543,20846 16616,20957 16667,21090 16717,21223 16744,21374 16744,21528 L 16744,21528 16744,21528 C 16744,21681 16717,21832 16667,21965 16616,22098 16543,22209 16455,22286 16368,22363 16268,22403 16167,22403 L 13729,22403 Z"/>
|
||||
<text class="SVGTextShape"><tspan class="TextParagraph" font-family="Droid Serif, serif" font-size="423px" font-weight="400"><tspan class="TextPosition" x="14222" y="21177"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">Run on </tspan></tspan><tspan class="TextPosition" x="14353" y="21673"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">target </tspan></tspan><tspan class="TextPosition" x="14074" y="22169"><tspan fill="rgb(0,0,0)" stroke="none" style="white-space: pre">machine</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.ConnectorShape">
|
||||
<g id="id49">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="14809" y="19891" width="281" height="762"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" stroke-width="53" stroke-linejoin="miter" stroke-linecap="round" d="M 14968,19918 L 14968,20313 14949,20313 14949,20392"/>
|
||||
<path fill="rgb(52,101,164)" stroke="none" d="M 15089,20373 L 14949,20652 14810,20373 15089,20373 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 97 KiB |
@ -107,10 +107,6 @@ The name of a block must be unique in your entire program.
|
||||
Be careful when importing other modules; blocks in your own code cannot have
|
||||
the same name as a block defined in an imported module or library.
|
||||
|
||||
If you omit both the name and address, the entire block is *ignored* by the compiler (and a warning is displayed).
|
||||
This is a way to quickly "comment out" a piece of code that is unfinshed or may contain errors that you
|
||||
want to work on later, because the contents of the ignored block are not fully parsed either.
|
||||
|
||||
The address can be used to place a block at a specific location in memory.
|
||||
Usually it is omitted, and the compiler will automatically choose the location (usually immediately after
|
||||
the previous block in memory).
|
||||
@ -201,12 +197,12 @@ Values will usually be part of an expression or assignment statement::
|
||||
|
||||
*putting a variable in zeropage:*
|
||||
If you add the ``@zp`` tag to the variable declaration, the compiler will prioritize this variable
|
||||
when selecting variables to put into zero page (but no guarantees). If there are enough free locations in the zeropage,
|
||||
when selecting variables to put into zeropage (but no guarantees). If there are enough free locations in the zeropage,
|
||||
it will try to fill it with as much other variables as possible (before they will be put in regular memory pages).
|
||||
Use ``@requirezp`` tag to *force* the variable into zeropage, but if there is no more free space the compilation will fail.
|
||||
It's possible to put strings, arrays and floats into zeropage too, however because Zp space is really scarce
|
||||
this is not advised as they will eat up the available space very quickly. It's best to only put byte or word
|
||||
variables in Zeropage.
|
||||
variables in zeropage.
|
||||
|
||||
Example::
|
||||
|
||||
@ -230,7 +226,7 @@ Integers
|
||||
Integers are 8 or 16 bit numbers and can be written in normal decimal notation,
|
||||
in hexadecimal and in binary notation.
|
||||
A single character in single quotes such as ``'a'`` is translated into a byte integer,
|
||||
which is the Petscii value for that character.
|
||||
which is the PETSCII value for that character.
|
||||
|
||||
Unsigned integers are in the range 0-255 for unsigned byte types, and 0-65535 for unsigned word types.
|
||||
The signed integers integers are in the range -128..127 for bytes,
|
||||
@ -260,9 +256,9 @@ Floating point numbers
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Floats are stored in the 5-byte 'MFLPT' format that is used on CBM machines,
|
||||
and currently all floating point operations are specific to the Commodore-64.
|
||||
This is because routines in the C-64 BASIC and KERNAL ROMs are used for that.
|
||||
So floating point operations will only work if the C-64 BASIC ROM (and KERNAL ROM)
|
||||
and currently all floating point operations are specific to the Commodore 64.
|
||||
This is because routines in the C64 BASIC and Kernal ROMs are used for that.
|
||||
So floating point operations will only work if the C64 BASIC ROM (and Kernal ROM)
|
||||
are banked in.
|
||||
|
||||
Also your code needs to import the ``floats`` library to enable floating point support
|
||||
@ -286,7 +282,7 @@ Here are some examples of arrays::
|
||||
|
||||
byte[10] array ; array of 10 bytes, initially set to 0
|
||||
byte[] array = [1, 2, 3, 4] ; initialize the array, size taken from value
|
||||
byte[99] array = 255 ; initialize array with 99 times 255 [255, 255, 255, 255, ...]
|
||||
ubyte[99] array = 255 ; initialize array with 99 times 255 [255, 255, 255, 255, ...]
|
||||
byte[] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
|
||||
str[] names = ["ally", "pete"] ; array of string pointers/addresses (equivalent to uword)
|
||||
uword[] others = [names, array] ; array of pointers/addresses to other arrays
|
||||
@ -341,7 +337,7 @@ Strings (without encoding prefix) will be encoded (translated from ASCII/UTF-8)
|
||||
Alternative encodings can be specified with a ``encodingname:`` prefix to the string or character literal.
|
||||
The following encodings are currently recognised:
|
||||
|
||||
- ``petscii`` Petscii, the default encoding on CBM machines (c64, c128, cx16)
|
||||
- ``petscii`` PETSCII, the default encoding on CBM machines (c64, c128, cx16)
|
||||
- ``sc`` CBM-screencodes aka 'poke' codes (c64, c128, cx16)
|
||||
- ``iso`` iso-8859-15 text (supported on cx16)
|
||||
|
||||
@ -353,7 +349,7 @@ It can be correctly displayed on the screen only if a iso-8859-15 charset has be
|
||||
|
||||
You can concatenate two string literals using '+', which can be useful to
|
||||
split long strings over separate lines. But remember that the length
|
||||
of the total string still cannot exceed 255 characaters.
|
||||
of the total string still cannot exceed 255 characters.
|
||||
A string literal can also be repeated a given number of times using '*', where the repeat number must be a constant value.
|
||||
And a new string value can be assigned to another string, but no bounds check is done
|
||||
so be sure the destination string is large enough to contain the new value (it is overwritten in memory)::
|
||||
@ -370,7 +366,7 @@ as newlines, quote characters themselves, and so on. The ones used most often ar
|
||||
``\\``, ``\"``, ``\n``, ``\r``. For a detailed description of all of them and what they mean,
|
||||
read the syntax reference on strings.
|
||||
|
||||
Using the ``in`` operator you can easily check if a characater is present in a string,
|
||||
Using the ``in`` operator you can easily check if a character is present in a string,
|
||||
example: ``if '@' in email_address {....}`` (however this gives no clue about the location
|
||||
in the string where the character is present, if you need that, use the ``string.find()``
|
||||
library function instead)
|
||||
@ -412,10 +408,10 @@ for the constant itself). This is only valid for the simple numeric types (byte,
|
||||
When using ``&`` (the address-of operator but now applied to a datatype), the variable will point to specific location in memory,
|
||||
rather than being newly allocated. The initial value (mandatory) must be a valid
|
||||
memory address. Reading the variable will read the given data type from the
|
||||
address you specified, and setting the varible will directly modify that memory location(s)::
|
||||
address you specified, and setting the variable will directly modify that memory location(s)::
|
||||
|
||||
const byte max_age = 2000 - 1974 ; max_age will be the constant value 26
|
||||
&word SCREENCOLORS = $d020 ; a 16-bit word at the addres $d020-$d021
|
||||
&word SCREENCOLORS = $d020 ; a 16-bit word at the address $d020-$d021
|
||||
|
||||
.. _pointervars_programming:
|
||||
|
||||
@ -452,9 +448,9 @@ Many type conversions are possible by just writing ``as <type>`` at the end of a
|
||||
f = 56.777
|
||||
ub = f as ubyte ; ub will be 56
|
||||
|
||||
Sometimes it is a straight 'type cast' where the value is simply interpreted as being of the other type,
|
||||
sometimes an actual value conversion is done to convert it into the targe type.
|
||||
Try to avoid type conversions as much as possible.
|
||||
Sometimes it is a straight reinterpretation of the given value as being of the other type,
|
||||
sometimes an actual value conversion is done to convert it into the other type.
|
||||
Try to avoid those type conversions as much as possible.
|
||||
|
||||
|
||||
Initial values across multiple runs of the program
|
||||
@ -511,19 +507,29 @@ Conditional Execution
|
||||
if statements
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Conditional execution means that the flow of execution changes based on certiain conditions,
|
||||
Conditional execution means that the flow of execution changes based on certain conditions,
|
||||
rather than having fixed gotos or subroutine calls::
|
||||
|
||||
if aa>4 goto overflow
|
||||
if xx==5 {
|
||||
yy = 99
|
||||
zz = 42
|
||||
} else {
|
||||
aa = 3
|
||||
bb = 9
|
||||
}
|
||||
|
||||
if xx==3 yy = 4
|
||||
if xx==3 yy = 4 else aa = 2
|
||||
if xx==5
|
||||
yy = 42
|
||||
else if xx==6
|
||||
yy = 43
|
||||
else
|
||||
yy = 44
|
||||
|
||||
if xx==5 {
|
||||
yy = 99
|
||||
} else {
|
||||
aa = 3
|
||||
}
|
||||
if aa>4 goto some_label
|
||||
|
||||
if xx==3 yy = 4
|
||||
|
||||
if xx==3 yy = 4 else aa = 2
|
||||
|
||||
|
||||
Conditional jumps (``if condition goto label``) are compiled using 6502's branching instructions (such as ``bne`` and ``bcc``) so
|
||||
@ -557,7 +563,7 @@ So ``if_cc goto target`` will directly translate into the single CPU instruction
|
||||
.. caution::
|
||||
These special ``if_XX`` branching statements are only useful in certain specific situations where you are *certain*
|
||||
that the status register (still) contains the correct status bits.
|
||||
This is not always the case after a fuction call or other operations!
|
||||
This is not always the case after a function call or other operations!
|
||||
If in doubt, check the generated assembly code!
|
||||
|
||||
.. note::
|
||||
@ -633,7 +639,7 @@ If possible, the expression is parsed and evaluated by the compiler itself at co
|
||||
Expressions that cannot be compile-time evaluated will result in code that calculates them at runtime.
|
||||
Expressions can contain procedure and function calls.
|
||||
There are various built-in functions such as sin(), cos() that can be used in expressions (see :ref:`builtinfunctions`).
|
||||
You can also reference idendifiers defined elsewhere in your code.
|
||||
You can also reference identifiers defined elsewhere in your code.
|
||||
|
||||
Read the :ref:`syntaxreference` chapter for all details on the available operators and kinds of expressions you can write.
|
||||
|
||||
@ -670,7 +676,7 @@ Logical expressions are expressions that calculate a boolean result: true or fal
|
||||
logical expressions will compile more efficiently than when you're using regular integer type operands
|
||||
(because these have to be converted to 0 or 1 every time)
|
||||
|
||||
You can use parentheses to group parts of an expresion to change the precedence.
|
||||
You can use parentheses to group parts of an expression to change the precedence.
|
||||
Usually the normal precedence rules apply (``*`` goes before ``+`` etc.) but subexpressions
|
||||
within parentheses will be evaluated first. So ``(4 + 8) * 2`` is 24 and not 20,
|
||||
and ``(true or false) and false`` is false instead of true.
|
||||
@ -835,24 +841,18 @@ pokemon(address, value)
|
||||
Doesn't have anything to do with a certain video game.
|
||||
|
||||
push(value)
|
||||
pushes a byte value on the CPU hardware stack. Lowlevel function that should normally not be used.
|
||||
pushes a byte value on the CPU hardware stack. Low-level function that should normally not be used.
|
||||
|
||||
pushw(value)
|
||||
pushes a 16-bit word value on the CPU hardware stack. Lowlevel function that should normally not be used.
|
||||
pushes a 16-bit word value on the CPU hardware stack. Low-level function that should normally not be used.
|
||||
|
||||
pop(variable)
|
||||
pops a byte value off the CPU hardware stack into the given variable. Only variables can be used.
|
||||
Lowlevel function that should normally not be used.
|
||||
Low-level function that should normally not be used.
|
||||
|
||||
popw(value)
|
||||
pops a 16-bit word value off the CPU hardware stack into the given variable. Only variables can be used.
|
||||
Lowlevel function that should normally not be used.
|
||||
|
||||
rnd()
|
||||
returns a pseudo-random byte from 0..255
|
||||
|
||||
rndw()
|
||||
returns a pseudo-random word from 0..65535
|
||||
Low-level function that should normally not be used.
|
||||
|
||||
rol(x)
|
||||
Rotate the bits in x (byte or word) one position to the left.
|
||||
@ -885,7 +885,7 @@ ror2(x)
|
||||
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).
|
||||
For an 10 element array of floats, it is 50 (on the C64, where a float is 5 bytes).
|
||||
Note: usually you will be interested in the number of elements in an array, use len() for that.
|
||||
|
||||
memory(name, size, alignment)
|
||||
@ -903,7 +903,7 @@ memory(name, size, alignment)
|
||||
You can only treat it as a pointer or use it in inline assembly.
|
||||
|
||||
callfar(bank, address, argumentaddress) ; NOTE: specific to cx16 target for now
|
||||
Calls an assembly routine in another ram-bank on the CommanderX16 (using the ``jsrfar`` routine)
|
||||
Calls an assembly routine in another ram-bank on the Commander X16 (using the ``jsrfar`` routine)
|
||||
The banked RAM is located in the address range $A000-$BFFF (8 kilobyte), but you can specify
|
||||
any address in system ram (why this can be useful is explained at the end of this paragraph)
|
||||
The third argument can be used to designate the memory address
|
||||
@ -917,7 +917,7 @@ callfar(bank, address, argumentaddress) ; NOTE: specific to cx16 target for
|
||||
This is not very efficient though, so maybe you should write a small piece of inline assembly for this instead.
|
||||
|
||||
callrom(bank, address, argumentaddress) ; NOTE: specific to cx16 target for now
|
||||
Calls an assembly routine in another rom-bank on the CommanderX16
|
||||
Calls an assembly routine in another rom-bank on the Commander X16
|
||||
The banked ROM is located in the address range $C000-$FFFF (16 kilobyte).
|
||||
There are 32 banks (0 to 31).
|
||||
The third argument can be used to designate the memory address
|
||||
@ -931,7 +931,7 @@ callrom(bank, address, argumentaddress) ; NOTE: specific to cx16 target for
|
||||
syscall(callnr), syscall1(callnr, arg), syscall2(callnr, arg1, arg2), syscall3(callnr, arg1, arg2, arg3)
|
||||
Functions for doing a system call on targets that support this. Currently no actual target
|
||||
uses this though except, possibly, the experimental code generation target!
|
||||
The regular 6502 based compiler targets just use a subroutine call to asmsub kernal routines at
|
||||
The regular 6502 based compiler targets just use a subroutine call to asmsub Kernal routines at
|
||||
specific memory locations. So these builtin function calls are not useful yet except for
|
||||
experimentation in new code generation targets.
|
||||
|
||||
|
@ -8,7 +8,7 @@ Module file
|
||||
-----------
|
||||
|
||||
This is a file with the ``.p8`` suffix, containing *directives* and *code blocks*, described below.
|
||||
The file is a text file wich can also contain:
|
||||
The file is a text file which can also contain:
|
||||
|
||||
Lines, whitespace, indentation
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -48,28 +48,28 @@ Directives
|
||||
Global setting, selects the program launcher stub to use.
|
||||
Only relevant when using the ``prg`` output type. Defaults to ``basic``.
|
||||
|
||||
- type ``basic`` : add a tiny C64 BASIC program, whith a SYS statement calling into the machine code
|
||||
- type ``basic`` : add a tiny C64 BASIC program, with a SYS statement calling into the machine code
|
||||
- type ``none`` : no launcher logic is added at all
|
||||
|
||||
.. data:: %zeropage <style>
|
||||
|
||||
Level: module.
|
||||
Global setting, select ZeroPage handling style. Defaults to ``kernalsafe``.
|
||||
Global setting, select zeropage handling style. Defaults to ``kernalsafe``.
|
||||
|
||||
- style ``kernalsafe`` -- use the part of the ZP that is 'free' or only used by BASIC routines,
|
||||
and don't change anything else. This allows full use of KERNAL ROM routines (but not BASIC routines),
|
||||
and don't change anything else. This allows full use of Kernal ROM routines (but not BASIC routines),
|
||||
including default IRQs during normal system operation.
|
||||
It's not possible to return cleanly to BASIC when the program exits. The only choice is
|
||||
to perform a system reset. (A ``system_reset`` subroutine is available in the syslib to help you do this)
|
||||
- style ``floatsafe`` -- like the previous one but also reserves the addresses that
|
||||
are required to perform floating point operations (from the BASIC kernal). No clean exit is possible.
|
||||
are required to perform floating point operations (from the BASIC Kernal). No clean exit is possible.
|
||||
- style ``basicsafe`` -- the most restricted mode; only use the handful 'free' addresses in the ZP, and don't
|
||||
touch change anything else. This allows full use of BASIC and KERNAL ROM routines including default IRQs
|
||||
touch change anything else. This allows full use of BASIC and Kernal ROM routines including default IRQs
|
||||
during normal system operation.
|
||||
When the program exits, it simply returns to the BASIC ready prompt.
|
||||
- style ``full`` -- claim the whole ZP for variables for the program, overwriting everything,
|
||||
except the few addresses mentioned above that are used by the system's IRQ routine.
|
||||
Even though the default IRQ routine is still active, it is impossible to use most BASIC and KERNAL ROM routines.
|
||||
Even though the default IRQ routine is still active, it is impossible to use most BASIC and Kernal ROM routines.
|
||||
This includes many floating point operations and several utility routines that do I/O, such as ``print``.
|
||||
This option makes programs smaller and faster because even more variables can
|
||||
be stored in the ZP (which allows for more efficient assembly code).
|
||||
@ -84,9 +84,9 @@ Directives
|
||||
16 virtual registers cx16.r0...cx16.r15 from the Commander X16 into the zeropage as well
|
||||
(but not on the same locations). They are relocated automatically by the compiler.
|
||||
The other options need those locations for other things so those virtual registers have
|
||||
to be put into memory elsewhere (outside of the zeropage). Trying to use them as zero page
|
||||
to be put into memory elsewhere (outside of the zeropage). Trying to use them as zeropage
|
||||
variables or pointers etc. will be a lot slower in those cases!
|
||||
On the CommanderX16 the registers are always in zeropage. On other targets, for now, they
|
||||
On the Commander X16 the registers are always in zeropage. On other targets, for now, they
|
||||
are always outside of the zeropage.
|
||||
|
||||
.. data:: %zpreserved <fromaddress>,<toaddress>
|
||||
@ -100,8 +100,8 @@ Directives
|
||||
|
||||
Level: module.
|
||||
Global setting, set the program's start memory address. It's usually fixed at ``$0801`` because the
|
||||
default launcher type is a CBM-basic program. But you have to specify this address yourself when
|
||||
you don't use a CBM-basic launcher.
|
||||
default launcher type is a CBM-BASIC program. But you have to specify this address yourself when
|
||||
you don't use a CBM-BASIC launcher.
|
||||
|
||||
|
||||
.. data:: %import <name>
|
||||
@ -119,7 +119,7 @@ Directives
|
||||
Sets special compiler options.
|
||||
|
||||
- ``enable_floats`` (module level) tells the compiler
|
||||
to deal with floating point numbers (by using various subroutines from the Commodore-64 kernal).
|
||||
to deal with floating point numbers (by using various subroutines from the Commodore 64 Kernal).
|
||||
Otherwise, floating point support is not enabled. Normally you don't have to use this yourself as
|
||||
importing the ``floats`` library is required anyway and that will enable it for you automatically.
|
||||
- ``no_sysinit`` (module level) which cause the resulting program to *not* include
|
||||
@ -150,7 +150,7 @@ Directives
|
||||
The assembler will include the file as raw assembly source text at this point,
|
||||
prog8 will not process this at all. Symbols defined in the included assembly can not be referenced
|
||||
from prog8 code. However they can be referenced from other assembly code if properly prefixed.
|
||||
You can ofcourse use a label in your prog8 code just before the %asminclude directive, and reference
|
||||
You can of course use a label in your prog8 code just before the %asminclude directive, and reference
|
||||
that particular label to get to (the start of) the included assembly.
|
||||
Be careful: you risk symbol redefinitions or duplications if you include a piece of
|
||||
assembly into a prog8 block that already defines symbols itself.
|
||||
@ -225,7 +225,7 @@ and after that, a combination of letters, numbers, or underscores. Examples of v
|
||||
Code blocks
|
||||
-----------
|
||||
|
||||
A named block of actual program code. Itefines a *scope* (also known as 'namespace') and
|
||||
A named block of actual program code. It defines a *scope* (also known as 'namespace') and
|
||||
can only contain *directives*, *variable declarations*, *subroutines* or *inline assembly*::
|
||||
|
||||
<blockname> [<address>] {
|
||||
@ -270,7 +270,7 @@ Variable declarations
|
||||
|
||||
Variables should be declared with their exact type and size so the compiler can allocate storage
|
||||
for them. You can give them an initial value as well. That value can be a simple literal value,
|
||||
or an expression. If you don't provide an intial value yourself, zero will be used.
|
||||
or an expression. If you don't provide an initial value yourself, zero will be used.
|
||||
You can add a ``@zp`` zeropage-tag, to tell the compiler to prioritize it
|
||||
when selecting variables to be put into zeropage (but no guarantees). If the ZP is full,
|
||||
the variable will be allocated in normal memory elsewhere.
|
||||
@ -295,7 +295,7 @@ Various examples::
|
||||
byte[5] values = 255 ; initialize with five 255 bytes
|
||||
|
||||
word @zp zpword = 9999 ; prioritize this when selecting vars for zeropage storage
|
||||
uword @requirezp zpaddr = $3000 ; we require this variable in Zeropage
|
||||
uword @requirezp zpaddr = $3000 ; we require this variable in zeropage
|
||||
word @shared asmvar ; variable is used in assembly code but not elsewhere
|
||||
|
||||
|
||||
@ -329,7 +329,7 @@ type identifier type storage size example var declara
|
||||
``float[]`` floating-point array depends on value ``float[] myvar = [1.1, 2.2, 3.3, 4.4]``
|
||||
``bool[]`` boolean array depends on value ``bool[] myvar = [true, false, true]`` note: consider using bit flags in a byte or word instead to save space
|
||||
``str[]`` array with string ptrs 2*x bytes + strs ``str[] names = ["ally", "pete"]``
|
||||
``str`` string (petscii) varies ``str myvar = "hello."``
|
||||
``str`` string (PETSCII) varies ``str myvar = "hello."``
|
||||
implicitly terminated by a 0-byte
|
||||
=============== ======================= ================= =========================================
|
||||
|
||||
@ -342,7 +342,7 @@ value is given, the array size in the declaration can be omitted.
|
||||
Note that ``%`` is also the remainder operator so be careful: if you want to take the remainder
|
||||
of something with an operand starting with 1 or 0, you'll have to add a space in between.
|
||||
|
||||
**character values:** you can use a single character in quotes like this ``'a'`` for the Petscii byte value of that character.
|
||||
**character values:** you can use a single character in quotes like this ``'a'`` for the PETSCII byte value of that character.
|
||||
|
||||
|
||||
**``byte`` versus ``word`` values:**
|
||||
@ -461,7 +461,7 @@ There are several escape sequences available to put special characters into your
|
||||
- ``\uHHHH`` - a unicode codepoint \u0000 - \uffff (16-bit hexadecimal)
|
||||
- ``\xHH`` - 8-bit hex value that will be copied verbatim *without encoding*
|
||||
|
||||
- String literals can contain many symbols directly if they have a petscii equivalent, such as "♠♥♣♦π▚●○╳".
|
||||
- String literals can contain many symbols directly if they have a PETSCII equivalent, such as "♠♥♣♦π▚●○╳".
|
||||
Characters like ^, _, \\, {, } and | (that have no direct PETSCII counterpart) are still accepted and converted to the closest PETSCII equivalents. (Make sure you save the source file in UTF-8 encoding if you use this.)
|
||||
|
||||
|
||||
@ -505,7 +505,7 @@ logical: ``not`` ``and`` ``or`` ``xor``
|
||||
the ``bool`` variable type instead, where this conversion doesn't need to occur.
|
||||
|
||||
.. note::
|
||||
Unlike most other programming languages, there is no short-cirquit or McCarthy-evaluation
|
||||
Unlike most other programming languages, there is no short-circuit or McCarthy evaluation
|
||||
for the logical ``and`` and ``or`` operators. This means that prog8 currently always evaluates
|
||||
all operands from these logical expressions, even when one of them already determines the outcome!
|
||||
|
||||
@ -580,7 +580,7 @@ Multiple return values
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
Normal subroutines can only return zero or one return values.
|
||||
However, the special ``asmsub`` routines (implemented in assembly code) or ``romsub`` routines
|
||||
(referencing a routine in kernal ROM) can return more than one return value.
|
||||
(referencing a routine in Kernal ROM) can return more than one return value.
|
||||
For example a status in the carry bit and a number in A, or a 16-bit value in A/Y registers.
|
||||
It is not possible to process the results of a call to these kind of routines
|
||||
directly from the language, because only single value assignments are possible.
|
||||
@ -648,21 +648,22 @@ the statement body of such a subroutine should consist of just an inline assembl
|
||||
|
||||
The ``@ <register>`` part is required for rom and assembly-subroutines, as it specifies for the compiler
|
||||
what cpu registers should take the routine's arguments. You can use the regular set of registers
|
||||
(A, X, Y), the special 16-bit register pairs to take word values (AX, AY and XY) and even a processor status
|
||||
(A, X, Y), special 16-bit register pairs to take word values (AX, AY and XY) and even a processor status
|
||||
flag such as Carry (Pc).
|
||||
|
||||
It is not possible to use floating point arguments or return values in an asmsub.
|
||||
|
||||
.. note::
|
||||
Asmsubs can also be tagged as ``inline asmsub`` to make trivial pieces of assembly inserted
|
||||
directly instead of a call to them. Note that it is literal copy-paste of code that is done,
|
||||
so make sure the assembly is actually written to behave like such - which probably means you
|
||||
don't want a ``rts`` or ``jmp`` or ``bra`` in it!
|
||||
|
||||
|
||||
.. note::
|
||||
The 'virtual' 16-bit registers from the Commander X16 can also be specified as ``R0`` .. ``R15`` .
|
||||
This means you don't have to set them up manually before calling a subroutine that takes
|
||||
one or more parameters in those 'registers'. You can just list the arguments directly.
|
||||
*This also works on the Commodore-64!* (however they are not as efficient there because they're not in zeropage)
|
||||
*This also works on the Commodore 64!* (however they are not as efficient there because they're not in zeropage)
|
||||
In prog8 and assembly code these 'registers' are directly accessible too via
|
||||
``cx16.r0`` .. ``cx16.r15`` (these are memory mapped uword values),
|
||||
``cx16.r0s`` .. ``cx16.r15s`` (these are memory mapped word values),
|
||||
@ -715,7 +716,8 @@ And this is a loop over the values of the array ``fibonacci_numbers``::
|
||||
|
||||
uword number
|
||||
for number in fibonacci_numbers {
|
||||
; do something with number
|
||||
; do something with number...
|
||||
break ; break out of the loop early
|
||||
}
|
||||
|
||||
|
||||
@ -765,7 +767,7 @@ Conditional Execution and Jumps
|
||||
Unconditional jump: goto
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To jump to another part of the program, you use a ``goto`` statement with an addres or the name
|
||||
To jump to another part of the program, you use a ``goto`` statement with an address or the name
|
||||
of a label or subroutine::
|
||||
|
||||
goto $c000 ; address
|
||||
@ -783,31 +785,35 @@ Note: to do an indirect *JSR* to a routine with a varying address, you can use t
|
||||
(which is not very efficient) or you have to write a small piece of inline assembly.
|
||||
|
||||
|
||||
Conditional execution
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
if statements
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
With the 'if' / 'else' statement you can execute code depending on the value of a condition::
|
||||
|
||||
if <expression> <statements> [else <statements> ]
|
||||
|
||||
where <statements> can be just a single statement for instance just a ``goto``, or it can be a block such as this::
|
||||
If <statements> is just a single statement, for instance just a ``goto`` or a single assignment,
|
||||
it's possible to just write the statement without any curly braces.
|
||||
However if <statements> is a block of multiple statements, you'll have to enclose it in curly braces::
|
||||
|
||||
if <expression> {
|
||||
<statements>
|
||||
} else if <expression> {
|
||||
<statements>
|
||||
} else {
|
||||
<alternative statements>
|
||||
<statements>
|
||||
}
|
||||
|
||||
|
||||
**Special status register branch form:**
|
||||
|
||||
There is a special form of the if-statement that immediately translates into one of the 6502's branching instructions.
|
||||
It is almost the same as the regular if-statement but it lacks a contional expression part, because the if-statement
|
||||
It is almost the same as the regular if-statement but it lacks a conditional expression part, because the if-statement
|
||||
itself defines on what status register bit it should branch on::
|
||||
|
||||
if_XX <statements> [else <statements> ]
|
||||
|
||||
where <statements> can be just a single statement for instance just a ``goto``, or it can be a block such as this::
|
||||
where <statements> can be just a single statement or a block again::
|
||||
|
||||
if_XX {
|
||||
<statements>
|
||||
@ -822,7 +828,7 @@ It can also be one of the four aliases that are easier to read: ``if_z``, ``if_n
|
||||
.. caution::
|
||||
These special ``if_XX`` branching statements are only useful in certain specific situations where you are *certain*
|
||||
that the status register (still) contains the correct status bits.
|
||||
This is not always the case after a fuction call or other operations!
|
||||
This is not always the case after a function call or other operations!
|
||||
If in doubt, check the generated assembly code!
|
||||
|
||||
|
||||
|
@ -20,7 +20,7 @@ Currently these machines can be selected as a compilation target (via the ``-tar
|
||||
This chapter explains some relevant system details of the c64 and cx16 machines.
|
||||
|
||||
.. hint::
|
||||
If you only use standard kernal and prog8 library routines,
|
||||
If you only use standard Kernal and prog8 library routines,
|
||||
it is often possible to compile the *exact same program* for
|
||||
different machines (just change the compilation target flag)!
|
||||
|
||||
@ -39,7 +39,7 @@ This is a hard limit: there is no built-in support for RAM expansions or bank sw
|
||||
====================== ================== ========
|
||||
memory area type note
|
||||
====================== ================== ========
|
||||
``$00``--``$ff`` ZeroPage contains many sensitive system variables
|
||||
``$00``--``$ff`` zeropage contains many sensitive system variables
|
||||
``$100``--``$1ff`` Hardware stack used by the CPU, normally not accessed directly
|
||||
``$0200``--``$ffff`` Free RAM or ROM free to use memory area, often a mix of RAM and ROM
|
||||
====================== ================== ========
|
||||
@ -64,9 +64,9 @@ reserved address in use for
|
||||
================== =======================
|
||||
|
||||
The actual machine will often have many other special addresses as well,
|
||||
For example, the Commodore-64 has:
|
||||
For example, the Commodore 64 has:
|
||||
|
||||
- ROMs installed in the machine: BASIC, kernal and character roms. Occupying ``$a000``--``$bfff`` and ``$e000``--``$ffff``.
|
||||
- ROMs installed in the machine: BASIC, Kernal and character roms. Occupying ``$a000``--``$bfff`` and ``$e000``--``$ffff``.
|
||||
- memory-mapped I/O registers, for the video and sound chips, and the CIA's. Occupying ``$d000``--``$dfff``.
|
||||
- RAM areas that are used for screen graphics and sprite data: usually at ``$0400``--``$07ff``.
|
||||
|
||||
@ -75,18 +75,18 @@ Prog8 programs can access all of those special memory locations but it will have
|
||||
|
||||
.. _zeropage:
|
||||
|
||||
ZeroPage ("ZP")
|
||||
Zeropage ("ZP")
|
||||
---------------
|
||||
|
||||
The ZeroPage memory block ``$02``--``$ff`` can be regarded as 254 CPU 'registers', because
|
||||
The zeropage memory block ``$02``--``$ff`` can be regarded as 254 CPU 'registers', because
|
||||
they take less clock cycles to access and need fewer instruction bytes than accessing other memory locations outside of the ZP.
|
||||
Theoretically they can all be used in a program, with the follwoing limitations:
|
||||
Theoretically they can all be used in a program, with the following limitations:
|
||||
|
||||
- several addresses (``$02``, ``$03``, ``$fb - $fc``, ``$fd - $fe``) are reserved for internal use
|
||||
- most other addresses will already be in use by the machine's operating system or kernal,
|
||||
- most other addresses will already be in use by the machine's operating system or Kernal,
|
||||
and overwriting them will probably crash the machine. It is possible to use all of these
|
||||
yourself, but only if the program takes over the entire system (and seizes control from the regular kernal).
|
||||
This means it can no longer use (most) BASIC and kernal routines from ROM.
|
||||
yourself, but only if the program takes over the entire system (and seizes control from the regular Kernal).
|
||||
This means it can no longer use (most) BASIC and Kernal routines from ROM.
|
||||
- it's more convenient and safe to let the compiler allocate these addresses for you and just
|
||||
use symbolic names in the program code.
|
||||
|
||||
@ -95,19 +95,19 @@ It will use the free ZP addresses to place its ZP variables in,
|
||||
until they're all used up. If instructed to output a program that takes over the entire
|
||||
machine, (almost) all of the ZP addresses are suddenly available and will be used.
|
||||
|
||||
**ZeroPage handling is configurable:**
|
||||
**zeropage handling is configurable:**
|
||||
There's a global program directive to specify the way the compiler
|
||||
treats the ZP for the program. The default is to be reasonably restrictive to use the
|
||||
part of the ZP that is not used by the C64's kernal routines.
|
||||
It's possible to claim the whole ZP as well (by disabling the operating system or kernal).
|
||||
If you want, it's also possible to be more restricive and stay clear of the addresses used by BASIC routines too.
|
||||
part of the ZP that is not used by the C64's Kernal routines.
|
||||
It's possible to claim the whole ZP as well (by disabling the operating system or Kernal).
|
||||
If you want, it's also possible to be more restrictive and stay clear of the addresses used by BASIC routines too.
|
||||
This allows the program to exit cleanly back to a BASIC ready prompt - something that is not possible in the other modes.
|
||||
|
||||
|
||||
IRQs and the ZeroPage
|
||||
IRQs and the zeropage
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The normal IRQ routine in the C-64's kernal will read and write several addresses in the ZP
|
||||
The normal IRQ routine in the C64's Kernal will read and write several addresses in the ZP
|
||||
(such as the system's software jiffy clock which sits in ``$a0 - $a2``):
|
||||
|
||||
``$a0 - $a2``; ``$91``; ``$c0``; ``$c5``; ``$cb``; ``$f5 - $f6``
|
||||
@ -144,7 +144,7 @@ IRQ Handling
|
||||
|
||||
Normally, the system's default IRQ handling is not interfered with.
|
||||
You can however install your own IRQ handler (for clean separation, it is advised to define it inside its own block).
|
||||
There are a few library routines available to make setting up C-64 60hz IRQs and Raster IRQs a lot easier (no assembly code required).
|
||||
There are a few library routines available to make setting up C64 60hz IRQs and Raster IRQs a lot easier (no assembly code required).
|
||||
|
||||
For the C64 these routines are::
|
||||
|
||||
@ -155,7 +155,7 @@ For the C64 these routines are::
|
||||
And for the Commander X16::
|
||||
|
||||
cx16.set_irq(uword handler_address, boolean useKernal) ; vsync irq
|
||||
cx16.set_rasterirq(uword handler_address, uword rasterline) ; note: disables kernal irq handler! sys.wait() won't work anymore
|
||||
cx16.set_rasterirq(uword handler_address, uword rasterline) ; note: disables Kernal irq handler! sys.wait() won't work anymore
|
||||
cx16.restore_irq() ; set everything back to the systems default irq handler
|
||||
|
||||
|
||||
@ -163,5 +163,5 @@ The Commander X16 provides two additional routines that should be used *in your
|
||||
|
||||
cx16.push_vera_context()
|
||||
; ... do your work that uses vera here...
|
||||
cx15.pop_vera_context()
|
||||
cx16.pop_vera_context()
|
||||
|
||||
|
@ -7,7 +7,7 @@ All variables are static in memory
|
||||
|
||||
All variables are allocated statically, there is no concept of dynamic heap or stack frames.
|
||||
Essentially all variables are global (but scoped) and can be accessed and modified anywhere,
|
||||
but care should be taken ofcourse to avoid unexpected side effects.
|
||||
but care should be taken of course to avoid unexpected side effects.
|
||||
|
||||
Especially when you're dealing with interrupts or re-entrant routines: don't modify variables
|
||||
that you not own or else you will break stuff.
|
||||
@ -24,7 +24,7 @@ directly into the target variable, register, or memory location.
|
||||
The software stack is implemented as follows:
|
||||
|
||||
- 2 pages of memory are allocated for this, exact locations vary per machine target.
|
||||
For the C-64 they are set at $ce00 and $cf00 (so $ce00-$cfff is reserved).
|
||||
For the C64 they are set at $ce00 and $cf00 (so $ce00-$cfff is reserved).
|
||||
For the Commander X16 they are set at $0400 and $0500 (so $0400-$05ff are reserved).
|
||||
This default location can be overridden using the `-esa` command line option.
|
||||
- these are the high and low bytes of the values on the stack (it's a 'split 16 bit word stack')
|
||||
@ -42,7 +42,7 @@ Calling a subroutine requires three steps:
|
||||
|
||||
#. preparing the arguments (if any) and passing them to the routine
|
||||
#. calling the routine
|
||||
#. preparig the return value (if any) and returning that from the call.
|
||||
#. preparing the return value (if any) and returning that from the call.
|
||||
|
||||
|
||||
Calling the routine is just a simple JSR instruction, but the other two work like this:
|
||||
@ -51,7 +51,7 @@ Calling the routine is just a simple JSR instruction, but the other two work lik
|
||||
``asmsub`` routines
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
These are usually declarations of kernal (ROM) routines or low-level assembly only routines,
|
||||
These are usually declarations of Kernal (ROM) routines or low-level assembly only routines,
|
||||
that have their arguments solely passed into specific registers.
|
||||
Sometimes even via a processor status flag such as the Carry flag.
|
||||
Return values also via designated registers.
|
||||
@ -73,7 +73,7 @@ regular subroutines
|
||||
- the return value is passed back to the caller via cpu register(s):
|
||||
Byte values will be put in ``A`` .
|
||||
Word values will be put in ``A`` + ``Y`` register pair.
|
||||
Float values will be put in the ``FAC1`` float 'register' (Basic allocated this somewhere in ram).
|
||||
Float values will be put in the ``FAC1`` float 'register' (BASIC allocated this somewhere in ram).
|
||||
|
||||
|
||||
Calls to builtin functions are treated in a special way:
|
||||
@ -83,7 +83,7 @@ Some builtin functions have a fully custom implementation.
|
||||
|
||||
|
||||
The compiler will warn about routines that are called and that return a value, if you're not
|
||||
doing something with that returnvalue. This can be on purpuse if you're simply not interested in it.
|
||||
doing something with that returnvalue. This can be on purpose if you're simply not interested in it.
|
||||
Use the ``void`` keyword in front of the subroutine call to get rid of the warning in that case.
|
||||
|
||||
|
||||
@ -92,7 +92,7 @@ The 6502 CPU's X-register: off-limits
|
||||
|
||||
Prog8 uses the cpu's X-register as a pointer in its internal expression evaluation stack.
|
||||
When only writing code in Prog8, this is taken care of behind the scenes for you by the compiler.
|
||||
However when you are including or linking with assembly routines or kernal/ROM calls that *do*
|
||||
However when you are including or linking with assembly routines or Kernal/ROM calls that *do*
|
||||
use the X register (either clobbering it internally, or using it as a parameter, or return value register),
|
||||
those calls will destroy Prog8's stack pointer and this will result in invalid calculations.
|
||||
|
||||
@ -138,7 +138,7 @@ Some notes and references into the compiler's source code modules:
|
||||
Most notably, node type information is now baked in. (``codeCore`` module)
|
||||
#. An *Intermediate Representation* has been defined that is generated from the intermediate AST. This IR
|
||||
is more or less a machine code language for a virtual machine - and indeed this is what the built-in
|
||||
prog8 VM will execute if you use the 'virtual' compilaton target and use ``-emu`` to launch the VM.
|
||||
prog8 VM will execute if you use the 'virtual' compilation target and use ``-emu`` to launch the VM.
|
||||
(``intermediate`` and ``codeGenIntermediate`` modules, and ``virtualmachine`` module for the VM related stuff)
|
||||
#. Currently the 6502 ASM code generator still works directly on the *Compiler AST*. A future version
|
||||
should replace this by working on the IR code, and should be much smaller and simpler.
|
||||
|
@ -3,6 +3,9 @@ TODO
|
||||
|
||||
For next release
|
||||
^^^^^^^^^^^^^^^^
|
||||
- ir: register allocation per data type a specific allocation, so we are certain when a reg is used it's just for one specific datatype
|
||||
- ir: write addresses as hex into p8ir file
|
||||
|
||||
...
|
||||
|
||||
|
||||
@ -17,11 +20,12 @@ Future Things and Ideas
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Compiler:
|
||||
|
||||
- vm/ir: put variables and arrays in BSS section (unless -noreinit is specified)
|
||||
- vm: Jumps go to a code block rather than a specific address(label) -> also helps future dead code elimination?
|
||||
- vm: the above means that every label introduces a new code block. This eliminates the use of actual labels altogether.
|
||||
- vm: add more optimizations in IRPeepholeOptimizer
|
||||
- vm: how to remove all unused subroutines? (the 6502 assembly codegen relies on 64tass solve this for us)
|
||||
- create BSS section in output program and put StStaticVariables in there with bss=true. Don't forget to add init code to zero out everything that was put in bss. If array in bss->only zero ONCE! So requires self-modifying code
|
||||
- ir: mechanism to determine for chunks which registers are getting input values from "outside"
|
||||
- ir: mechanism to determine for chunks which registers are passing values out? (i.e. are used again in another chunk)
|
||||
- ir: peephole opt: renumber registers in chunks to start with 1 again every time (but keep entry values in mind!)
|
||||
- ir peephole opt: reuse registers in chunks (but keep result registers in mind that pass values out!)
|
||||
- ir: add more optimizations in IRPeepholeOptimizer
|
||||
- see if we can let for loops skip the loop if end<start, like other programming languages. Without adding a lot of code size/duplicating the loop condition.
|
||||
this is documented behavior to now loop around but it's too easy to forget about!
|
||||
Lot of work because of so many special cases in ForLoopsAsmgen.....
|
||||
|
@ -1,5 +1,6 @@
|
||||
%import syslib
|
||||
%import textio
|
||||
%import math
|
||||
%import test_stack
|
||||
%zeropage basicsafe
|
||||
|
||||
@ -41,7 +42,7 @@ main {
|
||||
active_height--
|
||||
upwards = false
|
||||
} else {
|
||||
target_height = 8 + rnd() % 16
|
||||
target_height = 8 + math.rnd() % 16
|
||||
if upwards
|
||||
mountain = 233
|
||||
else
|
||||
@ -56,7 +57,7 @@ main {
|
||||
txt.scroll_left(true)
|
||||
|
||||
; float the balloon
|
||||
if rnd() & %10000
|
||||
if math.rnd() & %10000
|
||||
c64.SPXY[1] ++
|
||||
else
|
||||
c64.SPXY[1] --
|
||||
@ -70,10 +71,10 @@ main {
|
||||
txt.setcc(39, yy, 160, 8) ; draw mountain
|
||||
}
|
||||
|
||||
yy = rnd()
|
||||
yy = math.rnd()
|
||||
if yy > 100 {
|
||||
; draw a star
|
||||
txt.setcc(39, yy % (active_height-1), '.', rnd())
|
||||
txt.setcc(39, yy % (active_height-1), '.', math.rnd())
|
||||
}
|
||||
|
||||
if yy > 200 {
|
||||
@ -84,7 +85,7 @@ main {
|
||||
tree = 88
|
||||
else if yy & %00100000 != 0
|
||||
tree = 65
|
||||
if rnd() > 130
|
||||
if math.rnd() > 130
|
||||
treecolor = 13
|
||||
txt.setcc(39, active_height, tree, treecolor)
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
%import textio
|
||||
%import math
|
||||
%zeropage basicsafe
|
||||
|
||||
; Note: this program is compatible with C64 and CX16.
|
||||
@ -22,11 +23,11 @@ main {
|
||||
; Setup Starting Ball Positions
|
||||
ubyte lp
|
||||
for lp in 0 to ballCount-1 {
|
||||
BX[lp] = rnd() % txt.DEFAULT_WIDTH
|
||||
BY[lp] = rnd() % txt.DEFAULT_HEIGHT
|
||||
BC[lp] = rnd() & 15
|
||||
DX[lp] = rnd() & 1
|
||||
DY[lp] = rnd() & 1
|
||||
BX[lp] = math.rnd() % txt.DEFAULT_WIDTH
|
||||
BY[lp] = math.rnd() % txt.DEFAULT_HEIGHT
|
||||
BC[lp] = math.rnd() & 15
|
||||
DX[lp] = math.rnd() & 1
|
||||
DY[lp] = math.rnd() & 1
|
||||
}
|
||||
|
||||
; display balls
|
||||
|
@ -1,4 +1,5 @@
|
||||
%import graphics
|
||||
%import math
|
||||
|
||||
; note: this program is tuned for the CX16, but with some minor modifications can run on other systems too.
|
||||
|
||||
@ -15,7 +16,7 @@ main {
|
||||
graphics.enable_bitmap_mode()
|
||||
|
||||
repeat {
|
||||
background_color = rnd()
|
||||
background_color = math.rnd()
|
||||
graphics.clear_screen(0, background_color)
|
||||
num_circles = 0
|
||||
draw_circles()
|
||||
@ -28,14 +29,14 @@ main {
|
||||
ubyte @zp radius
|
||||
|
||||
while num_circles<MAX_NUM_CIRCLES {
|
||||
x = rndw() % graphics.WIDTH
|
||||
y = rndw() % graphics.HEIGHT
|
||||
x = math.rndw() % graphics.WIDTH
|
||||
y = math.rndw() % graphics.HEIGHT
|
||||
radius = GROWTH_RATE * 2 ; use a bit of a buffer between circles.
|
||||
if not_colliding() {
|
||||
radius -= GROWTH_RATE
|
||||
ubyte color = rnd()
|
||||
ubyte color = math.rnd()
|
||||
while color==background_color
|
||||
color = rnd()
|
||||
color = math.rnd()
|
||||
graphics.colors(color, 0)
|
||||
while not_edge() and not_colliding() {
|
||||
graphics.disc(x, y as ubyte, radius)
|
||||
|
@ -1,4 +1,5 @@
|
||||
%import textio
|
||||
%import math
|
||||
|
||||
; Amiga 'copper' bars color cycling effect
|
||||
|
||||
@ -126,7 +127,7 @@ colors {
|
||||
|
||||
sub random_rgb12() {
|
||||
do {
|
||||
uword rr = rndw()
|
||||
uword rr = math.rndw()
|
||||
target_red = msb(rr) & 15
|
||||
target_green = lsb(rr)
|
||||
target_blue = target_green & 15
|
||||
|
@ -1,6 +1,7 @@
|
||||
%import gfx2
|
||||
%import floats
|
||||
%import textio
|
||||
%import math
|
||||
%zeropage dontuse
|
||||
|
||||
main {
|
||||
@ -76,7 +77,7 @@ main {
|
||||
sys.wait(2*60)
|
||||
|
||||
repeat 255
|
||||
gfx2.line(rndw() % 640, rndw() % 480, rndw() % 640, rndw() % 480, 1)
|
||||
gfx2.line(math.rndw() % 640, math.rndw() % 480, math.rndw() % 640, math.rndw() % 480, 1)
|
||||
|
||||
sys.wait(1*60)
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
%import palette
|
||||
%import math
|
||||
%import gfx2
|
||||
%option no_sysinit
|
||||
|
||||
; Vertical rasterbars a.k.a. "Kefren bars"
|
||||
; also see: rasterbars.p8
|
||||
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
@ -23,9 +23,8 @@ main {
|
||||
|
||||
; Not yet implemented in ROM: cx16.FB_set_palette(&colors, 0, len(colors)*3)
|
||||
palette.set_rgb(&colors, len(colors))
|
||||
void cx16.screen_mode(128, false) ; low-res bitmap 256 colors
|
||||
cx16.FB_init()
|
||||
cx16.VERA_DC_VSCALE = 0 ; display trick spoiler.......: stretch display all the way to the bottom
|
||||
gfx2.screen_mode(4) ; lores 256 colors
|
||||
cx16.VERA_DC_VSCALE = 0 ; display trick spoiler.......: stretch 1 line of display all the way to the bottom
|
||||
cx16.set_rasterirq(&irq.irqhandler, 0)
|
||||
|
||||
repeat {
|
||||
@ -36,32 +35,30 @@ main {
|
||||
|
||||
|
||||
irq {
|
||||
const ubyte BAR_Y_OFFSET = 5
|
||||
uword next_irq_line = 0
|
||||
ubyte anim1 = 0
|
||||
ubyte av1 = 0
|
||||
ubyte anim2 = 0
|
||||
ubyte av2 = 0
|
||||
|
||||
ubyte[32] pixels = 0 to 31
|
||||
|
||||
const ubyte y_offset = 6
|
||||
sub irqhandler() {
|
||||
next_irq_line += y_offset
|
||||
anim1 += 4
|
||||
anim2 += 6
|
||||
if next_irq_line > 480-y_offset {
|
||||
next_irq_line += BAR_Y_OFFSET
|
||||
anim1 += 7
|
||||
anim2 += 4
|
||||
if next_irq_line > 480-BAR_Y_OFFSET {
|
||||
av1++
|
||||
av2 += 2
|
||||
anim1 = av1
|
||||
anim2 = av2
|
||||
next_irq_line = 0
|
||||
; erase the bars
|
||||
cx16.FB_cursor_position(0, 0)
|
||||
cx16.FB_fill_pixels(320, 1, 0)
|
||||
gfx2.horizontal_line(0, 0, 320, 3)
|
||||
} else {
|
||||
; add new bar
|
||||
cx16.FB_cursor_position(math.sin8u(anim1)/2 + math.cos8u(anim2)/2 + $0010, 0)
|
||||
cx16.FB_set_pixels(pixels, len(pixels))
|
||||
; add new bar on top
|
||||
gfx2.position(math.sin8u(anim1)/2 + math.cos8u(anim2)/2 + $0010, 0)
|
||||
gfx2.next_pixels(pixels, len(pixels))
|
||||
}
|
||||
|
||||
cx16.set_rasterline(next_irq_line)
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
%import syslib
|
||||
%import textio
|
||||
%import math
|
||||
%import test_stack
|
||||
%import psg
|
||||
|
||||
@ -346,7 +347,7 @@ waitkey:
|
||||
xpos = startXpos
|
||||
ypos = startYpos
|
||||
speedlevel = 1
|
||||
nextBlock = rnd() % 7
|
||||
nextBlock = math.rnd() % 7
|
||||
holding = 255
|
||||
holdingAllowed = true
|
||||
ticks_since_previous_action = 0
|
||||
@ -362,7 +363,7 @@ waitkey:
|
||||
|
||||
sub spawnNextBlock() {
|
||||
swapBlock(nextBlock)
|
||||
nextBlock = (rnd() + lsb(c64.RDTIM16())) % 7
|
||||
nextBlock = (math.rnd() + lsb(c64.RDTIM16())) % 7
|
||||
drawNextBlock()
|
||||
holdingAllowed = true
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
%import textio
|
||||
%import math
|
||||
%import cx16logo
|
||||
|
||||
; Note: this program is compatible with C64 and CX16.
|
||||
@ -6,8 +7,8 @@
|
||||
main {
|
||||
sub start() {
|
||||
repeat {
|
||||
ubyte col = rnd() % (txt.DEFAULT_WIDTH-13) + 3
|
||||
ubyte row = rnd() % (txt.DEFAULT_HEIGHT-7)
|
||||
ubyte col = math.rnd() % (txt.DEFAULT_WIDTH-13) + 3
|
||||
ubyte row = math.rnd() % (txt.DEFAULT_HEIGHT-7)
|
||||
cx16logo.logo_at(col, row)
|
||||
txt.plot(col-3, row+7 )
|
||||
txt.print("commander x16")
|
||||
|
@ -1,4 +1,5 @@
|
||||
%import textio
|
||||
%import math
|
||||
|
||||
; Even though prog8 only has support for extremely limited recursion,
|
||||
; you can write recursive algorithms with a bit of extra work by building your own explicit stack structure.
|
||||
@ -135,8 +136,8 @@ carve_restart_after_repath:
|
||||
; for too long on bad rng... just accept a few unused cells in that case.
|
||||
repeat 255 {
|
||||
do {
|
||||
cx = rnd() % numCellsHoriz
|
||||
cy = rnd() % numCellsVert
|
||||
cx = math.rnd() % numCellsHoriz
|
||||
cy = math.rnd() % numCellsVert
|
||||
} until not @(celladdr(cx, cy)) & STONE
|
||||
if available_uncarved()
|
||||
return true
|
||||
@ -164,7 +165,7 @@ carve_restart_after_repath:
|
||||
|
||||
ubyte[4] bitflags = [LEFT,RIGHT,UP,DOWN]
|
||||
repeat {
|
||||
ubyte choice = candidates & bitflags[rnd() & 3]
|
||||
ubyte choice = candidates & bitflags[math.rnd() & 3]
|
||||
if choice
|
||||
return choice
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
%import textio
|
||||
%import conv
|
||||
%import math
|
||||
%import test_stack
|
||||
%zeropage basicsafe
|
||||
|
||||
@ -12,7 +13,7 @@ main {
|
||||
sub start() {
|
||||
str name = "????????????????????????????????????????"
|
||||
str input = "??????????"
|
||||
ubyte secretnumber = rnd() % 99 + 1 ; random number 1..100
|
||||
ubyte secretnumber = math.rnd() % 99 + 1 ; random number 1..100
|
||||
ubyte attempts_left
|
||||
|
||||
txt.lowercase()
|
||||
|
@ -106,7 +106,7 @@ main {
|
||||
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 rnd() > s {
|
||||
if math.rnd() > s {
|
||||
b |= bittab[ii]
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
%import textio
|
||||
%import syslib
|
||||
%import math
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
@ -41,7 +42,7 @@ main {
|
||||
for i in 0 to 7 {
|
||||
c64.SPRPTR[i] = $0a00 / 64
|
||||
c64.SPXY[i*2] = 50+25*i
|
||||
c64.SPXY[i*2+1] = rnd()
|
||||
c64.SPXY[i*2+1] = math.rnd()
|
||||
}
|
||||
|
||||
c64.SPENA = 255 ; enable all sprites
|
||||
@ -59,7 +60,7 @@ irq {
|
||||
ubyte @zp i
|
||||
for i in 0 to 14 step 2 {
|
||||
c64.SPXY[i+1]--
|
||||
ubyte @zp r = rnd()
|
||||
ubyte @zp r = math.rnd()
|
||||
if r>200
|
||||
c64.SPXY[i]++
|
||||
else if r<40
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
%import syslib
|
||||
%import textio
|
||||
%import math
|
||||
%import test_stack
|
||||
|
||||
|
||||
@ -261,7 +262,7 @@ waitkey:
|
||||
xpos = startXpos
|
||||
ypos = startYpos
|
||||
speedlevel = 1
|
||||
nextBlock = rnd() % 7
|
||||
nextBlock = math.rnd() % 7
|
||||
holding = 255
|
||||
holdingAllowed = true
|
||||
}
|
||||
@ -276,7 +277,7 @@ waitkey:
|
||||
|
||||
sub spawnNextBlock() {
|
||||
swapBlock(nextBlock)
|
||||
nextBlock = (rnd() + c64.RASTER) % 7
|
||||
nextBlock = (math.rnd() + c64.RASTER) % 7
|
||||
drawNextBlock()
|
||||
holdingAllowed = true
|
||||
}
|
||||
|
@ -1,5 +1,27 @@
|
||||
%import textio
|
||||
%import floats
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
%asmbinary "bsieve.prg", 10, 200
|
||||
float f1
|
||||
|
||||
floats.rndseedf(-1.2345)
|
||||
txt.spc()
|
||||
floats.print_f(floats.rndf())
|
||||
txt.spc()
|
||||
floats.print_f(floats.rndf())
|
||||
txt.spc()
|
||||
floats.print_f(floats.rndf())
|
||||
txt.nl()
|
||||
|
||||
floats.rndseedf(1.2345)
|
||||
txt.spc()
|
||||
floats.print_f(floats.rndf())
|
||||
txt.spc()
|
||||
floats.print_f(floats.rndf())
|
||||
txt.spc()
|
||||
floats.print_f(floats.rndf())
|
||||
txt.nl()
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
; NOTE: meant to test to virtual machine output target (use -target virtual)
|
||||
|
||||
%import math
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
@ -10,10 +12,10 @@ main {
|
||||
|
||||
ubyte pi
|
||||
for pi in 0 to 127 {
|
||||
particleX[pi] = rndw() % 319 as word
|
||||
particleY[pi] = rndw() % 240 as word
|
||||
particleDX[pi] = (rnd() & 1)*2 as byte - 1
|
||||
particleDY[pi] = (rnd() & 1)*2 as byte - 1
|
||||
particleX[pi] = math.rndw() % 319 as word
|
||||
particleY[pi] = math.rndw() % 240 as word
|
||||
particleDX[pi] = (math.rnd() & 1)*2 as byte - 1
|
||||
particleDY[pi] = (math.rnd() & 1)*2 as byte - 1
|
||||
}
|
||||
|
||||
sys.gfx_enable(0) ; enable lo res screen
|
||||
|
@ -4,4 +4,4 @@ org.gradle.parallel=true
|
||||
org.gradle.daemon=true
|
||||
kotlin.code.style=official
|
||||
javaVersion=11
|
||||
kotlinVersion=1.7.10
|
||||
kotlinVersion=1.7.20
|
@ -4,18 +4,24 @@ package prog8.intermediate
|
||||
// these use the SYSCALL instruction instead.
|
||||
// Note that in the VM these are translated into whatever the corresponding Syscall number in the VM is.
|
||||
|
||||
enum class IMSyscall {
|
||||
SORT_UBYTE,
|
||||
SORT_BYTE,
|
||||
SORT_UWORD,
|
||||
SORT_WORD,
|
||||
ANY_BYTE,
|
||||
ANY_WORD,
|
||||
ANY_FLOAT,
|
||||
ALL_BYTE,
|
||||
ALL_WORD,
|
||||
ALL_FLOAT,
|
||||
REVERSE_BYTES,
|
||||
REVERSE_WORDS,
|
||||
REVERSE_FLOATS
|
||||
}
|
||||
enum class IMSyscall(val number: Int) {
|
||||
SORT_UBYTE(10000),
|
||||
SORT_BYTE(10001),
|
||||
SORT_UWORD(10002),
|
||||
SORT_WORD(10003),
|
||||
ANY_BYTE(10004),
|
||||
ANY_WORD(10005),
|
||||
ANY_FLOAT(10006),
|
||||
ALL_BYTE(10007),
|
||||
ALL_WORD(10008),
|
||||
ALL_FLOAT(10009),
|
||||
REVERSE_BYTES(10010),
|
||||
REVERSE_WORDS(10011),
|
||||
REVERSE_FLOATS(10012),
|
||||
COMPARE_STRINGS(10013),
|
||||
STRING_CONTAINS(10014),
|
||||
BYTEARRAY_CONTAINS(10015),
|
||||
WORDARRAY_CONTAINS(10016)
|
||||
}
|
||||
|
||||
const val SyscallRegisterBase = 65500
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user