Compare commits

..
Author SHA1 Message Date
Irmen de Jong 6fad90af6e long for loop warning 2026-05-31 23:42:58 +02:00
Irmen de Jong 3076318d7e implement missing for loops on long rages for 6502 2026-05-31 23:38:35 +02:00
Irmen de Jong 9c99110738 doc 2026-05-31 23:07:16 +02:00
Irmen de Jong 1457d36997 beta3 2026-05-31 17:47:37 +02:00
Irmen de Jong 43fb9296dd clarify serial module docs 2026-05-31 17:46:33 +02:00
Irmen de Jong 8bf744cc4a fix some bitwise expression optimization bugs 2026-05-31 07:35:32 +02:00
Irmen de Jong f1f5f070cf fix invalid asm for some in-place long operations on zp vars 2026-05-31 05:14:33 +02:00
Irmen de Jong 191a240d0d adjust extsub jump table offsets to more closely match reality 2026-05-30 23:42:52 +02:00
Irmen de Jong cafb2e355b implement long loop counter support 2026-05-30 23:36:08 +02:00
Irmen de Jong c99c5751f8 tweak serial 2026-05-30 21:11:19 +02:00
Irmen de Jong 435b7e56fa IR VM refactor to reduce class size 2026-05-29 23:14:44 +02:00
Irmen de Jong 14f13fac53 IR tweaks 2026-05-29 22:17:56 +02:00
Irmen de Jong f8b5037924 spelling corrections 2026-05-29 20:58:06 +02:00
Irmen de Jong d2870cb02e implement hex mode file transfer in serial module
as an attempt to have a working file transfer because the binary mode is still failing right now
2026-05-29 20:04:40 +02:00
Irmen de Jong 8e43fc426c IR: handle extsub with banked addresses properly 2026-05-28 03:25:04 +02:00
Irmen de Jong ba1b6e127a IR: properly consume registers for void targets 2026-05-28 00:57:00 +02:00
Irmen de Jong 4f36a6958c fix a few if-expression typecast issues 2026-05-28 00:55:15 +02:00
Irmen de Jong df8f08b4ab more serial tweaks to attempt to fix binary downloads 2026-05-27 23:42:55 +02:00
Irmen de Jong fd5746d3fc tweak the serial module a bit for more robustness 2026-05-27 02:22:35 +02:00
Irmen de Jong 6ab74fd51d don't erroneously allow taking the address of const values 2026-05-27 00:58:01 +02:00
Irmen de Jong 245082ca1d plain console output in github action builds 2026-05-24 12:40:43 +02:00
Irmen de Jong 845e58ea10 64tass install step 2026-05-24 12:33:47 +02:00
Irmen de Jong 2a5dbc1ef7 update project urls that have been moved from github to codeberg 2026-05-24 12:29:09 +02:00
84 changed files with 2234 additions and 1376 deletions

No files matched your search

