Return addresses are now 64 bits long, return statements now compile to jumps, initial (broken) work on generics

This commit is contained in:
Mattia Giambirtone 2022-10-13 13:12:24 +02:00
parent 807b48bac9
commit d4d1034cef
7 changed files with 460 additions and 396 deletions

View File

@ -181,7 +181,7 @@ proc mark(self: ptr HeapObject): bool =
return true
proc markRoots(self: PeonGC): seq[ptr HeapObject] =
proc markRoots(self: PeonGC): seq[ptr HeapObject] =
## Marks root objects *not* to be
## collected by the GC and returns
## their addresses
@ -433,7 +433,7 @@ proc peekc(self: PeonVM, distance: int = 0): uint64 {.used.} =
return self.calls[self.calls.high() + distance]
proc getc(self: PeonVM, idx: int): uint64 =
proc getc(self: PeonVM, idx: int): uint64 =
## Getter method that abstracts
## indexing our call stack through
## stack frames
@ -474,7 +474,7 @@ proc popClosure(self: PeonVM, idx: int): uint64 =
# Byte-level primitives to read and decode
# bytecode
proc readByte(self: PeonVM): uint8 =
proc readByte(self: PeonVM): uint8 =
## Reads a single byte from the
## bytecode and returns it as an
## unsigned 8 bit integer
@ -482,7 +482,7 @@ proc readByte(self: PeonVM): uint8 =
return self.chunk.code[self.ip - 1]
proc readShort(self: PeonVM): uint16 =
proc readShort(self: PeonVM): uint16 =
## Reads two bytes from the
## bytecode and returns them
## as an unsigned 16 bit
@ -490,7 +490,7 @@ proc readShort(self: PeonVM): uint16 =
return [self.readByte(), self.readByte()].fromDouble()
proc readLong(self: PeonVM): uint32 =
proc readLong(self: PeonVM): uint32 =
## Reads three bytes from the
## bytecode and returns them
## as an unsigned 32 bit
@ -500,7 +500,7 @@ proc readLong(self: PeonVM): uint32 =
return uint32([self.readByte(), self.readByte(), self.readByte()].fromTriple())
proc readUInt(self: PeonVM): uint32 =
proc readUInt(self: PeonVM): uint32 =
## Reads three bytes from the
## bytecode and returns them
## as an unsigned 32 bit
@ -511,7 +511,7 @@ proc readUInt(self: PeonVM): uint32 =
# Functions to read primitives from the chunk's
# constants table
proc constReadInt64(self: PeonVM, idx: int): int64 =
proc constReadInt64(self: PeonVM, idx: int): int64 =
## Reads a constant from the
## chunk's constant table and
## returns it as an int64
@ -523,7 +523,7 @@ proc constReadInt64(self: PeonVM, idx: int): int64 =
copyMem(result.addr, arr.addr, sizeof(arr))
proc constReadUInt64(self: PeonVM, idx: int): uint64 =
proc constReadUInt64(self: PeonVM, idx: int): uint64 =
## Reads a constant from the
## chunk's constant table and
## returns it as an uint64
@ -535,7 +535,7 @@ proc constReadUInt64(self: PeonVM, idx: int): uint64 =
copyMem(result.addr, arr.addr, sizeof(arr))
proc constReadUInt32(self: PeonVM, idx: int): uint32 =
proc constReadUInt32(self: PeonVM, idx: int): uint32 =
## Reads a constant from the
## chunk's constant table and
## returns it as an int32
@ -544,7 +544,7 @@ proc constReadUInt32(self: PeonVM, idx: int): uint32 =
copyMem(result.addr, arr.addr, sizeof(arr))
proc constReadInt32(self: PeonVM, idx: int): int32 =
proc constReadInt32(self: PeonVM, idx: int): int32 =
## Reads a constant from the
## chunk's constant table and
## returns it as an uint32
@ -553,7 +553,7 @@ proc constReadInt32(self: PeonVM, idx: int): int32 =
copyMem(result.addr, arr.addr, sizeof(arr))
proc constReadInt16(self: PeonVM, idx: int): int16 =
proc constReadInt16(self: PeonVM, idx: int): int16 =
## Reads a constant from the
## chunk's constant table and
## returns it as an int16
@ -561,7 +561,7 @@ proc constReadInt16(self: PeonVM, idx: int): int16 =
copyMem(result.addr, arr.addr, sizeof(arr))
proc constReadUInt16(self: PeonVM, idx: int): uint16 =
proc constReadUInt16(self: PeonVM, idx: int): uint16 =
## Reads a constant from the
## chunk's constant table and
## returns it as an uint16
@ -569,14 +569,14 @@ proc constReadUInt16(self: PeonVM, idx: int): uint16 =
copyMem(result.addr, arr.addr, sizeof(arr))
proc constReadInt8(self: PeonVM, idx: int): int8 =
proc constReadInt8(self: PeonVM, idx: int): int8 =
## Reads a constant from the
## chunk's constant table and
## returns it as an int8
result = int8(self.chunk.consts[idx])
proc constReadUInt8(self: PeonVM, idx: int): uint8 =
proc constReadUInt8(self: PeonVM, idx: int): uint8 =
## Reads a constant from the
## chunk's constant table and
## returns it as an uint8
@ -628,7 +628,7 @@ when debugVM: # So nim shuts up
stdout.styledWrite(fgGreen, "Call Stack: ", fgMagenta, "[")
for i, e in self.calls:
stdout.styledWrite(fgYellow, $e)
if i < self.calls.len():
if i < self.calls.high():
stdout.styledWrite(fgYellow, ", ")
styledEcho fgMagenta, "]"
if self.operands.len() !> 0:
@ -724,7 +724,7 @@ proc dispatch*(self: PeonVM) =
# Calls a peon function. The calling convention here
# is pretty simple: the first value in the frame is
# the new instruction pointer to jump to, then a
# 32-bit return address follows. After that, all
# 64-bit return address follows. After that, all
# arguments and locals follow. Note that, due to
# how the stack works, all arguments before the call
# are in the reverse order in which they are passed
@ -780,10 +780,6 @@ proc dispatch*(self: PeonVM) =
# in a hidden function, so this
# will also exit the VM if we're
# at the end of the program
while self.calls.len().uint64 !> self.frames[^1] + 2'u64:
# Discards the function's local variables,
# if there is any
discard self.popc()
let ret = self.popc() # Return address
discard self.popc() # Function address
if self.readByte() == 1:
@ -796,7 +792,12 @@ proc dispatch*(self: PeonVM) =
if self.frames.len() == 0:
# End of the program!
return
self.ip = ret.uint
# We change the instruction
# pointer just now because
# if we did it beforehand,
# our readByte() call would've
# read from the wrong offset
self.ip = ret
of SetResult:
# Sets the result of the
# current function. A Return
@ -1023,6 +1024,8 @@ proc dispatch*(self: PeonVM) =
# cannot be converted to a date. The number
# is in seconds
self.push(cast[uint64](getMonoTime().ticks().float() / 1_000_000_000))
of LogicalNot:
self.push(uint64(not self.pop().bool))
else:
discard

