mirror of
https://github.com/irmen/prog8.git
synced 2025-01-10 20:30:23 +00:00
working on vm
This commit is contained in:
parent
4c1bb18956
commit
9b16d7c786
@ -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()
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -2,4 +2,5 @@ package prog8.code.core
|
||||
|
||||
interface IMemSizer {
|
||||
fun memorySize(dt: DataType): Int
|
||||
fun memorySize(arrayDt: DataType, numElements: Int): Int
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -12,4 +12,7 @@ internal object CbmMemorySizer: IMemSizer {
|
||||
else -> Int.MIN_VALUE
|
||||
}
|
||||
}
|
||||
|
||||
override fun memorySize(arrayDt: DataType, numElements: Int) =
|
||||
memorySize(ArrayToElementTypes.getValue(arrayDt)) * numElements
|
||||
}
|
@ -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)
|
||||
|
@ -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"""
|
||||
|
||||
}
|
||||
*/
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
7
compiler/res/prog8lib/virtual/math.p8
Normal file
7
compiler/res/prog8lib/virtual/math.p8
Normal 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
|
||||
}
|
11
compiler/res/prog8lib/virtual/prog8_lib.p8
Normal file
11
compiler/res/prog8lib/virtual/prog8_lib.p8
Normal 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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,10 @@ TODO
|
||||
|
||||
For next release
|
||||
^^^^^^^^^^^^^^^^
|
||||
- move prog8_lib.pattern_match to string module
|
||||
- move SourceCode class?
|
||||
- move internedStringsModuleName ?
|
||||
|
||||
...
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user