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 ## this system and is not handled
## manually by the VM ## manually by the VM
bytesAllocated: tuple[total, current: int] bytesAllocated: tuple[total, current: int]
cycles: int when debugGC or debugAlloc:
cycles: int
nextGC: int nextGC: int
pointers: HashSet[uint64] pointers: HashSet[uint64]
PeonVM* = object PeonVM* = object
@ -93,9 +94,10 @@ type
frames: seq[uint64] # Stores the bottom of stack frames frames: seq[uint64] # Stores the bottom of stack frames
results: seq[uint64] # Stores function return values results: seq[uint64] # Stores function return values
gc: PeonGC # A reference to the VM's garbage collector gc: PeonGC # A reference to the VM's garbage collector
breakpoints: seq[uint64] # Breakpoints where we call our debugger when debugVM:
debugNext: bool # Whether to debug the next instruction breakpoints: seq[uint64] # Breakpoints where we call our debugger
lastDebugCommand: string # The last debugging command input by the user debugNext: bool # Whether to debug the next instruction
lastDebugCommand: string # The last debugging command input by the user
# Implementation of peon's memory manager # Implementation of peon's memory manager
@ -105,7 +107,8 @@ proc newPeonGC*: PeonGC =
## garbage collector ## garbage collector
result.bytesAllocated = (0, 0) result.bytesAllocated = (0, 0)
result.nextGC = FirstGC result.nextGC = FirstGC
result.cycles = 0 when debugGC or debugAlloc:
result.cycles = 0
proc collect*(self: var PeonVM) 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 # will mistakenly assume the object to be reachable, potentially
# leading to a nasty memory leak. Let's just hope a 48+ bit address # 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 # 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()) var result = initHashSet[uint64](self.gc.pointers.len())
for obj in self.calls: for obj in self.calls:
if obj in self.gc.pointers: if obj in self.gc.pointers:
@ -285,7 +298,6 @@ proc sweep(self: var PeonVM) =
## during the mark phase. ## during the mark phase.
when debugGC: when debugGC:
echo "DEBUG - GC: Beginning sweeping phase" echo "DEBUG - GC: Beginning sweeping phase"
when debugGC:
var count = 0 var count = 0
var current: ptr HeapObject var current: ptr HeapObject
var freed: HashSet[uint64] var freed: HashSet[uint64]
@ -1050,10 +1062,11 @@ proc run*(self: var PeonVM, chunk: Chunk, breakpoints: seq[uint64] = @[], repl:
self.frames = @[] self.frames = @[]
self.calls = @[] self.calls = @[]
self.operands = @[] self.operands = @[]
self.breakpoints = breakpoints
self.results = @[] self.results = @[]
self.ip = 0 self.ip = 0
self.lastDebugCommand = "" when debugVM:
self.breakpoints = breakpoints
self.lastDebugCommand = ""
try: try:
self.dispatch() self.dispatch()
except NilAccessDefect: except NilAccessDefect:

View File

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