working on vm

This commit is contained in:
Irmen de Jong 2022-03-19 01:20:01 +01:00
parent 4c1bb18956
commit 9b16d7c786
18 changed files with 277 additions and 27 deletions

View File

@ -39,6 +39,8 @@ class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) {
collect(this)
vars
}
override fun lookup(scopedName: List<String>) = flat[scopedName]
}
@ -73,7 +75,7 @@ open class StNode(val name: String,
fun lookup(name: String) =
lookupUnqualified(name)
fun lookup(scopedName: List<String>) =
open fun lookup(scopedName: List<String>) =
if(scopedName.size>1) lookupQualified(scopedName) else lookupUnqualified(scopedName[0])
fun lookupOrElse(name: String, default: () -> StNode) =
lookupUnqualified(name) ?: default()

View File

@ -100,7 +100,7 @@ class PtBreakpoint(position: Position): PtNode(position) {
}
class PtInlineBinary(val file: Path, val offset: UInt?, val length: UInt?, position: Position) : PtNode(position) {
class PtIncludeBinary(val file: Path, val offset: UInt?, val length: UInt?, position: Position) : PtNode(position) {
override fun printProperties() {
print("filename=$file offset=$offset length=$length")
}

View File

@ -2,4 +2,5 @@ package prog8.code.core
interface IMemSizer {
fun memorySize(dt: DataType): Int
fun memorySize(arrayDt: DataType, numElements: Int): Int
}

View File

@ -57,7 +57,7 @@ abstract class Zeropage(protected val options: CompilationOptions) {
when (datatype) {
in IntegerDatatypes -> options.compTarget.memorySize(datatype)
DataType.STR, in ArrayDatatypes -> {
val memsize = numElements!! * options.compTarget.memorySize(ArrayToElementTypes.getValue(datatype))
val memsize = options.compTarget.memorySize(datatype, numElements!!)
if(position!=null)
errors.warn("allocating a large value in zeropage; str/array $memsize bytes", position)
else

View File

@ -22,4 +22,7 @@ class AtariTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
else -> Int.MIN_VALUE
}
}
override fun memorySize(arrayDt: DataType, numElements: Int) =
memorySize(ArrayToElementTypes.getValue(arrayDt)) * numElements
}

View File

@ -21,4 +21,7 @@ class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
else -> Int.MIN_VALUE
}
}
override fun memorySize(arrayDt: DataType, numElements: Int) =
memorySize(ArrayToElementTypes.getValue(arrayDt)) * numElements
}

View File

@ -12,4 +12,7 @@ internal object CbmMemorySizer: IMemSizer {
else -> Int.MIN_VALUE
}
}
override fun memorySize(arrayDt: DataType, numElements: Int) =
memorySize(ArrayToElementTypes.getValue(arrayDt)) * numElements
}

View File