File diff suppressed because it is too large Load Diff

View File

@ -530,7 +530,7 @@ proc parseBackticks(self: Lexer) =
## parser complaining about syntax
## errors
while not self.match("`") and not self.done():
if self.peek().isAlphaNumeric() or self.symbols.existsSymbol(self.peek()) or self.peek() in ["&", "|"]:
if self.peek().isAlphaNumeric() or self.symbols.existsSymbol(self.peek()):
discard self.step()
continue
self.error(&"unexpected character: '{self.peek()}'")

View File

@ -147,7 +147,6 @@ type
StoreVar, # Stores the value of b at position a in the stack
LoadClosure, # Pushes the object position x in the closure array onto the stack
StoreClosure, # Stores the value of b at position a in the closure array
LiftArgument, # Closes over a function argument
PopClosure,
## Looping and jumping
Jump, # Absolute, unconditional jump into the bytecode
@ -171,11 +170,12 @@ type
## Coroutines
Await, # Calls an asynchronous function
## Misc
Assert, # Raises an AssertionFailed exception if x is false
NoOp, # Just a no-op
PopC, # Pop off the call stack onto the operand stack
PushC, # Pop off the operand stack onto the call stack
SysClock64 # Pushes the output of a monotonic clock on the stack
Assert, # Raises an AssertionFailed exception if x is false
NoOp, # Just a no-op
PopC, # Pop off the call stack onto the operand stack
PushC, # Pop off the operand stack onto the call stack
SysClock64, # Pushes the output of a monotonic clock on the stack
LogicalNot
# We group instructions by their operation/operand types for easier handling when debugging
@ -239,6 +239,7 @@ const simpleInstructions* = {Return, LoadNil,
PrintNan,
PrintInf,
PrintString,
LogicalNot
}
# Constant instructions are instructions that operate on the bytecode constant table
@ -251,7 +252,7 @@ const constantInstructions* = {LoadInt64, LoadUInt64,
# Stack triple instructions operate on the stack at arbitrary offsets and pop arguments off of it in the form
# of 24 bit integers
const stackTripleInstructions* = {StoreVar, LoadVar, LoadCLosure, LiftArgument, PopClosure}
const stackTripleInstructions* = {StoreVar, LoadVar, LoadCLosure, PopClosure}
# Stack double instructions operate on the stack at arbitrary offsets and pop arguments off of it in the form
# of 16 bit integers

View File

@ -59,5 +59,5 @@ proc fillSymbolTable*(tokenizer: Lexer) =
tokenizer.symbols.addKeyword("ref", TokenType.Ref)
tokenizer.symbols.addKeyword("ptr", TokenType.Ptr)
for sym in [">", "<", "=", "~", "/", "+", "-", "_", "*", "?", "@", ":", "==", "!=",
">=", "<=", "+=", "-=", "/=", "*=", "**=", "!", "%"]:
">=", "<=", "+=", "-=", "/=", "*=", "**=", "!", "%", "&", "|", "^"]:
tokenizer.symbols.addSymbol(sym, Symbol)

