Initial work on calls (with runtime support too)

This commit is contained in:
Mattia Giambirtone 2022-05-22 17:23:52 +02:00
parent 50b0f98f6a
commit 5f43ea9490
7 changed files with 159 additions and 33 deletions

View File

@ -14,6 +14,7 @@
## The Peon runtime environment
import types
import strformat
import ../config
import ../frontend/meta/bytecode
import ../util/multibyte
@ -24,9 +25,9 @@ type
## The Peon Virtual Machine
stack: seq[PeonObject]
ip: int # Instruction pointer
sp: int # Stack pointer
cache: array[6, PeonObject] # Singletons cache
chunk: Chunk # Piece of bytecode to execute
depth: int
proc initCache*(self: PeonVM) =
@ -45,11 +46,10 @@ proc newPeonVM*: PeonVM =
## for executing Peon bytecode
new(result)
result.ip = 0
result.sp = 0
result.stack = newSeqOfCap[PeonObject](INITIAL_STACK_SIZE)
result.depth = 0
result.stack = newSeq[PeonObject]()
result.initCache()
for _ in 0..<INITIAL_STACK_SIZE:
result.stack.add(result.cache[0])
## Getters for singleton types (they are cached!)
@ -72,26 +72,21 @@ proc getNan*(self: PeonVM): PeonObject = self.cache[5]
proc push(self: PeonVM, obj: PeonObject) =
## Pushes a Peon object onto the
## stack
if self.sp >= self.stack.high():
for _ in 0..self.stack.len():
self.stack.add(self.getNil())
self.stack[self.sp] = obj
inc(self.sp)
self.stack.add(obj)
proc pop(self: PeonVM): PeonObject =
## Pops a Peon object off the
## stack, decreasing the stack
## pointer. The object is returned
dec(self.sp)
return self.stack[self.sp]
return self.stack.pop()
proc peek(self: PeonVM): PeonObject =
## Returns the element at the top
## of the stack without consuming
## it
return self.stack[self.sp]
return self.stack[^1]
proc readByte(self: PeonVM): uint8 =
@ -126,7 +121,10 @@ proc readInt64(self: PeonVM, idx: int): PeonObject =
## returns a Peon object. Assumes
## the constant is an Int64
var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1],
self.chunk.consts[idx + 2], self.chunk.consts[idx + 3]]
self.chunk.consts[idx + 2], self.chunk.consts[idx + 3],
self.chunk.consts[idx + 4], self.chunk.consts[idx + 5],
self.chunk.consts[idx + 6], self.chunk.consts[idx + 7],
]
result = PeonObject(kind: Int64)
copyMem(result.long.addr, arr.addr, sizeof(arr))
@ -137,16 +135,36 @@ proc readUInt64(self: PeonVM, idx: int): PeonObject =
## returns a Peon object. Assumes
## the constant is an UInt64
var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1],
self.chunk.consts[idx + 2], self.chunk.consts[idx + 3]]
self.chunk.consts[idx + 2], self.chunk.consts[idx + 3],
self.chunk.consts[idx + 4], self.chunk.consts[idx + 5],
self.chunk.consts[idx + 6], self.chunk.consts[idx + 7],
]
result = PeonObject(kind: UInt64)
copyMem(result.uLong.addr, arr.addr, sizeof(arr))
proc readUInt32(self: PeonVM, idx: int): PeonObject =
## Reads a constant from the
## chunk's constant table and
## returns a Peon object. Assumes
## the constant is an UInt32
var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1],
self.chunk.consts[idx + 2], self.chunk.consts[idx + 3]]
result = PeonObject(kind: UInt32)
copyMem(result.uInt.addr, arr.addr, sizeof(arr))
proc dispatch*(self: PeonVM) =
## Main bytecode dispatch loop
var instruction: OpCode
while true:
instruction = OpCode(self.readByte())
when DEBUG_TRACE_VM:
echo &"Stack: {self.stack}"
echo &"PC: {self.ip}"
echo &"SP: {self.stack.high()}"
echo &"Instruction: {instruction}"
discard readLine stdin
case instruction:
of LoadTrue:
self.push(self.getBool(true))
@ -162,15 +180,25 @@ proc dispatch*(self: PeonVM) =
self.push(self.readInt64(int(self.readLong())))
of LoadUInt64:
self.push(self.readUInt64(int(self.readLong())))
of LoadUInt32:
self.push(self.readUInt32(int(self.readLong())))
of OpCode.Return:
# TODO
return
if self.depth == 0:
return
else:
self.ip = int(self.pop().uInt)
of ReturnPop:
var retVal = self.pop()
self.ip = int(self.pop().uInt)
self.push(retVal)
of OpCode.LoadVar:
self.push(self.stack[self.readLong()])
of NoOp:
continue
of Pop:
discard self.pop()
of Call:
self.ip = int(self.readLong())
of Jump:
self.ip = int(self.readShort())
of JumpForwards:
@ -217,6 +245,5 @@ proc dispatch*(self: PeonVM) =
proc run*(self: PeonVM, chunk: Chunk) =
## Executes a piece of Peon bytecode.
self.chunk = chunk
self.sp = 0
self.ip = 0
self.dispatch()

