implement a new native call bytecode mechanism that can be optimized by the vm

This commit is contained in:
Alex Zenla 2025-03-18 19:57:27 -07:00
parent 10308eae5c
commit d2eaffafcc
No known key found for this signature in database
GPG Key ID: 067B238899B51269
8 changed files with 83 additions and 38 deletions

View File

@ -11,6 +11,25 @@ data class Constant(val id: UInt, val tag: ConstantTag, val value: ByteArray) {
return String(value)
}
fun readAsNativeDefinition(): List<String> {
val defs = mutableListOf<String>()
val buffer = mutableListOf<Byte>()
for (b in value) {
if (b == 0.toByte()) {
defs.add(String(buffer.toByteArray()))
buffer.clear()
continue
}
buffer.add(b)
}
if (buffer.isNotEmpty()) {
defs.add(String(buffer.toByteArray()))
}
return defs
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
other as Constant

View File

@ -4,5 +4,6 @@ import kotlinx.serialization.Serializable
@Serializable
enum class ConstantTag {
String
String,
NativeDefinition,
}

View File

@ -129,6 +129,7 @@ class IrStubOpEmitter(val irDefinition: IrDefinition, val code: CodeBuilder) : I
code.emit(Opcode.CompareEqual)
code.emit(Opcode.Not)
}
IrInfixOp.EuclideanModulo -> code.emit(Opcode.EuclideanModulo)
IrInfixOp.Remainder -> code.emit(Opcode.Remainder)
IrInfixOp.Lesser -> code.emit(Opcode.CompareLesser)
@ -207,7 +208,8 @@ class IrStubOpEmitter(val irDefinition: IrDefinition, val code: CodeBuilder) : I
code.emit(Opcode.Integer, listOf(1u))
code.emit(Opcode.Add, emptyList())
}
IrSuffixOp.Decrement-> {
IrSuffixOp.Decrement -> {
code.emit(Opcode.Integer, listOf(1u))
code.emit(Opcode.Subtract, emptyList())
}
@ -216,18 +218,22 @@ class IrStubOpEmitter(val irDefinition: IrDefinition, val code: CodeBuilder) : I
}
override fun visitIrNativeDefinition(ir: IrNativeDefinition) {
for (def in ir.definitions.reversed()) {
val defConstant = symbol.compilableSlab.compiler.constantPool.assign(
ConstantTag.String,
def.encodeToByteArray()
)
code.emit(Opcode.Constant, listOf(defConstant))
val encodedDefinitions = ir.definitions.map { def -> def.encodeToByteArray() }.toMutableList()
encodedDefinitions.add(0, ir.form.encodeToByteArray())
val buffer = ByteArray(encodedDefinitions.sumOf { it.size } + encodedDefinitions.size - 1) { 0 }
var i = 0
for ((index, encoded) in encodedDefinitions.withIndex()) {
encoded.copyInto(buffer, i, 0)
i += encoded.size
if (index != encodedDefinitions.lastIndex) {
i += 1
}
}
val formConstant = symbol.compilableSlab.compiler.constantPool.assign(
ConstantTag.String,
ir.form.encodeToByteArray()
val nativeDefinitionConstant = symbol.compilableSlab.compiler.constantPool.assign(
ConstantTag.NativeDefinition,
buffer,
)
code.emit(Opcode.Native, listOf(formConstant, ir.definitions.size.toUInt(), functionArgumentCount.toUInt()))
code.emit(Opcode.Native, listOf(nativeDefinitionConstant, functionArgumentCount.toUInt()))
}
override fun visitIrIndex(ir: IrIndex) {

View File

@ -22,9 +22,11 @@ class CompileCommand : CliktCommand(help = "Compile Pork", name = "compile") {
val path by argument("file")
private val yaml = Yaml(configuration = Yaml.default.configuration.copy(
polymorphismStyle = PolymorphismStyle.Property
))
private val yaml = Yaml(
configuration = Yaml.default.configuration.copy(
polymorphismStyle = PolymorphismStyle.Property
)
)
override fun run() {
val tool = FileTool(PlatformFsProvider.resolve(path))
@ -61,13 +63,14 @@ class CompileCommand : CliktCommand(help = "Compile Pork", name = "compile") {
var annotation = ""
val annotations = compiledWorld.annotations.filter { it.inst == (symbol.offset + index.toUInt()) }
if (annotations.isNotEmpty()) {
annotation = " ; ${annotations.joinToString(", ") { it.text}}"
annotation = " ; ${annotations.joinToString(", ") { it.text }}"
}
print(" ${symbol.offset + index.toUInt()} ${op}${annotation}")
if (op.code == Opcode.Constant) {
if (op.code == Opcode.Constant || op.code == Opcode.Native) {
val constant = compiledWorld.constantPool.constants[op.args[0].toInt()]
val constantString = when (constant.tag) {
ConstantTag.String -> "\"" + constant.readAsString() + "\""
ConstantTag.String -> "string = \"" + constant.readAsString() + "\""
ConstantTag.NativeDefinition -> "native definition = " + constant.readAsNativeDefinition().joinToString(" ") { def -> "\"${def}\"" }
}
print(" ; constant: $constantString")
}

View File

@ -6,9 +6,9 @@ import gay.pizza.pork.execution.NativeRegistry
class InternalMachine(val world: CompiledWorld, val nativeRegistry: NativeRegistry, val handlers: List<OpHandler>) {
private val inlined = world.code.map { op ->
val handler = handlers.firstOrNull { it.code == op.code } ?:
throw VirtualMachineException("Opcode ${op.code.name} does not have a handler.")
op to handler
val handler = handlers.firstOrNull { it.code == op.code }
?: throw VirtualMachineException("Opcode ${op.code.name} does not have a handler.")
op to (handler.optimize(machine = this, op) ?: handler)
}
private var inst: UInt = 0u

View File

@ -5,4 +5,6 @@ import gay.pizza.pork.bytecode.Opcode
abstract class OpHandler(val code: Opcode) {
abstract fun handle(machine: InternalMachine, op: Op)
open fun optimize(machine: InternalMachine, op: Op): OpHandler? = null
}

View File

@ -8,23 +8,15 @@ import gay.pizza.pork.vm.OpHandler
object NativeOpHandler : OpHandler(Opcode.Native) {
override fun handle(machine: InternalMachine, op: Op) {
val argumentCount = op.args[2]
val arguments = mutableListOf<Any>()
var x = argumentCount
while (x > 0u) {
x--
arguments.add(machine.localAt(x))
}
val formConstant = machine.world.constantPool.read(op.args[0])
val form = formConstant.readAsString()
val handler = optimize(machine, op)
handler.handle(machine, op)
}
override fun optimize(machine: InternalMachine, op: Op): OpHandler {
val nativeDefinition = machine.world.constantPool.read(op.args[0]).readAsNativeDefinition()
val form = nativeDefinition[0]
val provider = machine.nativeRegistry.of(form)
val countOfNativeDefs = op.args[1].toInt()
val defs = mutableListOf<String>()
for (i in 0 until countOfNativeDefs) {
defs.add(machine.pop())
}
val function = provider.provideNativeFunction(defs)
val result = function.invoke(arguments)
machine.push(if (result == Unit) None else result)
val function = provider.provideNativeFunction(nativeDefinition.subList(1, nativeDefinition.size))
return OptimizedNativeOpHandler(function)
}
}

View File

@ -0,0 +1,22 @@
package gay.pizza.pork.vm.ops
import gay.pizza.pork.bytecode.Op
import gay.pizza.pork.bytecode.Opcode
import gay.pizza.pork.execution.NativeFunction
import gay.pizza.pork.execution.None
import gay.pizza.pork.vm.InternalMachine
import gay.pizza.pork.vm.OpHandler
class OptimizedNativeOpHandler(val function: NativeFunction) : OpHandler(Opcode.Native) {
override fun handle(machine: InternalMachine, op: Op) {
val argumentCount = op.args[1]
val arguments = mutableListOf<Any>()
var x = argumentCount
while (x > 0u) {
x--
arguments.add(machine.localAt(x))
}
val result = function.invoke(arguments)
machine.push(if (result == Unit) None else result)
}
}