View File

@ -1,18 +1,11 @@
import std;
operator `+`(a, b: int): int {
#pragma[magic: "AddInt64", pure]
}
operator `+`(a, b: int32): int32 {
#pragma[magic: "AddInt64", pure]
}
fn sum[T: int | int32](a, b: T): T {
return a + b;
}
sum(1, 2);
sum(1'i32, 2'i32);
# print(sum(1, 2));
# print(sum(1'i32, 2'i32));
print(sum(1'i16, 2'i16)); # Will not work if uncommented!

View File

@ -535,19 +535,44 @@ operator `or`*(a, b: bool): bool {
}
operator `&`*(a, b: bool): bool {
#pragma[magic: "LogicalAnd", pure]
operator `not`*(a: bool): bool {
#pragma[magic: "LogicalNot", pure]
}
operator `|`*(a, b: bool): bool {
#pragma[magic: "LogicalOr", pure]
operator `&`*(a, b: int): bool {
#pragma[magic: "And", pure]
}
operator `|`*(a, b: int): int {
#pragma[magic: "Or", pure]
}
operator `~`*(a: int): int {
#pragma[magic: "Not", pure]
}
operator `>>`*(a, b: int): int {
#pragma[magic: "RShift", pure]
}
operator `<<`*(a, b: int): int {
#pragma[magic: "LShift", pure]
}
operator `^`*(a, b: int): int {
#pragma[magic: "Xor", pure]
}
# Assignment operators
operator `=`*[T: Any](a: var T, b: T) {
operator `=`*[T: all](a: var T, b: T) {
#pragma[magic: "GenericAssign"]
}