View File

@ -27,11 +27,10 @@ when len(PEON_COMMIT_HASH) != 40:
const PEON_BRANCH* = "master"
when len(PEON_BRANCH) > 255:
{.fatal: "The git branch name's length must be less than or equal to 255 characters".}
const DEBUG_TRACE_VM* = false # Traces VM execution
const DEBUG_TRACE_VM* = true # Traces VM execution
const DEBUG_TRACE_GC* = false # Traces the garbage collector (TODO)
const DEBUG_TRACE_ALLOCATION* = false # Traces memory allocation/deallocation
const DEBUG_TRACE_COMPILER* = false # Traces the compiler
const INITIAL_STACK_SIZE* = 0
const PEON_VERSION_STRING* = &"Peon {PEON_VERSION.major}.{PEON_VERSION.minor}.{PEON_VERSION.patch} {PEON_RELEASE} ({PEON_BRANCH}, {CompileDate}, {CompileTime}, {PEON_COMMIT_HASH[0..8]}) [Nim {NimVersion}] on {hostOS} ({hostCPU})"
const HELP_MESSAGE* = """The peon programming language, Copyright (C) 2022 Mattia Giambirtone & All Contributors

View File

@ -140,6 +140,7 @@ type
closedOver: seq[IdentExpr]
proc newCompiler*(enableOptimizations: bool = true): Compiler =
## Initializes a new Compiler object
new(result)
@ -165,7 +166,7 @@ proc inferType(self: Compiler, node: Expression): Type
proc findByName(self: Compiler, name: string): seq[Name]
proc findByType(self: Compiler, name: string, kind: Type): seq[Name]
proc compareTypes(self: Compiler, a, b: Type): bool
proc patchReturnAddress(self: Compiler, retAddr: int)
## End of forward declarations
## Public getter for nicer error formatting
@ -724,6 +725,22 @@ proc unary(self: Compiler, node: UnaryExpr) =
let impl = self.findByType(node.token.lexeme, Type(kind: Function, returnType: valueType, node: nil, args: @[valueType]))
if impl.len() == 0:
self.error(&"cannot find a suitable implementation for '{node.token.lexeme}'")
elif impl.len() > 2:
var msg = &"multiple matching implementations of '{node.token.lexeme}' found:\n"
for fn in reversed(impl):
var node = FunDecl(fn.valueType.node)
discard self.typeToStr(fn.valueType)
msg &= &"- '{node.name.token.lexeme}' at line {node.token.line} of type {self.typeToStr(fn.valueType)}\n"
self.error(msg)
else:
# Pushes the return address
self.emitByte(LoadUInt32)
# We patch it later!
let idx = self.chunk.consts.len()
self.emitBytes(self.chunk.writeConstant((0xffffffff'u32).toQuad()))
self.emitByte(Call)
self.emitBytes(impl[0].codePos.toTriple())
self.patchReturnAddress(idx)
proc binary(self: Compiler, node: BinaryExpr) =
@ -795,7 +812,7 @@ proc declareName(self: Compiler, node: Declaration) =
returnType: self.inferType(
node.returnType),
args: @[]),
codePos: self.chunk.code.len(),
codePos: self.chunk.code.high(),
name: node.name,
isLet: false))
let fn = self.names[^1]
@ -813,8 +830,6 @@ proc declareName(self: Compiler, node: Declaration) =
self.names[^1].valueType = self.inferType(argument.valueType)
self.names[^1].valueType.node = argument.name
fn.valueType.args.add(self.names[^1].valueType)
self.emitByte(LoadVar)
self.emitBytes(self.names.high().toTriple())
else:
discard # Unreachable
@ -839,7 +854,11 @@ proc identifier(self: Compiler, node: IdentExpr) =
if not t.closedOver:
# Static name resolution, loads value at index in the stack. Very fast. Much wow.
self.emitByte(LoadVar)
self.emitBytes(index.toTriple())
if self.scopeDepth > 0:
# Skips function's return address
self.emitBytes((index - 1).toTriple())
else:
self.emitBytes(index.toTriple())
else:
if self.closedOver.len() == 0:
self.error("error: closure variable array is empty but LoadHeap would be emitted (this is an internal error and most likely a bug)")
@ -1277,6 +1296,16 @@ proc funDecl(self: Compiler, node: FunDecl) =
self.currentFunction = function
proc patchReturnAddress(self: Compiler, retAddr: int) =
## Patches the return address of a function
## call. This is called at each iteration of
## the compiler's loop
let address = self.chunk.code.len().toQuad()
self.chunk.consts[retAddr] = address[0]
self.chunk.consts[retAddr + 1] = address[1]
self.chunk.consts[retAddr + 2] = address[2]
self.chunk.consts[retAddr + 3] = address[3]
proc declaration(self: Compiler, node: Declaration) =
## Compiles all declarations

View File

@ -147,9 +147,11 @@ const stackTripleInstructions* = {StoreVar, LoadVar, LoadHeap, StoreHeap}
# of 16 bit integers
const stackDoubleInstructions* = {}
# Argument double argument instructions take hardcoded arguments on the stack as 16 bit integers
# Argument double argument instructions take hardcoded arguments as 16 bit integers
const argumentDoubleInstructions* = {PopN, }
# Argument double argument instructions take hardcoded arguments as 24 bit integers
const argumentTripleInstructions* = {Call, }
# Jump instructions jump at relative or absolute bytecode offsets
const jumpInstructions* = {Jump, LongJump, JumpIfFalse, JumpIfFalsePop,
JumpForwards, JumpBackwards,

View File

@ -67,7 +67,6 @@ proc repl =
input = editor.read()
if input.len() == 0:
continue
# Currently the parser doesn't handle these tokens well
tokens = tokenizer.lex(input, "stdin")
if tokens.len() == 0:
continue
@ -168,13 +167,72 @@ proc repl =
proc runFile(f: string) =
var
tokens: seq[Token] = @[]
tree: seq[Declaration] = @[]
compiled: Chunk
serialized: Serialized
tokenizer = newLexer()
parser = newParser()
compiler = newCompiler()
serializer = newSerializer()
vm = newPeonVM()
input: string
tokenizer.fillSymbolTable()
try:
vm.run(compiler.compile(parser.parse(tokenizer.lex(readFile(f), f), f), f))
input = readFile(f)
tokens = tokenizer.lex(input, f)
if tokens.len() == 0:
return
when debugLexer:
styledEcho fgCyan, "Tokenization step:"
for i, token in tokens:
if i == tokens.high():
# Who cares about EOF?
break
styledEcho fgGreen, "\t", $token
echo ""
tree = parser.parse(tokens, f)
if tree.len() == 0:
return
when debugParser:
styledEcho fgCyan, "Parsing step:"
for node in tree:
styledEcho fgGreen, "\t", $node
echo ""
compiled = compiler.compile(tree, f)
when debugCompiler:
styledEcho fgCyan, "Compilation step:"
stdout.styledWrite(fgCyan, "\tRaw byte stream: ", fgGreen, "[", fgYellow, compiled.code.join(", "), fgGreen, "]")
styledEcho fgCyan, "\n\nBytecode disassembler output below:\n"
disassembleChunk(compiled, f)
echo ""
serializer.dumpFile(compiled, input, f, splitFile(f).name & ".pbc")
serialized = serializer.loadFile(splitFile(f).name & ".pbc")
when debugSerializer:
var hashMatches = computeSHA256(input).toHex().toLowerAscii() == serialized.fileHash
styledEcho fgCyan, "Serialization step: "
styledEcho fgBlue, &"\t- File hash: ", fgYellow, serialized.fileHash, fgBlue, " (", if hashMatches: fgGreen else: fgRed, if hashMatches: "OK" else: "Fail", fgBlue, ")"
styledEcho fgBlue, "\t- Peon version: ", fgYellow, &"{serialized.peonVer.major}.{serialized.peonVer.minor}.{serialized.peonVer.patch}", fgBlue, " (commit ", fgYellow, serialized.commitHash[0..8], fgBlue, ") on branch ", fgYellow, serialized.peonBranch
stdout.styledWriteLine(fgBlue, "\t- Compilation date & time: ", fgYellow, fromUnix(serialized.compileDate).format("d/M/yyyy HH:mm:ss"))
stdout.styledWrite(fgBlue, &"\t- Constants segment: ")
if serialized.chunk.consts == compiled.consts:
styledEcho fgGreen, "OK"
else:
styledEcho fgRed, "Corrupted"
stdout.styledWrite(fgBlue, &"\t- Code segment: ")
if serialized.chunk.code == compiled.code:
styledEcho fgGreen, "OK"
else:
styledEcho fgRed, "Corrupted"
stdout.styledWrite(fgBlue, "\t- Line info segment: ")
if serialized.chunk.lines == compiled.lines:
styledEcho fgGreen, "OK"
else:
styledEcho fgRed, "Corrupted"
when debugRuntime:
styledEcho fgCyan, "\n\nExecution step: "
vm.run(serialized.chunk)
except LexingError:
let exc = LexingError(getCurrentException())
let relPos = tokenizer.getRelPos(exc.line)
@ -218,9 +276,9 @@ proc runFile(f: string) =
let exc = SerializationError(getCurrentException())
stderr.styledWriteLine(fgRed, "A fatal error occurred while (de-)serializing", fgYellow, &"'{exc.file}'", fgGreen, ": ", getCurrentExceptionMsg())
except IOError:
stderr.styledWriteLine("An error occurred while trying to read ", fgYellow, &"'{f}'", fgRed, &": {getCurrentExceptionMsg()}")
stderr.styledWriteLine(fgRed, "An error occurred while trying to read ", fgYellow, &"'{f}'", fgGreen, &": {getCurrentExceptionMsg()}")
except OSError:
stderr.styledWriteLine("An error occurred while trying to read ", fgYellow, &"'{f}'", fgRed, &": {osErrorMsg(osLastError())} [errno {osLastError()}]")
stderr.styledWriteLine(fgRed, "An error occurred while trying to read ", fgYellow, &"'{f}'", fgGreen, &": {osErrorMsg(osLastError())} [errno {osLastError()}]")
when isMainModule:

View File

@ -3,4 +3,3 @@ operator `+`(a: int): int {
}
+1; # Works: defined for int64
+1'u8; # No definition for int8: error!

View File

@ -85,6 +85,17 @@ proc argumentDoubleInstruction(instruction: OpCode, chunk: Chunk, offset: int):
return offset + 3
proc argumentTripleInstruction(instruction: OpCode, chunk: Chunk, offset: int): int =
## Debugs instructions that operate on a hardcoded value on the stack using a 16-bit operand
var slot = [chunk.code[offset + 1], chunk.code[offset + 2], chunk.code[offset + 3]].fromTriple()
printInstruction(instruction)
stdout.styledWrite(fgGreen, &", has argument ")
setForegroundColor(fgYellow)
stdout.write(&"{slot}")
nl()
return offset + 4
proc constantInstruction(instruction: OpCode, chunk: Chunk, offset: int): int =
## Debugs instructions that operate on the constant table
var constant = [chunk.code[offset + 1], chunk.code[offset + 2], chunk.code[
@ -94,9 +105,8 @@ proc constantInstruction(instruction: OpCode, chunk: Chunk, offset: int): int =
setForegroundColor(fgYellow)
stdout.write(&"{constant}")
nl()
let obj = chunk.consts[constant]
printDebug("Operand: ")
stdout.styledWrite(fgYellow, &"{obj}\n")
stdout.styledWriteLine(fgYellow, &"{chunk.consts[constant]}")
return offset + 4
@ -138,6 +148,8 @@ proc disassembleInstruction*(chunk: Chunk, offset: int): int =
result = stackTripleInstruction(opcode, chunk, offset)
of argumentDoubleInstructions:
result = argumentDoubleInstruction(opcode, chunk, offset)
of argumentTripleInstructions:
result = argumentTripleInstruction(opcode, chunk, offset)
of jumpInstructions:
result = jumpInstruction(opcode, chunk, offset)
else: