This commit is contained in:
Mattia Giambirtone 2023-05-09 11:00:35 +02:00
parent 7bae3ad249
commit 20da594116
2 changed files with 58 additions and 45 deletions

View File

@ -68,7 +68,8 @@ type
## this system and is not handled
## manually by the VM
bytesAllocated: tuple[total, current: int]
cycles: int
when debugGC or debugAlloc:
cycles: int
nextGC: int
pointers: HashSet[uint64]
PeonVM* = object
@ -93,9 +94,10 @@ type
frames: seq[uint64] # Stores the bottom of stack frames
results: seq[uint64] # Stores function return values
gc: PeonGC # A reference to the VM's garbage collector
breakpoints: seq[uint64] # Breakpoints where we call our debugger
debugNext: bool # Whether to debug the next instruction
lastDebugCommand: string # The last debugging command input by the user
when debugVM:
breakpoints: seq[uint64] # Breakpoints where we call our debugger
debugNext: bool # Whether to debug the next instruction
lastDebugCommand: string # The last debugging command input by the user
# Implementation of peon's memory manager
@ -105,7 +107,8 @@ proc newPeonGC*: PeonGC =
## garbage collector
result.bytesAllocated = (0, 0)
result.nextGC = FirstGC
result.cycles = 0
when debugGC or debugAlloc:
result.cycles = 0
proc collect*(self: var PeonVM)
@ -214,6 +217,16 @@ proc markRoots(self: var PeonVM): HashSet[ptr HeapObject] =
# will mistakenly assume the object to be reachable, potentially
# leading to a nasty memory leak. Let's just hope a 48+ bit address
# space makes this occurrence rare enough not to be a problem
# handles a single type (uint64), while Lox has a stack
# of heap-allocated structs (which is convenient, but slow).
# What we do instead is store all pointers allocated by us
# in a hash set and then check if any source of roots contained
# any of the integer values that we're keeping track of. Note
# that this means that if a primitive object's value happens to
# collide with an active pointer, the GC will mistakenly assume
# the object to be reachable (potentially leading to a nasty
# memory leak). Hopefully, in a 64-bit address space, this
# occurrence is rare enough for us to ignore
var result = initHashSet[uint64](self.gc.pointers.len())
for obj in self.calls:
if obj in self.gc.pointers:
@ -285,7 +298,6 @@ proc sweep(self: var PeonVM) =
## during the mark phase.
when debugGC:
echo "DEBUG - GC: Beginning sweeping phase"
when debugGC:
var count = 0
var current: ptr HeapObject
var freed: HashSet[uint64]
@ -1050,10 +1062,11 @@ proc run*(self: var PeonVM, chunk: Chunk, breakpoints: seq[uint64] = @[], repl:
self.frames = @[]
self.calls = @[]
self.operands = @[]
self.breakpoints = breakpoints
self.results = @[]
self.ip = 0
self.lastDebugCommand = ""
when debugVM:
self.breakpoints = breakpoints
self.lastDebugCommand = ""
try:
self.dispatch()
except NilAccessDefect:

View File

@ -124,11 +124,11 @@ type
of Reference:
# A managed reference
nullable*: bool # Is null a valid value for this type? (false by default)
value*: Type # The type the reference points to
value*: TypedNode # The type the reference points to
of Pointer:
# An unmanaged reference. Much
# like a raw pointer in C
data*: Type # The type we point to
data*: TypedNode # The type we point to
of TypeDecl:
# A user-defined type
fields*: seq[TypedArgument] # List of fields in the object. May be empty
@ -317,17 +317,17 @@ proc step*(self: Compiler): ASTNode {.inline.} =
# Some forward declarations
proc compareUnions*(self: Compiler, a, b: seq[tuple[match: bool, kind: Type]]): bool
proc expression*(self: Compiler, node: Expression, compile: bool = true): Type {.discardable.} = nil
proc identifier*(self: Compiler, node: IdentExpr, name: Name = nil, compile: bool = true, strict: bool = true): Type {.discardable.} = nil
proc call*(self: Compiler, node: CallExpr, compile: bool = true): Type {.discardable.} = nil
proc getItemExpr*(self: Compiler, node: GetItemExpr, compile: bool = true, matching: Type = nil): Type {.discardable.} = nil
proc unary*(self: Compiler, node: UnaryExpr, compile: bool = true): Type {.discardable.} = nil
proc binary*(self: Compiler, node: BinaryExpr, compile: bool = true): Type {.discardable.} = nil
proc lambdaExpr*(self: Compiler, node: LambdaExpr, compile: bool = true): Type {.discardable.} = nil
proc literal*(self: Compiler, node: ASTNode, compile: bool = true): Type {.discardable.} = nil
proc infer*(self: Compiler, node: LiteralExpr): Type
proc infer*(self: Compiler, node: Expression): Type
proc inferOrError*(self: Compiler, node: Expression): Type
proc expression*(self: Compiler, node: Expression, compile: bool = true): TypedNode {.discardable.} = nil
proc identifier*(self: Compiler, node: IdentExpr, name: Name = nil, compile: bool = true, strict: bool = true): TypedNode {.discardable.} = nil
proc call*(self: Compiler, node: CallExpr, compile: bool = true): TypedNode {.discardable.} = nil
proc getItemExpr*(self: Compiler, node: GetItemExpr, compile: bool = true, matching: Type = nil): TypedNode {.discardable.} = nil
proc unary*(self: Compiler, node: UnaryExpr, compile: bool = true): TypedNode {.discardable.} = nil
proc binary*(self: Compiler, node: BinaryExpr, compile: bool = true): TypedNode {.discardable.} = nil
proc lambdaExpr*(self: Compiler, node: LambdaExpr, compile: bool = true): TypedNode {.discardable.} = nil
proc literal*(self: Compiler, node: ASTNode, compile: bool = true): TypedNode {.discardable.} = nil
proc infer*(self: Compiler, node: LiteralExpr): TypedNode
proc infer*(self: Compiler, node: Expression): TypedNode
proc inferOrError*(self: Compiler, node: Expression): TypedNode
proc findByName*(self: Compiler, name: string): seq[Name]
proc findInModule*(self: Compiler, name: string, module: Name): seq[Name]
proc findByType*(self: Compiler, name: string, kind: Type): seq[Name]
@ -420,7 +420,7 @@ proc compare*(self: Compiler, a, b: Type): bool =
# a and b are of either of the two
# types in this branch, so we just need
# to compare their values
return self.compare(a.value, b.value)
return self.compare(a.value.value, b.value.value)
of Function:
# Functions are a bit trickier to compare
if a.arguments.len() != b.arguments.len():
@ -569,7 +569,7 @@ proc toIntrinsic*(name: string): Type =
return Type(kind: String)
proc infer*(self: Compiler, node: LiteralExpr): Type =
proc infer*(self: Compiler, node: LiteralExpr): TypedNode =
## Infers the type of a given literal expression
if node.isNil():
return nil
@ -577,32 +577,32 @@ proc infer*(self: Compiler, node: LiteralExpr): Type =
of intExpr, binExpr, octExpr, hexExpr:
let size = node.token.lexeme.split("'")
if size.len() == 1:
return Type(kind: Int64)
return TypedNode(node: node, value: Type(kind: Int64))
let typ = size[1].toIntrinsic()
if not self.compare(typ, nil):
return typ
return TypedNode(node: node, value: typ)
else:
self.error(&"invalid type specifier '{size[1]}' for int", node)
of floatExpr:
let size = node.token.lexeme.split("'")
if size.len() == 1:
return Type(kind: Float64)
return TypedNode(node: node, value: Type(kind: Float64))
let typ = size[1].toIntrinsic()
if not typ.isNil():
return typ
return TypedNode(node: node, value: typ)
else:
self.error(&"invalid type specifier '{size[1]}' for float", node)
of trueExpr:
return Type(kind: Bool)
return TypedNode(node: node, value: Type(kind: Bool))
of falseExpr:
return Type(kind: Bool)
return TypedNode(node: node, value: Type(kind: Bool))
of strExpr:
return Type(kind: String)
return TypedNode(node: node, value: Type(kind: String))
else:
discard # Unreachable
proc infer*(self: Compiler, node: Expression): Type =
proc infer*(self: Compiler, node: Expression): TypedNode =
## Infers the type of a given expression and
## returns it
if node.isNil():
@ -621,9 +621,9 @@ proc infer*(self: Compiler, node: Expression): Type =
of NodeKind.callExpr:
result = self.call(CallExpr(node), compile=false)
of NodeKind.refExpr:
result = Type(kind: Reference, value: self.infer(Ref(node).value))
result = TypedNode(node: node, value: Type(kind: Reference, value: self.infer(Ref(node).value)))
of NodeKind.ptrExpr:
result = Type(kind: Pointer, data: self.infer(Ptr(node).value))
result = TypedNode(node: node, value: Type(kind: Pointer, data: self.infer(Ptr(node).value)))
of NodeKind.groupingExpr:
result = self.infer(GroupingExpr(node).expression)
of NodeKind.getItemExpr:
@ -634,7 +634,7 @@ proc infer*(self: Compiler, node: Expression): Type =
discard # TODO
proc inferOrError*(self: Compiler, node: Expression): Type =
proc inferOrError*(self: Compiler, node: Expression): TypedNode =
## Attempts to infer the type of
## the given expression and raises an
## error if it fails
@ -648,16 +648,16 @@ proc stringify*(self: Compiler, typ: Type): string =
## type object
if typ.isNil():
return "nil"
case typ.value.kind:
case typ.kind:
of Int8, UInt8, Int16, UInt16, Int32,
UInt32, Int64, UInt64, Float32, Float64,
Char, Byte, String, Nil, TypeKind.Nan, Bool,
TypeKind.Inf, Auto:
result &= ($typ.value.kind).toLowerAscii()
result &= ($typ.kind).toLowerAscii()
of Pointer:
result &= &"ptr {self.stringify(typ.value)}"
result &= &"ptr {self.stringify(typ)}"
of Reference:
result &= &"ref {self.stringify(typ.value)}"
result &= &"ref {self.stringify(typ)}"
of Any:
return "any"
of Union:
@ -770,9 +770,9 @@ proc check*(self: Compiler, term: Expression, kind: Type) {.inline.} =
## Raises an error if appropriate and returns
## otherwise
let k = self.inferOrError(term)
if not self.compare(k, kind):
if not self.compare(k.value, kind):
self.error(&"expecting value of type {self.stringify(kind)}, got {self.stringify(k)}", term)
elif k.kind == Any and kind.kind != Any:
elif k.value.kind == Any and kind.kind != Any:
self.error(&"any is not a valid type in this context")
@ -857,7 +857,7 @@ proc unpackGenerics*(self: Compiler, condition: Expression, list: var seq[tuple[
## Recursively unpacks a type constraint in a generic type
case condition.kind:
of identExpr:
list.add((accept, self.inferOrError(condition)))
list.add((accept, self.inferOrError(condition).value))
if list[^1].kind.kind == Auto:
self.error("automatic types cannot be used within generics", condition)
of binaryExpr:
@ -883,7 +883,7 @@ proc unpackUnion*(self: Compiler, condition: Expression, list: var seq[tuple[mat
## Recursively unpacks a type union
case condition.kind:
of identExpr:
list.add((accept, self.inferOrError(condition)))
list.add((accept, self.inferOrError(condition).value))
of binaryExpr:
let condition = BinaryExpr(condition)
case condition.operator.lexeme:
@ -966,13 +966,13 @@ proc declare*(self: Compiler, node: ASTNode): Name {.discardable.} =
n.isGeneric = true
var typ: Type
for argument in node.arguments:
typ = self.infer(argument.valueType)
typ = self.infer(argument.valueType).value
if not typ.isNil() and typ.kind == Auto:
n.obj.value.isAuto = true
if n.isGeneric:
self.error("automatic types cannot be used within generics", argument.valueType)
break
typ = self.infer(node.returnType)
typ = self.infer(node.returnType).value
if not typ.isNil() and typ.kind == Auto:
n.obj.value.isAuto = true
if n.isGeneric:
@ -1023,7 +1023,7 @@ proc declare*(self: Compiler, node: ASTNode): Name {.discardable.} =
else:
case node.value.kind:
of identExpr:
n.obj.value = self.inferOrError(node.value)
n.obj.value = self.inferOrError(node.value).value
of binaryExpr:
# Type union
n.obj.value = Type(kind: Union, types: @[])