+1 -1
View File
@@ -244,7 +244,7 @@ Used to call routines at fixed memory addresses (like ROM KERNAL routines or thi
- Indentation: 4 spaces for .p8 and .asm files (no tabs)
- Character encoding: 6502 targets use PETSCII by default (call `txt.lowercase()` at start for lowercase). Virtual target uses ISO (`%encoding iso` + `txt.iso()`)
- Array size inferred from initializer: `str[] types = ["a", "b", "c"]`
- Enums: `Enum::Value` syntax (double colon), declared inside a block. Use enums for related values, `const` for standalone
- Enums: `Enum::Value` syntax (double colon), declared inside a block. They are syntactic sugar for a list of `const` declarations, not a type. Use enums for related values, `const` for standalone
- Avoid `globals.XXXX` — move constants closer to where they're used
- Member access through pointers: use `.` for both direct and pointer access. The compiler infers the type. For complex assignment targets, `^^` may be needed: `ptr^^.field = value`
- Qualified names: must use full path from top level (e.g., `cx16.r0`, not relative)
+8 -7
View File
@@ -12,13 +12,11 @@ jobs:
- name: Checkout code
uses: actions/checkout@v5
- name: build and install recent 64tass
- name: install 64tass
run: |
sudo apt-get install -y make build-essential
git clone --depth=1 https://github.com/irmen/64tass
cd 64tass
make -j4
sudo make install
sudo apt update
sudo apt install 64tass
64tass --version
- name: Set up JDK 17
uses: actions/setup-java@v5
@@ -26,9 +24,12 @@ jobs:
java-version: 17
distribution: temurin
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
- name: Build and test with Gradle
run: |
./gradlew build shadowJar --no-daemon
./gradlew build shadowJar --no-daemon --console=plain
sha256sum -b compiler/build/libs/*-all.jar > compiler/build/libs/hash.txt
- name: Create compiler shadowJar artifact
Generated
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="" vcs="Git" />
</component>
</project>
+1
View File
@@ -63,6 +63,7 @@ What does Prog8 provide?
- programs run very fast because it's compiled to native machine code
- compiled code is very small; much smaller than equivalent C code compiled with CC65, and usually runs faster as well
- modularity, symbol scoping, subroutines. No need for forward declarations.
- Can make vars and routines private, as a simple way to restrict access and hide implementation details
- various data types other than just bytes (16-bit words, long integers, floats, strings)
- Structs and typed pointers
- 2D arrays (`matrix[row][col]` syntax)
@@ -8,7 +8,7 @@ import java.io.CharConversionException
object PetsciiEncoding {
// decoding: from Petscii/Screencodes (0-255) to Unicode
// character tables used from https://github.com/irmen/cbmcodecs2
// character tables used from https://codeberg.org/irmen/cbmcodecs2.git
private val decodingPetsciiLowercase = charArrayOf(
'\u0000', // 0x00 -> \u0000
@@ -1024,7 +1024,7 @@ class AsmGen6502Internal (
assignExpressionToRegister(value, target.register!!)
} else {
assignExpressionToRegister(value, RegisterOrPair.AX)
pointerGen.assignWordReg(PtrTarget(target), RegisterOrPair.AX)
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AX)
}
}
target.datatype.isWord || target.datatype.isPassByRef -> {
@@ -1175,6 +1175,7 @@ class AsmGen6502Internal (
when {
iterations == 0 -> {}
iterations == 1 -> translate(stmt.statements)
iterations > 65536 -> repeatLongCount(iterations.toLong(), stmt)
iterations !in 0..65536 -> throw AssemblyError("invalid number of iterations")
iterations <= 256 -> repeatByteCount(iterations, stmt)
else -> repeatWordCount(iterations, stmt)
@@ -1186,26 +1187,36 @@ class AsmGen6502Internal (
val name = asmVariableName(stmt.count as PtIdentifier)
when {
vardecl.type.isByte -> {
assignVariableToRegister(name, RegisterOrPair.Y, stmt.definingISub(), stmt.count.position)
assignVariableToRegister(name, RegisterOrPair.Y, stmt.definingISub(), stmt.count.position, vardecl.type.isSigned)
repeatCountInY(stmt, endLabel)
}
vardecl.type.isWord -> {
assignVariableToRegister(name, RegisterOrPair.AY, stmt.definingISub(), stmt.count.position)
assignVariableToRegister(name, RegisterOrPair.AY, stmt.definingISub(), stmt.count.position, vardecl.type.isSigned)
repeatWordCountInAY(endLabel, stmt)
}
vardecl.type.isLong -> {
val reg = CombinedLongRegisters.last()
assignVariableToRegister(name, reg, stmt.definingISub(), (stmt.count as PtIdentifier).position, vardecl.type.isSigned)
repeatLongCountInReg(reg, endLabel, stmt)
}
else -> throw AssemblyError("invalid loop variable datatype ${vardecl.type}")
}
}
else -> {
when {
stmt.count.type.isByte -> {
assignExpressionToRegister(stmt.count, RegisterOrPair.Y)
assignExpressionToRegister(stmt.count, RegisterOrPair.Y, stmt.count.type.isSigned)
repeatCountInY(stmt, endLabel)
}
stmt.count.type.isWord -> {
assignExpressionToRegister(stmt.count, RegisterOrPair.AY)
assignExpressionToRegister(stmt.count, RegisterOrPair.AY, stmt.count.type.isSigned)
repeatWordCountInAY(endLabel, stmt)
}
stmt.count.type.isLong -> {
val reg = CombinedLongRegisters.last()
assignExpressionToRegister(stmt.count, reg, stmt.count.type.isSigned)
repeatLongCountInReg(reg, endLabel, stmt)
}
else -> throw AssemblyError("invalid loop expression datatype ${stmt.count.type}")
}
}
@@ -1213,6 +1224,75 @@ class AsmGen6502Internal (
loopEndLabels.removeLast()
}
private fun repeatLongCount(iterations: Long, stmt: PtRepeatLoop) {
require(iterations > 65536) { "invalid repeat count ${stmt.position}" }
val repeatLabel = makeLabel("repeat")
val counterVar = createTempVarReused(BaseDataType.LONG, true, stmt)
val bytes = mutableListOf<Int>()
var currentN = iterations
while (currentN > 0) {
val b = (currentN % 256).toInt()
bytes.add(b)
currentN = if (b == 0) currentN / 256 else currentN / 256 + 1
if (currentN == 1L) break
}
for (i in bytes.indices) {
val v = bytes[i]
val suffix = if (i > 0) "+$i" else ""
if (v == 0 && isTargetCpu(CpuType.CPU65C02)) {
out(" stz $counterVar$suffix")
} else {
out(" lda #$v")
out(" sta $counterVar$suffix")
}
}
out(repeatLabel)
translate(stmt.statements)
for (i in bytes.indices) {
val suffix = if (i > 0) "+$i" else ""
out(" dec $counterVar$suffix")
out(" bne $repeatLabel")
}
}
private fun repeatLongCountInReg(reg: RegisterOrPair, endLabel: String, stmt: PtRepeatLoop) {
require(reg in CombinedLongRegisters)
val repeatLabel = makeLabel("repeat")
val regName = "cx16." + reg.startregname()
val d1 = makeLabel("skip_dec1")
val d2 = makeLabel("skip_dec2")
val d3 = makeLabel("skip_dec3")
out("""
lda $regName
ora $regName+1
ora $regName+2
ora $regName+3
beq $endLabel
$repeatLabel""")
translate(stmt.statements)
out("""
lda $regName
bne $d3
lda $regName+1
bne $d2
lda $regName+2
bne $d1
dec $regName+3
$d1 dec $regName+2
$d2 dec $regName+1
$d3 dec $regName
lda $regName
ora $regName+1
ora $regName+2
ora $regName+3
bne $repeatLabel""")
out(endLabel)
}
private fun repeatWordCount(iterations: Int, stmt: PtRepeatLoop) {
require(iterations in 257..65536) { "invalid repeat count ${stmt.position}" }
@@ -42,12 +42,129 @@ internal class ForLoopsAsmGen(
when {
iterableDt.isByteArray -> forOverNonconstByteRange(stmt, iterableDt, range)
iterableDt.isWordArray && !iterableDt.isSplitWordArray -> forOverNonconstWordRange(stmt, iterableDt, range)
else -> throw AssemblyError("range expression can only be byte or word")
iterableDt.isLongArray -> forOverNonconstLongRange(stmt, iterableDt, range)
else -> throw AssemblyError("range expression can only be byte, word or long")
}
asmgen.loopEndLabels.removeLast()
}
private fun forOverNonconstLongRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
val stepsize = range.step.asConstInteger()!!
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.add(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.assignExpressionToVariable(range.from, varname, iterableDt.elementType())
if (stepsize == 1 || stepsize == -1)
forOverLongsRangeStepOne(range, varname, iterableDt, loopLabel, endLabel, stmt)
else
forOverLongsRangeStepGreaterOne(range, varname, iterableDt, loopLabel, endLabel, stmt)
}
private fun forOverLongsRangeStepOne(range: PtRange, varname: String, iterableDt: DataType, loopLabel: String, endLabel: String, forloop: PtForLoop) {
val stepsize = range.step.asConstInteger()!!
// Use temporary variable to store the limit
val toValueVar = asmgen.createTempVarReused(iterableDt.elementType().base, false, range)
asmgen.assignExpressionToVariable(range.to, toValueVar, iterableDt.elementType())
if (stepsize == 1) {
// Increment toValueVar to make the loop inclusive (0 to N runs N+1 times)
asmgen.out("""
inc $toValueVar
bne +
inc $toValueVar+1
bne +
inc $toValueVar+2
bne +
inc $toValueVar+3
+
""")
} else {
// Decrement toValueVar to make the loop inclusive (N downto 0 runs N+1 times)
val label1 = asmgen.makeLabel("dec_1")
val label2 = asmgen.makeLabel("dec_2")
val label3 = asmgen.makeLabel("dec_3")
asmgen.out("""
lda $toValueVar
bne $label1
lda $toValueVar+1
bne $label2
lda $toValueVar+2
bne $label3
dec $toValueVar+3
$label3: dec $toValueVar+2
$label2: dec $toValueVar+1
$label1: dec $toValueVar
""")
}
asmgen.out(loopLabel)
asmgen.translate(forloop.statements)
if (stepsize == 1) {
asmgen.out("""
inc $varname
bne +
inc $varname+1
bne +
inc $varname+2
bne +
inc $varname+3
+ lda $varname
cmp $toValueVar
bne $loopLabel
lda $varname+1
cmp $toValueVar+1
bne $loopLabel
lda $varname+2
cmp $toValueVar+2
bne $loopLabel
lda $varname+3
cmp $toValueVar+3
bne $loopLabel
""")
} else {
asmgen.out("""
lda $varname
bne +++
lda $varname+1
bne ++
lda $varname+2
bne +
dec $varname+3
+ dec $varname+2
+ dec $varname+1
+ dec $varname
lda $varname
cmp $toValueVar
bne $loopLabel
lda $varname+1
cmp $toValueVar+1
bne $loopLabel
lda $varname+2
cmp $toValueVar+2
bne $loopLabel
lda $varname+3
cmp $toValueVar+3
bne $loopLabel
""")
}
asmgen.out(endLabel)
}
private fun forOverLongsRangeStepGreaterOne(range: PtRange, varname: String, iterableDt: DataType, loopLabel: String, endLabel: String, forloop: PtForLoop) {
// TODO: implement 32-bit loop
asmgen.romableError("for loops over long ranges with step != 1 not yet fully implemented", forloop.position)
asmgen.out(loopLabel)
asmgen.translate(forloop.statements)
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
}
private fun forOverNonconstByteRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
val stepsize = range.step.asConstInteger()!!
val loopLabel = asmgen.makeLabel("for_loop")
@@ -83,7 +83,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
asmgen.out(endLabel)
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, false, false)
}
expr.type.isWord || expr.type.isString -> {
expr.type.isWord || expr.type.isString || expr.type.isPointer -> {
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.AY)
asmgen.jmp(endLabel)
asmgen.out(trueLabel)
@@ -318,14 +318,13 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
SourceStorageKind.REGISTER -> inplacemodificationLongWithVariable(targetVarName, operator, regName(value))
SourceStorageKind.MEMORY -> TODO("inplace long modification with memread value ${target.position}")
SourceStorageKind.ARRAY -> TODO("inplace long modification with array value ${target.position}")
SourceStorageKind.EXPRESSION -> {
SourceStorageKind.EXPRESSION ->
if(value.expression is PtTypeCast) {
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
TODO("inplace long modification ${target.position}")
} else {
TODO("inplace long modification ${target.position}")
}
}
}
}
@@ -577,24 +576,40 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
internal fun inplacemodificationLongWithVariable(targetVar: String, operator: String, sourceVar: String) {
when(operator) {
"+" -> {
// need to unroll to correctly keep Carry flag for multi-byte arithmetic
asmgen.out("""
clc
ldx #252
- lda $targetVar+4,x
adc $sourceVar+4,x
sta $targetVar+4,x
inx
bne -""")
lda $targetVar+0
adc $sourceVar+0
sta $targetVar+0
lda $targetVar+1
adc $sourceVar+1
sta $targetVar+1
lda $targetVar+2
adc $sourceVar+2
sta $targetVar+2
lda $targetVar+3
adc $sourceVar+3
sta $targetVar+3
""")
}
"-" -> {
// need to unroll to correctly keep Carry flag for multi-byte arithmetic
asmgen.out("""
sec
ldx #252
- lda $targetVar+4,x
sbc $sourceVar+4,x
sta $targetVar+4,x
inx
bne -""")
lda $targetVar+0
sbc $sourceVar+0
sta $targetVar+0
lda $targetVar+1
sbc $sourceVar+1
sta $targetVar+1
lda $targetVar+2
sbc $sourceVar+2
sta $targetVar+2
lda $targetVar+3
sbc $sourceVar+3
sta $targetVar+3
""")
}
"<<" -> {
asmgen.out("""
@@ -33,10 +33,10 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
extsub.returns.zip(assignmentTargets).forEach { pair ->
val returns = pair.first
val target = pair.second as PtAssignTarget
// consume the register even for void targets to keep indices aligned
val isFpRegister = returns.register.registerOrPair in listOf(RegisterOrPair.FAC1, RegisterOrPair.FAC2)
val regNumber = if(isFpRegister) fpRegs.removeAt(0) else cpuRegs.removeAt(0)
if (!target.void) {
// Determine if this return uses an FP register or CPU register
val isFpRegister = returns.register.registerOrPair in listOf(RegisterOrPair.FAC1, RegisterOrPair.FAC2)
val regNumber = if(isFpRegister) fpRegs.removeAt(0) else cpuRegs.removeAt(0)
result += assignCpuRegister(returns, regNumber, target)
}
}
@@ -100,7 +100,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
is PtContainmentCheck -> translate(expr)
is PtPointerDeref -> translate(expr)
is PtConstant -> {
val name = expr.name
val slab = expr.memorySlab
// Check if the constant has a numeric value or is a memory slab reference
if(slab != null) {
@@ -1176,8 +1175,22 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
address = address.address.toAddress(),
fcallArgs = FunctionCallArgs(argRegisters, returnRegisters)
)
} else if(address.constbank!=null) {
IRInstruction(
Opcode.CALLFAR,
address = address.address.toAddress(),
immediate = address.constbank!!.toInt()
)
} else {
val tr = translateExpression(address.varbank!!)
require(tr.dt==IRDataType.BYTE)
result += tr.chunks
IRInstruction(
Opcode.CALLFARVB,
address = address.address.toAddress(),
reg1 = tr.resultReg
)
}
else TODO("extsub with banked address got called ${callTarget.name} ${fcall.position}")
}
addInstr(result, call, null)
val resultRegs = returnRegisters.filter{it.dt!=IRDataType.FLOAT}.map{it.registerNum.value}
@@ -347,6 +347,12 @@ internal class ConstantIdentifierReplacer(
override fun before(addressOf: AddressOf, parent: Node): Iterable<AstModification> {
val constValue = addressOf.constValue(program)
if(constValue!=null) {
// don't replace &constVar (without array index); leave it for AstChecker to reject as invalid address-of.
// But still allow &constArray[idx] (those are valid address-of operations).
val target = addressOf.identifier?.targetVarDecl()
if(target?.type==VarDeclType.CONST && addressOf.arrayIndex==null) {
return noModifications
}
return listOf(AstReplaceNode(addressOf, constValue, parent))
}
return noModifications
@@ -355,7 +361,7 @@ internal class ConstantIdentifierReplacer(
override fun after(identifier: IdentifierReference, parent: Node): Iterable<AstModification> {
// replace identifiers that refer to const value, with the value itself
// if it's a simple type and if it's not a left hand side variable
if (identifier.parent is AssignTarget || identifier.parent is Alias)
if (identifier.parent is AssignTarget || identifier.parent is Alias || identifier.parent is AddressOf)
return noModifications
var forloop = identifier.parent as? ForLoop
if (forloop == null)
@@ -463,12 +463,12 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
when {
leftVal?.number==0.0 -> expr.right
rightVal?.number==0.0 -> expr.left
rightIDt.isBytes && rightVal?.number==255.0 -> NumericLiteral(BaseDataType.UBYTE, 255.0, rightVal.position)
rightIDt.isWords && rightVal?.number==65535.0 -> NumericLiteral(BaseDataType.UWORD, 65535.0, rightVal.position)
rightIDt.isLong && rightVal?.number==-1.0 -> NumericLiteral(BaseDataType.LONG, -1.0, rightVal.position)
leftIDt.isBytes && leftVal?.number==255.0 -> NumericLiteral(BaseDataType.UBYTE, 255.0, leftVal.position)
leftIDt.isWords && leftVal?.number==65535.0 -> NumericLiteral(BaseDataType.UWORD, 65535.0, leftVal.position)
leftIDt.isLong && leftVal?.number==-1.0 -> NumericLiteral(BaseDataType.LONG, -1.0, leftVal.position)
leftIDt.isBytes && rightIDt.isBytes && rightVal?.number==255.0 -> NumericLiteral(BaseDataType.UBYTE, 255.0, rightVal.position)
leftIDt.isWords && rightIDt.isWords && rightVal?.number==65535.0 -> NumericLiteral(BaseDataType.UWORD, 65535.0, rightVal.position)
leftIDt.isLong && rightIDt.isLong && rightVal?.number==-1.0 -> NumericLiteral(BaseDataType.LONG, -1.0, rightVal.position)
leftIDt.isBytes && rightIDt.isBytes && leftVal?.number==255.0 -> NumericLiteral(BaseDataType.UBYTE, 255.0, leftVal.position)
leftIDt.isWords && rightIDt.isWords && leftVal?.number==65535.0 -> NumericLiteral(BaseDataType.UWORD, 65535.0, leftVal.position)
leftIDt.isLong && rightIDt.isLong && leftVal?.number==-1.0 -> NumericLiteral(BaseDataType.LONG, -1.0, leftVal.position)
else -> null
}
}
@@ -476,12 +476,12 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
when {
leftVal?.number==0.0 -> expr.right
rightVal?.number==0.0 -> expr.left
rightIDt.isBytes && rightVal?.number==255.0 -> PrefixExpression("~", expr.left, expr.left.position)
rightIDt.isWords && rightVal?.number==65535.0 -> PrefixExpression("~", expr.left, expr.left.position)
rightIDt.isLong && rightVal?.number==-1.0 -> PrefixExpression("~", expr.left, expr.left.position)
leftIDt.isBytes && leftVal?.number==255.0 -> PrefixExpression("~", expr.right, expr.right.position)
leftIDt.isWords && leftVal?.number==65535.0 -> PrefixExpression("~", expr.right, expr.right.position)
leftIDt.isLong && leftVal?.number==-1.0 -> PrefixExpression("~", expr.right, expr.right.position)
leftIDt.isBytes && rightIDt.isBytes && rightVal?.number==255.0 -> PrefixExpression("~", expr.left, expr.left.position)
leftIDt.isWords && rightIDt.isWords && rightVal?.number==65535.0 -> PrefixExpression("~", expr.left, expr.left.position)
leftIDt.isLong && rightIDt.isLong && rightVal?.number==-1.0 -> PrefixExpression("~", expr.left, expr.left.position)
leftIDt.isBytes && rightIDt.isBytes && leftVal?.number==255.0 -> PrefixExpression("~", expr.right, expr.right.position)
leftIDt.isWords && rightIDt.isWords && leftVal?.number==65535.0 -> PrefixExpression("~", expr.right, expr.right.position)
leftIDt.isLong && rightIDt.isLong && leftVal?.number==-1.0 -> PrefixExpression("~", expr.right, expr.right.position)
else -> null
}
}
@@ -489,12 +489,12 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
when {
leftVal?.number==0.0 -> constFalse
rightVal?.number==0.0 -> constFalse
rightIDt.isBytes && rightVal?.number==255.0 -> expr.left
rightIDt.isWords && rightVal?.number==65535.0 -> expr.left
rightIDt.isLong && rightVal?.number==-1.0 -> expr.left
leftIDt.isBytes && leftVal?.number==255.0 -> expr.right
leftIDt.isWords && leftVal?.number==65535.0 -> expr.right
leftIDt.isLong && leftVal?.number==-1.0 -> expr.right
leftIDt.isBytes && rightIDt.isBytes && rightVal?.number==255.0 -> expr.left
leftIDt.isWords && rightIDt.isWords && rightVal?.number==65535.0 -> expr.left
leftIDt.isLong && rightIDt.isLong && rightVal?.number==-1.0 -> expr.left
leftIDt.isBytes && rightIDt.isBytes && leftVal?.number==255.0 -> expr.right
leftIDt.isWords && rightIDt.isWords && leftVal?.number==65535.0 -> expr.right
leftIDt.isLong && rightIDt.isLong && leftVal?.number==-1.0 -> expr.right
else -> null
}
}
+1 -1
View File
@@ -14,7 +14,7 @@ txt {
const ubyte DEFAULT_WIDTH = 40
const ubyte DEFAULT_HEIGHT = 25
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly of course. Note: takes a PETSCII encoded character.
sub bell() {
+1 -1
View File
@@ -13,7 +13,7 @@ txt {
const ubyte DEFAULT_WIDTH = 40
const ubyte DEFAULT_HEIGHT = 25
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly of course. Note: takes a PETSCII encoded character.
sub bell() {
+1 -1
View File
@@ -872,7 +872,7 @@ exit:
}
sub get_loadaddress(str filename) -> uword {
; get the load adress from a PRG file (usually $0801 but it can be different)
; get the load address from a PRG file (usually $0801 but it can be different)
cbm.SETNAM(strings.length(filename), filename)
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, READ_IO_CHANNEL)
+1 -1
View File
@@ -185,7 +185,7 @@ gfx_hires {
sub vertical_line(uword xx, uword yy, uword lheight, ubyte color) {
; highres 4c
; use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment is possible
; use TWO vera address pointers simultaneously one for reading, one for writing, so auto-increment is possible
if lheight==0
return
position2(xx,yy,true)
+2 -2
View File
@@ -1,5 +1,5 @@
; optimized graphics routines for just the single screen mode: lores 320*240, 256c (8bpp)
; bitmap image needs to start at VRAM addres $00000.
; bitmap image needs to start at VRAM address $00000.
; This is compatible with the CX16's screen mode 128.
@@ -33,7 +33,7 @@ gfx_lores {
sub clear_screen(ubyte color) {
if verafx.available() {
; use verafx cache writes to quicly clear the screen
; use verafx cache writes to quickly clear the screen
const ubyte vbank = 0
const uword vaddr = 0
cx16.VERA_CTRL = 0
+192 -102
View File
@@ -19,6 +19,26 @@ serial {
const ubyte REG_DIVISOR_LATCH_LOW = 0 ;same as TXRX, but when bit-7 of line control high
const ubyte REG_DIVISOR_LATCH_HI = 1 ;same as IRQE, but when bit-7 of line control high
; Baud rate divisor values for TL16C2550 with 14.7456 MHz clock
; Formula: divisor = 14745600 / (16 * baudrate)
enum BAUD {
B921600 = $0001,
B460800 = $0002,
B230400 = $0004,
B115200 = $0008,
B57600 = $0010,
B38400 = $0018,
B28800 = $0020,
B19200 = $0030,
B14400 = $0040,
B9600 = $0060,
B4800 = $00C0,
B2400 = $0180,
B1200 = $0300,
B600 = $0600,
B300 = $0C00,
}
; Bitfield masks for TL16C2550's Line Status Register
enum LSR {
DR = %00000001, ; Data Ready
@@ -54,13 +74,12 @@ serial {
}
}
}
return uart1, uart2
private sub probe(uword address) -> bool {
sys.set_irqd()
address[REG_INTERRUPT_ENABLE] = $ff
bool found = address[REG_INTERRUPT_ENABLE] == $0f ; The high nybble of IER is always clear
address[REG_INTERRUPT_ENABLE] = $f0
bool found = address[REG_INTERRUPT_ENABLE] & $f0 == 0 ; The high nybble of IER is always clear
address[REG_INTERRUPT_ENABLE] = 0
if found {
address[REG_MODEM_CONTROL] = $ff
@@ -73,15 +92,14 @@ serial {
}
sub initialize_uart(uword uart_addr) {
; TODO baud rate is currently fixed at 921600, make this configurable
; 921600 baud, 8,N,1, AutoFlow Control, FIFOS, no interrupts
uart_addr[REG_INTERRUPT_ENABLE] = $00 ; No Interrupts
; 921600 baud, 8,N,1, AutoFlow Control, FIFOS, no interrupts
uart_addr[REG_LINE_CONTROL] = $80 ; Set DLAB
uart_addr[REG_DIVISOR_LATCH_HI] = $00
uart_addr[REG_DIVISOR_LATCH_LOW] = $01 ; $0001 = 921600
uart_addr[REG_LINE_CONTROL] = $03 ; 8,N,1
uart_addr[REG_FIFO_CONTROL] = $C7 ; FIFO enable & reset
uart_addr[REG_MODEM_CONTROL] = $23 ; DTR/RTS & AutoFlow Control
uart_addr[REG_DIVISOR_LATCH_HI] = msb(BAUD::B921600)
uart_addr[REG_DIVISOR_LATCH_LOW] = lsb(BAUD::B921600)
uart_addr[REG_LINE_CONTROL] = $03 ; 8,N,1, DLAB off
uart_addr[REG_FIFO_CONTROL] = $C3 ; FIFO enable & reset, trigger 14
uart_addr[REG_MODEM_CONTROL] = $23 ; DTR/RTS + AutoFlow Control
uart_addr[REG_INTERRUPT_ENABLE] = $00 ; No interrupts
}
sub write(uword @zp uart_addr, str data) {
@@ -98,7 +116,8 @@ serial {
}
sub read_until(uword @zp uart_addr, str match, ^^ubyte buffer, uword max_size) -> uword {
; Reads up to and including the match string from the uart, into buffer.
; Reads bytes from the uart into buffer until the match string is found.
; The match string itself is also included in the buffer.
; Stops when the match string is found or max_size bytes have been read.
; Returns the number of bytes read.
@@ -119,6 +138,7 @@ serial {
}
@(buffer_ptr) = data = @(uart_addr)
buffer_ptr++
read_size++
if data == @(match_ptr) {
match_ptr++
if @(match_ptr)==0
@@ -131,96 +151,9 @@ serial {
return read_size
}
; zimodem routines:
sub zi_initialize(uword uart_addr) {
; initializes the ZiModem on the given uart address
zi_uart = uart_addr
if zi_uart == 0
return ; no uart present
initialize_uart(zi_uart)
; ZiModem sends a version banner of the `ati` command when the ESP32 boots up.
; Read it off if present. It is not always ready immediately so wait a tiny bit
sys.wait(10)
zi_write_cmd(iso:"\x03ate0") ; first send CTRL-C to abort any previous file streams
sys.wait(5)
if zi_uart[REG_LINE_STATUS] & LSR::DR != 0
discard_until(zi_uart, iso:"OK\x0d\x0a")
zi_write_cmd("atq0v1x1f0r1s45=0s40=4096&p0b921600")
discard_until(zi_uart, iso:"OK\x0d\x0a")
}
sub zi_reset() {
; reset all zimodem connections
serial.zi_write_cmd(iso:"atz")
discard_until(zi_uart, iso:"OK\x0d\x0a")
}
sub zi_write_cmd(str command) {
; writes the zimodem command (followed by a proper line ending)
write(zi_uart, command)
write_eol(zi_uart)
}
sub zi_start_get_file(str filename) -> long {
; starts a file download from the given url, returns the size of the file to download.
; you can download chunks of the file by repeatedly calling zi_get_file_chunk() until that returns 0
write(zi_uart, iso:"at&g\"")
write(zi_uart, filename)
write(zi_uart, "\"")
write_eol(zi_uart)
if read_one(zi_uart)=='[' {
; parse header [ 0 <filesize> <sum> ]
ubyte[25] header_buf
void read_until(zi_uart, iso:"]\x0d\x0a", &header_buf, sizeof(header_buf))
return conv.str2long(&header_buf + 3)
} else {
discard_until(zi_uart, iso:"RROR\x0d\x0a")
return 0
}
}
sub zi_get_file_chunk(^^ubyte @zp buffer, uword buffer_size, long remaining_file_size) -> uword {
; read the next chunk of data from the file, into buffer, up to buffer_size bytes.
; returns the number of bytes read. 0 if no more data was available.
uword bytes_to_read = min(remaining_file_size, buffer_size) as uword
if bytes_to_read == 0
return 0
repeat bytes_to_read {
while zi_uart[REG_LINE_STATUS] & LSR::DR == 0 {
; wait until data is present
}
@(buffer) = @(zi_uart)
buffer++
}
return bytes_to_read
}
sub zi_get_ip_address() -> str {
; returns the ip address of the zimodem wifi connection
ubyte[30] ip_buffer
zi_write_cmd(iso:"ati2")
void read_until(zi_uart, iso:"OK\x0d\x0a", &ip_buffer, len(ip_buffer))
cx16.r0 = &ip_buffer
while not strings.isspace(@(cx16.r0)) {
cx16.r0++
}
@(cx16.r0) = 0
return &ip_buffer
}
; private stuff
private uword @zp zi_uart ; the uart address to use for ZiModem
private sub discard_until(uword @zp uart_addr, str match) {
; Reads up to and including the match string from the uart, discards all data.
sub discard_until(uword @zp uart_addr, str match) {
; Reads bytes from the uart until the match string is found, discards all data.
; The match string itself is also discarded.
alias match_ptr = cx16.r0
match_ptr = match
@@ -239,6 +172,163 @@ serial {
}
}
; zimodem routines:
sub zi_initialize(uword uart_addr) {
; initializes the ZiModem on the given uart address
zi_uart = uart_addr
if zi_uart == 0
return ; no uart present
initialize_uart(zi_uart)
; ZiModem sends a version banner of the `ati` command when the ESP32 boots up.
; Read it off if present. It is not always ready immediately so wait a tiny bit
sys.wait(5)
write(zi_uart, "\x03") ; first send CTRL-C (+ no echo) to abort any previous file streams
sys.wait(10)
zi_write_cmd(iso:"ate0q0v1x1f0r1s45=3&p0&k3")
discard_until(zi_uart, iso:"OK\x0d\x0a")
}
sub zi_reset() {
; reset all zimodem connections
write(zi_uart, "\x03") ; first send CTRL-C (+ no echo) to abort any previous file streams
sys.wait(10)
serial.zi_write_cmd(iso:"atz")
discard_until(zi_uart, iso:"OK\x0d\x0a")
}
sub zi_write_cmd(str command) {
; writes the zimodem command (followed by a proper line ending)
write(zi_uart, command)
write_eol(zi_uart)
}
sub zi_start_get_file_hexmode(str filename) -> bool {
; starts a file download from the given url. Returns true if ok, false if file could not be found
; you can download chunks of the file by repeatedly calling zi_get_file_chunk_hexmode() until that returns 0.
; Note: this uses zimodem hex mode transfer for now because binary mode transfer is broken at the moment.
zi_write_cmd(iso:"ats45=1") ; enable hex mode transfer (unfortunately this also forces caching mode on the esp32)
discard_until(zi_uart, iso:"OK\x0d\x0a")
write(zi_uart, iso:"at&g\"")
write(zi_uart, filename)
write(zi_uart, "\"\x0d\x0a")
if read_one(zi_uart)=='[' {
; note: zimodem file size in hex mode is bogus (only can go up to $FFFF), just skip the whole header line.
discard_until(zi_uart, "\x0d\x0a")
return true
} else {
discard_until(zi_uart, iso:"RROR\x0d\x0a")
return false
}
}
sub zi_start_get_file(str filename) -> long {
; TODO: binary file transfer seems unreliable at the moment (text files are still ok, but often freezes on binary files. Could be a problem with my particular X16 serial card or zimodem firmware though), use hex mode transfer for reliable results.
; starts a file download from the given url, returns the size of the file to download.
; you can download chunks of the file by repeatedly calling zi_get_file_chunk() until that returns 0
write(zi_uart, iso:"at&g\"")
write(zi_uart, filename)
write(zi_uart, "\"\x0d\x0a")
if read_one(zi_uart)=='[' {
; parse header [ 0 <filesize> <sum> ]
ubyte[25] header_buf
void read_until(zi_uart, iso:"]\x0d\x0a", &header_buf, sizeof(header_buf))
return conv.str2long(&header_buf + 3)
} else {
discard_until(zi_uart, iso:"RROR\x0d\x0a")
return 0
}
}
sub zi_get_file_chunk_hexmode(^^ubyte @zp buffer, uword buffer_size) -> uword {
; read the next chunk of data from the file (opened in hex mode), into buffer, up to buffer_size bytes (minimum 40 bytes!!)
; returns the number of bytes read. 0 if no more data was available.
ubyte[90] linebuffer
cx16.r0 = read_until(zi_uart, "\x0d\x0a", linebuffer, sizeof(linebuffer))
if cx16.r0 == 4 and linebuffer[0] == iso:'O' ; OK\r\n was received -- end of transmission
return 0
ubyte digits = lsb(cx16.r0)-2
decode(digits)
return digits/2
private sub decode(ubyte numdigits) {
cx16.r1L = 0
for cx16.r0L in 0 to numdigits-1 step 2 {
buffer[cx16.r1L] = decodehex(cx16.r0L)
cx16.r1L++
}
asmsub decodehex(ubyte digitindex @Y) -> ubyte @A {
%asm {{
lda p8v_linebuffer,y
sec
sbc #$30
cmp #$0a
bmi +
sbc #$07
+ asl a
asl a
asl a
asl a
sta P8ZP_SCRATCH_REG
lda p8v_linebuffer+1,y
sec
sbc #$30
cmp #$0a
bmi +
sbc #$07
+ ora P8ZP_SCRATCH_REG
rts
}}
}
}
}
sub zi_get_file_chunk(^^ubyte @zp buffer, uword buffer_size, long remaining_file_size) -> uword {
; TODO: binary file transfer seems unreliable at the moment (text files are still ok, but often freezes on binary files. Could be a problem with my particular X16 serial card or zimodem firmware though), use hex mode transfer for reliable results.
; read the next chunk of data from the file, into buffer, up to buffer_size bytes.
; returns the number of bytes read. 0 if no more data was available.
uword bytes_to_read = if msw(remaining_file_size)!=0 then buffer_size else min(lsw(remaining_file_size), buffer_size)
repeat bytes_to_read {
while zi_uart[REG_LINE_STATUS] & LSR::DR == 0 {
; wait until data is present
}
@(buffer) = @(zi_uart)
buffer++
}
return bytes_to_read
}
sub zi_end_get_file() {
; makes sure the 'OK' response after the actual file data is also read away
discard_until(zi_uart, iso:"OK\x0d\x0a")
}
sub zi_get_ip_address() -> str {
; returns the ip address of the zimodem wifi connection. Assumes IPV4 only.
ubyte[25] ip_buffer
zi_write_cmd(iso:"ati2")
void read_until(zi_uart, iso:"OK\x0d\x0a", &ip_buffer, len(ip_buffer))
cx16.r0 = &ip_buffer
while not strings.isspace(@(cx16.r0)) {
cx16.r0++
}
@(cx16.r0) = 0
return &ip_buffer
}
; private stuff
private uword @zp zi_uart ; the uart address to use for ZiModem
private sub read_one(uword @zp uart_addr) -> ubyte {
while uart_addr[REG_LINE_STATUS] & LSR::DR == 0 {
; wait until data is present
+1 -1
View File
@@ -14,7 +14,7 @@ const ubyte DEFAULT_WIDTH = 80
const ubyte DEFAULT_HEIGHT = 60
const long VERA_TEXTMATRIX = $1b000
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly of course. Note: takes a PETSCII encoded character.
sub bell() {
+1 -1
View File
@@ -439,7 +439,7 @@ divmod_uw_asm .proc
dividend = P8ZP_SCRATCH_W1
remainder = P8ZP_SCRATCH_W2
result = dividend ;save memory by reusing divident to store the result
result = dividend ;save memory by reusing dividend to store the result
cpy #0
bne _nonzero
+2 -2
View File
@@ -212,7 +212,7 @@ _sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
; some simpler multiplications will be optimized away into faster routines. These will not set the upper 16 bits at all!
; - THE RESULT IS ONLY VALID IF THE MULTIPLICATION WAS DONE WITH UWORD ARGUMENTS (or two positive WORD arguments)
; as soon as a negative word value (or 2) was used in the multiplication, these upper 16 bits are not valid!!
; Suggestion (if you are on the Commander X16): use verafx.muls() to get a hardware accelerated 32 bit signed multplication.
; Suggestion (if you are on the Commander X16): use verafx.muls() to get a hardware accelerated 32 bit signed multiplication.
%asm {{
lda prog8_math.multiply_words.result+2
ldy prog8_math.multiply_words.result+3
@@ -385,7 +385,7 @@ _quadrant_region_to_direction:
asmsub atan2(ubyte x1 @R0, ubyte y1 @R1, ubyte x2 @R2, ubyte y2 @R3) -> ubyte @A {
;; Calculate the angle, in a 256-degree circle, between two points into A.
;; The points (x1, y1) and (x2, y2) have to use *unsigned coordinates only* from the positive quadrant in the carthesian plane!
;; The points (x1, y1) and (x2, y2) have to use *unsigned coordinates only* from the positive quadrant in the cartesian plane!
;; http://codebase64.net/doku.php?id=base:8bit_atan2_8-bit_angle
;; This uses 2 large lookup tables so uses a lot of memory but is super fast.
+1 -1
View File
@@ -416,7 +416,7 @@ exit:
}
sub get_loadaddress(str filename) -> uword {
; get the load adress from a PRG file (usually $0801 but it can be different)
; get the load address from a PRG file (usually $0801 but it can be different)
cbm.SETNAM(strings.length(filename), filename)
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
+1 -1
View File
@@ -12,7 +12,7 @@ txt {
const ubyte DEFAULT_WIDTH = 40
const ubyte DEFAULT_HEIGHT = 25
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly of course. Note: takes a PETSCII encoded character.
sub bell() {
+1 -1
View File
@@ -482,7 +482,7 @@ from_scratchW1
.pend
func_peekl .proc
; -- read the ;pmg value on the address in AY, into R14:R15
; -- read the long value on the address in AY, into R14:R15
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
from_scratchW1
+1 -1
View File
@@ -716,7 +716,7 @@ exit:
}
sub get_loadaddress(str filename) -> uword {
; get the load adress from a PRG file (usually $0801 but it can be different)
; get the load address from a PRG file (usually $0801 but it can be different)
cbm.SETNAM(strings.length(filename), filename)
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, READ_IO_CHANNEL)
@@ -293,7 +293,7 @@ sub interpolate(float v, float inputMin, float inputMax, float outputMin, float
}
asmsub internal_long_AY_to_FAC() {
; Used by the compiler to cast a long to a a float in FAC
; Used by the compiler to cast a long to a float in FAC
; convert the long pointed to by AY into a float and store it in FAC
%asm {{
sta cx16.r1L
@@ -308,7 +308,7 @@ sub interpolate(float v, float inputMin, float inputMax, float outputMin, float
}
private sub internal_long_R1_to_float_AY() {
; Used by the compiler to cast a long to a a float variable:
; Used by the compiler to cast a long to a float variable:
; make the float pointed to by AY equal to the long pointed to by R1 (as float)
%asm {{
sta cx16.r0L
+4 -4
View File
@@ -44,7 +44,7 @@ _done
}
sub gnomesort_by_ub(^^ubyte @requirezp ub_keys, ^^uword wordvalues, ubyte num_elements) {
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted ofcourse).
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted of course).
ubyte @zp pos=1
while pos != num_elements {
if ub_keys[pos]>=ub_keys[pos-1]
@@ -88,7 +88,7 @@ _done
}
sub gnomesort_by_uw(uword @requirezp uw_keys, uword wordvalues, ubyte num_elements) {
; Sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted ofcourse).
; Sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted of course).
; both arrays should be no-split array of words. uw_keys are unsigned.
; Max number of elements is 128 to keep indexing code size small and fast. Clobbers R0 and R1.
ubyte @zp pos=2
@@ -161,7 +161,7 @@ _done
}
sub shellsort_by_ub(^^ubyte @requirezp ub_keys, ^^uword @requirezp wordvalues, ubyte num_elements) {
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted ofcourse).
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted of course).
num_elements--
ubyte @zp gap
for gap in [132, 57, 23, 10, 4, 1] {
@@ -187,7 +187,7 @@ _done
}
sub shellsort_by_uw(^^uword @requirezp uw_keys, ^^uword @requirezp wordvalues, ubyte num_elements) {
; sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted ofcourse).
; sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted of course).
; both arrays should be no-split array of words. uw_keys are unsigned.
num_elements--
ubyte gap
+13 -13
View File
@@ -7,7 +7,7 @@ strings {
asmsub length(str string @AY) clobbers(A) -> ubyte @Y {
; Returns the number of bytes in the string.
; This value is determined during runtime and counts upto the first terminating 0 byte in the string,
; This value is determined during runtime and counts up to 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!
; uses P8ZP_SCRATCH_W1 to store the string address, do not change this, other routines here may depend on it (as optimization implementation detail)
@@ -29,7 +29,7 @@ strings {
; Also, you have to make sure yourself that length is smaller or equal to the length of the source string.
; Modifies in-place, doesn’t return a value (so can’t be used in an expression).
%asm {{
; need to copy the the cx16 virtual registers to zeropage to be compatible with C64...
; need to copy the cx16 virtual registers to zeropage to be compatible with C64...
sta P8ZP_SCRATCH_W1
stx P8ZP_SCRATCH_W1+1
lda cx16.r1
@@ -56,7 +56,7 @@ _loop dey
; Also, you have to make sure yourself that length is smaller or equal to the length of the source string.
; Modifies in-place, doesn’t return a value (so can’t be used in an expression).
%asm {{
; need to copy the the cx16 virtual registers to zeropage to be compatible with C64...
; need to copy the cx16 virtual registers to zeropage to be compatible with C64...
stx P8ZP_SCRATCH_B1
sta cx16.r0
sty cx16.r0+1
@@ -96,7 +96,7 @@ _loop dey
; Also, you have to make sure yourself that start and length are within bounds of the strings.
; Modifies in-place, doesn’t return a value (so can’t be used in an expression).
%asm {{
; need to copy the the cx16 virtual registers to zeropage to be compatible with C64...
; need to copy the cx16 virtual registers to zeropage to be compatible with C64...
; substr(source, target, start, length)
sta P8ZP_SCRATCH_B1
lda cx16.r0
@@ -131,7 +131,7 @@ _startloop dey
; Locates the first position of the given character in the string,
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
%asm {{
; need to copy the the cx16 virtual registers to zeropage to make this run on C64...
; need to copy the cx16 virtual registers to zeropage to make this run on C64...
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
stx P8ZP_SCRATCH_B1
@@ -157,7 +157,7 @@ _found tya
; This is a convenience function that looks for both a CR or LF (byte 13 or byte 10) as being a possible Line Ending.
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
%asm {{
; need to copy the the cx16 virtual registers to zeropage to make this run on C64...
; need to copy the cx16 virtual registers to zeropage to make this run on C64...
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
@@ -297,7 +297,7 @@ _max_too_small
asmsub compare(str string1 @R0, str string2 @AY) clobbers(Y) -> byte @A {
; Compares two strings for sorting, case-sensitively.
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
; Note that you can also directly compare strings and string values with eachother using
; Note that you can also directly compare strings and string values with each other using
; comparison operators ==, < etcetera (this will use strcmp automatically).
%asm {{
sta P8ZP_SCRATCH_W2
@@ -311,7 +311,7 @@ _max_too_small
asmsub compare_nocase(str string1 @R0, str string2 @AY) clobbers(Y) -> byte @A {
; Compares two strings for sorting, case-insensitively for PETSCII strings.
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
; Note that you can also directly compare strings and string values with eachother using
; Note that you can also directly compare strings and string values with each other using
; comparison operators ==, < etcetera (this will use strcmp automatically).
%asm {{
sta P8ZP_SCRATCH_W2
@@ -350,7 +350,7 @@ _c1_zero
asmsub compare_nocase_iso(str string1 @R0, str string2 @AY) clobbers(Y) -> byte @A {
; Compares two strings for sorting, case-insensitively for ISO strings.
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
; Note that you can also directly compare strings and string values with eachother using
; Note that you can also directly compare strings and string values with each other using
; comparison operators ==, < etcetera (this will use strcmp automatically).
%asm {{
sta P8ZP_SCRATCH_W2
@@ -525,7 +525,7 @@ _done rts
; ? matches any one character. For example, F?? matches FOO but not FU, and ?? matches all two-character strings.
; * matches any string, including the empty string.
; For example, F* matches all strings starting with F. *O*O* matches all strings with at least two Os. Finally, ?* matches all non-empty strings.
; Both the pattern and the string must be NUL-terminated (that it, followed with a 00 byte) and at most 255 characters long (excluding the NUL).
; Both the pattern and the string must be NUL-terminated (that is, followed with a 00 byte) and at most 255 characters long (excluding the NUL).
; Code taken from http://6502.org/source/strings/patmatch.htm
;
; Input: cx16.r0: A NUL-terminated, <255-length pattern
@@ -562,7 +562,7 @@ next lda $ffff,x ; look at next pattern character MODIFIED
cmp #'*' ; is it a star?
beq star ; yes, do the complicated stuff
iny ; no, let's look at the string
cmp #'?' ; is the pattern caracter a ques?
cmp #'?' ; is the pattern character a ques?
bne reg ; no, it's a regular character
lda (strptr),y ; yes, so it will match anything
beq fail ; except the end of string
@@ -602,7 +602,7 @@ fail clc ; yes, no match found, return with c=0
; ? matches any one character. For example, F?? matches FOO but not FU, and ?? matches all two-character strings.
; * matches any string, including the empty string.
; For example, F* matches all strings starting with F. *O*O* matches all strings with at least two Os. Finally, ?* matches all non-empty strings.
; Both the pattern and the string must be NUL-terminated (that it, followed with a 00 byte) and at most 255 characters long (excluding the NUL).
; Both the pattern and the string must be NUL-terminated (that is, followed with a 00 byte) and at most 255 characters long (excluding the NUL).
; Code taken from http://6502.org/source/strings/patmatch.htm
;
; Input: cx16.r0: A NUL-terminated, <255-length pattern
@@ -651,7 +651,7 @@ next lda $ffff,x ; look at next pattern character MODIFIED
cmp #'*' ; is it a star?
beq star ; yes, do the complicated stuff
iny ; no, let's look at the string
cmp #'?' ; is the pattern caracter a ques?
cmp #'?' ; is the pattern character a ques?
bne reg ; no, it's a regular character
lda (strptr),y ; yes, so it will match anything
beq fail ; except the end of string
+1 -1
View File
@@ -300,7 +300,7 @@ diskio {
}
sub get_loadaddress(str filename) -> uword {
; get the load adress from a PRG file (usually $0801 but it can be different)
; get the load address from a PRG file (usually $0801 but it can be different)
if f_open(filename) {
uword address
void f_read(&address, 2)
+1 -1
View File
@@ -277,7 +277,7 @@ math {
sub atan2(ubyte x1, ubyte y1, ubyte x2, ubyte y2) -> ubyte {
;; Calculate the angle, in a 256-degree circle, between two points into A.
;; The points (x1, y1) and (x2, y2) have to use *unsigned coordinates only* from the positive quadrant in the carthesian plane!
;; The points (x1, y1) and (x2, y2) have to use *unsigned coordinates only* from the positive quadrant in the cartesian plane!
%ir {{
loadm.b r99100,math.atan2.x1
loadm.b r99101,math.atan2.y1
+2 -2
View File
@@ -50,7 +50,7 @@ sorting {
sub shellsort_by_ub(^^ubyte @requirezp ub_keys, ^^uword @requirezp wordvalues, ubyte num_elements) {
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted ofcourse).
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted of course).
num_elements--
ubyte @zp gap
for gap in [132, 57, 23, 10, 4, 1] {
@@ -76,7 +76,7 @@ sorting {
}
sub shellsort_by_uw(^^uword @requirezp uw_keys, ^^uword @requirezp wordvalues, ubyte num_elements) {
; sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted ofcourse).
; sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted of course).
; both arrays should be no-split array of words. uw_keys are unsigned.
num_elements--
ubyte gap
+2 -2
View File
@@ -147,7 +147,7 @@ strings {
sub compare(str st1, str st2) -> byte {
; Compares two strings for sorting (case sensitive).
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
; Note that you can also directly compare strings and string values with eachother using
; Note that you can also directly compare strings and string values with each other using
; comparison operators ==, < etcetera (this will use strcmp automatically).
%ir {{
loadm.w r99000,strings.compare.st1
@@ -160,7 +160,7 @@ strings {
sub compare_nocase(str st1, str st2) -> byte {
; Compares two strings for sorting (case insensitive).
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
; Note that you can also directly compare strings and string values with eachother using
; Note that you can also directly compare strings and string values with each other using
; comparison operators ==, < etcetera (this will use strcmp automatically).
%ir {{
loadm.w r99000,strings.compare_nocase.st1
@@ -274,8 +274,7 @@ internal class AstChecker(private val program: Program,
}
BaseDataType.LONG -> {
if(!iterableDt.elementType().isInteger)
errors.err("long loop variable can only loop over integers", forLoop.position)
errors.warn("for loop using a long counter could be very slow", forLoop.position)
}
BaseDataType.FLOAT -> {
@@ -698,7 +697,12 @@ internal class AstChecker(private val program: Program,
val iterations = repeatLoop.iterations?.constValue(program)
if (iterations != null) {
require(floor(iterations.number)==iterations.number)
if (iterations.number.toInt() > 65536) errors.err("repeat cannot exceed 65536 iterations", iterations.position)
if (iterations.number.toInt() > 65536)
errors.warn("repeat using a long counter could be very slow", iterations.position)
}
if(repeatLoop.iterations?.inferType(program)?.isLong==true) {
errors.warn("repeat using a long counter could be very slow", repeatLoop.iterations!!.position)
}
val ident = repeatLoop.iterations as? IdentifierReference
@@ -907,7 +911,7 @@ internal class AstChecker(private val program: Program,
val variable=addressOf.identifier?.targetVarDecl()
if (variable!=null) {
if (variable.type == VarDeclType.CONST && addressOf.arrayIndex == null)
errors.err("invalid pointer-of operand type", addressOf.position)
errors.err("invalid address-of operand type", addressOf.position)
}
if(addressOf.msb) {
@@ -47,8 +47,36 @@ internal class BeforeAsmTypecastCleaner(val program: Program,
}
} else if(sourceDt.isString && typecast.type.isPointer && typecast.type.sub==BaseDataType.UBYTE) {
// casting a string to a ^^ubyte is just taking the address of the string.
val addr = AddressOf(typecast.expression as IdentifierReference, null, null, false, true, typecast.position)
return listOf(AstReplaceNode(typecast, addr, parent))
when (typecast.expression) {
is IfExpression -> {
val ifExpr = typecast.expression as IfExpression
val trueCast = TypecastExpression(ifExpr.truevalue, typecast.type, typecast.implicit, typecast.position)
val falseCast = TypecastExpression(ifExpr.falsevalue, typecast.type, typecast.implicit, typecast.position)
trueCast.linkParents(ifExpr)
falseCast.linkParents(ifExpr)
ifExpr.truevalue = trueCast
ifExpr.falsevalue = falseCast
return listOf(AstReplaceNode(typecast, ifExpr, parent))
}
is BranchConditionExpression -> {
val branchExpr = typecast.expression as BranchConditionExpression
val trueCast = TypecastExpression(branchExpr.truevalue, typecast.type, typecast.implicit, typecast.position)
val falseCast = TypecastExpression(branchExpr.falsevalue, typecast.type, typecast.implicit, typecast.position)
trueCast.linkParents(branchExpr)
falseCast.linkParents(branchExpr)
branchExpr.truevalue = trueCast
branchExpr.falsevalue = falseCast
return listOf(AstReplaceNode(typecast, branchExpr, parent))
}
else -> {
val identifier = typecast.expression as? IdentifierReference
if (identifier != null) {
val addr = AddressOf(identifier, null, null, false, true, typecast.position)
return listOf(AstReplaceNode(typecast, addr, parent))
}
errors.err("cannot take address of this expression", typecast.position)
}
}
} else {
errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position)
}
+70 -82
View File
@@ -2,7 +2,6 @@ package prog8tests.compiler
import io.kotest.common.ExperimentalKotest
import io.kotest.core.spec.style.FunSpec
import io.kotest.datatest.withData
import io.kotest.engine.spec.tempdir
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
@@ -146,10 +145,8 @@ private fun verifyIrRegisterCount(result: CompilationResult, expectedCount: Int)
class TestCompilerOnExamplesC64: FunSpec({
@OptIn(ExperimentalKotest::class)
//testExecutionMode = TestExecutionMode.LimitedConcurrency(6)
this.concurrency = 4
val outputDir = tempdir().toPath()
this.threads = 4
val onlyC64 = cartesianProduct(
listOf(
@@ -176,17 +173,17 @@ class TestCompilerOnExamplesC64: FunSpec({
)
val target = C64Target()
withData(
nameFn = { "${it.first.first.name}: ${target.name}, optimize=${it.first.second}" },
onlyC64.map { it to prepareTestFiles(it.first.name, it.second, target) }
) { (params, prep) ->
val filepath = prep.second
val optimize = params.second
val expectedSize = if (optimize) params.first.c64SizeOptimized else params.first.c64SizeUnoptimized
val result = compileTheThing(filepath, optimize, target, outputDir)
result shouldNotBe null
if (expectedSize > 0) {
verifyOutputFileSize(result!!, expectedSize)
onlyC64.forEach { (params, optimize) ->
val prep = prepareTestFiles(params.name, optimize, target)
test(prep.first) {
val testOutputDir = tempdir().toPath()
val filepath = prep.second
val expectedSize = if (optimize) params.c64SizeOptimized else params.c64SizeUnoptimized
val result = compileTheThing(filepath, optimize, target, testOutputDir)
result shouldNotBe null
if (expectedSize > 0) {
verifyOutputFileSize(result!!, expectedSize)
}
}
}
})
@@ -194,16 +191,14 @@ class TestCompilerOnExamplesC64: FunSpec({
class TestCompilerOnExamplesCx16: FunSpec({
@OptIn(ExperimentalKotest::class)
//testExecutionMode = TestExecutionMode.LimitedConcurrency(6)
this.concurrency = 4
val outputDir = tempdir().toPath()
this.threads = 4
val onlyCx16 = cartesianProduct(
listOf(
ExampleSizes("vtui/testvtui", cx16SizeOptimized=3041, cx16SizeUnoptimized=3041),
ExampleSizes("pcmaudio/play-adpcm", cx16SizeOptimized=36147, cx16SizeUnoptimized=36645),
ExampleSizes("pcmaudio/stream-wav", cx16SizeOptimized=5627, cx16SizeUnoptimized=7863),
ExampleSizes("pcmaudio/stream-wav", cx16SizeOptimized=5636, cx16SizeUnoptimized=7878),
ExampleSizes("pcmaudio/stream-simple-aflow", cx16SizeOptimized=1850, cx16SizeUnoptimized=3753),
ExampleSizes("pcmaudio/stream-simple-poll", cx16SizeOptimized=1564, cx16SizeUnoptimized=3462),
ExampleSizes("pcmaudio/vumeter", cx16SizeOptimized=3888, cx16SizeUnoptimized=6381),
@@ -245,7 +240,7 @@ class TestCompilerOnExamplesCx16: FunSpec({
ExampleSizes("multi-irq-old", cx16SizeOptimized=962, cx16SizeUnoptimized=1410),
ExampleSizes("plasma", cx16SizeOptimized=1739, cx16SizeUnoptimized=2066),
ExampleSizes("rasterbars", cx16SizeOptimized=1268, cx16SizeUnoptimized=1685),
ExampleSizes("serialdownload", cx16SizeOptimized=2471, cx16SizeUnoptimized=3649),
ExampleSizes("serialdownload", cx16SizeOptimized=2516, cx16SizeUnoptimized=3945),
ExampleSizes("showbmx", cx16SizeOptimized=2745, cx16SizeUnoptimized=5269),
ExampleSizes("snow", cx16SizeOptimized=3120, cx16SizeUnoptimized=5295),
ExampleSizes("sortingbench", cx16SizeOptimized=2928, cx16SizeUnoptimized=3847),
@@ -260,17 +255,17 @@ class TestCompilerOnExamplesCx16: FunSpec({
)
val target = Cx16Target()
withData(
nameFn = { "${it.first.first.name}: ${target.name}, optimize=${it.first.second}" },
onlyCx16.map { it to prepareTestFiles(it.first.name, it.second, target) }
) { (params, prep) ->
val filepath = prep.second
val optimize = params.second
val expectedSize = if (optimize) params.first.cx16SizeOptimized else params.first.cx16SizeUnoptimized
val result = compileTheThing(filepath, optimize, target, outputDir)
result shouldNotBe null
if (expectedSize > 0) {
verifyOutputFileSize(result!!, expectedSize)
onlyCx16.forEach { (params, optimize) ->
val prep = prepareTestFiles(params.name, optimize, target)
test(prep.first) {
val testOutputDir = tempdir().toPath()
val filepath = prep.second
val expectedSize = if (optimize) params.cx16SizeOptimized else params.cx16SizeUnoptimized
val result = compileTheThing(filepath, optimize, target, testOutputDir)
result shouldNotBe null
if (expectedSize > 0) {
verifyOutputFileSize(result!!, expectedSize)
}
}
}
})
@@ -278,10 +273,8 @@ class TestCompilerOnExamplesCx16: FunSpec({
class TestCompilerOnExamplesBothC64andCx16: FunSpec({
@OptIn(ExperimentalKotest::class)
//testExecutionMode = TestExecutionMode.LimitedConcurrency(6)
this.concurrency = 4
val outputDir = tempdir().toPath()
this.threads = 4
val bothCx16AndC64 = cartesianProduct(
listOf(
@@ -315,33 +308,30 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
listOf(C64Target(), Cx16Target())
)
withData(
nameFn = { "${it.first.name}: ${it.third.first.name}, optimize=${it.second}" },
bothCx16AndC64.map {
val prep = prepareTestFiles(it.first.name, it.second, it.third)
Triple(it.first, it.second, Triple(it.third, prep.first, prep.second))
}
) { params ->
val filepath = params.third.third
val optimize = params.second
val target = params.third.first
val exampleSizes = params.first
val expectedSize = when (target) {
is C64Target -> if (optimize) exampleSizes.c64SizeOptimized else exampleSizes.c64SizeUnoptimized
is Cx16Target -> if (optimize) exampleSizes.cx16SizeOptimized else exampleSizes.cx16SizeUnoptimized
else -> 0
}
val result = compileTheThing(filepath, optimize, target, outputDir)
result shouldNotBe null
if (expectedSize > 0) {
verifyOutputFileSize(result!!, expectedSize)
bothCx16AndC64.forEach { (exampleSizes, optimize, target) ->
val prep = prepareTestFiles(exampleSizes.name, optimize, target)
test(prep.first) {
val testOutputDir = tempdir().toPath()
val filepath = prep.second
val expectedSize = when (target) {
is C64Target -> if (optimize) exampleSizes.c64SizeOptimized else exampleSizes.c64SizeUnoptimized
is Cx16Target -> if (optimize) exampleSizes.cx16SizeOptimized else exampleSizes.cx16SizeUnoptimized
else -> 0
}
val result = compileTheThing(filepath, optimize, target, testOutputDir)
result shouldNotBe null
if (expectedSize > 0) {
verifyOutputFileSize(result!!, expectedSize)
}
}
}
})
class TestCompilerOnExamplesVirtual: FunSpec({
val outputDir = tempdir().toPath()
@OptIn(ExperimentalKotest::class)
this.concurrency = 4
this.threads = 4
val onlyVirtual = cartesianProduct(
listOf(
@@ -361,19 +351,19 @@ class TestCompilerOnExamplesVirtual: FunSpec({
)
val target = VMTarget()
withData(
nameFn = { "${it.first.first.name}: ${target.name}, optimize=${it.first.second}" },
onlyVirtual.map { it to prepareTestFiles(it.first.name, it.second, target) }
) { (params, prep) ->
val filepath = prep.second
val optimize = params.second
val expectedInstrCount = if (optimize) params.first.virtualInstrCountOptimized else params.first.virtualInstrCountUnoptimized
val expectedRegCount = if (optimize) params.first.virtualRegCountOptimized else params.first.virtualRegCountUnoptimized
val result = compileTheThing(filepath, optimize, target, outputDir)
result shouldNotBe null
if (expectedInstrCount > 0) {
verifyIrInstructionCount(result!!, expectedInstrCount)
verifyIrRegisterCount(result, expectedRegCount)
onlyVirtual.forEach { (params, optimize) ->
val prep = prepareTestFiles(params.name, optimize, target)
test(prep.first) {
val testOutputDir = tempdir().toPath()
val filepath = prep.second
val expectedInstrCount = if (optimize) params.virtualInstrCountOptimized else params.virtualInstrCountUnoptimized
val expectedRegCount = if (optimize) params.virtualRegCountOptimized else params.virtualRegCountUnoptimized
val result = compileTheThing(filepath, optimize, target, testOutputDir)
result shouldNotBe null
if (expectedInstrCount > 0) {
verifyIrInstructionCount(result!!, expectedInstrCount)
verifyIrRegisterCount(result, expectedRegCount)
}
}
}
})
@@ -381,10 +371,8 @@ class TestCompilerOnExamplesVirtual: FunSpec({
class TestCompilerOnExamplesPET32: FunSpec({
@OptIn(ExperimentalKotest::class)
//testExecutionMode = TestExecutionMode.LimitedConcurrency(6)
this.concurrency = 4
val outputDir = tempdir().toPath()
this.threads = 4
val onlyPET32 = cartesianProduct(
listOf(
@@ -395,17 +383,17 @@ class TestCompilerOnExamplesPET32: FunSpec({
)
val target = PETTarget()
withData(
nameFn = { "${it.first.first.name}: ${target.name}, optimize=${it.first.second}" },
onlyPET32.map { it to prepareTestFiles(it.first.name, it.second, target) }
) { (params, prep) ->
val filepath = prep.second
val optimize = params.second
val expectedSize = if (optimize) params.first.pet32SizeOptimized else params.first.pet32SizeUnoptimized
val result = compileTheThing(filepath, optimize, target, outputDir)
result shouldNotBe null
if (expectedSize > 0) {
verifyOutputFileSize(result!!, expectedSize)
onlyPET32.forEach { (params, optimize) ->
val prep = prepareTestFiles(params.name, optimize, target)
test(prep.first) {
val testOutputDir = tempdir().toPath()
val filepath = prep.second
val expectedSize = if (optimize) params.pet32SizeOptimized else params.pet32SizeUnoptimized
val result = compileTheThing(filepath, optimize, target, testOutputDir)
result shouldNotBe null
if (expectedSize > 0) {
verifyOutputFileSize(result!!, expectedSize)
}
}
}
})
@@ -23,7 +23,11 @@ class TestComparisonIssues : FunSpec({
val result = compileText(Cx16Target(), false, text, outputDir, writeAssembly = true)!!
val asmFile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".asm")
val asm = asmFile.readText()
asm.shouldContainInOrder("ldx", "#252", "lda", "p8v_a+4,x", "adc", "p8v_b+4,x", "sta", "p8v_a+4,x", "inx", "bne")
asm.shouldContainInOrder("clc",
"lda", "p8v_a+0", "adc", "p8v_b+0", "sta", "p8v_a+0",
"lda", "p8v_a+1", "adc", "p8v_b+1", "sta", "p8v_a+1",
"lda", "p8v_a+2", "adc", "p8v_b+2", "sta", "p8v_a+2",
"lda", "p8v_a+3", "adc", "p8v_b+3", "sta", "p8v_a+3")
}
test("long augmented subtraction generates loop") {
@@ -39,7 +43,28 @@ class TestComparisonIssues : FunSpec({
val result = compileText(Cx16Target(), false, text, outputDir, writeAssembly = true)!!
val asmFile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".asm")
val asm = asmFile.readText()
asm.shouldContainInOrder("ldx", "#252", "lda", "p8v_a+4,x", "sbc", "p8v_b+4,x", "sta", "p8v_a+4,x", "inx", "bne")
asm.shouldContainInOrder("sec",
"lda", "p8v_a+0", "sbc", "p8v_b+0", "sta", "p8v_a+0",
"lda", "p8v_a+1", "sbc", "p8v_b+1", "sta", "p8v_a+1",
"lda", "p8v_a+2", "sbc", "p8v_b+2", "sta", "p8v_a+2",
"lda", "p8v_a+3", "sbc", "p8v_b+3", "sta", "p8v_a+3")
}
test("long augmented addition generates loop with nozp variables") {
val text = """
main {
sub start() {
long @nozp a = 1000
long @nozp b = 2000
a += b
}
}
""".trimIndent()
val result = compileText(Cx16Target(), false, text, outputDir, writeAssembly = true)!!
val asmFile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".asm")
val asm = asmFile.readText()
// verify unrolled structure
asm.shouldContainInOrder("clc", "lda", "adc", "sta", "lda", "adc", "sta", "lda", "adc", "sta", "lda", "adc", "sta")
}
test("word <= 0 signed comparison (identifier)") {
@@ -119,6 +119,43 @@ class TestExecution6502 : FunSpec({
machine.cpu.totalCycles shouldBeLessThan 200
}
test("long repeat loops") {
val src = $$"""
%option no_sysinit
%launcher none
%address $1000
main {
&ubyte poweroff = $f203
&long result = $02 ; result is stored in memory at $02
sub start() {
long @shared qq = 0
repeat 70000 {
qq++
}
result = qq
long @shared iters = 70000
repeat iters {
qq++
}
result = qq
poweroff = 1
}
}
""".trimIndent()
val compileResult = compileText(Cx16Target(), false, src, outputDir)
// 140000 iterations * ~40 cycles = 5.6 million. Let's use 10 million to be safe.
val machine = compileResult!!.simulate(maxCycles = 10000000L)
machine.assertMemory(0x02, (140000L and 0xff).toInt())
machine.assertMemory(0x03, ((140000L shr 8) and 0xff).toInt())
machine.assertMemory(0x04, ((140000L shr 16) and 0xff).toInt())
machine.assertMemory(0x05, ((140000L shr 24) and 0xff).toInt())
}
test("top level RTS exits simulator") {
val src = $$"""
%option no_sysinit
@@ -0,0 +1,46 @@
package prog8tests.codegeneration
import io.kotest.core.spec.style.FunSpec
import io.kotest.engine.spec.tempdir
import io.kotest.matchers.shouldNotBe
import prog8.code.target.Cx16Target
import prog8tests.helpers.compileText
class TestExperiCodeGen: FunSpec({
val outputDir = tempdir().toPath()
test("banked extsub with multiple return values (constbank)") {
val src = $$"""
main {
sub start() {
uword ptr
ubyte bank
void, ptr, bank = mymodule.myfunc(0)
}
}
mymodule {
const ubyte MYBANK = 1
extsub @bank MYBANK $A000 = myfunc(ubyte x @X) -> bool @Pc, uword @XY, ubyte @A
}"""
compileText(Cx16Target(), false, src, outputDir, experimentalCodegen = true) shouldNotBe null
}
test("banked extsub with multiple return values (varbank)") {
val src = $$"""
main {
sub start() {
uword ptr
ubyte bank
void, ptr, bank = mymodule.myfunc(0)
}
}
mymodule {
ubyte @shared bankvar = 1
extsub @bank bankvar $A000 = myfunc(ubyte x @X) -> bool @Pc, uword @XY, ubyte @A
}"""
compileText(Cx16Target(), false, src, outputDir, experimentalCodegen = true) shouldNotBe null
}
})
@@ -0,0 +1,49 @@
package prog8tests.codegeneration
import io.kotest.core.spec.style.FunSpec
import io.kotest.engine.spec.tempdir
import prog8.code.target.Cx16Target
import prog8tests.helpers.compileText
class TestLongAugmentedAssignment : FunSpec({
val outputDir = tempdir().toPath()
test("long augmented assignment with literal") {
val text = """
main {
sub start() {
long @shared a = 1000
a += 500
}
}
""".trimIndent()
// Should compile successfully
compileText(Cx16Target(), false, text, outputDir, writeAssembly = true)
}
test("long augmented assignment with expression") {
val text = """
main {
sub start() {
long @shared a = 1000
long @shared b = 500
a += b + 100
}
}
""".trimIndent()
compileText(Cx16Target(), false, text, outputDir, writeAssembly = true)
}
test("long augmented assignment with ubyte") {
val text = """
main {
sub start() {
long @shared a = 1000
ubyte @shared b = 200
a += b
}
}
""".trimIndent()
compileText(Cx16Target(), false, text, outputDir, writeAssembly = true)
}
})
@@ -18,6 +18,7 @@ import prog8.code.target.*
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
import prog8tests.helpers.shouldContainInOrder
import prog8tests.helpers.simulate
import kotlin.io.path.readText
class TestVariousCodeGen: FunSpec({
@@ -904,4 +905,93 @@ main {
asm shouldContain "sta (p8v_s),y"
}
}
test("IfExpression string to pointer typecast") {
val src = """
%option no_sysinit
%launcher none
%address $1000
main {
sub start() {
str a = "hello"
str b = "world"
bool flag = true
^^ubyte p = if flag then a else b
}
}
""".trimIndent()
// This should not crash during compilation
val compileResult = compileText(Cx16Target(), false, src, outputDir)
compileResult shouldNotBe null
// Also test with virtual target
val compileResultVM = compileText(VMTarget(), false, src, outputDir)
compileResultVM shouldNotBe null
}
test("BranchConditionExpression string to pointer typecast") {
val src = """
%option no_sysinit
%launcher none
%address $1000
main {
sub start() {
str a = "hello"
str b = "world"
^^ubyte p = if_z then a else b
}
}
""".trimIndent()
// This should not crash during compilation
val compileResult = compileText(Cx16Target(), false, src, outputDir)
compileResult shouldNotBe null
}
test("pointer assignment to variable in 6502 backend") {
val src = """
%option no_sysinit
%launcher none
%address $1000
main {
ubyte value = 42
^^ubyte @shared ptr = 0
sub start() {
ptr = &value
}
}
""".trimIndent()
// This should not crash during compilation
val compileResult = compileText(Cx16Target(), false, src, outputDir)
compileResult shouldNotBe null
// Verify it actually works by simulating
compileResult!!.simulate()
}
test("pointer assignment to array element in 6502 backend") {
val src = """
%option no_sysinit
%launcher none
%address $1000
main {
ubyte value = 42
^^ubyte[2] ptrs
sub start() {
ptrs[1] = &value
}
}
""".trimIndent()
val compileResult = compileText(Cx16Target(), false, src, outputDir)
compileResult shouldNotBe null
}
})
+7 -4
View File
@@ -18,7 +18,8 @@ internal fun compileFile(
errors: IErrorReporter? = null,
writeAssembly: Boolean = true,
varshigh: Int? = null,
slabshigh: Int? = null
slabshigh: Int? = null,
experimentalCodegen: Boolean = false
) : CompilationResult? {
val filepath = fileDir.resolve(fileName)
assumeReadableFile(filepath)
@@ -33,7 +34,7 @@ internal fun compileFile(
showTimings = false,
asmListfile = false,
includeSourcelines = false,
experimentalCodegen = false,
experimentalCodegen = experimentalCodegen,
dumpVariables = false,
dumpSymbols = false,
varsHighBank = varshigh,
@@ -67,11 +68,13 @@ internal fun compileText(
errors: IErrorReporter? = null,
writeAssembly: Boolean = true,
varshigh: Int? = null,
slabshigh: Int? = null
slabshigh: Int? = null,
experimentalCodegen: Boolean = false
) : CompilationResult? {
val filePath = outputDir.resolve("on_the_fly_test_${sourceText.hashCode().toUInt().toString(16)}.p8")
// we don't assumeNotExists(filePath) - should be ok to just overwrite it
filePath.toFile().writeText(sourceText)
return compileFile(platform, optimize, filePath.parent, filePath.name, outputDir,
errors=errors, writeAssembly=writeAssembly, varshigh=varshigh, slabshigh=slabshigh)
errors=errors, writeAssembly=writeAssembly, varshigh=varshigh, slabshigh=slabshigh,
experimentalCodegen = experimentalCodegen)
}
@@ -0,0 +1,49 @@
package prog8tests.optimizer
import io.kotest.core.spec.style.FunSpec
import io.kotest.engine.spec.tempdir
import io.kotest.matchers.shouldBe
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.Assignment
import prog8.code.target.Cx16Target
import prog8tests.helpers.compileText
class TestExpressionSimplifier : FunSpec({
val outputDir = tempdir().toPath()
test("long & -1 is correctly simplified") {
val text = """
main {
sub start() {
long @shared x = 1000
long @shared y = x & -1
}
}
""".trimIndent()
val result = compileText(Cx16Target(), true, text, outputDir, writeAssembly = false)!!
val s = result.compilerAst.entrypoint.statements
val expr = (s[3] as Assignment).value as BinaryExpression
expr.operator shouldBe "&"
(expr.left as IdentifierReference).nameInSource shouldBe listOf("x")
(expr.right as NumericLiteral).number shouldBe -1.0
}
test("long & 255 is correctly handled") {
val text = """
main {
sub start() {
long @shared x = 1000
long @shared y = x & 255
}
}
""".trimIndent()
val result = compileText(Cx16Target(), true, text, outputDir, writeAssembly = false)!!
val s = result.compilerAst.entrypoint.statements
val expr = (s[3] as Assignment).value as BinaryExpression
expr.operator shouldBe "&"
(expr.left as IdentifierReference).nameInSource shouldBe listOf("x")
(expr.right as NumericLiteral).number shouldBe 255.0
}
})
@@ -654,7 +654,7 @@ data class AddressOf(var identifier: IdentifierReference?, var arrayIndex: Array
val target = this.identifier?.targetStatement()
val targetVar = target as? VarDecl
if(targetVar!=null) {
if (targetVar.type == VarDeclType.MEMORY || targetVar.type == VarDeclType.CONST) {
if (targetVar.type == VarDeclType.MEMORY || (targetVar.type == VarDeclType.CONST && arrayIndex != null)) {
var address = targetVar.value?.constValue(program)?.number
if (address != null) {
if (arrayIndex != null) {
@@ -1,6 +1,6 @@
Prog8 compiler v12.2-BETA2 by Irmen de Jong (irmen@razorvine.net)
Prerelease version from git commit 1dbaa6e0 in branch master
Prog8 compiler v12.2-SNAPSHOT by Irmen de Jong (irmen@razorvine.net)
Prerelease version from git commit 8bf744cc in branch master
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-cx16.p8
@@ -788,6 +788,21 @@ LIBRARY MODULE NAME: serial
---------------------------
serial {
const uword BAUD::B115200
const uword BAUD::B1200
const uword BAUD::B14400
const uword BAUD::B19200
const uword BAUD::B230400
const uword BAUD::B2400
const uword BAUD::B28800
const uword BAUD::B300
const uword BAUD::B38400
const uword BAUD::B460800
const uword BAUD::B4800
const uword BAUD::B57600
const uword BAUD::B600
const uword BAUD::B921600
const uword BAUD::B9600
const ubyte LSR::BI
const ubyte LSR::DR
const ubyte LSR::FE
@@ -808,14 +823,18 @@ serial {
const ubyte REG_SCRATCH
const ubyte REG_TXRX_BUFFER
detect_uarts () -> uword, uword
discard_until (uword uart_addr, str match)
initialize_uart (uword uart_addr)
read_until (uword uart_addr, str match, ^^ubyte buffer, uword max_size) -> uword
write (uword uart_addr, str data)
zi_end_get_file ()
zi_get_file_chunk (^^ubyte buffer, uword buffer_size, long remaining_file_size) -> uword
zi_get_file_chunk_hexmode (^^ubyte buffer, uword buffer_size) -> uword
zi_get_ip_address () -> str
zi_initialize (uword uart_addr)
zi_reset ()
zi_start_get_file (str filename) -> long
zi_start_get_file_hexmode (str filename) -> bool
zi_write_cmd (str command)
}
+15 -14
View File
@@ -42,7 +42,7 @@ No system initialization and startup code
Variable initialization
The library still has to initialize any variables it might use and clear
uninitialized "BSS" variables! Otherwise the code will not run predictably as prog8 code.
So, the library must still have a "start" entrypoint subroutine like any outher prog8 program,
So, the library must still have a "start" entrypoint subroutine like any other prog8 program,
that must be called before any other library routine can be called.
Binary output and loaded into a fixed memory address
@@ -79,7 +79,7 @@ Jump table
For ease of use, libraries should probably have a fixed "jump table" where the offsets of the
library routines stay the same across different versions of the library. Without needing new syntax,
there's a trick in Prog8 that you can use to build such a jumptable:
add a non-splitted word array at the top of the library main block that contains JMP instructions
add a non-split word array at the top of the library main block that contains JMP instructions
and the addresses of the individual library subroutines. Do NOT change the order of the subroutines
in this table!
Also note that the Prog8 compiler will insert a single JMP instruction at the very start of the library,
@@ -105,13 +105,14 @@ Assuming the load address of the library is $A000:
BLOAD "LIBRARY.BIN",8,1,$A000
SYS $A000 : REM TO INITIALIZE VARIABLES, REQUIRED!
SYS $A004 : REM CALL FIRST ROUTINE
SYS $A008 : REM CALL SECOND ROUTINE, ETC.
SYS $A003 : REM CALL FIRST ROUTINE
SYS $A006 : REM CALL SECOND ROUTINE, ETC.
**From Prog8**
.. index:: single: Examples; Library from Prog8
**From Prog8:**
The ``diskio`` module actually provides a convenience routine called ``loadlib`` that loads a Prog8-compiled
library blob into memory. It internally automatically uses either regular load() or load_raw(),
as required by the compilation target (so you don't have to bother with target machine
@@ -122,8 +123,8 @@ differences if you want to write portable code)::
main {
extsub $A000 = lib_init() clobbers(A)
extsub $A004 = lib_func1() clobbers(A,X,Y)
extsub $A008 = lib_func2() clobbers(A,X,Y)
extsub $A003 = lib_func1() clobbers(A,X,Y)
extsub $A006 = lib_func2() clobbers(A,X,Y)
sub start() {
if diskio.loadlib("library.bin", $a000) != 0 {
@@ -144,12 +145,12 @@ differences if you want to write portable code)::
int main() {
void (*lib_init)(void) = (void (*)()) 0xa000;
void (*lib_func1)(void) = (void (*)()) 0xa004;
void (*lib_func2)(void) = (void (*)()) 0xa008;
void (*lib_func1)(void) = (void (*)()) 0xa003;
void (*lib_func2)(void) = (void (*)()) 0xa006;
cbm_k_setlfs(0, 8, 2);
cbm_k_setnam("library.bin");
cbm_k_load(0, 0xa000);
cbm_k_setlfs(0, 8, 2);
cbm_k_setnam("library.bin");
cbm_k_load(0, 0xa000);
lib_init();
lib_func1();
@@ -178,8 +179,8 @@ differences if you want to write portable code)::
jsr $ffd2 ; CHROUT
jsr $A000 ; library init
jsr $A004 ; lib func 1
jsr $A008 ; lib func 2
jsr $A003 ; lib func 1
jsr $A006 ; lib func 2
rts
+6 -6
View File
@@ -16,7 +16,7 @@ The language
- It is meant to sit well above low level assembly code, but still allows that low level access to the system it runs on.
Via language features, or even simply by using inline hand-written assembly code.
- Prog8 is targeting very CPU and memory constrained 8-bit systems, this reflects many design choices to work within those limitations
(single digit Megaherz cpu clock speeds, and memory capacity counted in Kilobytes)
(single digit Megahertz cpu clock speeds, and memory capacity counted in Kilobytes)
- Identifiers and string literals can contain non-ASCII characters so for example ``knäckebröd`` and ``見せしめ`` are valid identifiers.
- There's usually a single statement per line. There is no statement separator.
- Semicolon ``;`` is used to start a line comment. Multi-line comments are also possible by enclosing it all in ``/*`` and ``*/``.
@@ -43,7 +43,7 @@ Data types
- **There is no automatic type enlargement:** all calculations remain within the data type of the operands. Any overflow silently wraps or truncates.
You'll have to add explicit casts to increase the size of the value if required.
For example when adding two byte variables having values 100 and 200, the result won't be 300, because that doesn't fit in a byte. It will be 44.
You'll have to cast one or both of the *operands* to a word type first if you want to accomodate the actual result value of 300.
You'll have to cast one or both of the *operands* to a word type first if you want to accommodate the actual result value of 300.
Similarly, ``long v = w1 * w2`` doesn't automatically give you the full 32 bits multiplication result, instead it is still constrained in the word range.
If you need the full 32 bits result you'll have to call a specialized routine such as ``math.mul32`` or ``math.mul16_last_upper()``.
- strings and arrays are allocated once, statically, and never resized.
@@ -66,7 +66,7 @@ Variables
are moved to the top of the subroutine. A for loop, or if/else blocks do not introduce a new scope.
A subroutine (also nested ones) *do* introduce a new scope.
- All variables are initialized at the start of the program. There is no random garbage in them: they are zero or any other initialization value you provide.
- This als means you can run a Prog8 program multiple times without having to reload it from disk, unlike programs produced by most other compilers targeting these 8 bit platforms.
- This also means you can run a Prog8 program multiple times without having to reload it from disk, unlike programs produced by most other compilers targeting these 8 bit platforms.
Subroutines
@@ -102,7 +102,7 @@ Pointers and Structs
Variables of the ``uword`` datatype can be used as a pointer to one of the possible 65536 memory locations,
so the value it points to is always a single byte. This is similar to ``uint8_t*`` from C.
You have to deal with the uword manually if the object it points to is something different.
- Note that there is the ``peekw`` builtin function that *does* allow you to directy obtain the *word* value at the given memory location.
- Note that there is the ``peekw`` builtin function that *does* allow you to directly obtain the *word* value at the given memory location.
So if you use this, you can use uword pointers as pointers to word values without much hassle.
- "dereferencing" a uword pointer is done via array indexing ``ptr[index]`` (where index value can be 0-65535!) or via the memory read operator ``@(ptr)``, or ``peek/peekw(ptr)``.
- Pointers don't have to be a variable, you can immediately access the value of a given memory location using ``@($d020)`` for instance.
@@ -125,14 +125,14 @@ Foreign function interface (external/ROM calls)
- You can use the ``extsub`` keyword to define the call signature of foreign functions (ROM routines or external routines elsewhere in RAM) in a natural way.
Calling those generates code that is as efficient or even more efficient as calling regular subroutines.
No additional stubs are needed. Y
No additional stubs are needed.
- High level support of memory banking: an ``extsub`` can be defined with the memory bank number (constant or variable) where the routine is located in,
and then when you call it as usual, the compiler takes care of the required bank switching.
Optimizations
-------------
- Prog8 contains many compiler optimizations to generate efficient code, but also lacks many optimizations that modern compilers do have.
While empirical evidence shows that Prog8 generates more efficent code than some C compilers that also target the same 8 bit systems,
While empirical evidence shows that Prog8 generates more efficient code than some C compilers that also target the same 8 bit systems,
the optimizations it makes on your code aren't super sophisticated.
- For time critical code, it may be worth it to inspect the generated assembly code to see if you can write things differently
to help the compiler generate more efficient code (or even replace it with hand written inline assembly altogether).
+10 -10
View File
@@ -23,7 +23,7 @@ Then you can choose a few ways to get a compiler:
Arch Linux:
Currently, it's available on `AUR <https://wiki.archlinux.org/title/Arch_User_Repository>`_ for Arch Linux and derivative systems.
The package is called `"prog8" <https://aur.archlinux.org/packages/prog8>`_. There should be no need to install aything else as
The package is called `"prog8" <https://aur.archlinux.org/packages/prog8>`_. There should be no need to install anything else as
it can automatically pull in the required dependencies.
This package, alongside the compiler itself, also globally installs syntax highlighting for ``vim`` and ``nano``.
@@ -65,12 +65,12 @@ The most interesting gradle commands to run are probably the ones listed below.
``./gradlew installShadowDist``
Creates a 'fat-jar' that contains the compiler and all dependencies, in a single
executable .jar file, and includes few start scripts to run it.
The output can be found in ``.compiler/build/install/prog8c-shadow/``
The output can be found in ``./compiler/build/install/prog8c-shadow/``
``./gradlew shadowDistZip``
Creates a zipfile with the above in it, for easy distribution.
This file can be found in ``./compiler/build/distributions/``
For normal use, the ``installDist`` task should suffice and after succesful completion, you can start the compiler with:
For normal use, the ``installDist`` task should suffice and after successful completion, you can start the compiler with:
``./compiler/build/install/prog8c/bin/prog8c <options> <sourcefile>``
@@ -163,7 +163,7 @@ One or more .p8 module files
This can be useful on emulators/systems that don't parse the breakpoint information in the list file,
such as the X16Emu emulator for the Commander X16.
Useful instructions to consider are ``brk`` and ``stp``.
For example for the Commander X16 emulator, ``stp`` is useful because it can actually tyrigger
For example for the Commander X16 emulator, ``stp`` is useful because it can actually trigger
a breakpoint halt in the debugger when this is enabled by running the emulator with -debug.
``-bytes2float <bytes>``
@@ -219,7 +219,7 @@ One or more .p8 module files
With this option you're basically saying: "Yes, I know I am treading on mighty thin ice here, but I don't want to be reminded about that".
``-libsearch pattern``
Search in the embedded library files for occurences of the given regex pattern.
Search in the embedded library files for occurrences of the given regex pattern.
``-libdump path``
Extract all embedded library files into the given output directory.
@@ -269,7 +269,7 @@ One or more .p8 module files
``-nosourcelines``
Do not include the original prog8 source code lines as comments in the generated assembly code file,
mixed in between the actual generated assembly code. The default behavior is to include the sourcel lines.
mixed in between the actual generated assembly code. The default behavior is to include the source lines.
``-srcdirs <pathlist>``
Specify a list of extra paths (separated with ':'), to search in for imported modules.
@@ -280,7 +280,7 @@ One or more .p8 module files
Sets the target output of the compiler. This option is required.
``c64`` = Commodore 64, ``c128`` = Commodore 128, ``cx16`` = Commander X16, ``pet32`` - Commodore PET model 4032,
``virtual`` = builtin virtual machine.
You can also specify a file name as target, prog8 will when try to read the target
You can also specify a file name as target, prog8 will then try to read the target
machine's configuration and properties from that configuration file instead of using one of the built-in targets.
See :ref:`customizable_target` for details about this.
@@ -303,7 +303,7 @@ One or more .p8 module files
cx16: $A000 - $BFFF ; 8 kB in the specified HIRAM bank (note: no auto bank switching is done, you must make sure yourself that this HIRAM bank is active when accessing these variables!)
If you use this option, you can no longer use the part of the above memory area that is
alotted to the variables, for your own purposes. The output of the 64tass assembler step at the
allotted to the variables, for your own purposes. The output of the 64tass assembler step at the
end of compilation shows precise details of where and how much memory is used by the variables
(it's called 'BSS' section or Gap at the address mentioned above).
Assembling the program will fail if there are too many variables to fit in a single high ram bank.
@@ -435,7 +435,7 @@ WARNING
a compiled program is still produced, but it is a good idea to investigate the warnings that are printed.
ERROR
unrecoverable problem that prevented succesful compilation
unrecoverable problem that prevented successful compilation
Compiler doesn't run, complains about "UnsupportedClassVersionError"
@@ -486,7 +486,7 @@ you don't use symbols named just a single letter (especially 'a', 'x' and 'y').
Sometimes these are interpreted as the CPU register of that name. To avoid such confusions,
always use 2 or more letters for symbols in your assembly code.
'shadowing' warnings form the assembler
'shadowing' warnings from the assembler
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Avoid using 'a', 'x' or 'y' as symbols in your inlined assembly code.
Also avoid using 64tass' built-in function or type names as symbols in your inlined assembly code.
+1 -1
View File
@@ -8,7 +8,7 @@ Prog8 documentation - |version|
|
|
Prog8 is is a compiled programming language targeting the 8-bit 6502 CPU family.
Prog8 is a compiled programming language targeting the 8-bit 6502 CPU family.
It 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.
+4 -3
View File
@@ -10,10 +10,10 @@ while still being low level enough to create high performance programs.
The targeted CPUs are the `6502 <https://en.wikipedia.org/wiki/MOS_Technology_6502>`_ /
`6510 <https://en.wikipedia.org/wiki/MOS_Technology_6510>`_ /
`65c02 <https://en.wikipedia.org/wiki/MOS_Technology_65C02>`_ microprocessors.
They are from the late 1970's and early 1980's and was used in many home computers from that era,
They are from the late 1970's and early 1980's and were used in many home computers from that era,
such as the `Commodore 64 <https://en.wikipedia.org/wiki/Commodore_64>`_.
You can compile programs for various machines that are built in into the compiler:
You can compile programs for various machines that are built into the compiler:
* Commander X16 (with 65c02 cpu, 65816 cpu specifics are currently not supported by prog8 itself)
* Commodore 64
@@ -79,6 +79,7 @@ Language :index:`Features`
the compiled program in an emulator and provide debugging information to this emulator.
- the language looks like a mix of Python and C so should be quite easy to learn
- Modular programming, scoping via module source files, code blocks, and subroutines. No need for forward declarations.
- Can make vars and routines private, as a simple way to restrict access and hide implementation details
- Provides high level programming constructs but at the same time stay close to the metal;
still able to directly use memory addresses and ROM subroutines,
and inline assembly to have full control when every register, cycle or byte matters
@@ -227,7 +228,7 @@ To use it, configure your editor to launch ``prog8-language-server`` as the LSP
.. index:: pair: Tools; CTags
**CTags:** for editors that support them, such as Vim, there is an effort by a member of the cummunity to provide "ctags" files for Prog8.
**CTags:** for editors that support them, such as Vim, there is an effort by a member of the community to provide "ctags" files for Prog8.
The basic purpose of ctags is to index the definitions from your source code and let you find them easily.
It is not really to find each use of print for example, but to find where print is defined.
Visit `the Github repository <https://github.com/gillham/prog8ctags>`_ for the files and detailed usage information.
+35 -14
View File
@@ -282,7 +282,7 @@ sizeof (name) ; sizeof(datatype) ; sizeof(&name) ; sizeof(&&name) ; size
:index:`memory` (name, size, alignment) ; memory(name)
Returns the address of the first location of a statically "reserved" block of memory with the given name.
The 3-argument version reserves a block of the given size in bytes and optional alignment, and returns the adress of it.
The 3-argument version reserves a block of the given size in bytes and optional alignment, and returns the address of it.
The 1-argument version acts only as a reference to a block that must be reserved elsewhere in the program.
The name must be a string literal, it cannot be empty or be a variable.
This routine can be used to "reserve" parts of the memory where a normal byte array variable would
@@ -301,7 +301,7 @@ sizeof (name) ; sizeof(datatype) ; sizeof(&name) ; sizeof(&&name) ; size
:index:`call` (address) -> uword
Calls a subroutine given by its memory address. You cannot pass arguments directly,
although it is ofcourse possible to do this via the global ``cx16.r0...`` registers for example.
although it is of course possible to do this via the global ``cx16.r0...`` registers for example.
It is *not* possible to use cpu registers to pass arguments, because these are clobbered while performing the call!
It is assumed the subroutine returns a word value (in AY), if it does not, just add void to the call to ignore the result value.
This function effectively creates an "indirect JSR" if you use it on a ``uword`` pointer variable.
@@ -382,7 +382,7 @@ bcd
Decimal addition and subtraction routines, so for example $0987 + $1111 = $2098 (rather than the usual hex outcome $1a98)
Utilizes the BCD mode of the CPU (note: not all 6502 variants support this mode).
This mode is useful for example for counting decimal score in a game, to avoid costly conversion to a decimal display string:
just print the hexadecimal score representation. (This gets especially noticable with long integers)
just print the hexadecimal score representation. (This gets especially noticeable with long integers)
Available routines:
- ``sub addb(byte a, byte b) -> byte``
@@ -491,7 +491,7 @@ Decompressors are available for RLE, TSCrunch and ZX0 (Salvador).
``decode_tscrunch (uword compressed, uword target)``
Decompress a block of data compressed in the TSCrunch format.
It has extremely fast decompression (approaching RLE speeds),
better compression as RLE, but slightly worse compression ration than LZSA.
better compression as RLE, but slightly worse compression ratio than LZSA.
See https://github.com/tonysavon/TSCrunch for the compression format and compressor tool.
**NOTE:** for speed reasons this decompressor is *not* bank-aware and *not* I/O register aware;
it only outputs to a memory buffer somewhere in the active 64 Kb address range.
@@ -607,7 +607,7 @@ and allows you to print it anywhere on the screen.
``logo ()``
prints the logo at the current cursor position
``logo_at (column, row)``
printss the logo at the given position
prints the logo at the given position
diskio
@@ -637,7 +637,7 @@ On the Commander X16 it tries to use that machine's fast Kernal loading routines
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)
Als contains routines for operating on subdirectories (chdir, mkdir, rmdir), to relabel the disk,
Also contains routines for operating on subdirectories (chdir, mkdir, rmdir), to relabel the disk,
and to seek in open files.
Read the :source:`diskio source code <compiler/res/prog8lib/cx16/diskio.p8>`
@@ -692,7 +692,7 @@ floats
.. note::
Floating point support is available on most cbm-compatible targets (except the C128 for now), and the virtual target.
On the X16, make sure rom bank 4 is still active before doing floationg point operations (it's the bank that contains the fp routines).
On the X16, make sure rom bank 4 is still active before doing floating point operations (it's the bank that contains the fp routines).
On the C64, you have to make sure the Basic ROM is still banked in (same reason).
Provides definitions for the ROM/Kernal subroutines and utility routines dealing with floating point variables.
@@ -740,7 +740,7 @@ Provides definitions for the ROM/Kernal subroutines and utility routines dealing
``lerp_fast(v0, v1, t)``
Linear interpolation (LERP). Imprecise (but faster) method, which does not guarantee v = v1 when t = 1
Teturns an interpolation between two inputs (v0, v1) for a parameter t in the closed unit interval [0.0, 1.0]
Returns an interpolation between two inputs (v0, v1) for a parameter t in the closed unit interval [0.0, 1.0]
``ln (x)``
Natural logarithm (base e).
@@ -866,7 +866,7 @@ checksumming
.. index:: pair: Libraries; checksumming
``crc16 (uword data, uword length, uword initvalue, uword xorout) -> uword``
Returns a CRC-16 checksum over the given data buffer, with configuarable initialization value and final result xor value.
Returns a CRC-16 checksum over the given data buffer, with configurable initialization value and final result xor value.
For XMODEM type checksum, use initvalue=0 and xorout=0.
For IBM-3740 type checksum, use initvalue=$ffff and xorout=0. (this is then equivalent to the cx16.memory_crc routine).
Many other types are possible with different values...
@@ -909,7 +909,7 @@ large multiplications
'''''''''''''''''''''
.. index:: pair: Libraries; large multiplications
``mul32 (woord w1, word w2) -> long``
``mul32 (word w1, word w2) -> long``
Returns the 32 bits signed long result of w1 * w2
``mul16_last_upper () -> uword``
@@ -1141,7 +1141,7 @@ psg (cx16 only)
.. index:: pair: Libraries; psg
Available for the Cx16 target.
**Note: New code should probaly use the psg2 module instead!**
**Note: New code should probably use the psg2 module instead!**
Contains a simple abstraction for the Vera's PSG (programmable sound generator) to play simple waveforms.
It includes an interrupt handler routine for handling automatic ASR volume envelopes as well.
@@ -1183,7 +1183,12 @@ Generic UART routines:
``sub read_until(uword uart_addr, str match, ^^ubyte buffer, uword max_size) -> uword``
Reads bytes from the UART into buffer until the match string is
found or max_size bytes have been read. Returns the number of bytes read.
found (the match string itself is also included in the buffer)
or max_size bytes have been read. Returns the number of bytes read.
``sub discard_until(uword uart_addr, str match)``
Reads and discards bytes from the UART until the match string is
found (the match string itself is also discarded).
ZiModem wifi routines:
@@ -1199,12 +1204,28 @@ ZiModem wifi routines:
``sub zi_start_get_file(str filename) -> long``
Starts a file download from the given URL and returns the file
size. Use ``zi_get_file_chunk()`` repeatedly to download the data.
Note: binary mode transfer can be unreliable; prefer hex mode.
``sub zi_start_get_file_hexmode(str filename) -> bool``
Starts a file download in hex mode from the given URL.
Returns true if the file was found, false otherwise.
Use ``zi_get_file_chunk_hexmode()`` repeatedly to download chunks.
Hex mode is more reliable than binary mode for file transfers.
``sub zi_get_file_chunk(^^ubyte buffer, uword buffer_size, long remaining_file_size) -> uword``
Reads the next chunk of data (up to buffer_size bytes) from an
active file download started with ``zi_start_get_file()``.
Returns the number of bytes read, or 0 when done.
``sub zi_get_file_chunk_hexmode(^^ubyte buffer, uword buffer_size) -> uword``
Reads the next chunk of hex-encoded data (up to buffer_size bytes)
from an active file download started with ``zi_start_get_file_hexmode()``.
Buffer must be at least 40 bytes. Returns the number of bytes decoded, or 0 when done.
``sub zi_end_get_file()``
Consumes the trailing ``OK`` response after downloading a file
with ``zi_start_get_file()`` / ``zi_get_file_chunk()``.
``sub zi_get_ip_address() -> str``
Returns the IP address of the ZiModem wifi connection.
@@ -1219,7 +1240,7 @@ Various sorting routines (gnome sort and shell sort variants) for byte, word and
**NOTE:** all word and str arrays have to be @nosplit! Words and pointers need to be consecutive in memory for now.
**NOTE:** sorting is done in ascending order.
Read the :source:`sorting source code <compiler/res/prog8lib/sorting.p8>`
to see what's in there. Also check out the `sortingbech` example.
to see what's in there. Also check out the `sortingbench` example.
sprites (cx16 only)
@@ -1586,7 +1607,7 @@ There is a very specialized function in the sys module here as well:
``push_returnaddress (address)``
pushes a 16 bit memory address on the CPU hardware stack in the same byte order as a JSR instruction would,
which means the next RTS instruction will jump to that address instead.you
which means the next RTS instruction will jump to that address instead.
You cannot use pushw() for this because the bytes pushed by JSR are different
+12 -12
View File
@@ -128,7 +128,7 @@ The ``alias`` statement makes it easier to refer to symbols from other places, a
you from having to type the fully scoped name everytime you need to access that symbol.
Aliases can be created in any scope except at the module level.
An alias is created with ``alias <name> = <target>`` and then you can use ``<name>`` as if it were ``<target>``.
It is possible to alias variables, labels and subroutines, and even whole blocks.words
It is possible to alias variables, labels and subroutines, and even whole blocks and words
The name has to be an unscoped identifier name, the target can be any scoped or unscoped symbol.
Please consider using aliases sparingly because it may lead to confusing code if you alias
well-known block names for example.
@@ -153,7 +153,7 @@ They can only contain *directives*, *variable declarations*, *subroutines* and *
}
The <blockname> must be a valid identifier, and must be unique in the entire program (there's
a directive to merge multiple occurences).
a directive to merge multiple occurrences).
The <address> is optional. If specified it must be a valid memory address such as ``$c000``.
It's used to tell the compiler to put the block at a certain position in memory.
@@ -164,7 +164,7 @@ It's used to tell the compiler to put the block at a certain position in memory.
You can use the ``private`` keyword to hide symbols from other blocks - see :ref:`private-symbols`.
So, accessing a variable ``counter`` defined in subroutine ``worker`` in block ``main``,
can be done from anywhere by using ``main.worker.counter``.
Unlike most other programming langues, as soon as a name is scoped,
Unlike most other programming languages, as soon as a name is scoped,
Prog8 treats it as a name starting in the *global* namespace.
Relative name lookup is only performed for *non-scoped* names.
@@ -372,7 +372,7 @@ Directives
Level: not at module scope.
Defines a debugging breakpoint at this location. See :ref:`debugging`
The version with the explamation point '!' at the end can be used even
The version with the exclamation point '!' at the end can be used even
if the breakpoint follows an expression. If you don't use the '!' version in this case
the compiler may think it is just a term in the expression (modulo operator and breakpoint operand value),
instead of a breakpoint directive::
@@ -888,7 +888,7 @@ using qualified "dotted names"::
uword address = $4000
goto address ; jump via address variable
goto address + idx ; jump to an adress that is the result of an expression
goto address + idx ; jump to an address that is the result of an expression
Notice that this is a valid way to end a subroutine (you can either ``return`` from it, or jump
to another piece of code that eventually returns).
@@ -910,7 +910,7 @@ for the three individual assignments with the same value 42.
It is also possible to do a multi-value assignment: ``x, y, z = 11, 22, 33``. This is
just a shorter way to write the three separate assignments ``x=11`` followed by ``y=22`` followed by ``z=33``.
For subroutines that return multiple values, you have to write sucha multi-value assigment statement
For subroutines that return multiple values, you have to write such a multi-value assignment statement
as well, to assigns those multiple values. Details can be found here: :ref:`multiassign`.
@@ -1285,7 +1285,7 @@ You *can* tell the compiler to not allocate a new variable, but instead to reuse
(accessible in the code as ``cx16.r0`` - ``cx16.r15``) for the parameter. This is done by adding a ``@Rx`` tag
to the parameter. This can only be done for booleans, byte, and word types.
Note: the R0-R15 *virtual registers* are described in more detail below for the Assembly subroutines.
Here's an example that reuses the R0 and the R1L (lower byte of R1) virtual registers for the paremeters::
Here's an example that reuses the R0 and the R1L (lower byte of R1) virtual registers for the parameters::
sub get_indexed_byte(uword pointer @R0, ubyte index @R1) -> ubyte {
return @(cx16.r0 + cx16.r1L)
@@ -1357,7 +1357,7 @@ External subroutines
^^^^^^^^^^^^^^^^^^^^
.. index:: pair: Subroutines; External
Thse define an external subroutine that's implemented outside of the program
These define an external subroutine that's implemented outside of the program
(for instance, a ROM routine, or a routine in a library loaded elsewhere in RAM).
External subroutines are usually defined by compiler library files, with the following syntax::
@@ -1374,8 +1374,8 @@ make it easier to define jump tables for example, like this::
const uword APIBASE = $8000
extsub APIBASE+0 = firstroutine()
extsub APIBASE+10 = secondroutine()
extsub APIBASE+20 = thirdroutine()
extsub APIBASE+3 = secondroutine()
extsub APIBASE+6 = thirdroutine()
**Banks:** it is possible to declare a non-standard ROM or RAM bank that the routine is living in, with ``@bank`` like this:
``extsub @bank 10 $C09F = audio_init()`` to define a routine at $C09F in bank 10. You can also specify a variable for the bank.
@@ -1445,13 +1445,13 @@ Remember that you can use ``void`` to skip a value. So for instance::
.. sidebar:: register usage
Subroutines with multiple return values use cpu registers A, Y, and the R0-R15 "virtual registers" to return those,
depending on the number of values returend. A floating point value is passed via the FAC 'register'.
depending on the number of values returned. A floating point value is passed via the FAC 'register'.
Multiple float return values are supported on the virtual target, but limited to a single float on 6502 targets
(because the ROM float routines use FAC1/FAC2 as operand registers which would clobber earlier return values).
**Using just one of the values:**
Sometimes it is easier to just have a single return value in a subroutine's signagure (even though it
Sometimes it is easier to just have a single return value in a subroutine's signature (even though it
actually may return multiple values): this avoids having to put ``void`` for all other values if you aren't really interested in those.
It also allows it to be called in expressions such as if-statements again.
Examples of these second 'convenience' definition are library routines such as ``cbm.STOP2`` and ``cbm.GETIN2``,
+8 -8
View File
@@ -6,32 +6,32 @@ Apart from the many :source:`examples <examples>` available in the source code r
there are also larger pieces of software written using Prog8. Here's a list.
`Assembler <https://github.com/irmen/cx16assem>`_
`Assembler <https://codeberg.org/irmen/cx16assem>`_
File-based assembler for the Commander X16.
`Chess <https://github.com/irmen/cx16chess>`_
`Chess <https://codeberg.org/irmen/cx16chess>`_
Chess game for the Commander X16, with 2-player or computer opponent game modes.
`Image viewer <https://github.com/irmen/cx16imageviewer>`_
`Image viewer <https://codeberg.org/irmen/cx16imageviewer>`_
Multi-format image viewer for the Commander X16.
Can display cx16 BMX, C64 Koala, C64 Doodle, BMP, PCX and Amiga IFF images, including color cycling.
`Paint program <https://github.com/irmen/x16paint>`_
`Paint program <https://codeberg.org/irmen/cx16paint>`_
Bitmap image paint program for the Commander X16, work in progress.
`Petaxian <https://github.com/cyborgar/Petaxian>`_
Galaga type shoot em up game using only petscii graphics. Runs on C64 and Commander X16.
`Rock Runner <https://github.com/irmen/cx16rockrunner>`_
`Rock Runner <https://codeberg.org/irmen/cx16rockrunner>`_
Faithful Boulderdash clone, a well known arcade puzzle game from the 80's.
where you must collect all diamonds in a level while avoiding the hazards to reach the exit.
Can load the thousands of available fan made level files.
This game is for the Commander X16.
`Shell <https://github.com/irmen/cx16shell>`_
`Shell <https://codeberg.org/irmen/cx16shell>`_
Unix like command shell for the Commander X16.
`Streaming Music Demo <https://github.com/irmen/cx16musicdemo>`_
`Streaming Music Demo <https://codeberg.org/irmen/cx16musicdemo>`_
Demoscene like "music demos" for the Commander X16.
They display graphics, animated song lyrics, and play a high quality sampled song streamed from disk.
@@ -55,7 +55,7 @@ Various things:
`Library blob link example <https://github.com/FearLabsAudio/Prog8_blobLink_example/>`_
An example of a simple utility that can link symbols in a main Prog8 program
so that they are accessable from an externally loaded library blob.
so that they are accessible from an externally loaded library blob.
It pre-processes the debug symbols list file at compile time,
and substitutes references in a template module file.
+3 -3
View File
@@ -21,7 +21,7 @@ Structs and Pointers
.. note::
Due to a few limitations in the language parser, some pointer related syntax is currently unsupported.
The compiler tries its best to give a descriptive error message but sometimes there is still a
parser limitation that has to be worked around at the moment. For example, this assigment syntax doesn't parse correctly::
parser limitation that has to be worked around at the moment. For example, this assignment syntax doesn't parse correctly::
^^Node np
np[2].field = 9999 ; cannot use this syntax as assignment target right now
@@ -152,7 +152,7 @@ You can copy the whole contents of a struct to another one by assigning the dere
e1^^ = e2^^ ; copies all fields of e2 into e1
The struct type creates a new name scape, so accessing the fields of a struct is done as usual with the dotted notation.
The struct type creates a new name scope, so accessing the fields of a struct is done as usual with the dotted notation.
Because it implies pointer dereferencing you can usually omit the explicit `^^`, prog8 will know what it means::
if e1.ypos > 300
@@ -179,7 +179,7 @@ Because it implies pointer dereferencing you can usually omit the explicit `^^`,
In the future more cases may be supported.
.. note::
Using structs instead of plain arrays usually results in more and less efficent code being generated.
Using structs instead of plain arrays usually results in more and less efficient code being generated.
This is because the 6502 CPU is not particularly well equipped to dealing with pointers and accessing struct fields via offsets,
as compared to direct variable access or array indexing. The prog8 program code may be easier to work with though!
+3 -2
View File
@@ -53,7 +53,7 @@ The path you need to provide for the ``library`` variable can be relative (to th
and you can use a tilde ``~`` in it like in a shell path, to refer to a user's home directory.
Note that library modules not unique to a specific compilation target (for example, `buffers`, `sorting` or `strings`) will
be picked up from the internal library files just fine as was always the case. You can still provide custom versions of them
in your own library folder ofcourse, like you already could with using the ``-srcdirs`` compiler flag.
in your own library folder of course, like you already could with using the ``-srcdirs`` compiler flag.
Most of the things discussed in the :ref:`portingguide` can and must be configured properly in the target configuration file.
You also need to create some essential ``syslib`` library module for the configured target if its name does not match
@@ -110,6 +110,7 @@ Footnotes for the Commander X16
$00 and $01 are hardwired as Rom and Ram banking registers.
$02 - $21 are the 16 virtual cx16 registers R0-R15.
Note: these virtual registers are not automatically initialized to zero at program startup.
$22 - $7F are used by Prog8 to put variables in.
@@ -307,7 +308,7 @@ Here they are, all available in ``cx16``:
The order in which the handlers are invoked if multiple interrupts occur simultaneously is: LINE, TIMER1(VIA1), VSYNC, SPRCOL, AFLOW.
``set_vsync_irq_handler (uword address)``
Sets the verical sync interrupt handler routine. Also enables VSYNC interrupts.
Sets the vertical sync interrupt handler routine. Also enables VSYNC interrupts.
``set_line_irq_handler (uword rasterline, uword address)``
Sets the rasterline interrupt handler routine to trigger on the specified raster line.
+3 -3
View File
@@ -168,7 +168,7 @@ Regular subroutines
For *single* byte, word, and pointer arguments (not long or float), the values are simply loaded in cpu registers by the caller before calling the subroutine.
*The subroutine itself will take care of putting the values into the parameter variables.* This saves on code size because
otherwise all callers would have to store the values in those variables themselves.
Note that his convention is also still used for subroutines that specify parameters to be put into
Note that this convention is also still used for subroutines that specify parameters to be put into
one of the *virtual registers* R0-R15, as those are in the end just variables too (see :ref:`reusevirtualregs_params`)
The rules are as follows:
@@ -224,7 +224,7 @@ Float values can be put in the FAC1 or FAC2 floating point 'registers'.
The return values also get returned via designated registers, or via processor status flags again.
This means that after calling such a routine you can immediately act on the status
via a special branch instruction such as ``if_z`` or ``if_cs`` etc.
The register/status flag usage is fully specified in the asmsub or extsub signature defintion
The register/status flag usage is fully specified in the asmsub or extsub signature definition
for both the parameters and the return values::
extsub $2000 = extfunction(ubyte arg1 @A, uword arg2 @XY, uword arg3 @R0,
@@ -261,7 +261,7 @@ Some notes and references into the compiler's source code modules:
(``intermediate`` and ``codeGenIntermediate`` modules, and ``virtualmachine`` module for the VM related stuff)
Note that this IR is still *targeted to one specific compilation target only*; various properties and all library
code for the selected target machine is encoded into the IR. It is *not possible* to eventually create a C64 program
from an IR file crated for the CommanderX16 target.
from an IR file created for the CommanderX16 target.
#. The code generator backends all implement a common interface ``ICodeGeneratorBackend`` defined in the ``codeCore`` module.
Currently they get handed the program Ast, Symboltable and several other things.
If the code generator wants it can use the ``IRCodeGen`` class from the ``codeGenIntermediate`` module
+5 -5
View File
@@ -19,16 +19,17 @@ Weird Heisenbug
Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^
- make enums strongly typed instead of just syntactic sugar for ints (see enum-strong-type.md for the plan)
- symboldump: some sort of javadocs generated from the p8 source files (instead of just the function signatures). Use markdown for formatting, not html.
- why are (interned) strings stored as initialization value in the SymbolTable AND as string nodes in the interned string block? Something seems redundant here?
- when implementing unsigned longs: remove the (multiple?) "TODO "hack" to allow unsigned long constants to be used as values for signed longs, without needing a cast
- struct/ptr: implement the remaining TODOs in PointerAssignmentsGen.
- struct/ptr: support pointer to pointer?
- struct/ptr: support for typed function pointers, so that we can call them via functptr^^(args) maybe even without the ^^ operator? (&routine could be typed by default as well then)
- struct/ptr: really fixing the pointer dereferencing issues (cursed hybrid beween IdentifierReference, PtrDereferece and PtrIndexedDereference) may require getting rid of scoped identifiers altogether and treat '.' as a "scope or pointer following operator"
- struct/ptr: really fixing the pointer dereferencing issues (cursed hybrid between IdentifierReference, PtrDereferece and PtrIndexedDereference) may require getting rid of scoped identifiers altogether and treat '.' as a "scope or pointer following operator"
- struct/ptr: (later, nasty parser problem:) support chaining pointer dereference on function calls that return a pointer. (type checking now fails on stuff like func().field and func().next.field)
- Make all constants long by default? or not? (remove type name altogether), reduce to target type implictly if the actual value fits. -> long-consts branch
This will break some existing programs that depend on value wrap arounds, but gives more intuitive constant number handling.
- Make all constants long by default? or not? (remove type name altogether), reduce to target type implicitly if the actual value fits. -> long-consts branch
This will break some existing programs that depend on value wraparound, but gives more intuitive constant number handling.
Can give descriptive error message for old syntax that still includes the type name?
- add documentation for more library modules instead of just linking to the source code
- sizeof(pointer) is now always 2 (an uword), make this a variable in the ICompilationTarget so that it could be 4 at the time we might ad a 32-bits 68000 target for example. Much code assumes word size addresses though.
@@ -65,7 +66,7 @@ Romable (%option romable)
IR/VM
^^^^^
- getting it in shape for code generation: the IR file should be able to encode every detail about a prog8 program (the VM doesn't have to actually be able to run all of it though!)
- maybe change all branch instructions to have 2 exits (label if branch condition ture, and label if false) instead of 1, and get rid of the implicit "next code chunk" link between chunks.
- maybe change all branch instructions to have 2 exits (label if branch condition true, and label if false) instead of 1, and get rid of the implicit "next code chunk" link between chunks.
- implement more TODOs in AssignmentGen?
- add more optimizations in IRPeepholeOptimizer?
- **Multi-Level IR Design**: Consider introducing a High-Level IR (HLIR) layer before the current low-level IR to preserve semantics like loop bounds, array indexing, and structure field access.
@@ -83,7 +84,6 @@ IR/VM
Libraries
^^^^^^^^^
- add a serialio library to interface with the serial/wifi card, see https://github.com/safiire/cx16_wget/blob/master/uart.s
- Add split-word array sorting routines to sorting module?
- make a list of all floats.* routines that the compiler expects for full float support?
+13 -14
View File
@@ -75,7 +75,7 @@ Tag Effect
@requirezp force the variable into Zero page. If ZP is full, compilation will fail.
@nozp force the variable to normal system ram, never place it into zeropage.
@shared means the variable is shared with some assembly code and that it cannot be optimized away if not used elsewhere.
@nosplit (only valid on (u)word arrays) Store the array as a single inear array instead of a separate array for lsb and msb values
@nosplit (only valid on (u)word arrays) Store the array as a single linear array instead of a separate array for lsb and msb values
@alignword aligns string or array variable on an even memory address
@align64 aligns string or array variable on a 64 byte address interval (example: for C64 sprite data)
@alignpage aligns string or array variable on a 256 byte address interval (example: to avoid page boundaries)
@@ -177,13 +177,13 @@ Initializing a variable
You can specify an initialization value in the variable declaration.
This will then be used to initialize the variable with at the start of the subroutine, instead of the default value 0.
The provided value doesn't have to be a constant; it can be any expression.
It is a shorter notation for declaring the variables and then assigning the values to them in separate assignment statment(s).
It is a shorter notation for declaring the variables and then assigning the values to them in separate assignment statement(s).
There are a few special situations:
initializing an array: ``ubyte[3] array = [11,22,33]``
The initiazation value has to be a range value or an array literal (remember you can use '[4] * 3' and such).
Ofcourse the size of the range or the number of values in the array has to match the declared array size.
The initialization value has to be a range value or an array literal (remember you can use '[4] * 3' and such).
Of course the size of the range or the number of values in the array has to match the declared array size.
initializing a multi variable declaration with different values: ``ubyte a,b,c = 11,22,33``
Here we have separate initialization values for each of the declared variables in that order.
@@ -194,7 +194,7 @@ initializing a multi variable declaration with the same value for all: ``ubyte a
initializing from a subroutine returning multiple result values: ``ubyte a,b,c = multi()``
Here the initialization value can also be a subroutine call to a subroutine returning multiple result values, which will then be put
into the declared variables in order. Ofcourse the number of values has to match the number of variables.
into the declared variables in order. Of course the number of values has to match the number of variables.
Constants
@@ -214,9 +214,9 @@ Enums
-----
.. index:: single: Enums
There is a more convenient way to define a bunch of constants that belong togther: a "enum".
There is a more convenient way to define a bunch of constants that belong together: a "enum".
That is a grouped list of constants that get autonumbered for you (unless you override the numeric value yourself).
It starts numbering from zero by default. Here's an example::
It starts numbering from zero by default. Right now the enum declaration does not define a new type, only a list of constants. Here's an example::
enum Priority {
LOW = 1,
@@ -232,7 +232,6 @@ This will define a bunch of constants like so (the "Enum::Field" syntax is speci
const ubyte Priority::HIGH = 3
const ubyte Priority::EXTREME = 255
Data Types
----------
.. index:: pair: Data Types; Overview
@@ -244,7 +243,7 @@ type identifier type storage size example var declara
=============== ======================= ================= =========================================
``byte`` signed byte 1 byte = 8 bits ``byte myvar = -22``
``ubyte`` unsigned byte 1 byte = 8 bits ``ubyte myvar = $8f``, ``ubyte c = 'a'``
``bool`` boolean 1 byte = 8 bits ``bool myvar = true`` or ``bool myvar == false``
``bool`` boolean 1 byte = 8 bits ``bool myvar = true`` or ``bool myvar = false``
``word`` signed word 2 bytes = 16 bits ``word myvar = -12345``
``uword`` unsigned word 2 bytes = 16 bits ``uword myvar = $8fee``
``long`` signed 32 bits integer 4 bytes ``long large = $12345678``
@@ -366,7 +365,7 @@ any underscores in the number are ignored by the compiler.
For instance ``30_000.999_999`` is a valid floating point number 30000.999999.
.. attention::
On the X16, make sure rom bank 4 is still active before doing floationg point operations (it's the bank that contains the fp routines).
On the X16, make sure rom bank 4 is still active before doing floating point operations (it's the bank that contains the fp routines).
On the C64, you have to make sure the Basic ROM is still banked in (same reason).
@@ -401,7 +400,7 @@ or when adding more stuff to the array later. Here are some examples of arrays::
To allow the 6502 CPU to efficiently access values in an array, the array should be small enough to be
indexable by a single byte index.
This means byte arrays should be <= 256 elements, word arrays <= 256 elements as well (if split, which
is the default. When not split, the maximum length is 128. See below for details about this disctinction).t
is the default. When not split, the maximum length is 128. See below for details about this distinction).
Float arrays should be <= 51 elements.
Arrays can be initialized with a range expression or an array literal value.
@@ -499,9 +498,9 @@ As an optimization, (u)word arrays, pointer arrays, and str arrays are split by
one with the LSBs and one with the MSBs of the word values. This is more efficient to access by the 6502 cpu.
It also allows a maximum length of 256 for word arrays, where normally it would have been 128.
For normal prog8 array indexing, the compiler takes care of the distiction for you under water.
For normal prog8 array indexing, the compiler takes care of the distinction for you under water.
*But for assembly code, or code that otherwise accesses the array elements directly, you have to be aware of the distinction from 'normal' arrays.*
In the assembly code, the array is generated as two byte arrays namely ``name_lsb`` and ``name_msb``, immediately following eachother in memory.
In the assembly code, the array is generated as two byte arrays namely ``name_lsb`` and ``name_msb``, immediately following each other in memory.
The ``@nosplit`` tag can be added to the variable declaration to *not* split the array. This is useful for compatibility with
code that expects the words to be sequentially in memory (such as the cx16.FB_set_palette routine).
@@ -515,7 +514,7 @@ code that expects the words to be sequentially in memory (such as the cx16.FB_se
.. note::
Array literals are stored as split arrays if they're initializing a split word array, otherwise,
they are stored as sequential words! So if you pass one directly to a subroutine (like ``func([1111,2222,3333])``),
the array values are sequential in memory. If this is undesiarable (i.e. the subroutine expects a split word array),
the array values are sequential in memory. If this is undesirable (i.e. the subroutine expects a split word array),
you have to create a normal array variable first and then pass that to the subroutine.
.. caution::
+51
View File
@@ -0,0 +1,51 @@
# Requirements
### Overview & Goals
The goal is to transition Prog8 enums from being merely syntactic sugar for integer constants into a first-class, strongly typed data type in the Prog8 language. This will improve type safety by preventing erroneous assignments or comparisons between different enums or between enums and raw integers.
### Scope
#### In Scope
- Definition of a new `EnumSubType` within the compiler's type system to extend existing `DataType`s.
- Modifications to `SymbolTable` and `TypeChecker` to enforce enum type strictness.
- Updates to AST processing phases (`SimplifiedAstMaker`) to preserve enum declarations instead of lowering them to constants.
- Code generation adjustments to handle enum variables correctly.
- Verification through new unit and integration tests.
#### Out of Scope
- Architectural changes to non-enum types.
- Backend-specific optimizations for enums (unless directly required for correctness).
# Technical Design
### Current Implementation
Currently, `Enumeration` nodes in the `compilerAst` are lowered into standard constant variable declarations (`const`) early in the `processAst()` phase. This is confirmed by `SimplifiedAstMaker.kt` which expects enums to have been removed.
### Key Decisions
1. **Represent Enum as a `DataType` with `EnumSubType`**: We will implement a new `EnumSubType` class (implementing `ISubType`) in `codeCore`. This will be used by `DataType` to uniquely identify strongly typed enums.
2. **Preserve `Enumeration` AST nodes in CompilerAST**: The `SimplifiedAstMaker` will be updated to treat `Enumeration` as a type definition during the initial compiler phases.
3. **Reduction in `SimplifiedAstMaker`**: The `SimplifiedAstMaker` will lower `EnumType` to its underlying `Int` type constants when generating the `SimpleAST`. This keeps the SimpleAST, IR, and downstream code generation simple, as they only need to deal with integers.
4. **Strict Type Checking & Casting**: The `TypeChecker` will be updated to disallow *implicit* conversions for enums. *Explicit* casting between an Enum and its underlying integer type will be allowed. Casting *between* different Enum types will be explicitly prohibited.
5. **SimplifiedAstMaker Erasure & Folding**: The `SimplifiedAstMaker` will be updated to perform the enum erasure (lowering `EnumType` to its underlying `Int` type constants) *and* the associated constant folding for enum-typed expressions that can be reduced to integers at that point. This approach centralizes the erasure logic and keeps the `ConstantFoldingOptimizer` focused on general-purpose expression simplifications.
6. **AST Printer Updates**: `AstToSourceTextConverter` (`printAst1`) will be updated to recognize and correctly output the new strongly typed enum node structure. `AstPrinter` (`printAst2`) does not need changes as it operates on the erased `SimpleAST`.
### Proposed Changes
- **`codeCore`**: Add `BaseDataType.ENUM` and introduce `EnumSubType` class implementing `ISubType` to extend `DataType`.
- **`compilerAst` / `compiler`**: Modify `SimplifiedAstMaker` and `AstPreprocessor` to stop lowering `Enumeration` to constants. Update `AstToSourceTextConverter` to support printing the typed enum nodes.
- **`simpleAst` / `SymbolTable`**: Update the `SymbolTable` and `TypeChecker` to register, validate enum types, and handle explicit casting rules (disallow implicit enum conversion, disallow cross-enum conversion).
- **Code Generators**: No changes needed. The enum type is erased to its underlying integer representation during the `SimplifiedAstMaker` phase, and the code generators only deal with plain integer values.
### Risk & Mitigations
- **Existing Code Compatibility**: Existing programs relying on implicit enum-to-integer conversion will break. This is intentional to improve type safety.
- *Mitigation*: Provide clear compiler error messages when implicit conversion fails, and document the new explicit casting requirement.
# Testing
### Validation Approach
- **Type Checking Tests**: Add test cases that explicitly verify:
- Implicit assignment/comparison of incompatible types (expect failure).
- Explicit cast (Enum ↔ Int) (expect success).
- Explicit cast between different Enum types (expect failure).
- Compare two different enum types (expect failure).
- Use an enum member without proper type qualification.
- **AST Printing Verification**: Use `-printast1` and `-printast2` to verify that strongly typed enum nodes are correctly displayed in the AST output.
- **Runtime Verification**: Ensure that the code generation produces correct code for enums (e.g., correct values, proper size) using the `virtual` target and `vmtrace`.
+1 -1
View File
@@ -65,7 +65,7 @@ main {
}
}
; Put the new ball possition
; Put the new ball position
txt.setcc(BX[lp], BY[lp], sc:'●', BC[lp])
}
+1 -1
View File
@@ -118,7 +118,7 @@ main {
; Scroll the bottom half (approx.) of the character screen 1 character to the left
; contents of the rightmost column are unchanged, you should clear/refill this yourself
; Without clever split-screen tricks, the C64 is not fast enough to scroll the whole
; screen smootly without tearing. So for simplicity it's constrained to less rows
; screen smoothly without tearing. So for simplicity it's constrained to less rows
; such that what is scrolled, *does* scrolls smoothly.
; For maximum performance the scrolling is done in unrolled assembly code.
+2 -2
View File
@@ -35,7 +35,7 @@ irq {
; Here is the actual multiplexing routine.
; it's a raster irq just after the start of the sprite,
; that updates the Y position of all the sprits,
; that updates the Y position of all the sprites,
; and registers a new raster irq for that next row of sprites.
; If the bottom of the screen is reached, it resets the X position of the sprites as well,
; and moves the sprites back to the top of the screen.
@@ -56,7 +56,7 @@ irq {
sprites.set_sprites_X(first_sprite_X) ; we can now update the X positions without risk of sprite tearing
system_irq = true
} else {
; only set the new Y positions. But it's possible to change other attributes as well ofcourse (colors, x-position, data)
; only set the new Y positions. But it's possible to change other attributes as well of course (colors, x-position, data)
; but raster timing is critical for that if you want to avoid tearing and glitches. Can probably not be done here at this raster position...
sprites.set_sprites_Y(sprites_Y)
c64.SPXY[0]++
+1 -1
View File
@@ -147,7 +147,7 @@ main {
}
}
; Dark starfield so it doesnt distract from bullets and text
; Dark starfield so it doesn't distract from bullets and text
ubyte[20] starfieldCols = [
14,10,12,15,14,13,12,11,10,14,
14,10,14,15,14,13,12,11,10,12
@@ -2,7 +2,7 @@
%option no_symbol_prefixing, ignore_unused
; compatiblity layer
; compatibility layer
cbm {
%option no_symbol_prefixing, ignore_unused
+1 -1
View File
@@ -3,7 +3,7 @@
%option no_sysinit
; NOTE: This is a small example of the Kernal's AUDIO routines. They target the Vara PSG but also the FM YM2151.
; NOTE: This is a small example of the Kernal's AUDIO routines. They target the Vera PSG but also the FM YM2151.
; There is also the "psg" library module that implements some Vera PSG routines
; of its own, but that's not used here at all. See the "cx16/bdmusic" example for that.
+1 -1
View File
@@ -27,7 +27,7 @@ main {
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = 0
; limit display heigth to 200 pixels to have enough vram for 14 backbuffers
; limit display height to 200 pixels to have enough vram for 14 backbuffers
const ubyte vstart = 20
const ubyte vheight = 200
cx16.VERA_CTRL = %00000010
+1 -1
View File
@@ -158,7 +158,7 @@ main {
}
matrix_math {
%option verafxmuls ; accellerate all word-multiplications in this block using Vera FX hardware muls
%option verafxmuls ; accelerate all word-multiplications in this block using Vera FX hardware muls
; storage for rotated coordinates
word[shipdata.totalNumberOfPoints] rotatedx
+1 -1
View File
@@ -55,7 +55,7 @@ main {
}
matrix_math {
%option verafxmuls ; accellerate all word-multiplications in this block using Vera FX hardware muls
%option verafxmuls ; accelerate all word-multiplications in this block using Vera FX hardware muls
; vertices
word[] xcoor = [ -40, -40, -40, -40, 40, 40, 40, 40 ]
+1 -1
View File
@@ -3,7 +3,7 @@
; It is hardwired to play a 16 bit stereo PCM file,
; but you can type in the sample rate you want to play it in.
;
; It uses simple polling on the AFLOW flag rather than actually using a IRQ hander,
; It uses simple polling on the AFLOW flag rather than actually using a IRQ handler,
; to see when to refill the FIFO buffer.
;
; The sample data is simply read using diskio.f_read() routine, but that uses MACPTR internally for fast loads.
+12 -6
View File
@@ -23,19 +23,21 @@ main {
txt.nl()
txt.nl()
long size = serial.zi_start_get_file("http://mockhttp.org/get?arg=hello-from-prog8-serial")
str url = "http://mockhttp.org/get?arg=hello-from-prog8-serial"
long size = serial.zi_start_get_file(url)
if size>0 {
txt.print("downloading file of size: ")
txt.print("downloading: ")
txt.print(url)
txt.print("\nfile size: ")
txt.print_l(size)
txt.nl()
ubyte[256] buffer
while size>0 {
uword readsize = serial.zi_get_file_chunk(&buffer, sizeof(buffer), size)
uword readsize = serial.zi_get_file_chunk(&buffer, sizeof(buffer)-1, size)
size -= readsize
for cx16.r0L in 0 to lsb(readsize-1) {
txt.chrout(buffer[cx16.r0L])
}
@(&buffer+readsize) = 0
txt.print(buffer)
}
txt.nl()
@@ -43,6 +45,10 @@ main {
} else {
txt.print("file not found\n")
}
serial.zi_end_get_file()
txt.print("\ndownload complete")
serial.zi_reset()
} else {
+1 -1
View File
@@ -533,7 +533,7 @@ blocklogic {
0,0,0,0,
0,0,0,0]
ubyte[16] edgecolors = [11, 1, 10, 0, 10, 13, 14, 1, 7, 7, 11, 12, 15, 1, 1, 1] ; highlighed colors for the edges
ubyte[16] edgecolors = [11, 1, 10, 0, 10, 13, 14, 1, 7, 7, 11, 12, 15, 1, 1, 1] ; highlighted colors for the edges
uword[] blocks = [&blockI, &blockJ, &blockL, &blockO, &blockS, &blockT, &blockZ]
+3 -3
View File
@@ -89,7 +89,7 @@ carve_restart_after_repath:
stackptr--
if stackptr==255 {
; stack empty.
; repath if we are not done yet. (this is a workaround for the prog8 256 array lenght limit)
; repath if we are not done yet. (this is a workaround for the prog8 256 array length limit)
if cells_to_carve!=0 {
if repath()
goto carve_restart_after_repath
@@ -104,7 +104,7 @@ carve_restart_after_repath:
stackptr++
if stackptr==0 {
; stack overflow, we can't track our path any longer.
; repath if we are not done yet. (this is a workaround for the prog8 256 array lenght limit)
; repath if we are not done yet. (this is a workaround for the prog8 256 array length limit)
if cells_to_carve!=0 {
if repath()
goto carve_restart_after_repath
@@ -138,7 +138,7 @@ carve_restart_after_repath:
sub repath() -> bool {
; repath: try to find a new start cell with possible directions.
; we limit our number of searches so that the algorith doesn't get stuck
; we limit our number of searches so that the algorithm doesn't get stuck
; for too long on bad rng... just accept a few unused cells in that case.
repeat 255 {
do {
+10 -36
View File
@@ -1,44 +1,18 @@
%import textio
%zeropage basicsafe
%option no_sysinit
%encoding iso
main {
sub start() {
ubyte v1, v2
cx16.r0L = 99
v1 = inline_paramless_single()
txt.print_ub(v1)
long @shared @nozp size = 147320
while size>0 {
size--
if size & $ff == 0 {
txt.chrout('.')
}
}
txt.nl()
v1, v2 = inline_paramless_multi()
txt.print_ub(v1)
txt.spc()
txt.print_ub(v2)
txt.nl()
v1 = inline_single( cx16.r0L)
txt.print_ub(v1)
txt.nl()
v1, v2 = inline_multi( cx16.r0L)
txt.print_ub(v1)
txt.spc()
txt.print_ub(v2)
txt.nl()
}
inline sub inline_paramless_single() -> ubyte {
return cx16.r0L+100
}
inline sub inline_paramless_multi() -> ubyte, ubyte {
return cx16.r0L+10, cx16.r0L+20
}
inline sub inline_single(ubyte v) -> ubyte {
return v+100
}
inline sub inline_multi(ubyte v) -> ubyte, ubyte {
return v+10, v+20
;;sys.poweroff_system()
}
}
+1 -1
View File
@@ -726,7 +726,7 @@ planet {
str[] @nosplit words92 = ["wasp", "moth", "grub", "ant", "\xB2"]
str[] @nosplit words93 = ["poet", "arts graduate", "yak", "snail", "slug"]
str[] @nosplit words94 = ["tropical", "dense", "rain", "impenetrable", "exuberant"]
str[] @nosplit words95 = ["funny", "wierd", "unusual", "strange", "peculiar"]
str[] @nosplit words95 = ["funny", "weird", "unusual", "strange", "peculiar"]
str[] @nosplit words96 = ["frequent", "occasional", "unpredictable", "dreadful", "deadly"]
str[] @nosplit words97 = ["\x82 \x81 for \x8A", "\x82 \x81 for \x8A and \x8A", "\x88 by \x89", "\x82 \x81 for \x8A but \x88 by \x89", "a\x90 \x91"]
str[] @nosplit words98 = ["\x9B", "mountain", "edible", "tree", "spotted"]
+1 -1
View File
@@ -4,4 +4,4 @@ org.gradle.parallel=true
org.gradle.daemon=true
org.gradle.configuration-cache=false
kotlin.code.style=official
version=12.2-BETA2
version=12.2-BETA3
@@ -98,8 +98,8 @@ class IRFileReader {
program.addGlobalInits(initGlobals)
blocks.forEach{ program.addBlock(it) }
program.linkChunks()
program.convertAsmChunks()
program.linkChunks()
program.validate()
return program
@@ -57,7 +57,7 @@ NOTE: status flags are only affected by the CMP instruction or explicit CLC/SEC,
Instruction set is mostly a load/store architecture, there are few instructions operating on memory directly.
Value types: integers (.b=byte=8 bits, .w=word=16 bits, .l=long=32 bits) and float (.f=64 bits). Omitting it defaults to b if the instruction requires a type.
There is no distinction between signed and unsigned. Instead, a different instruction is used if a distinction should be made (for example div and divs).
There is no distinction between signed and unsigned for many instructions. Instead, a different instruction is used if a distinction should be made (for example div and divs).
Floating point operations are just 'f' typed regular instructions, however there are a few unique fp conversion instructions.
NOTE: Labels in source text should always start with an underscore.
@@ -65,7 +65,7 @@ NOTE: Labels in source text should always start with an underscore.
LOAD/STORE
----------
All have type b or w or f.
All have type b or w or l or f.
load reg1, value - load immediate value into register. If you supply a symbol, loads the *address* of the symbol! (variable values are loaded from memory via the loadm instruction)
loadm reg1, address - load reg1 with value at memory address
@@ -124,7 +124,7 @@ returni number - like return, but also returns the immedi
BRANCHING and CONDITIONALS
--------------------------
All have type b or w except the branches that only check status bits.
All have type b or w or l except the branches that only check status bits.
bstcc address - branch to location if Status bit Carry is clear
bstcs address - branch to location if Status bit Carry is set
@@ -158,7 +158,7 @@ bgesr reg1, reg2, address - jump to location in program given by l
ARITHMETIC
----------
All have type b or w or f. Note: result types are the same as operand types! E.g. byte*byte->byte.
All have type b or w or l or f. Note: result types are the same as operand types! E.g. byte*byte->byte.
exts reg1, reg2 - reg1 = signed extension of reg2 (byte to word, or word to long) (note: unlike M68k, exts.b -> word and exts.w -> long.)
ext reg1, reg2 - reg1 = unsigned extension of reg2 (which in practice just means clearing the MSB / MSW) (note: unlike M68k, ext.b -> word and ext.w -> long. )
@@ -174,18 +174,18 @@ addm reg1, address - memory at address += reg1
subr reg1, reg2 - reg1 -= reg2
sub reg1, value - reg1 -= value
subm reg1, address - memory at address -= reg1
mulr reg1, reg2 - unsigned multiply reg1 *= reg2 note: byte*byte->byte, no type extension to word!
mulsr reg1, reg2 - signed multiply reg1 *= reg2 note: byte*byte->byte, no type extension to word!
mul reg1, value - unsigned multiply reg1 *= value note: byte*byte->byte, no type extension to word!
muls reg1, value - signed multiply reg1 *= value note: byte*byte->byte, no type extension to word!
mulm reg1, address - unsigned memory at address *= reg2 note: byte*byte->byte, no type extension to word!
mulsm reg1, address - signed memory at address *= reg2 note: byte*byte->byte, no type extension to word!
mulr reg1, reg2 - unsigned multiply reg1 *= reg2 note: byte*byte->byte, no type extension!
mulsr reg1, reg2 - signed multiply reg1 *= reg2 note: byte*byte->byte, no type extension!
mul reg1, value - unsigned multiply reg1 *= value note: byte*byte->byte, no type extension!
muls reg1, value - signed multiply reg1 *= value note: byte*byte->byte, no type extension!
mulm reg1, address - unsigned memory at address *= reg2 note: byte*byte->byte, no type extension!
mulsm reg1, address - signed memory at address *= reg2 note: byte*byte->byte, no type extension!
divr reg1, reg2 - unsigned division reg1 /= reg2 note: division by zero yields max int $ff/$ffff
divsr reg1, reg2 - signed division reg1 /= reg2 note: division by zero yields max signed int 127 / 32767
divsr reg1, reg2 - signed division reg1 /= reg2 note: division by zero yields max signed int 127 / 32767 / 2147483647
div reg1, value - unsigned division reg1 /= value note: division by zero yields max int $ff/$ffff
divs reg1, value - signed division reg1 /= value note: division by zero yields max signed int 127 / 32767
divs reg1, value - signed division reg1 /= value note: division by zero yields max signed int 127 / 32767 / 2147483647
divm reg1, address - memory at address /= reg2 note: division by zero yields max int $ff/$ffff
divsm reg1, address - signed memory at address /= reg2 note: division by zero yields max signed int 127 / 32767
divsm reg1, address - signed memory at address /= reg2 note: division by zero yields max signed int 127 / 32767 / 2147483647
modr reg1, reg2 - remainder (modulo) of unsigned division reg1 %= reg2 note: division by zero yields max signed int $ff/$ffff
mod reg1, value - remainder (modulo) of unsigned division reg1 %= value note: division by zero yields max signed int $ff/$ffff
modsr reg1, reg2 - remainder (modulo) of signed division reg1 %= reg2 note: division by zero yields max signed long
@@ -205,7 +205,7 @@ NOTE: because mul/div are constrained (truncated) to remain in 8 or 16 or 32 bit
LOGICAL/BITWISE
---------------
All have type b or w.
All have type b or w or l.
andr reg1, reg2 - reg1 = reg1 bitwise and reg2
and reg1, value - reg1 = reg1 bitwise and value
@@ -709,7 +709,6 @@ val instructionFormats = mutableMapOf(
Opcode.LOADX to InstructionFormat.from("BWL,>r1,<r2,<a | F,>fr1,<r1,<a"),
Opcode.LOADR to InstructionFormat.from("BWL,>r1,<r2 | F,>fr1,<fr2"),
Opcode.LOADHA to InstructionFormat.from("B,>r1"),
Opcode.LOADHA to InstructionFormat.from("B,>r1"),
Opcode.LOADHX to InstructionFormat.from("B,>r1"),
Opcode.LOADHY to InstructionFormat.from("B,>r1"),
Opcode.LOADHAX to InstructionFormat.from("W,>r1"),
@@ -724,7 +723,6 @@ val instructionFormats = mutableMapOf(
Opcode.STOREZI to InstructionFormat.from("BWL,<r1,<i | F,<r1,<i"),
Opcode.STOREZX to InstructionFormat.from("BWL,<r1,>a | F,<r1,>a"),
Opcode.STOREHA to InstructionFormat.from("B,<r1"),
Opcode.STOREHA to InstructionFormat.from("B,<r1"),
Opcode.STOREHX to InstructionFormat.from("B,<r1"),
Opcode.STOREHY to InstructionFormat.from("B,<r1"),
Opcode.STOREHAX to InstructionFormat.from("W,<r1"),
@@ -893,12 +891,12 @@ class FunctionCallArgs(
data class IRInstruction(
val opcode: Opcode,
val type: IRDataType?=null,
val reg1: Int?=null, // 0-$ffff
val reg2: Int?=null, // 0-$ffff
val reg3: Int?=null, // 0-$ffff
val fpReg1: RegisterNum?=null, // 0-$ffff
val fpReg2: RegisterNum?=null, // 0-$ffff
val immediate: Int?=null, // 0-$ff or $ffff if word
val reg1: Int?=null, // 0-99999
val reg2: Int?=null, // 0-99999
val reg3: Int?=null, // 0-99999
val fpReg1: RegisterNum?=null, // 0-99999
val fpReg2: RegisterNum?=null, // 0-99999
val immediate: Int?=null, // 0-$ff or $ffff or $ffffffff
val immediateFp: Double?=null,
val address: MemoryAddress? = null, // 0-$ffff
val labelSymbol: String?=null, // symbolic label name as alternative to address (so only for Branch/jump/call Instructions!)
@@ -950,7 +948,7 @@ data class IRInstruction(
if(format.fpReg2==OperandDirection.UNUSED) require(fpReg2==null) { "invalid fpReg2" }
if(format.immediate) {
if(type==IRDataType.FLOAT) {
if(opcode !in setOf(Opcode.LOADI, Opcode.STOREI, Opcode.STOREZI))
if(opcode !in setOf(Opcode.LOAD, Opcode.LOADI, Opcode.STOREI, Opcode.STOREZI))
requireNotNull(immediateFp) { "missing immediate fp value" }
}
else
@@ -1223,7 +1221,7 @@ data class IRInstruction(
IRDataType.BYTE -> append("r${returnspec.registerNum.value}.b@$cpuReg")
IRDataType.WORD -> append("r${returnspec.registerNum.value}.w@$cpuReg")
IRDataType.LONG -> append("r${returnspec.registerNum.value}.l@$cpuReg")
IRDataType.FLOAT -> append("r${returnspec.registerNum.value}.f@$cpuReg")
IRDataType.FLOAT -> append("fr${returnspec.registerNum.value}.f@$cpuReg")
}
}
}
@@ -122,11 +122,16 @@ class IRProgram(val name: String,
blocks.forEach { block ->
block.children.forEach { child ->
when(child) {
is IRAsmSubroutine -> result[child.asmChunk.label] = child.asmChunk
is IRAsmSubroutine -> result[child.label] = child.asmChunk
is IRCodeChunk -> result[child.label] = child
is IRInlineAsmChunk -> result[child.label] = child
is IRInlineBinaryChunk -> result[child.label] = child
is IRSubroutine -> result.putAll(child.chunks.associateBy { it.label })
is IRSubroutine -> {
result.putAll(child.chunks.associateBy { it.label })
if (child.chunks.isNotEmpty()) {
result[child.label] = child.chunks.first()
}
}
}
}
}
@@ -160,17 +165,12 @@ class IRProgram(val name: String,
fun linkCodeChunk(chunk: IRCodeChunk, next: IRCodeChunkBase?) {
// link sequential chunks
val jump = chunk.instructions.lastOrNull()?.opcode
if (jump == null || jump !in OpcodesThatBranchUnconditionally) {
val lastInstr = chunk.instructions.lastOrNull()
if (lastInstr == null || lastInstr.opcode !in OpcodesThatBranchUnconditionally) {
// no jump at the end, so link to next chunk (if it exists)
if(next!=null) {
when (next) {
is IRCodeChunk if chunk.instructions.lastOrNull()?.opcode !in OpcodesThatBranchUnconditionally -> chunk.next = next
is IRInlineAsmChunk -> chunk.next = next
is IRInlineBinaryChunk -> chunk.next =next
else -> throw AssemblyError("code chunk followed by invalid chunk type $next")
}
}
chunk.next = next
} else {
chunk.next = null
}
// link all jump and branching instructions to their target
@@ -180,46 +180,48 @@ class IRProgram(val name: String,
// it's a call to an address (extsub most likely)
requireNotNull(it.address)
} else {
it.branchTarget = labeledChunks.getValue(it.labelSymbol)
it.branchTarget = labeledChunks[it.labelSymbol] ?: throw AssemblyError("Missing jump/call target: ${it.labelSymbol}")
}
}
}
}
fun linkBaseChunk(chunk: IRCodeChunkBase, next: IRCodeChunkBase?) {
when (chunk) {
is IRCodeChunk -> linkCodeChunk(chunk, next)
is IRInlineAsmChunk -> {
val lastInstr = chunk.instructions.lastOrNull()
if (lastInstr == null || lastInstr.opcode !in OpcodesThatBranchUnconditionally)
chunk.next = next
else
chunk.next = null
}
is IRInlineBinaryChunk -> {
chunk.next = next
}
}
}
fun linkSubroutineChunks(sub: IRSubroutine) {
sub.chunks.withIndex().forEach { (index, chunk) ->
val next = if(index<sub.chunks.size-1) sub.chunks[index + 1] else null
when (chunk) {
is IRCodeChunk -> {
linkCodeChunk(chunk, next)
}
is IRInlineAsmChunk -> {
if(next!=null) {
val lastInstr = chunk.instructions.lastOrNull()
if(lastInstr==null || lastInstr.opcode !in OpcodesThatBranchUnconditionally)
chunk.next = next
}
}
is IRInlineBinaryChunk -> { }
}
val next = if(index < sub.chunks.size - 1) sub.chunks[index + 1] else null
linkBaseChunk(chunk, next)
}
}
blocks.forEach { block ->
block.children.forEachIndexed { index, child ->
val next = if(index<block.children.lastIndex) block.children[index+1] as? IRCodeChunkBase else null
val next = if(index < block.children.lastIndex) block.children[index+1] as? IRCodeChunkBase else null
when (child) {
is IRAsmSubroutine -> child.asmChunk.next = next
is IRCodeChunk -> child.next = next
is IRInlineAsmChunk -> child.next = next
is IRInlineBinaryChunk -> child.next = next
is IRAsmSubroutine -> linkBaseChunk(child.asmChunk, next)
is IRCodeChunk -> linkBaseChunk(child, next)
is IRInlineAsmChunk -> linkBaseChunk(child, next)
is IRInlineBinaryChunk -> linkBaseChunk(child, next)
is IRSubroutine -> linkSubroutineChunks(child)
}
}
}
linkCodeChunk(globalInits, globalInits.next)
linkBaseChunk(globalInits, globalInits.next)
}
fun validate() {
@@ -243,10 +245,11 @@ class IRProgram(val name: String,
chunk.instructions.forEach { instr ->
if(instr.labelSymbol!=null && instr.opcode in OpcodesThatBranch) {
if(instr.opcode==Opcode.JUMPI) {
when(val pointervar = st.lookup(instr.labelSymbol)!!) {
is IRStStaticVariable -> require(pointervar.dt.isUnsignedWord)
is IRStMemVar -> require(pointervar.dt.isUnsignedWord)
else -> throw AssemblyError("weird pointervar type")
val symbol = st.lookup(instr.labelSymbol) ?: throw AssemblyError("Missing jump target symbol: ${instr.labelSymbol}")
when(symbol) {
is IRStStaticVariable -> require(symbol.dt.isUnsignedWord)
is IRStMemVar -> require(symbol.dt.isUnsignedWord)
else -> throw AssemblyError("Invalid jump target symbol type: ${instr.labelSymbol}")
}
}
else if(!instr.labelSymbol.startsWith('$') && !instr.labelSymbol.first().isDigit())
+1 -1
View File
@@ -130,7 +130,7 @@ fun parseIRCodeLine(line: String): Either<IRInstruction, String> {
}
if(format.sysCall) {
val call = parseCall(rest)
val syscallNum = call.address?.toInt() ?: parseIRValue(call.target ?: "").toInt()
val syscallNum = call.address?.toInt() ?: (call.target?.let { parseIRValue(it).toInt() } ?: throw IRParseException("Missing syscall number"))
return left(IRInstruction(Opcode.SYSCALL, immediate = syscallNum, fcallArgs = FunctionCallArgs(call.args, call.returns)))
} else if (format.funcCall) {
val call = parseCall(rest)
+15 -863
View File
@@ -4,9 +4,6 @@ import prog8.code.core.toHex
import prog8.code.target.IVirtualMachineRunner
import prog8.code.target.VMTarget
import prog8.intermediate.*
import java.awt.Color
import java.awt.GraphicsEnvironment
import java.io.IOException
import kotlin.math.*
import kotlin.random.Random
@@ -41,8 +38,8 @@ class VirtualMachine(irProgram: IRProgram) {
private const val MIPS_COUNTER_MASK = 0xffffff
}
private var fileOutputStream: java.io.RandomAccessFile? = null
private var fileInputStream: java.io.RandomAccessFile? = null
internal var fileOutputStream: java.io.RandomAccessFile? = null
internal var fileInputStream: java.io.RandomAccessFile? = null
val memory = Memory()
val machine = VMTarget()
val program: List<IRCodeChunk>
@@ -466,8 +463,15 @@ class VirtualMachine(irProgram: IRProgram) {
}
private fun InsLOAD(i: IRInstruction) {
if(i.type==IRDataType.FLOAT)
registers.setFloat(i.fpReg1!!, i.immediateFp!!)
if(i.type==IRDataType.FLOAT) {
if (i.immediateFp != null)
registers.setFloat(i.fpReg1!!, i.immediateFp!!)
else {
if (i.labelSymbol == null)
throw IllegalArgumentException("expected LOAD of immediate or labelsymbol")
registers.setFloat(i.fpReg1!!, i.address!!.value.toDouble())
}
}
else {
if(i.immediate!=null) {
setResultReg(i.reg1!!, i.immediate!!, i.type!!)
@@ -1543,468 +1547,6 @@ class VirtualMachine(irProgram: IRProgram) {
}
internal fun statusbitsNZ(value: Int, type: IRDataType) {
statusZero = value == 0
when(type) {
IRDataType.BYTE -> statusNegative = (value and 0x80) == 0x80
IRDataType.WORD -> statusNegative = (value and 0x8000) == 0x8000
IRDataType.LONG -> statusNegative = value < 0
IRDataType.FLOAT -> { /* floats don't change the status bits */ }
}
}
private fun statusbitsComparison(comparison: Int, type: IRDataType) {
if(comparison==0) {
statusZero = true
statusCarry = true
} else if(comparison>0) {
statusZero = false
statusCarry = true
} else {
statusZero = false
statusCarry = false
}
when(type) {
IRDataType.BYTE -> statusNegative = (comparison and 0x80)!=0
IRDataType.WORD -> statusNegative = (comparison and 0x8000)!=0
IRDataType.LONG -> statusNegative = comparison<0
IRDataType.FLOAT -> { /* floats don't change the status bits */ }
}
}
private fun statusbitsComparisonWithOverflow(comparison: Int, leftOrig: Int, type: IRDataType) {
statusbitsComparison(comparison, type)
when(type) {
IRDataType.BYTE -> {
val signBit = 0x80
val leftSign = leftOrig and signBit
val rightSign = (leftOrig - comparison) and signBit
statusOverflow = ((leftSign xor rightSign) and (leftSign xor (comparison and signBit))) != 0
}
IRDataType.WORD -> {
val signBit = 0x8000
val leftSign = leftOrig and signBit
val rightSign = (leftOrig - comparison) and signBit
statusOverflow = ((leftSign xor rightSign) and (leftSign xor (comparison and signBit))) != 0
}
IRDataType.LONG -> {
val leftSign = leftOrig < 0
val rightSign = (leftOrig - comparison) < 0
val resultSign = comparison < 0
statusOverflow = leftSign != rightSign && leftSign != resultSign
}
IRDataType.FLOAT -> { /* floats don't set overflow */ }
}
}
private fun plusMinusMultAnyByte(operator: String, reg1: Int, reg2: Int) {
val left = registers.getUB(reg1)
val right = registers.getUB(reg2)
val result = when(operator) {
"+" -> left + right
"-" -> left - right
"*" -> left * right
else -> throw IllegalArgumentException("operator byte $operator")
}
registers.setUB(reg1, result.toUByte())
}
private fun multiplyAnyByteSigned(reg1: Int, reg2: Int) {
val left = registers.getSB(reg1)
val right = registers.getSB(reg2)
val result = left * right
registers.setSB(reg1, result.toByte())
}
private fun plusMinusMultConstByte(operator: String, reg1: Int, value: UByte) {
val left = registers.getUB(reg1)
val result = when(operator) {
"+" -> left + value
"-" -> left - value
"*" -> left * value
else -> throw IllegalArgumentException("operator byte $operator")
}
registers.setUB(reg1, result.toUByte())
}
private fun multiplyConstByteSigned(reg1: Int, value: Byte) {
val left = registers.getSB(reg1)
val result = left * value
registers.setSB(reg1, result.toByte())
}
private fun plusMinusMultAnyByteInplace(operator: String, reg1: Int, address: UInt) {
val memvalue = memory.getUB(address)
val operand = registers.getUB(reg1)
val result = when(operator) {
"+" -> memvalue + operand
"-" -> memvalue - operand
"*" -> memvalue * operand
else -> throw IllegalArgumentException("operator byte $operator")
}
memory.setUB(address, result.toUByte())
}
private fun multiplyAnyByteSignedInplace(reg1: Int, address: UInt) {
val memvalue = memory.getSB(address)
val operand = registers.getSB(reg1)
val result = memvalue * operand
memory.setSB(address, result.toByte())
}
private fun divModByteSigned(operator: String, reg1: Int, reg2: Int) {
val left = registers.getSB(reg1)
val right = registers.getSB(reg2)
val result = when(operator) {
"/" -> {
if(right==0.toByte()) 127
else left / right
}
"%" -> {
if(right==0.toByte()) 127
else left % right
}
else -> throw IllegalArgumentException("operator byte $operator")
}
registers.setSB(reg1, result.toByte())
}
private fun divModConstByteSigned(operator: String, reg1: Int, value: Byte) {
val left = registers.getSB(reg1)
val result = when(operator) {
"/" -> {
if(value==0.toByte()) 127
else left / value
}
"%" -> {
if(value==0.toByte()) 127
else left % value
}
else -> throw IllegalArgumentException("operator byte $operator")
}
registers.setSB(reg1, result.toByte())
}
private fun divModByteSignedInplace(operator: String, reg1: Int, address: UInt) {
val left = memory.getSB(address)
val right = registers.getSB(reg1)
val result = when(operator) {
"/" -> {
if(right==0.toByte()) 127
else left / right
}
"%" -> {
if(right==0.toByte()) 127
else left % right
}
else -> throw IllegalArgumentException("operator byte $operator")
}
memory.setSB(address, result.toByte())
}
private fun divOrModByteUnsigned(operator: String, reg1: Int, reg2: Int) {
val left = registers.getUB(reg1)
val right = registers.getUB(reg2)
val result = when(operator) {
"/" -> {
if(right==0.toUByte()) 0xffu
else left / right
}
"%" -> {
if(right==0.toUByte()) 0u
else left % right
}
else -> throw IllegalArgumentException("operator byte $operator")
}
registers.setUB(reg1, result.toUByte())
}
private fun divOrModConstByteUnsigned(operator: String, reg1: Int, value: UByte) {
val left = registers.getUB(reg1)
val result = when(operator) {
"/" -> {
if(value==0.toUByte()) 0xffu
else left / value
}
"%" -> {
if(value==0.toUByte()) 0u
else left % value
}
else -> throw IllegalArgumentException("operator byte $operator")
}
registers.setUB(reg1, result.toUByte())
}
private fun divAndModUByte(reg1: Int, reg2: Int) {
val left = registers.getUB(reg1)
val right = registers.getUB(reg2)
val division = if(right==0.toUByte()) 0xffu else left / right
val remainder = if(right==0.toUByte()) 0u else left % right
valueStack.add(division.toUByte())
valueStack.add(remainder.toUByte())
}
private fun divAndModConstUByte(reg1: Int, value: UByte) {
val left = registers.getUB(reg1)
val division = if(value==0.toUByte()) 0xffu else left / value
val remainder = if(value==0.toUByte()) 0u else left % value
valueStack.add(division.toUByte())
valueStack.add(remainder.toUByte())
}
private fun divAndModUWord(reg1: Int, reg2: Int) {
val left = registers.getUW(reg1)
val right = registers.getUW(reg2)
val division = if(right==0.toUShort()) 0xffffu else left / right
val remainder = if(right==0.toUShort()) 0u else left % right
valueStack.pushw(division.toUShort())
valueStack.pushw(remainder.toUShort())
}
private fun divAndModConstUWord(reg1: Int, value: UShort) {
val left = registers.getUW(reg1)
val division = if(value==0.toUShort()) 0xffffu else left / value
val remainder = if(value==0.toUShort()) 0u else left % value
valueStack.pushw(division.toUShort())
valueStack.pushw(remainder.toUShort())
}
private fun divAndModSByte(reg1: Int, reg2: Int) {
val left = registers.getSB(reg1)
val right = registers.getSB(reg2)
val division = if(right==0.toByte()) 127 else left / right
val remainder = if(right==0.toByte()) 0 else left % right
valueStack.add(division.toUByte())
valueStack.add(remainder.toUByte())
}
private fun divAndModConstSByte(reg1: Int, value: Byte) {
val left = registers.getSB(reg1)
val division = if(value==0.toByte()) 127 else left / value
val remainder = if(value==0.toByte()) 0 else left % value
valueStack.add(division.toUByte())
valueStack.add(remainder.toUByte())
}
private fun divAndModSWord(reg1: Int, reg2: Int) {
val left = registers.getSW(reg1)
val right = registers.getSW(reg2)
val division = if(right==0.toShort()) 32767 else left / right
val remainder = if(right==0.toShort()) 0 else left % right
valueStack.pushw(division.toUShort())
valueStack.pushw(remainder.toUShort())
}
private fun divAndModConstSWord(reg1: Int, value: Short) {
val left = registers.getSW(reg1)
val division = if(value==0.toShort()) 32767 else left / value
val remainder = if(value==0.toShort()) 0 else left % value
valueStack.pushw(division.toUShort())
valueStack.pushw(remainder.toUShort())
}
private fun divModByteUnsignedInplace(operator: String, reg1: Int, address: UInt) {
val left = memory.getUB(address)
val right = registers.getUB(reg1)
val result = when(operator) {
"/" -> {
if(right==0.toUByte()) 0xffu
else left / right
}
"%" -> {
if(right==0.toUByte()) 0u
else left % right
}
else -> throw IllegalArgumentException("operator byte $operator")
}
memory.setUB(address, result.toUByte())
}
private fun plusMinusMultAnyWord(operator: String, reg1: Int, reg2: Int) {
val left = registers.getUW(reg1)
val right = registers.getUW(reg2)
val result: UInt
when(operator) {
"+" -> result = left + right
"-" -> result = left - right
"*" -> {
result = left.toUInt() * right
mul16LastUpper = result shr 16
}
else -> throw IllegalArgumentException("operator word $operator")
}
registers.setUW(reg1, result.toUShort())
}
private fun multiplyAnyWordSigned(reg1: Int, reg2: Int) {
val left = registers.getSW(reg1)
val right = registers.getSW(reg2)
val result = left.toInt() * right
mul16LastUpper = result.toUInt() shr 16
registers.setSW(reg1, result.toShort())
}
private fun plusMinusMultConstWord(operator: String, reg1: Int, value: UShort) {
val left = registers.getUW(reg1)
val result: UInt
when(operator) {
"+" -> result = left + value
"-" -> result = left - value
"*" -> {
result = left.toUInt() * value
mul16LastUpper = result shr 16
}
else -> throw IllegalArgumentException("operator word $operator")
}
registers.setUW(reg1, result.toUShort())
}
private fun multiplyConstWordSigned(reg1: Int, value: Short) {
val left = registers.getSW(reg1)
val result = left.toInt() * value
mul16LastUpper = result.toUInt() shr 16
registers.setSW(reg1, result.toShort())
}
private fun plusMinusMultAnyWordInplace(operator: String, reg1: Int, address: UInt) {
val memvalue = memory.getUW(address)
val operand = registers.getUW(reg1)
val result: UInt
when(operator) {
"+" -> result = memvalue + operand
"-" -> result = memvalue - operand
"*" -> {
result = memvalue.toUInt() * operand
mul16LastUpper = result shr 16
}
else -> throw IllegalArgumentException("operator word $operator")
}
memory.setUW(address, result.toUShort())
}
private fun multiplyAnyWordSignedInplace(reg1: Int, address: UInt) {
val memvalue = memory.getSW(address)
val operand = registers.getSW(reg1)
val result = memvalue.toInt() * operand
mul16LastUpper = result.toUInt() shr 16
memory.setSW(address, result.toShort())
}
private fun divOrModWordUnsigned(operator: String, reg1: Int, reg2: Int) {
val left = registers.getUW(reg1)
val right = registers.getUW(reg2)
val result = when(operator) {
"/" -> {
if(right==0.toUShort()) 0xffffu
else left / right
}
"%" -> {
if(right==0.toUShort()) 0u
else left % right
}
else -> throw IllegalArgumentException("operator word $operator")
}
registers.setUW(reg1, result.toUShort())
}
private fun divOrModConstWordUnsigned(operator: String, reg1: Int, value: UShort) {
val left = registers.getUW(reg1)
val result = when(operator) {
"/" -> {
if(value==0.toUShort()) 0xffffu
else left / value
}
"%" -> {
if(value==0.toUShort()) 0u
else left % value
}
else -> throw IllegalArgumentException("operator word $operator")
}
registers.setUW(reg1, result.toUShort())
}
private fun divModWordUnsignedInplace(operator: String, reg1: Int, address: UInt) {
val left = memory.getUW(address)
val right = registers.getUW(reg1)
val result = when(operator) {
"/" -> {
if(right==0.toUShort()) 0xffffu
else left / right
}
"%" -> {
if(right==0.toUShort()) 0u
else left % right
}
else -> throw IllegalArgumentException("operator word $operator")
}
memory.setUW(address, result.toUShort())
}
private fun divModWordSigned(operator: String, reg1: Int, reg2: Int) {
val left = registers.getSW(reg1)
val right = registers.getSW(reg2)
val result = when(operator) {
"/" -> {
if(right==0.toShort()) 32767
else left / right
}
"%" -> {
if(right==0.toShort()) 32767
else left % right
}
else -> throw IllegalArgumentException("operator word $operator")
}
registers.setSW(reg1, result.toShort())
}
private fun divModConstWordSigned(operator: String, reg1: Int, value: Short) {
val left = registers.getSW(reg1)
val result = when(operator) {
"/" -> {
if(value==0.toShort()) 32767
else left / value
}
"%" -> {
if(value==0.toShort()) 32767
else left % value
}
else -> throw IllegalArgumentException("operator word $operator")
}
registers.setSW(reg1, result.toShort())
}
private fun divModWordSignedInplace(operator: String, reg1: Int, address: UInt) {
val left = memory.getSW(address)
val right = registers.getSW(reg1)
val result = when(operator) {
"/" -> {
if(right==0.toShort()) 32767
else left / right
}
"%" -> {
if(right==0.toShort()) 32767
else left % right
}
else -> throw IllegalArgumentException("operator word $operator")
}
memory.setSW(address, result.toShort())
}
private fun arithFloat(left: Double, operator: String, right: Double): Double = when(operator) {
"+" -> left + right
"-" -> left - right
"*" -> left * right
"/" -> {
if(right==0.0) Double.MAX_VALUE
else left / right
}
"%" -> {
if(right==0.0) Double.MAX_VALUE
else left % right
}
else -> throw IllegalArgumentException("operator word $operator")
}
private fun InsBIT(i: IRInstruction) {
if (i.type!! == IRDataType.BYTE) {
val value = memory.getUB(i.address!!.value)
@@ -2345,7 +1887,7 @@ class VirtualMachine(irProgram: IRProgram) {
}
IRDataType.LONG -> {
val memvalue = memory.getSL(address)
memory.setSL(address, memvalue shr operand)
memory.setSL(address, memvalue ushr operand)
}
IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i")
}
@@ -2367,7 +1909,7 @@ class VirtualMachine(irProgram: IRProgram) {
IRDataType.LONG -> {
val value = registers.getSL(i.reg1!!)
statusCarry = (value and 1)!=0
registers.setSL(i.reg1!!, value shr 1)
registers.setSL(i.reg1!!, value ushr 1)
}
IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i")
}
@@ -2390,7 +1932,7 @@ class VirtualMachine(irProgram: IRProgram) {
IRDataType.LONG -> {
val value = memory.getSL(address)
statusCarry = (value and 1)!=0
memory.setSL(address, value shr 1)
memory.setSL(address, value ushr 1)
}
IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i")
}
@@ -2938,398 +2480,8 @@ class VirtualMachine(irProgram: IRProgram) {
nextPc()
}
private fun getBranchOperands(i: IRInstruction): Pair<Int, Int> {
return when(i.type) {
IRDataType.BYTE -> Pair(registers.getSB(i.reg1!!).toInt(), registers.getSB(i.reg2!!).toInt())
IRDataType.WORD -> Pair(registers.getSW(i.reg1!!).toInt(), registers.getSW(i.reg2!!).toInt())
IRDataType.LONG -> Pair(registers.getSL(i.reg1!!), registers.getSL(i.reg2!!))
IRDataType.FLOAT -> {
throw IllegalArgumentException("can't use float here")
}
null -> throw IllegalArgumentException("need type for branch instruction")
}
}
internal var window: GraphicsWindow? = null
private fun getBranchOperandsImm(i: IRInstruction): Pair<Int, Int> {
return when(i.type) {
IRDataType.BYTE -> Pair(registers.getSB(i.reg1!!).toInt(), i.immediate!!)
IRDataType.WORD -> Pair(registers.getSW(i.reg1!!).toInt(), i.immediate!!)
IRDataType.LONG -> Pair(registers.getSL(i.reg1!!), i.immediate!!)
IRDataType.FLOAT -> {
throw IllegalArgumentException("can't use float here")
}
null -> throw IllegalArgumentException("need type for branch instruction")
}
}
private fun getBranchOperandsU(i: IRInstruction): Pair<UInt, UInt> {
return when(i.type) {
IRDataType.BYTE -> Pair(registers.getUB(i.reg1!!).toUInt(), registers.getUB(i.reg2!!).toUInt())
IRDataType.WORD -> Pair(registers.getUW(i.reg1!!).toUInt(), registers.getUW(i.reg2!!).toUInt())
IRDataType.LONG -> Pair(registers.getSL(i.reg1!!).toUInt(), registers.getSL(i.reg2!!).toUInt())
IRDataType.FLOAT -> {
throw IllegalArgumentException("can't use float here")
}
null -> throw IllegalArgumentException("need type for branch instruction")
}
}
private fun getBranchOperandsImmU(i: IRInstruction): Pair<UInt, UInt> {
return when(i.type) {
IRDataType.BYTE -> Pair(registers.getUB(i.reg1!!).toUInt(), i.immediate!!.toUInt())
IRDataType.WORD -> Pair(registers.getUW(i.reg1!!).toUInt(), i.immediate!!.toUInt())
IRDataType.LONG -> Pair(registers.getSL(i.reg1!!).toUInt(), i.immediate!!.toUInt())
IRDataType.FLOAT -> {
throw IllegalArgumentException("can't use float here")
}
null -> throw IllegalArgumentException("need type for branch instruction")
}
}
private fun getLogicalOperandsU(i: IRInstruction): Pair<UInt, UInt> {
return when(i.type) {
IRDataType.BYTE -> Pair(registers.getUB(i.reg1!!).toUInt(), registers.getUB(i.reg2!!).toUInt())
IRDataType.WORD -> Pair(registers.getUW(i.reg1!!).toUInt(), registers.getUW(i.reg2!!).toUInt())
IRDataType.LONG -> Pair(registers.getSL(i.reg1!!).toUInt(), registers.getSL(i.reg2!!).toUInt())
IRDataType.FLOAT -> {
throw IllegalArgumentException("can't use float here")
}
null -> throw IllegalArgumentException("need type for logical instruction")
}
}
private fun getLogicalOperandsS(i: IRInstruction): Pair<Int, Int> {
return when(i.type) {
IRDataType.BYTE -> Pair(registers.getSB(i.reg1!!).toInt(), registers.getSB(i.reg2!!).toInt())
IRDataType.WORD -> Pair(registers.getSW(i.reg1!!).toInt(), registers.getSW(i.reg2!!).toInt())
IRDataType.LONG -> Pair(registers.getSL(i.reg1!!), registers.getSL(i.reg2!!))
IRDataType.FLOAT -> {
throw IllegalArgumentException("can't use float here")
}
null -> throw IllegalArgumentException("need type for logical instruction")
}
}
private fun plusMinusMultAnyLong(operator: String, reg1: Int, reg2: Int) {
val left = registers.getSL(reg1)
val right = registers.getSL(reg2)
val result: Int = when(operator) {
"+" -> left + right
"-" -> left - right
"*" -> left * right
else -> throw IllegalArgumentException("operator word $operator")
}
registers.setSL(reg1, result)
}
private fun plusMinusMultConstLong(operator: String, reg1: Int, value: Int) {
val left = registers.getSL(reg1)
val result: Int = when(operator) {
"+" -> left + value
"-" -> left - value
"*" -> left * value
else -> throw IllegalArgumentException("operator long $operator")
}
registers.setSL(reg1, result)
}
private fun plusMinusMultAnyLongInplace(operator: String, reg1: Int, address: UInt) {
val memvalue = memory.getSL(address)
val operand = registers.getSL(reg1)
val result: Int = when(operator) {
"+" -> memvalue + operand
"-" -> memvalue - operand
"*" -> memvalue * operand
else -> throw IllegalArgumentException("operator word $operator")
}
memory.setSL(address, result)
}
private fun multiplyAnyLongSigned(reg1: Int, reg2: Int) {
val result = registers.getSL(reg1) * registers.getSL(reg2)
registers.setSL(reg1, result)
}
private fun multiplyConstLongSigned(reg1: Int, value: Int) {
val result = registers.getSL(reg1) * value
registers.setSL(reg1, result)
}
private fun multiplyAnyLongSignedInplace(reg1: Int, address: UInt) {
val result = memory.getSL(address) * registers.getSL(reg1)
memory.setSL(address, result)
}
private fun divModLongSigned(operator: String, reg1: Int, reg2: Int) {
val left = registers.getSL(reg1)
val right = registers.getSL(reg2)
val result = when(operator) {
"/" -> {
if(right==0) Int.MAX_VALUE
else left / right
}
"%" -> {
if(right==0) Int.MAX_VALUE
else left % right
}
else -> throw IllegalArgumentException("operator long $operator")
}
registers.setSL(reg1, result)
}
private fun divModConstLongSigned(operator: String, reg1: Int, immediate: Int) {
val left = registers.getSL(reg1)
val result = when(operator) {
"/" -> {
if(immediate==0) Int.MAX_VALUE
else left / immediate
}
"%" -> {
if(immediate==0) Int.MAX_VALUE
else left % immediate
}
else -> throw IllegalArgumentException("operator long $operator")
}
registers.setSL(reg1, result)
}
private fun divModLongSignedInplace(operator: String, reg1: Int, address: UInt) {
val left = memory.getSL(address)
val right = registers.getSL(reg1)
val result = when(operator) {
"/" -> {
if(right==0) Int.MAX_VALUE
else left / right
}
"%" -> {
if(right==0) Int.MAX_VALUE
else left % right
}
else -> throw IllegalArgumentException("operator long $operator")
}
memory.setSL(address, result)
}
private var window: GraphicsWindow? = null
fun gfx_enable(mode: UByte) {
window = when(mode.toInt()) {
0 -> GraphicsWindow(320, 240, 3)
1 -> GraphicsWindow(640, 480, 2)
else -> throw IllegalArgumentException("invalid screen mode")
}
window!!.start()
}
fun gfx_clear(color: UByte) {
window?.clear(color.toInt())
}
fun gfx_plot(x: UShort, y: UShort, color: UByte) {
window?.plot(x.toInt(), y.toInt(), color.toInt())
}
fun gfx_getpixel(x: UShort, y: UShort): UByte {
return if(window==null)
0u
else {
val color = Color(window!!.getpixel(x.toInt(), y.toInt()))
color.green.toUByte()
}
}
fun gfx_text(xx: UShort, yy: UShort, textptr: UShort, color: UByte) {
val text = memory.getString(textptr.toUInt())
window?.drawText(xx.toInt(), yy.toInt(), text, color.toInt())
}
fun gfx_close() {
window?.close()
}
fun waitvsync() {
// note: not a real vsync, but a sleep for approx. the duration of a frame
val ms = try {
val mode = GraphicsEnvironment.getLocalGraphicsEnvironment().defaultScreenDevice.displayMode
if(mode!=null && mode.refreshRate>0) 1000L / mode.refreshRate else 16L
} catch(_: Exception) {
16L
}
try {
Thread.sleep(ms)
} catch(_: InterruptedException) {
Thread.currentThread().interrupt()
}
}
fun randomSeed(seed1: UShort, seed2: UShort) {
randomGenerator = Random(((seed1.toUInt() shl 16) or seed2.toUInt()).toInt())
}
fun randomSeedFloat(seed: Double) {
randomGeneratorFloats = Random(seed.toBits())
}
fun open_file_read(name: String): Int {
try {
fileInputStream = java.io.RandomAccessFile(name, "r")
} catch (_: IOException) {
return 0
}
return 1
}
fun open_file_write(name: String): Int {
try {
fileOutputStream = java.io.RandomAccessFile(name, "rw")
} catch (_: IOException) {
return 0
}
return 1
}
fun close_file_read() {
fileInputStream?.close()
fileInputStream = null
}
fun close_file_write() {
fileOutputStream?.close()
fileOutputStream = null
}
fun read_file_byte(): Pair<Boolean, UByte> {
return if (fileInputStream == null)
false to 0u
else {
try {
val byte = fileInputStream!!.read()
if (byte >= 0)
true to byte.toUByte()
else
false to 0u
} catch (_: IOException) {
false to 0u
}
}
}
fun write_file_byte(byte: UByte): Boolean {
if (fileOutputStream == null)
return false
try {
fileOutputStream!!.write(byte.toInt())
return true
} catch (_: IOException) {
return false
}
}
fun seek_file(position: Int): Boolean {
return if (fileInputStream == null)
false
else {
try {
fileInputStream!!.seek(position.toLong())
true
} catch (_: IOException) {
false
}
}
}
fun tell_file_pos(): Int {
return if (fileInputStream == null)
0
else {
try {
fileInputStream!!.filePointer.toInt()
} catch (_: IOException) {
0
}
}
}
fun tell_file_size(): Int {
return if (fileInputStream == null)
0
else {
try {
fileInputStream!!.length().toInt()
} catch (_: IOException) {
0
}
}
}
}
internal fun ArrayDeque<UByte>.pushw(value: UShort) {
add((value and 255u).toUByte())
add((value.toInt() ushr 8).toUByte())
}
internal fun ArrayDeque<UByte>.pushl(value: Int) {
val uint = value.toUInt()
add((uint and 255u).toUByte())
add((uint shr 8 and 255u).toUByte())
add((uint shr 16 and 255u).toUByte())
add((uint shr 24 and 255u).toUByte())
}
internal fun ArrayDeque<UByte>.pushf(value: Double) {
// push float; lsb first, msb last
var bits = value.toBits()
add(bits.toUByte())
bits = bits ushr 8
add(bits.toUByte())
bits = bits ushr 8
add(bits.toUByte())
bits = bits ushr 8
add(bits.toUByte())
bits = bits ushr 8
add(bits.toUByte())
bits = bits ushr 8
add(bits.toUByte())
bits = bits ushr 8
add(bits.toUByte())
bits = bits ushr 8
add(bits.toUByte())
}
internal fun ArrayDeque<UByte>.popw(): UShort {
val msb = removeLast()
val lsb = removeLast()
return ((msb.toInt() shl 8) + lsb.toInt()).toUShort()
}
internal fun ArrayDeque<UByte>.popl(): Int {
val b0 = removeLast().toUInt()
val b1 = removeLast().toUInt()
val b2 = removeLast().toUInt()
val b3 = removeLast().toUInt()
return ((b0 shl 24) or (b1 shl 16) or (b2 shl 8) or b3).toInt()
}
internal fun ArrayDeque<UByte>.popf(): Double {
// pop float; lsb is on bottom, msb on top
val b0 = removeLast().toLong()
val b1 = removeLast().toLong()
val b2 = removeLast().toLong()
val b3 = removeLast().toLong()
val b4 = removeLast().toLong()
val b5 = removeLast().toLong()
val b6 = removeLast().toLong()
val b7 = removeLast().toLong()
val bits = b7 +
(1L shl 8)*b6 +
(1L shl 16)*b5 +
(1L shl 24)*b4 +
(1L shl 32)*b3 +
(1L shl 40)*b2 +
(1L shl 48)*b1 +
(1L shl 56)*b0
return Double.fromBits(bits)
}
// probably called via reflection
+638
View File
@@ -0,0 +1,638 @@
package prog8.vm
import prog8.intermediate.IRDataType
import prog8.intermediate.IRInstruction
internal fun VirtualMachine.statusbitsNZ(value: Int, type: IRDataType) {
statusZero = value == 0
when(type) {
IRDataType.BYTE -> statusNegative = (value and 0x80) == 0x80
IRDataType.WORD -> statusNegative = (value and 0x8000) == 0x8000
IRDataType.LONG -> statusNegative = value < 0
IRDataType.FLOAT -> { /* floats don't change the status bits */ }
}
}
internal fun VirtualMachine.statusbitsComparison(comparison: Int, type: IRDataType) {
if(comparison==0) {
statusZero = true
statusCarry = true
} else if(comparison>0) {
statusZero = false
statusCarry = true
} else {
statusZero = false
statusCarry = false
}
when(type) {
IRDataType.BYTE -> statusNegative = (comparison and 0x80)!=0
IRDataType.WORD -> statusNegative = (comparison and 0x8000)!=0
IRDataType.LONG -> statusNegative = comparison<0
IRDataType.FLOAT -> { /* floats don't change the status bits */ }
}
}
internal fun VirtualMachine.statusbitsComparisonWithOverflow(comparison: Int, leftOrig: Int, type: IRDataType) {
statusbitsComparison(comparison, type)
when(type) {
IRDataType.BYTE -> {
val signBit = 0x80
val leftSign = leftOrig and signBit
val rightSign = (leftOrig - comparison) and signBit
statusOverflow = ((leftSign xor rightSign) and (leftSign xor (comparison and signBit))) != 0
}
IRDataType.WORD -> {
val signBit = 0x8000
val leftSign = leftOrig and signBit
val rightSign = (leftOrig - comparison) and signBit
statusOverflow = ((leftSign xor rightSign) and (leftSign xor (comparison and signBit))) != 0
}
IRDataType.LONG -> {
val leftSign = leftOrig < 0
val rightSign = (leftOrig - comparison) < 0
val resultSign = comparison < 0
statusOverflow = leftSign != rightSign && leftSign != resultSign
}
IRDataType.FLOAT -> { /* floats don't set overflow */ }
}
}
internal fun VirtualMachine.plusMinusMultAnyByte(operator: String, reg1: Int, reg2: Int) {
val left = registers.getUB(reg1)
val right = registers.getUB(reg2)
val result = when(operator) {
"+" -> left + right
"-" -> left - right
"*" -> left * right
else -> throw IllegalArgumentException("operator byte $operator")
}
registers.setUB(reg1, result.toUByte())
}
internal fun VirtualMachine.multiplyAnyByteSigned(reg1: Int, reg2: Int) {
val left = registers.getSB(reg1)
val right = registers.getSB(reg2)
val result = left * right
registers.setSB(reg1, result.toByte())
}
internal fun VirtualMachine.plusMinusMultConstByte(operator: String, reg1: Int, value: UByte) {
val left = registers.getUB(reg1)
val result = when(operator) {
"+" -> left + value
"-" -> left - value
"*" -> left * value
else -> throw IllegalArgumentException("operator byte $operator")
}
registers.setUB(reg1, result.toUByte())
}
internal fun VirtualMachine.multiplyConstByteSigned(reg1: Int, value: Byte) {
val left = registers.getSB(reg1)
val result = left * value
registers.setSB(reg1, result.toByte())
}
internal fun VirtualMachine.plusMinusMultAnyByteInplace(operator: String, reg1: Int, address: UInt) {
val memvalue = memory.getUB(address)
val operand = registers.getUB(reg1)
val result = when(operator) {
"+" -> memvalue + operand
"-" -> memvalue - operand
"*" -> memvalue * operand
else -> throw IllegalArgumentException("operator byte $operator")
}
memory.setUB(address, result.toUByte())
}
internal fun VirtualMachine.multiplyAnyByteSignedInplace(reg1: Int, address: UInt) {
val memvalue = memory.getSB(address)
val operand = registers.getSB(reg1)
val result = memvalue * operand
memory.setSB(address, result.toByte())
}
internal fun VirtualMachine.divModByteSigned(operator: String, reg1: Int, reg2: Int) {
val left = registers.getSB(reg1)
val right = registers.getSB(reg2)
val result = when(operator) {
"/" -> {
if(right==0.toByte()) 127
else left / right
}
"%" -> {
if(right==0.toByte()) 0
else left % right
}
else -> throw IllegalArgumentException("operator byte $operator")
}
registers.setSB(reg1, result.toByte())
}
internal fun VirtualMachine.divModConstByteSigned(operator: String, reg1: Int, value: Byte) {
val left = registers.getSB(reg1)
val result = when(operator) {
"/" -> {
if(value==0.toByte()) 127
else left / value
}
"%" -> {
if(value==0.toByte()) 0
else left % value
}
else -> throw IllegalArgumentException("operator byte $operator")
}
registers.setSB(reg1, result.toByte())
}
internal fun VirtualMachine.divModByteSignedInplace(operator: String, reg1: Int, address: UInt) {
val left = memory.getSB(address)
val right = registers.getSB(reg1)
val result = when(operator) {
"/" -> {
if(right==0.toByte()) 127
else left / right
}
"%" -> {
if(right==0.toByte()) 0
else left % right
}
else -> throw IllegalArgumentException("operator byte $operator")
}
memory.setSB(address, result.toByte())
}
internal fun VirtualMachine.divOrModByteUnsigned(operator: String, reg1: Int, reg2: Int) {
val left = registers.getUB(reg1)
val right = registers.getUB(reg2)
val result = when(operator) {
"/" -> {
if(right==0.toUByte()) 0xffu
else left / right
}
"%" -> {
if(right==0.toUByte()) 0u
else left % right
}
else -> throw IllegalArgumentException("operator byte $operator")
}
registers.setUB(reg1, result.toUByte())
}
internal fun VirtualMachine.divOrModConstByteUnsigned(operator: String, reg1: Int, value: UByte) {
val left = registers.getUB(reg1)
val result = when(operator) {
"/" -> {
if(value==0.toUByte()) 0xffu
else left / value
}
"%" -> {
if(value==0.toUByte()) 0u
else left % value
}
else -> throw IllegalArgumentException("operator byte $operator")
}
registers.setUB(reg1, result.toUByte())
}
internal fun VirtualMachine.divAndModUByte(reg1: Int, reg2: Int) {
val left = registers.getUB(reg1)
val right = registers.getUB(reg2)
val division = if(right==0.toUByte()) 0xffu else left / right
val remainder = if(right==0.toUByte()) 0u else left % right
valueStack.add(division.toUByte())
valueStack.add(remainder.toUByte())
}
internal fun VirtualMachine.divAndModConstUByte(reg1: Int, value: UByte) {
val left = registers.getUB(reg1)
val division = if(value==0.toUByte()) 0xffu else left / value
val remainder = if(value==0.toUByte()) 0u else left % value
valueStack.add(division.toUByte())
valueStack.add(remainder.toUByte())
}
internal fun VirtualMachine.divAndModUWord(reg1: Int, reg2: Int) {
val left = registers.getUW(reg1)
val right = registers.getUW(reg2)
val division = if(right==0.toUShort()) 0xffffu else left / right
val remainder = if(right==0.toUShort()) 0u else left % right
valueStack.pushw(division.toUShort())
valueStack.pushw(remainder.toUShort())
}
internal fun VirtualMachine.divAndModConstUWord(reg1: Int, value: UShort) {
val left = registers.getUW(reg1)
val division = if(value==0.toUShort()) 0xffffu else left / value
val remainder = if(value==0.toUShort()) 0u else left % value
valueStack.pushw(division.toUShort())
valueStack.pushw(remainder.toUShort())
}
internal fun VirtualMachine.divAndModSByte(reg1: Int, reg2: Int) {
val left = registers.getSB(reg1)
val right = registers.getSB(reg2)
val division = if(right==0.toByte()) 127 else left / right
val remainder = if(right==0.toByte()) 0 else left % right
valueStack.add(division.toUByte())
valueStack.add(remainder.toUByte())
}
internal fun VirtualMachine.divAndModConstSByte(reg1: Int, value: Byte) {
val left = registers.getSB(reg1)
val division = if(value==0.toByte()) 127 else left / value
val remainder = if(value==0.toByte()) 0 else left % value
valueStack.add(division.toUByte())
valueStack.add(remainder.toUByte())
}
internal fun VirtualMachine.divAndModSWord(reg1: Int, reg2: Int) {
val left = registers.getSW(reg1)
val right = registers.getSW(reg2)
val division = if(right==0.toShort()) 32767 else left / right
val remainder = if(right==0.toShort()) 0 else left % right
valueStack.pushw(division.toUShort())
valueStack.pushw(remainder.toUShort())
}
internal fun VirtualMachine.divAndModConstSWord(reg1: Int, value: Short) {
val left = registers.getSW(reg1)
val division = if(value==0.toShort()) 32767 else left / value
val remainder = if(value==0.toShort()) 0 else left % value
valueStack.pushw(division.toUShort())
valueStack.pushw(remainder.toUShort())
}
internal fun VirtualMachine.divModByteUnsignedInplace(operator: String, reg1: Int, address: UInt) {
val left = memory.getUB(address)
val right = registers.getUB(reg1)
val result = when(operator) {
"/" -> {
if(right==0.toUByte()) 0xffu
else left / right
}
"%" -> {
if(right==0.toUByte()) 0u
else left % right
}
else -> throw IllegalArgumentException("operator byte $operator")
}
memory.setUB(address, result.toUByte())
}
internal fun VirtualMachine.plusMinusMultAnyWord(operator: String, reg1: Int, reg2: Int) {
val left = registers.getUW(reg1)
val right = registers.getUW(reg2)
val result: UInt
when(operator) {
"+" -> result = left + right
"-" -> result = left - right
"*" -> {
result = left.toUInt() * right
mul16LastUpper = result shr 16
}
else -> throw IllegalArgumentException("operator word $operator")
}
registers.setUW(reg1, result.toUShort())
}
internal fun VirtualMachine.multiplyAnyWordSigned(reg1: Int, reg2: Int) {
val left = registers.getSW(reg1)
val right = registers.getSW(reg2)
val result = left.toInt() * right
mul16LastUpper = result.toUInt() shr 16
registers.setSW(reg1, result.toShort())
}
internal fun VirtualMachine.plusMinusMultConstWord(operator: String, reg1: Int, value: UShort) {
val left = registers.getUW(reg1)
val result: UInt
when(operator) {
"+" -> result = left + value
"-" -> result = left - value
"*" -> {
result = left.toUInt() * value
mul16LastUpper = result shr 16
}
else -> throw IllegalArgumentException("operator word $operator")
}
registers.setUW(reg1, result.toUShort())
}
internal fun VirtualMachine.multiplyConstWordSigned(reg1: Int, value: Short) {
val left = registers.getSW(reg1)
val result = left.toInt() * value
mul16LastUpper = result.toUInt() shr 16
registers.setSW(reg1, result.toShort())
}
internal fun VirtualMachine.plusMinusMultAnyWordInplace(operator: String, reg1: Int, address: UInt) {
val memvalue = memory.getUW(address)
val operand = registers.getUW(reg1)
val result: UInt
when(operator) {
"+" -> result = memvalue + operand
"-" -> result = memvalue - operand
"*" -> {
result = memvalue.toUInt() * operand
mul16LastUpper = result shr 16
}
else -> throw IllegalArgumentException("operator word $operator")
}
memory.setUW(address, result.toUShort())
}
internal fun VirtualMachine.multiplyAnyWordSignedInplace(reg1: Int, address: UInt) {
val memvalue = memory.getSW(address)
val operand = registers.getSW(reg1)
val result = memvalue.toInt() * operand
mul16LastUpper = result.toUInt() shr 16
memory.setSW(address, result.toShort())
}
internal fun VirtualMachine.divOrModWordUnsigned(operator: String, reg1: Int, reg2: Int) {
val left = registers.getUW(reg1)
val right = registers.getUW(reg2)
val result = when(operator) {
"/" -> {
if(right==0.toUShort()) 0xffffu
else left / right
}
"%" -> {
if(right==0.toUShort()) 0u
else left % right
}
else -> throw IllegalArgumentException("operator word $operator")
}
registers.setUW(reg1, result.toUShort())
}
internal fun VirtualMachine.divOrModConstWordUnsigned(operator: String, reg1: Int, value: UShort) {
val left = registers.getUW(reg1)
val result = when(operator) {
"/" -> {
if(value==0.toUShort()) 0xffffu
else left / value
}
"%" -> {
if(value==0.toUShort()) 0u
else left % value
}
else -> throw IllegalArgumentException("operator word $operator")
}
registers.setUW(reg1, result.toUShort())
}
internal fun VirtualMachine.divModWordUnsignedInplace(operator: String, reg1: Int, address: UInt) {
val left = memory.getUW(address)
val right = registers.getUW(reg1)
val result = when(operator) {
"/" -> {
if(right==0.toUShort()) 0xffffu
else left / right
}
"%" -> {
if(right==0.toUShort()) 0u
else left % right
}
else -> throw IllegalArgumentException("operator word $operator")
}
memory.setUW(address, result.toUShort())
}
internal fun VirtualMachine.divModWordSigned(operator: String, reg1: Int, reg2: Int) {
val left = registers.getSW(reg1)
val right = registers.getSW(reg2)
val result = when(operator) {
"/" -> {
if(right==0.toShort()) 32767
else left / right
}
"%" -> {
if(right==0.toShort()) 0
else left % right
}
else -> throw IllegalArgumentException("operator word $operator")
}
registers.setSW(reg1, result.toShort())
}
internal fun VirtualMachine.divModConstWordSigned(operator: String, reg1: Int, value: Short) {
val left = registers.getSW(reg1)
val result = when(operator) {
"/" -> {
if(value==0.toShort()) 32767
else left / value
}
"%" -> {
if(value==0.toShort()) 0
else left % value
}
else -> throw IllegalArgumentException("operator word $operator")
}
registers.setSW(reg1, result.toShort())
}
internal fun VirtualMachine.divModWordSignedInplace(operator: String, reg1: Int, address: UInt) {
val left = memory.getSW(address)
val right = registers.getSW(reg1)
val result = when(operator) {
"/" -> {
if(right==0.toShort()) 32767
else left / right
}
"%" -> {
if(right==0.toShort()) 0
else left % right
}
else -> throw IllegalArgumentException("operator word $operator")
}
memory.setSW(address, result.toShort())
}
internal fun VirtualMachine.arithFloat(left: Double, operator: String, right: Double): Double = when(operator) {
"+" -> left + right
"-" -> left - right
"*" -> left * right
"/" -> {
if(right==0.0) Double.MAX_VALUE
else left / right
}
"%" -> {
if(right==0.0) 0.0
else left % right
}
else -> throw IllegalArgumentException("operator word $operator")
}
internal fun VirtualMachine.getBranchOperands(i: IRInstruction): Pair<Int, Int> {
return when(i.type) {
IRDataType.BYTE -> Pair(registers.getSB(i.reg1!!).toInt(), registers.getSB(i.reg2!!).toInt())
IRDataType.WORD -> Pair(registers.getSW(i.reg1!!).toInt(), registers.getSW(i.reg2!!).toInt())
IRDataType.LONG -> Pair(registers.getSL(i.reg1!!), registers.getSL(i.reg2!!))
IRDataType.FLOAT -> {
throw IllegalArgumentException("can't use float here")
}
null -> throw IllegalArgumentException("need type for branch instruction")
}
}
internal fun VirtualMachine.getBranchOperandsImm(i: IRInstruction): Pair<Int, Int> {
return when(i.type) {
IRDataType.BYTE -> Pair(registers.getSB(i.reg1!!).toInt(), i.immediate!!)
IRDataType.WORD -> Pair(registers.getSW(i.reg1!!).toInt(), i.immediate!!)
IRDataType.LONG -> Pair(registers.getSL(i.reg1!!), i.immediate!!)
IRDataType.FLOAT -> {
throw IllegalArgumentException("can't use float here")
}
null -> throw IllegalArgumentException("need type for branch instruction")
}
}
internal fun VirtualMachine.getBranchOperandsU(i: IRInstruction): Pair<UInt, UInt> {
return when(i.type) {
IRDataType.BYTE -> Pair(registers.getUB(i.reg1!!).toUInt(), registers.getUB(i.reg2!!).toUInt())
IRDataType.WORD -> Pair(registers.getUW(i.reg1!!).toUInt(), registers.getUW(i.reg2!!).toUInt())
IRDataType.LONG -> Pair(registers.getSL(i.reg1!!).toUInt(), registers.getSL(i.reg2!!).toUInt())
IRDataType.FLOAT -> {
throw IllegalArgumentException("can't use float here")
}
null -> throw IllegalArgumentException("need type for branch instruction")
}
}
internal fun VirtualMachine.getBranchOperandsImmU(i: IRInstruction): Pair<UInt, UInt> {
return when(i.type) {
IRDataType.BYTE -> Pair(registers.getUB(i.reg1!!).toUInt(), i.immediate!!.toUInt())
IRDataType.WORD -> Pair(registers.getUW(i.reg1!!).toUInt(), i.immediate!!.toUInt())
IRDataType.LONG -> Pair(registers.getSL(i.reg1!!).toUInt(), i.immediate!!.toUInt())
IRDataType.FLOAT -> {
throw IllegalArgumentException("can't use float here")
}
null -> throw IllegalArgumentException("need type for branch instruction")
}
}
internal fun VirtualMachine.getLogicalOperandsU(i: IRInstruction): Pair<UInt, UInt> {
return when(i.type) {
IRDataType.BYTE -> Pair(registers.getUB(i.reg1!!).toUInt(), registers.getUB(i.reg2!!).toUInt())
IRDataType.WORD -> Pair(registers.getUW(i.reg1!!).toUInt(), registers.getUW(i.reg2!!).toUInt())
IRDataType.LONG -> Pair(registers.getSL(i.reg1!!).toUInt(), registers.getSL(i.reg2!!).toUInt())
IRDataType.FLOAT -> {
throw IllegalArgumentException("can't use float here")
}
null -> throw IllegalArgumentException("need type for logical instruction")
}
}
internal fun VirtualMachine.getLogicalOperandsS(i: IRInstruction): Pair<Int, Int> {
return when(i.type) {
IRDataType.BYTE -> Pair(registers.getSB(i.reg1!!).toInt(), registers.getSB(i.reg2!!).toInt())
IRDataType.WORD -> Pair(registers.getSW(i.reg1!!).toInt(), registers.getSW(i.reg2!!).toInt())
IRDataType.LONG -> Pair(registers.getSL(i.reg1!!), registers.getSL(i.reg2!!))
IRDataType.FLOAT -> {
throw IllegalArgumentException("can't use float here")
}
null -> throw IllegalArgumentException("need type for logical instruction")
}
}
internal fun VirtualMachine.plusMinusMultAnyLong(operator: String, reg1: Int, reg2: Int) {
val left = registers.getSL(reg1)
val right = registers.getSL(reg2)
val result: Int = when(operator) {
"+" -> left + right
"-" -> left - right
"*" -> left * right
else -> throw IllegalArgumentException("operator word $operator")
}
registers.setSL(reg1, result)
}
internal fun VirtualMachine.plusMinusMultConstLong(operator: String, reg1: Int, value: Int) {
val left = registers.getSL(reg1)
val result: Int = when(operator) {
"+" -> left + value
"-" -> left - value
"*" -> left * value
else -> throw IllegalArgumentException("operator long $operator")
}
registers.setSL(reg1, result)
}
internal fun VirtualMachine.plusMinusMultAnyLongInplace(operator: String, reg1: Int, address: UInt) {
val memvalue = memory.getSL(address)
val operand = registers.getSL(reg1)
val result: Int = when(operator) {
"+" -> memvalue + operand
"-" -> memvalue - operand
"*" -> memvalue * operand
else -> throw IllegalArgumentException("operator word $operator")
}
memory.setSL(address, result)
}
internal fun VirtualMachine.multiplyAnyLongSigned(reg1: Int, reg2: Int) {
val result = registers.getSL(reg1) * registers.getSL(reg2)
registers.setSL(reg1, result)
}
internal fun VirtualMachine.multiplyConstLongSigned(reg1: Int, value: Int) {
val result = registers.getSL(reg1) * value
registers.setSL(reg1, result)
}
internal fun VirtualMachine.multiplyAnyLongSignedInplace(reg1: Int, address: UInt) {
val result = memory.getSL(address) * registers.getSL(reg1)
memory.setSL(address, result)
}
internal fun VirtualMachine.divModLongSigned(operator: String, reg1: Int, reg2: Int) {
val left = registers.getSL(reg1)
val right = registers.getSL(reg2)
val result = when(operator) {
"/" -> {
if(right==0) Int.MAX_VALUE
else left / right
}
"%" -> {
if(right==0) 0
else left % right
}
else -> throw IllegalArgumentException("operator long $operator")
}
registers.setSL(reg1, result)
}
internal fun VirtualMachine.divModConstLongSigned(operator: String, reg1: Int, immediate: Int) {
val left = registers.getSL(reg1)
val result = when(operator) {
"/" -> {
if(immediate==0) Int.MAX_VALUE
else left / immediate
}
"%" -> {
if(immediate==0) 0
else left % immediate
}
else -> throw IllegalArgumentException("operator long $operator")
}
registers.setSL(reg1, result)
}
internal fun VirtualMachine.divModLongSignedInplace(operator: String, reg1: Int, address: UInt) {
val left = memory.getSL(address)
val right = registers.getSL(reg1)
val result = when(operator) {
"/" -> {
if(right==0) Int.MAX_VALUE
else left / right
}
"%" -> {
if(right==0) 0
else left % right
}
else -> throw IllegalArgumentException("operator long $operator")
}
memory.setSL(address, result)
}
@@ -0,0 +1,69 @@
package prog8.vm
internal fun ArrayDeque<UByte>.pushw(value: UShort) {
add((value and 255u).toUByte())
add((value.toInt() ushr 8).toUByte())
}
internal fun ArrayDeque<UByte>.pushl(value: Int) {
val uint = value.toUInt()
add((uint and 255u).toUByte())
add((uint shr 8 and 255u).toUByte())
add((uint shr 16 and 255u).toUByte())
add((uint shr 24 and 255u).toUByte())
}
internal fun ArrayDeque<UByte>.pushf(value: Double) {
// push float; lsb first, msb last
var bits = value.toBits()
add(bits.toUByte())
bits = bits ushr 8
add(bits.toUByte())
bits = bits ushr 8
add(bits.toUByte())
bits = bits ushr 8
add(bits.toUByte())
bits = bits ushr 8
add(bits.toUByte())
bits = bits ushr 8
add(bits.toUByte())
bits = bits ushr 8
add(bits.toUByte())
bits = bits ushr 8
add(bits.toUByte())
}
internal fun ArrayDeque<UByte>.popw(): UShort {
val msb = removeLast()
val lsb = removeLast()
return ((msb.toInt() shl 8) + lsb.toInt()).toUShort()
}
internal fun ArrayDeque<UByte>.popl(): Int {
val b0 = removeLast().toUInt()
val b1 = removeLast().toUInt()
val b2 = removeLast().toUInt()
val b3 = removeLast().toUInt()
return ((b0 shl 24) or (b1 shl 16) or (b2 shl 8) or b3).toInt()
}
internal fun ArrayDeque<UByte>.popf(): Double {
// pop float; lsb is on bottom, msb on top
val b0 = removeLast().toLong()
val b1 = removeLast().toLong()
val b2 = removeLast().toLong()
val b3 = removeLast().toLong()
val b4 = removeLast().toLong()
val b5 = removeLast().toLong()
val b6 = removeLast().toLong()
val b7 = removeLast().toLong()
val bits = b7 +
(1L shl 8)*b6 +
(1L shl 16)*b5 +
(1L shl 24)*b4 +
(1L shl 32)*b3 +
(1L shl 40)*b2 +
(1L shl 48)*b1 +
(1L shl 56)*b0
return Double.fromBits(bits)
}
@@ -0,0 +1,156 @@
package prog8.vm
import java.awt.Color
import java.awt.GraphicsEnvironment
import java.io.IOException
import kotlin.random.Random
internal fun VirtualMachine.gfx_enable(mode: UByte) {
window = when(mode.toInt()) {
0 -> GraphicsWindow(320, 240, 3)
1 -> GraphicsWindow(640, 480, 2)
else -> throw IllegalArgumentException("invalid screen mode")
}
window!!.start()
}
internal fun VirtualMachine.gfx_clear(color: UByte) {
window?.clear(color.toInt())
}
internal fun VirtualMachine.gfx_plot(x: UShort, y: UShort, color: UByte) {
window?.plot(x.toInt(), y.toInt(), color.toInt())
}
internal fun VirtualMachine.gfx_getpixel(x: UShort, y: UShort): UByte {
return if(window==null)
0u
else {
val color = Color(window!!.getpixel(x.toInt(), y.toInt()))
color.green.toUByte()
}
}
internal fun VirtualMachine.gfx_text(xx: UShort, yy: UShort, textptr: UShort, color: UByte) {
val text = memory.getString(textptr.toUInt())
window?.drawText(xx.toInt(), yy.toInt(), text, color.toInt())
}
internal fun VirtualMachine.gfx_close() {
window?.close()
}
internal fun VirtualMachine.waitvsync() {
// note: not a real vsync, but a sleep for approx. the duration of a frame
val ms = try {
val mode = GraphicsEnvironment.getLocalGraphicsEnvironment().defaultScreenDevice.displayMode
if(mode!=null && mode.refreshRate>0) 1000L / mode.refreshRate else 16L
} catch(_: Exception) {
16L
}
try {
Thread.sleep(ms)
} catch(_: InterruptedException) {
Thread.currentThread().interrupt()
}
}
internal fun VirtualMachine.randomSeed(seed1: UShort, seed2: UShort) {
randomGenerator = Random(((seed1.toUInt() shl 16) or seed2.toUInt()).toInt())
}
internal fun VirtualMachine.randomSeedFloat(seed: Double) {
randomGeneratorFloats = Random(seed.toBits())
}
internal fun VirtualMachine.open_file_read(name: String): Int {
try {
fileInputStream = java.io.RandomAccessFile(name, "r")
} catch (_: IOException) {
return 0
}
return 1
}
internal fun VirtualMachine.open_file_write(name: String): Int {
try {
fileOutputStream = java.io.RandomAccessFile(name, "rw")
} catch (_: IOException) {
return 0
}
return 1
}
internal fun VirtualMachine.close_file_read() {
fileInputStream?.close()
fileInputStream = null
}
internal fun VirtualMachine.close_file_write() {
fileOutputStream?.close()
fileOutputStream = null
}
internal fun VirtualMachine.read_file_byte(): Pair<Boolean, UByte> {
return if (fileInputStream == null)
false to 0u
else {
try {
val byte = fileInputStream!!.read()
if (byte >= 0)
true to byte.toUByte()
else
false to 0u
} catch (_: IOException) {
false to 0u
}
}
}
internal fun VirtualMachine.write_file_byte(byte: UByte): Boolean {
if (fileOutputStream == null)
return false
try {
fileOutputStream!!.write(byte.toInt())
return true
} catch (_: IOException) {
return false
}
}
internal fun VirtualMachine.seek_file(position: Int): Boolean {
return if (fileInputStream == null)
false
else {
try {
fileInputStream!!.seek(position.toLong())
true
} catch (_: IOException) {
false
}
}
}
internal fun VirtualMachine.tell_file_pos(): Int {
return if (fileInputStream == null)
0
else {
try {
fileInputStream!!.filePointer.toInt()
} catch (_: IOException) {
0
}
}
}
internal fun VirtualMachine.tell_file_size(): Int {
return if (fileInputStream == null)
0
else {
try {
fileInputStream!!.length().toInt()
} catch (_: IOException) {
0
}
}
}
+128 -1
View File
@@ -2,7 +2,10 @@ import io.kotest.assertions.throwables.shouldThrowWithMessage
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.collections.shouldBeEmpty
import io.kotest.matchers.shouldBe
import prog8.code.core.*
import prog8.code.core.CompilationOptions
import prog8.code.core.OutputType
import prog8.code.core.Position
import prog8.code.core.ZeropageType
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.VMTarget
@@ -224,4 +227,128 @@ class TestVm: FunSpec( {
vm.run(false)
vm.registers.getSL(1) shouldBe Int.MAX_VALUE
}
test("vm register usage: LOADI") {
val program = IRProgram("test", IRSymbolTable(), getTestOptions(), VMTarget())
val block = IRBlock("main", false, IRBlock.Options(), Position.DUMMY)
val startSub = IRSubroutine("main.test", emptyList(), emptyList(), Position.DUMMY)
val code = IRCodeChunk(startSub.label, null)
// BWL: memory[registers[reg2] + offset] -> registers[reg1]
code += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1=1, reg2=2, immediate=10)
// FLOAT: memory[registers[reg1] + offset] -> registers[fpReg1]
code += IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1=RegisterNum(3), reg1=2, immediate=20)
code += IRInstruction(Opcode.RETURN)
startSub += code
block += startSub
program.addBlock(block)
val vm = VirtualMachine(program)
vm.registers.setUW(2, 1000u)
vm.memory.setUW(1010u, 1234u)
vm.memory.setFloat(1020u, 3.14)
vm.run(false)
vm.registers.getUW(1) shouldBe 1234u
vm.registers.getFloat(RegisterNum(3)) shouldBe 3.14
}
test("vm register usage: LOADX") {
val program = IRProgram("test", IRSymbolTable(), getTestOptions(), VMTarget())
val block = IRBlock("main", false, IRBlock.Options(), Position.DUMMY)
val startSub = IRSubroutine("main.test", emptyList(), emptyList(), Position.DUMMY)
val code = IRCodeChunk(startSub.label, null)
// BWL: memory[address + registers[reg2]] -> registers[reg1]
code += IRInstruction(Opcode.LOADX, IRDataType.WORD, reg1=1, reg2=2, address=1000u.toAddress())
// FLOAT: memory[address + registers[reg1]] -> registers[fpReg1]
code += IRInstruction(Opcode.LOADX, IRDataType.FLOAT, fpReg1=RegisterNum(3), reg1=2, address=2000u.toAddress())
code += IRInstruction(Opcode.RETURN)
startSub += code
block += startSub
program.addBlock(block)
val vm = VirtualMachine(program)
vm.registers.setUB(2, 10u)
vm.memory.setUW(1010u, 5678u)
vm.memory.setFloat(2010u, 2.718)
vm.run(false)
vm.registers.getUW(1) shouldBe 5678u
vm.registers.getFloat(RegisterNum(3)) shouldBe 2.718
}
test("vm register usage: STOREI") {
val program = IRProgram("test", IRSymbolTable(), getTestOptions(), VMTarget())
val block = IRBlock("main", false, IRBlock.Options(), Position.DUMMY)
val startSub = IRSubroutine("main.test", emptyList(), emptyList(), Position.DUMMY)
val code = IRCodeChunk(startSub.label, null)
// BWL: registers[reg1] -> memory[registers[reg2] + offset]
code += IRInstruction(Opcode.STOREI, IRDataType.WORD, reg1=1, reg2=2, immediate=10)
// FLOAT: registers[fpReg1] -> memory[registers[reg1] + offset]
code += IRInstruction(Opcode.STOREI, IRDataType.FLOAT, fpReg1=RegisterNum(3), reg1=2, immediate=20)
code += IRInstruction(Opcode.RETURN)
startSub += code
block += startSub
program.addBlock(block)
val vm = VirtualMachine(program)
vm.registers.setUW(1, 9999u)
vm.registers.setFloat(RegisterNum(3), 1.23)
vm.registers.setUW(2, 1000u)
vm.run(false)
vm.memory.getUW(1010u) shouldBe 9999u
vm.memory.getFloat(1020u) shouldBe 1.23
}
test("vm register usage: STOREX") {
val program = IRProgram("test", IRSymbolTable(), getTestOptions(), VMTarget())
val block = IRBlock("main", false, IRBlock.Options(), Position.DUMMY)
val startSub = IRSubroutine("main.test", emptyList(), emptyList(), Position.DUMMY)
val code = IRCodeChunk(startSub.label, null)
// BWL: registers[reg1] -> memory[address + registers[reg2]]
code += IRInstruction(Opcode.STOREX, IRDataType.WORD, reg1=1, reg2=2, address=1000u.toAddress())
// FLOAT: registers[fpReg1] -> memory[address + registers[reg1]]
code += IRInstruction(Opcode.STOREX, IRDataType.FLOAT, fpReg1=RegisterNum(3), reg1=2, address=2000u.toAddress())
code += IRInstruction(Opcode.RETURN)
startSub += code
block += startSub
program.addBlock(block)
val vm = VirtualMachine(program)
vm.registers.setUW(1, 8888u)
vm.registers.setFloat(RegisterNum(3), 4.56)
vm.registers.setUB(2, 10u)
vm.run(false)
vm.memory.getUW(1010u) shouldBe 8888u
vm.memory.getFloat(2010u) shouldBe 4.56
}
test("vm register usage: LSR.l") {
val program = IRProgram("test", IRSymbolTable(), getTestOptions(), VMTarget())
val block = IRBlock("main", false, IRBlock.Options(), Position.DUMMY)
val startSub = IRSubroutine("main.test", emptyList(), emptyList(), Position.DUMMY)
val code = IRCodeChunk(startSub.label, null)
code += IRInstruction(Opcode.LSR, IRDataType.LONG, reg1=1)
code += IRInstruction(Opcode.RETURN)
startSub += code
block += startSub
program.addBlock(block)
val vm = VirtualMachine(program)
vm.registers.setSL(1, -1) // 0xFFFFFFFF
vm.run(false)
// Logical shift right of 0xFFFFFFFF should be 0x7FFFFFFF (2147483647)
vm.registers.getSL(1) shouldBe 2147483647
}
})