@ -168,7 +168,7 @@ class AstToXmlConverter(internal val program: PtProgram,
is PtIdentifier -> write(it)
is PtIfElse -> write(it)
is PtInlineAssembly -> write(it)
is PtInlineBinary -> write(it)
is PtIncludeBinary -> write(it)
is PtJump -> write(it)
is PtMemoryByte -> write(it)
is PtMemMapped -> write(it)
@ -363,7 +363,7 @@ class AstToXmlConverter(internal val program: PtProgram,
xml.endElt()
}
private fun write(inlineBinary: PtInlineBinary) {
private fun write(inlineBinary: PtIncludeBinary) {
xml.elt("binary")
xml.attr("filename", inlineBinary.file.absolutePathString())
if(inlineBinary.offset!=null)

View File

@ -6,25 +6,23 @@ import kotlin.io.path.bufferedWriter
import kotlin.io.path.div
internal class AssemblyProgram(override val name: String) : IAssemblyProgram
{
internal class AssemblyProgram(override val name: String,
private val allocations: VariableAllocator,
private val instructions: MutableList<String>
) : IAssemblyProgram {
override fun assemble(options: CompilationOptions): Boolean {
val outfile = options.outputDir / ("$name.p8virt")
println("write code to ${outfile}")
outfile.bufferedWriter().use {
it.write(memsrc)
allocations.asVmMemory().forEach { alloc -> it.write(alloc + "\n") }
it.write("------PROGRAM------\n")
it.write(src)
instructions.forEach { ins -> it.write(ins + "\n") }
}
return true
}
}
val memsrc = """
$4000 strz "Hello from program! "derp" bye.\n"
$2000 ubyte 65,66,67,68,0
$2100 uword $1111,$2222,$3333,$4444
"""
val src = """
/*
; enable lores gfx screen
load r0, 0
syscall 8
@ -53,3 +51,4 @@ load.w r0,0
return"""
}
*/

View File

@ -1,11 +1,15 @@
package prog8.codegen.virtual
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import com.github.michaelbull.result.mapError
import prog8.code.SymbolTable
import prog8.code.ast.PtProgram
import prog8.code.core.CompilationOptions
import prog8.code.core.IAssemblyGenerator
import prog8.code.core.IAssemblyProgram
import prog8.code.core.IErrorReporter
import prog8.code.ast.*
import prog8.code.core.*
import prog8.vm.Instruction
import java.io.File
import kotlin.io.path.Path
import kotlin.io.path.isRegularFile
class CodeGen(internal val program: PtProgram,
internal val symbolTable: SymbolTable,
@ -13,8 +17,87 @@ class CodeGen(internal val program: PtProgram,
internal val errors: IErrorReporter
): IAssemblyGenerator {
override fun compileToAssembly(): IAssemblyProgram? {
private val instructions = mutableListOf<String>()
return AssemblyProgram(program.name)
override fun compileToAssembly(): IAssemblyProgram? {
instructions.clear()
val allocations = VariableAllocator(symbolTable, program, errors)
for (block in program.allBlocks())
translateNode(block)
return AssemblyProgram(program.name, allocations, instructions)
}
private fun out(ins: Instruction) {
instructions.add(ins.toString())
}
private fun out(ins: String) {
instructions.add(ins)
}
private fun translateNode(node: PtNode) {
when(node) {
is PtBlock -> translate(node)
is PtSub -> translate(node)
is PtVariable -> { /* var should be looked up via symbol table */ }
is PtMemMapped -> { /* memmapped var should be looked up via symbol table */ }
is PtConstant -> { /* constants have all been folded into the code */ }
is PtAsmSub -> translate(node)
is PtAssignTarget -> TODO()
is PtAssignment -> translate(node)
is PtBreakpoint -> TODO()
is PtConditionalBranch -> TODO()
is PtAddressOf -> TODO()
is PtArrayIndexer -> TODO()
is PtArrayLiteral -> TODO()
is PtBinaryExpression -> TODO()
is PtBuiltinFunctionCall -> TODO()
is PtContainmentCheck -> TODO()
is PtFunctionCall -> TODO()
is PtIdentifier -> TODO()
is PtMemoryByte -> TODO()
is PtNumber -> TODO()
is PtPipe -> TODO()
is PtPrefix -> TODO()
is PtRange -> TODO()
is PtString -> TODO()
is PtTypeCast -> TODO()
is PtForLoop -> TODO()
is PtGosub -> TODO()
is PtIfElse -> TODO()
is PtIncludeBinary -> TODO()
is PtJump -> TODO()
is PtNodeGroup -> TODO()
is PtNop -> { }
is PtPostIncrDecr -> TODO()
is PtProgram -> TODO()
is PtRepeatLoop -> TODO()
is PtReturn -> TODO()
is PtSubroutineParameter -> TODO()
is PtWhen -> TODO()
is PtWhenChoice -> TODO()
is PtInlineAssembly -> throw AssemblyError("inline assembly not supported on virtual machine target")
else -> TODO("missing codegen for $node")
}
}
private fun translate(sub: PtSub) {
out("; SUB: ${sub.scopedName} -> ${sub.returntype}")
}
private fun translate(asmsub: PtAsmSub) {
out("; ASMSUB: ${asmsub.scopedName} = ${asmsub.address} -> ${asmsub.retvalRegisters}")
}
private fun translate(assign: PtAssignment) {
out("; ASSIGN: ${assign.target.identifier?.targetName} = ${assign.value}")
}
private fun translate(block: PtBlock) {
out("\n; BLOCK '${block.name}' addr=${block.address} lib=${block.library}")
for (child in block.children) {
translateNode(child)
}
out("; BLOCK-END '${block.name}'\n")
}
}

View File

@ -0,0 +1,74 @@
package prog8.codegen.virtual
import prog8.code.SymbolTable
import prog8.code.ast.PtProgram
import prog8.code.core.*
class VariableAllocator(private val st: SymbolTable, private val program: PtProgram, errors: IErrorReporter) {
private val allocations = mutableMapOf<List<String>, Int>()
val freeStart: Int
init {
var nextLocation = 0
for (variable in st.allVariables) {
val memsize =
when (variable.dt) {
DataType.STR -> variable.initialStringValue!!.first.length + 1 // include the zero byte
in NumericDatatypes -> program.memsizer.memorySize(variable.dt)
in ArrayDatatypes -> program.memsizer.memorySize(variable.dt, variable.arraysize!!)
else -> throw InternalCompilerException("weird dt")
}
allocations[variable.scopedName] = nextLocation
nextLocation += memsize
}
freeStart = nextLocation
}
fun asVmMemory(): List<String> {
/*
$4000 strz "Hello from program! "derp" bye.\n"
$2000 ubyte 65,66,67,68,0
$2100 uword $1111,$2222,$3333,$4444
*/
val mm = mutableListOf<String>()
for (variable in st.allVariables) {
val location = allocations.getValue(variable.scopedName)
val typeStr = when(variable.dt) {
DataType.UBYTE, DataType.ARRAY_UB, DataType.STR -> "ubyte"
DataType.BYTE, DataType.ARRAY_B -> "byte"
DataType.UWORD, DataType.ARRAY_UW -> "uword"
DataType.WORD, DataType.ARRAY_W -> "word"
DataType.FLOAT, DataType.ARRAY_F -> "float"
else -> throw InternalCompilerException("weird dt")
}
val value = when(variable.dt) {
DataType.FLOAT -> (variable.initialNumericValue ?: 0.0).toString()
in NumericDatatypes -> (variable.initialNumericValue ?: 0).toHex()
DataType.STR -> {
val encoded = program.encoding.encodeString(variable.initialStringValue!!.first, variable.initialStringValue!!.second)
encoded.joinToString(",") { it.toInt().toHex() } + ",0"
}
DataType.ARRAY_F -> {
if(variable.initialArrayValue!=null) {
variable.initialArrayValue!!.joinToString(",") { it.number!!.toString() }
} else {
(1..variable.arraysize!!).joinToString(",") { "0" }
}
}
in ArrayDatatypes -> {
if(variable.initialArrayValue!==null) {
variable.initialArrayValue!!.joinToString(",") { it.number!!.toHex() }
} else {
(1..variable.arraysize!!).joinToString(",") { "0" }
}
}
else -> throw InternalCompilerException("weird dt")
}
mm.add("${location.toHex()} $typeStr $value")
}
return mm
}
}

View File

@ -0,0 +1,7 @@
; Internal Math library routines - always included by the compiler
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
math {
; TODO
}

View File

@ -0,0 +1,11 @@
; Internal library routines - always included by the compiler
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
prog8_lib {
sub pattern_match(str string, str pattern) -> ubyte {
; TODO
return 0
}
}

View File

@ -1,12 +1,19 @@
package prog8.compiler
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import com.github.michaelbull.result.getOrElse
import com.github.michaelbull.result.mapError
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.code.ast.*
import prog8.code.core.DataType
import prog8.parser.SourceCode
import java.io.File
import kotlin.io.path.Path
import kotlin.io.path.isRegularFile
class IntermediateAstMaker(val program: Program) {
@ -152,9 +159,14 @@ class IntermediateAstMaker(val program: Program) {
val length: UInt? = if(directive.args.size>=3) directive.args[2].int!! else null
val sourcePath = Path(directive.definingModule.source.origin)
val includedPath = sourcePath.resolveSibling(directive.args[0].str!!)
PtInlineBinary(includedPath, offset, length, directive.position)
PtIncludeBinary(includedPath, offset, length, directive.position)
}
else -> PtNop(directive.position)
"%asminclude" -> {
val result = loadAsmIncludeFile(directive.args[0].str!!, directive.definingModule.source)
val assembly = result.getOrElse { throw it }
PtInlineAssembly(assembly, directive.position)
}
else -> TODO("directive to PtNode $directive")
}
}
@ -422,4 +434,20 @@ class IntermediateAstMaker(val program: Program) {
cast.add(transformExpression(srcCast.expression))
return cast
}
private fun loadAsmIncludeFile(filename: String, source: SourceCode): Result<String, NoSuchFileException> {
return if (filename.startsWith(SourceCode.libraryFilePrefix)) {
return com.github.michaelbull.result.runCatching {
SourceCode.Resource("/prog8lib/${filename.substring(SourceCode.libraryFilePrefix.length)}").text
}.mapError { NoSuchFileException(File(filename)) }
} else {
val sib = Path(source.origin).resolveSibling(filename)
if (sib.isRegularFile())
Ok(SourceCode.File(sib).text)
else
Ok(SourceCode.File(Path(filename)).text)
}
}
}

View File

@ -21,6 +21,7 @@ internal object DummyFunctions : IBuiltinFunctions {
internal object DummyMemsizer : IMemSizer {
override fun memorySize(dt: DataType) = 0
override fun memorySize(arrayDt: DataType, numElements: Int) = 0
}
internal object DummyStringEncoder : IStringEncoding {
@ -59,4 +60,8 @@ internal object DummyCompilationTarget : ICompilationTarget {
override fun memorySize(dt: DataType): Int {
throw NotImplementedError("dummy")
}
override fun memorySize(arrayDt: DataType, numElements: Int): Int {
throw NotImplementedError("dummy")
}
}

View File

@ -3,6 +3,10 @@ TODO
For next release
^^^^^^^^^^^^^^^^
- move prog8_lib.pattern_match to string module
- move SourceCode class?
- move internedStringsModuleName ?
...

View File

@ -181,7 +181,34 @@ data class Instruction(
val reg2: Int?=null, // 0-$ffff
val reg3: Int?=null, // 0-$ffff
val value: Int?=null, // 0-$ffff
)
) {
override fun toString(): String {
val result = mutableListOf(opcode.name.lowercase())
when(type) {
DataType.BYTE -> result.add(".b ")
DataType.WORD -> result.add(".w ")
else -> result.add(" ")
}
reg1?.let {
result.add(it.toString())
result.add(",")
}
reg2?.let {
result.add(it.toString())
result.add(",")
}
reg3?.let {
result.add(it.toString())
result.add(",")
}
value?.let {
result.add(it.toString())
}
if(result.last() == ",")
result.dropLast(1)
return result.joinToString("").trimEnd()
}
}
data class InstructionFormat(val datatypes: Set<DataType>, val reg1: Boolean, val reg2: Boolean, val reg3: Boolean, val value: Boolean)

View File

@ -23,7 +23,7 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
throw IllegalArgumentException("program cannot contain more than 65536 instructions")
}
fun run() {
fun run(throttle: Boolean = true) {
try {
var before = System.nanoTime()
var numIns = 0
@ -31,7 +31,7 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
step()
numIns++
if(stepCount and 32767 == 0) {
if(throttle && stepCount and 32767 == 0) {
Thread.sleep(0, 10) // avoid 100% cpu core usage
}