diff --git a/Makefile b/Makefile deleted file mode 100644 index ce4b791..0000000 --- a/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -repl: - nim --hints:off --warnings:off r src/main.nim - -pretty: - nimpretty src/*.nim src/backend/*.nim src/frontend/*.nim src/frontend/meta/*.nim src/memory/*.nim src/util/*.nim diff --git a/src/backend/vm.nim b/src/backend/vm.nim index e83a540..c4f8136 100644 --- a/src/backend/vm.nim +++ b/src/backend/vm.nim @@ -57,7 +57,7 @@ type closures: seq[uint64] # Stores closure offsets envs: seq[uint64] # Stores variables that do not have stack semantics results: seq[uint64] # Stores function's results (return values) - gc: PeonGC + gc: PeonGC # Our memory manager ObjectKind* = enum ## A tag for heap-allocated ## peon objects @@ -77,7 +77,10 @@ type discard # TODO PeonGC* = ref object ## A simple Mark&Sweep collector - ## to manage peon's heap space + ## to manage peon's heap space. + ## All heap allocation goes through + ## this system and is not handled + ## manually by the VM vm: PeonVM bytesAllocated: tuple[total, current: int] nextGC: int @@ -155,7 +158,7 @@ template free*(self: PeonGC, kind: typedesc, p: pointer): untyped = proc allocate*(self: PeonGC, kind: ObjectKind, size: typedesc, count: int): ptr HeapObject {.inline.} = ## Allocates aobject on the heap - result = cast[ptr HeapObject](self.reallocate(nil, 0, sizeof(HeapObject) * 1)) + result = cast[ptr HeapObject](self.reallocate(nil, 0, sizeof(HeapObject))) result.marked = false self.bytesAllocated.total += sizeof(result) self.bytesAllocated.current += sizeof(result) @@ -182,7 +185,8 @@ proc markRoots(self: PeonGC): seq[ptr HeapObject] = ## Marks root objects *not* to be ## collected by the GC and returns ## their addresses - + when debugGC: + echo "DEBUG - GC: Starting mark phase" # Unlike what bob does in his book, # we keep track of objects in a different # way due to how the whole thing is designed. @@ -204,9 +208,7 @@ proc markRoots(self: PeonGC): seq[ptr HeapObject] = # we allocated and that would cause a memory leak, but # with a 64-bit address-space it probably hardly matters, # so I guess this is a mostly-precise Mark&Sweep collector - when debugGC: - echo "DEBUG - GC: Starting mark phase" - var live = initHashSet[uint64]() + var live = initHashSet[uint64](self.pointers.len()) for obj in self.vm.calls: if obj in self.pointers: live.incl(obj) @@ -223,7 +225,7 @@ proc markRoots(self: PeonGC): seq[ptr HeapObject] = obj = cast[ptr HeapObject](p) if obj.mark(): when debugGC: - echo &"DEBUG - GC: Marking object: {obj[]}" + echo &"DEBUG - GC: Marked object: {obj[]}" result.add(obj) when debugGC: echo "DEBUG - GC: Mark phase complete" @@ -277,21 +279,21 @@ proc sweep(self: PeonGC) = ## nim disallows changing the ## size of a sequence during ## iteration - when debugGC: echo "DEBUG - GC: Beginning sweeping phase" var j = -1 var idx = 0 - var count = 0 + when debugGC: + var count = 0 while j < self.objects.high(): inc(j) if self.objects[j].marked: # Object is marked: don't touch it, # but reset its mark so that it doesn't # stay alive forever - self.objects[j].marked = false when debugGC: echo &"DEBUG - GC: Unmarking object: {self.objects[j][]}" + self.objects[j].marked = false inc(idx) else: # Object is unmarked: its memory is @@ -299,7 +301,8 @@ proc sweep(self: PeonGC) = self.free(self.objects[idx]) self.objects.delete(idx) inc(idx) - inc(count) + when debugGC: + inc(count) when debugGC: echo &"DEBUG - GC: Swept {count} objects" @@ -308,9 +311,9 @@ proc collect(self: PeonGC) = ## Attempts to reclaim some ## memory from unreachable ## objects onto the heap - let before {.used.} = self.bytesAllocated.current - let time {.used.} = getMonoTime().ticks().float() / 1_000_000 when debugGC: + let before = self.bytesAllocated.current + let time = getMonoTime().ticks().float() / 1_000_000 echo &"DEBUG - GC: Starting collection cycle at heap size {self.bytesAllocated.current}" self.trace(self.markRoots()) self.sweep() @@ -793,14 +796,14 @@ proc dispatch*(self: PeonVM) = if self.frames.len() == 0: # End of the program! return - self.ip = ret.uInt + self.ip = ret.uint of SetResult: # Sets the result of the # current function. A Return # instruction will pop this # off the results array and # onto the operand stack when - # the current function exits. + # the current function exits self.results[self.frames.high()] = self.pop() of StoreVar: # Stores the value at the top of the operand stack @@ -813,10 +816,12 @@ proc dispatch*(self: PeonVM) = else: self.pushc(self.pop()) of LoadClosure: - # Loads a closed-over variable onto the - # stack + # Loads a closed-over variable from the current + # environment onto the operand stack self.push(self.getClosure(self.readLong().int)) of PopClosure: + # Discards a closed-over variable from the + # current environment discard self.popClosure(self.readLong().int) of StoreClosure: # Stores/updates the value of a closed-over @@ -824,8 +829,8 @@ proc dispatch*(self: PeonVM) = let item = self.getc(self.readLong().int) self.setClosure(self.readLong().int, item) of LoadVar: - # Pushes a variable onto the operand - # stack + # Pushes a variable from the call stack + # onto the operand stack self.push(self.getc(self.readLong().int)) of NoOp: # Does nothing @@ -863,32 +868,43 @@ proc dispatch*(self: PeonVM) = # Relative, backward-jump self.ip -= self.readLong() of JumpIfFalse: - # Conditional positive jump + # Conditional, forward-jump if not self.peek().bool: self.ip += self.readLong() of JumpIfTrue: - # Conditional positive jump + # Conditional (if the top of the stack + # equals true), forward-jump let ip = self.readLong() if self.peek().bool: self.ip += ip of JumpIfFalsePop: + # Conditional (if the top of the stack + # equals false), forward-jump. Always + # pops off the operand stack let ip = self.readLong() if not self.pop().bool: self.ip += ip of JumpIfFalseOrPop: + # Conditional (if the top of the stack + # equals false), forward-jump. Pops off + # the operand stack if the value at the + # top of the operand stack is true let ip = self.readLong() if not self.peek().bool: self.ip += ip else: discard self.pop() # Built-in operations on primitive types. - # Note: for operations where the order of - # the operands matters, we don't need to + # Note that, for operations where the order + # of the operands matters, we don't need to # swap the order of the calls to pop: this # is because operators are handled like peon # functions, which means the arguments are # already reversed on the stack when we - # execute the instruction + # execute the instruction. The beauty of the + # 2's complement system is that for most integer + # types, we don't need specialized instructions + # to operate on them of Negate: self.push(uint64(-int64(self.pop()))) of NegateFloat64: