Initial work on CFI-like functionality for better debugging

This commit is contained in:
Mattia Giambirtone 2022-05-24 22:26:45 +02:00
parent dbeae16dc4
commit 48d1c3fc8c
8 changed files with 227 additions and 93 deletions

View File

@ -239,7 +239,7 @@ proc dispatch*(self: PeonVM) =
# pushing it on the stack
let retVal = self.pop()
let frame = self.frames.pop()
for i in countdown(self.stack.high(), frame):
for i in countdown(0, frame):
discard self.pop()
self.ip = int(self.pop().uInt)
self.push(retVal)

View File

@ -290,14 +290,22 @@ proc patchJump(self: Compiler, offset: int) =
case OpCode(self.chunk.code[offset]):
of LongJumpForwards:
self.chunk.code[offset] = JumpForwards.uint8()
jump -= 4
of LongJumpBackwards:
self.chunk.code[offset] = JumpBackwards.uint8()
jump -= 4
of LongJumpIfFalse:
self.chunk.code[offset] = JumpIfFalse.uint8()
of LongJumpIfFalsePop:
self.chunk.code[offset] = JumpIfFalsePop.uint8()
of LongJumpIfFalseOrPop:
self.chunk.code[offset] = JumpIfFalseOrPop.uint8()
of JumpForwards, JumpBackwards:
# We do this because a relative jump
# does not normally take into account
# its argument, which is hardcoded in
# the bytecode itself
jump -= 3
else:
discard
self.chunk.code.delete(offset + 1) # Discards the first 8 bits of the jump offset (which are empty)
@ -308,14 +316,22 @@ proc patchJump(self: Compiler, offset: int) =
case OpCode(self.chunk.code[offset]):
of JumpForwards:
self.chunk.code[offset] = LongJumpForwards.uint8()
jump -= 3
of JumpBackwards:
self.chunk.code[offset] = LongJumpBackwards.uint8()
jump -= 3
of JumpIfFalse:
self.chunk.code[offset] = LongJumpIfFalse.uint8()
of JumpIfFalsePop:
self.chunk.code[offset] = LongJumpIfFalsePop.uint8()
of JumpIfFalseOrPop:
self.chunk.code[offset] = LongJumpIfFalseOrPop.uint8()
of LongJumpForwards, LongJumpBackwards:
# We do this because a relative jump
# does not normally take into account
# its argument, which is hardcoded in
# the bytecode itself
jump -= 4
else:
discard
let offsetArray = jump.toTriple()
@ -751,7 +767,7 @@ proc matchImpl(self: Compiler, name: string, kind: Type): Name =
if name.valueType.kind != Function:
msg &= ", not a callable"
elif kind.args.len() != name.valueType.args.len():
msg &= &", wrong number of arguments ({name.valueType.args.len()} expected, got {kind.args.len()})\n"
msg &= &", wrong number of arguments ({name.valueType.args.len()} expected, got {kind.args.len()})"
else:
for i, arg in kind.args:
if not self.compareTypes(arg, name.valueType.args[i]):
@ -930,7 +946,7 @@ 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 - self.frames[^1]).toTriple())
self.emitBytes((index - self.frames[self.scopeDepth]).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)")
@ -995,6 +1011,7 @@ proc beginScope(self: Compiler) =
## Begins a new local scope by incrementing the current
## scope's depth
inc(self.scopeDepth)
proc endScope(self: Compiler) =
@ -1181,7 +1198,6 @@ proc returnStmt(self: Compiler, node: ReturnStmt) =
self.emitByte(OpCode.ReturnValue)
else:
self.emitByte(OpCode.Return)
discard self.frames.pop()
proc yieldStmt(self: Compiler, node: YieldStmt) =
@ -1302,10 +1318,11 @@ proc funDecl(self: Compiler, node: FunDecl) =
## Compiles function declarations
# A function's code is just compiled linearly
# and then jumped over
let jmp = self.emitJump(Jump)
let jmp = self.emitJump(JumpForwards)
var function = self.currentFunction
self.declareName(node)
self.frames.add(self.names.high())
# TODO: Forward declarations
if node.body != nil:
if BlockStmt(node.body).code.len() == 0:
@ -1334,6 +1351,8 @@ proc funDecl(self: Compiler, node: FunDecl) =
# the try/finally block with the deferred
# code
var deferStart = self.deferred.len()
# We let our debugger know a function is starting
self.chunk.cfi.add(self.chunk.code.high().toTriple())
self.blockStmt(BlockStmt(node.body))
# Yup, we're done. That was easy, huh?
@ -1346,6 +1365,19 @@ proc funDecl(self: Compiler, node: FunDecl) =
# are resolved properly). There's a need for a bit
# of boilerplate code to make closures work, but
# that's about it
# Function is ending!
self.chunk.cfi.add(self.chunk.code.high().toTriple())
self.chunk.cfi.add(self.frames[^1].toTriple())
self.chunk.cfi.add(uint8(node.arguments.len()))
if not system.`==`(node.name, nil):
self.chunk.cfi.add(node.name.token.lexeme.len().toDouble())
var s = node.name.token.lexeme
if node.name.token.lexeme.len() >= uint16.high().int:
s = node.name.token.lexeme[0..uint16.high()]
self.chunk.cfi.add(s.toBytes())
else:
self.chunk.cfi.add(0.toDouble())
case self.currentFunction.kind:
of NodeKind.funDecl:
if not self.currentFunction.hasExplicitReturn:
@ -1368,6 +1400,7 @@ proc funDecl(self: Compiler, node: FunDecl) =
self.patchJump(jmp)
# This makes us compile nested functions correctly
self.currentFunction = function
discard self.frames.pop()
proc patchReturnAddress(self: Compiler, retAddr: int) =

View File

@ -38,9 +38,21 @@ type
## are 3 and 4"
## This is more efficient than using the naive approach, which would encode
## the same line number multiple times and waste considerable amounts of space.
## cfi represents Call Frame Information and encodes the following information:
## - Function name
## - Stack bottom
## - Argument count
## The encoding for CFI data is the following:
## - First, the position into the bytecode where the function begins is encoded
## - Second, the position into the bytecode where the function ends is encoded
## - Then, the frame's stack bottom is encoded as a 3 byte integer
## - After the frame's stack bottom follows the argument count as a 1 byte integer
## - Lastly, the function's name (optional) is encoded in ASCII, prepended with
## its size as a 2-byte integer
consts*: seq[uint8]
code*: seq[uint8]
lines*: seq[int]
cfi*: seq[uint8]
OpCode* {.pure.} = enum
## Enum of Peon's bytecode opcodes
@ -165,7 +177,7 @@ const jumpInstructions* = {Jump, LongJump, JumpIfFalse, JumpIfFalsePop,
proc newChunk*: Chunk =
## Initializes a new, empty chunk
result = Chunk(consts: @[], code: @[], lines: @[])
result = Chunk(consts: @[], code: @[], lines: @[], cfi: @[])
proc `$`*(self: Chunk): string = &"""Chunk(consts=[{self.consts.join(", ")}], code=[{self.code.join(", ")}], lines=[{self.lines.join(", ")}])"""

View File

@ -49,6 +49,7 @@ proc repl =
tokenizer = newLexer()
parser = newParser()
compiler = newCompiler()
debugger = newDebugger()
serializer = newSerializer()
vm = newPeonVM()
editor = getLineEditor()
@ -91,8 +92,9 @@ proc repl =
styledEcho fgCyan, "Compilation step:"
styledEcho fgCyan, "\tRaw byte stream: ", fgGreen, "[", fgYellow, compiled.code.join(", "), fgGreen, "]"
styledEcho fgCyan, "\tConstant table: ", fgGreen, "[", fgYellow, compiled.consts.join(", "), fgGreen, "]"
styledEcho fgCyan, "\tCFI data: ", fgGreen, "[", fgYellow, compiled.cfi.join(", "), fgGreen, "]"
styledEcho fgCyan, "\nBytecode disassembler output below:\n"
disassembleChunk(compiled, "stdin")
debugger.disassembleChunk(compiled, "stdin")
echo ""
serializer.dumpFile(compiled, input, "stdin", "stdin.pbc")
@ -175,6 +177,7 @@ proc runFile(f: string) =
tokenizer = newLexer()
parser = newParser()
compiler = newCompiler()
debugger = newDebugger()
serializer = newSerializer()
vm = newPeonVM()
input: string
@ -205,8 +208,10 @@ proc runFile(f: string) =
styledEcho fgCyan, "Compilation step:"
styledEcho fgCyan, "\tRaw byte stream: ", fgGreen, "[", fgYellow, compiled.code.join(", "), fgGreen, "]"
styledEcho fgCyan, "\tConstant table: ", fgGreen, "[", fgYellow, compiled.consts.join(", "), fgGreen, "]"
styledEcho fgCyan, "\tCFI data: ", fgGreen, "[", fgYellow, compiled.cfi.join(", "), fgGreen, "]"
styledEcho fgCyan, "\nBytecode disassembler output below:\n"
disassembleChunk(compiled, f)
debugger.disassembleChunk(compiled, f)
echo ""
serializer.dumpFile(compiled, input, f, splitFile(f).name & ".pbc")
@ -215,7 +220,7 @@ proc runFile(f: string) =
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
styledEcho fgBlue, "\t- Peon version: ", fgYellow, &"{serialized.version.major}.{serialized.version.minor}.{serialized.version.patch}", fgBlue, " (commit ", fgYellow, serialized.commit[0..8], fgBlue, ") on branch ", fgYellow, serialized.branch
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:

View File

@ -6,7 +6,11 @@ operator `+`(a: int32): int32 {
return a;
}
fn `+`(a, b: int): int {
return a + b;
}
var `+`: int = 1; # hehehehe
+1; # Works: defined for int64
+1'u8; # Nope!
# +1'u8; # Nope!

View File

@ -21,6 +21,22 @@ import strutils
import terminal
type
CFIElement = ref object
start, stop, bottom, argc: int
name: string
Debugger* = ref object
chunk: Chunk
cfiData: seq[CFIElement]
proc newDebugger*: Debugger =
## Initializes a new, empty
## debugger object
new(result)
result.cfiData = @[]
proc nl = stdout.write("\n")
@ -44,16 +60,29 @@ proc printInstruction(instruction: OpCode, newline: bool = false) =
nl()
proc simpleInstruction(instruction: OpCode, offset: int): int =
proc checkFrame(self: Debugger, n: int) =
for i, e in self.cfiData:
if n == e.start:
styledEcho fgBlue, "==== Peon Bytecode Debugger - Begin Frame ", fgYellow, &"'{e.name}' ", fgBlue, "(", fgYellow, $i, fgBlue, ") ===="
styledEcho fgGreen, "\t- Start offset: ", fgYellow, $e.start
styledEcho fgGreen, "\t- End offset: ", fgYellow, $e.stop
styledEcho fgGreen, "\t- Stack bottom: ", fgYellow, $e.bottom
styledEcho fgGreen, "\t- Argument count: ", fgYellow, $e.argc
echo ""
elif n == e.stop:
styledEcho fgBlue, "==== Peon Bytecode Debugger - End Frame ", fgYellow, &"'{e.name}' ", fgBlue, "(", fgYellow, $i, fgBlue, ") ====\n"
proc simpleInstruction(self: Debugger, instruction: OpCode, offset: int): int =
printInstruction(instruction)
nl()
return offset + 1
proc stackTripleInstruction(instruction: OpCode, chunk: Chunk,
proc stackTripleInstruction(self: Debugger, instruction: OpCode,
offset: int): int =
## Debugs instructions that operate on a single value on the stack using a 24-bit operand
var slot = [chunk.code[offset + 1], chunk.code[offset + 2], chunk.code[
var slot = [self.chunk.code[offset + 1], self.chunk.code[offset + 2], self.chunk.code[
offset + 3]].fromTriple()
printInstruction(instruction)
stdout.styledWrite(fgGreen, &", points to index ")
@ -61,10 +90,10 @@ proc stackTripleInstruction(instruction: OpCode, chunk: Chunk,
return offset + 4
proc stackDoubleInstruction(instruction: OpCode, chunk: Chunk,
proc stackDoubleInstruction(self: Debugger, instruction: OpCode,
offset: int): int =
## Debugs instructions that operate on a single value on the stack using a 16-bit operand
var slot = [chunk.code[offset + 1], chunk.code[offset + 2]].fromDouble()
var slot = [self.chunk.code[offset + 1], self.chunk.code[offset + 2]].fromDouble()
printInstruction(instruction)
stdout.write(&", points to index ")
stdout.styledWrite(fgGreen, &", points to index ")
@ -72,28 +101,28 @@ proc stackDoubleInstruction(instruction: OpCode, chunk: Chunk,
return offset + 3
proc argumentDoubleInstruction(instruction: OpCode, chunk: Chunk, offset: int): int =
proc argumentDoubleInstruction(self: Debugger, instruction: OpCode, 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]].fromDouble()
var slot = [self.chunk.code[offset + 1], self.chunk.code[offset + 2]].fromDouble()
printInstruction(instruction)
stdout.styledWrite(fgGreen, &", has argument ")
stdout.styledWriteLine(fgYellow, $slot)
return offset + 3
proc argumentTripleInstruction(instruction: OpCode, chunk: Chunk, offset: int): int =
proc argumentTripleInstruction(self: Debugger, instruction: OpCode, offset: int): int =
## Debugs instructions that operate on a hardcoded value on the stack using a 24-bit operand
var slot = [chunk.code[offset + 1], chunk.code[offset + 2], chunk.code[offset + 3]].fromTriple()
var slot = [self.chunk.code[offset + 1], self.chunk.code[offset + 2], self.chunk.code[offset + 3]].fromTriple()
printInstruction(instruction)
stdout.styledWrite(fgGreen, ", has argument ")
stdout.styledWriteLine(fgYellow, $slot)
return offset + 4
return offset + 3
proc callInstruction(instruction: OpCode, chunk: Chunk, offset: int): int =
proc callInstruction(self: Debugger, instruction: OpCode, offset: int): int =
## Debugs function calls
var slot = [chunk.code[offset + 1], chunk.code[offset + 2], chunk.code[offset + 3]].fromTriple()
var args = [chunk.code[offset + 4], chunk.code[offset + 5], chunk.code[offset + 6]].fromTriple()
var slot = [self.chunk.code[offset + 1], self.chunk.code[offset + 2], self.chunk.code[offset + 3]].fromTriple()
var args = [self.chunk.code[offset + 4], self.chunk.code[offset + 5], self.chunk.code[offset + 6]].fromTriple()
printInstruction(instruction)
stdout.styledWrite(fgGreen, &", jumps to address ", fgYellow, $slot, fgGreen, " with ", fgYellow, $args, fgGreen, " argument")
if args > 1:
@ -102,27 +131,27 @@ proc callInstruction(instruction: OpCode, chunk: Chunk, offset: int): int =
return offset + 7
proc constantInstruction(instruction: OpCode, chunk: Chunk, offset: int): int =
proc constantInstruction(self: Debugger, instruction: OpCode, offset: int): int =
## Debugs instructions that operate on the constant table
var constant = [chunk.code[offset + 1], chunk.code[offset + 2], chunk.code[
var constant = [self.chunk.code[offset + 1], self.chunk.code[offset + 2], self.chunk.code[
offset + 3]].fromTriple()
printInstruction(instruction)
stdout.styledWrite(fgGreen, &", points to constant at position ", fgYellow, $constant)
nl()
printDebug("Operand: ")
stdout.styledWriteLine(fgYellow, &"{chunk.consts[constant]}")
stdout.styledWriteLine(fgYellow, &"{self.chunk.consts[constant]}")
return offset + 4
proc jumpInstruction(instruction: OpCode, chunk: Chunk, offset: int): int =
proc jumpInstruction(self: Debugger, instruction: OpCode, offset: int): int =
## Debugs jumps
var jump: int
case instruction:
of Jump, JumpIfFalse, JumpIfTrue, JumpIfFalsePop, JumpForwards, JumpBackwards:
jump = [chunk.code[offset + 1], chunk.code[offset + 2]].fromDouble().int()
jump = [self.chunk.code[offset + 1], self.chunk.code[offset + 2]].fromDouble().int()
of LongJump, LongJumpIfFalse, LongJumpIfTrue, LongJumpIfFalsePop,
LongJumpForwards, LongJumpBackwards:
jump = [chunk.code[offset + 1], chunk.code[offset + 2], chunk.code[
jump = [self.chunk.code[offset + 1], self.chunk.code[offset + 2], self.chunk.code[
offset + 3]].fromTriple().int()
else:
discard # Unreachable
@ -133,42 +162,67 @@ proc jumpInstruction(instruction: OpCode, chunk: Chunk, offset: int): int =
return offset + 3
proc disassembleInstruction*(chunk: Chunk, offset: int): int =
proc disassembleInstruction*(self: Debugger, offset: int): int =
## Takes one bytecode instruction and prints it
self.checkFrame(offset)
printDebug("Offset: ")
stdout.styledWriteLine(fgYellow, $offset)
printDebug("Line: ")
stdout.styledWriteLine(fgYellow, &"{chunk.getLine(offset)}")
var opcode = OpCode(chunk.code[offset])
stdout.styledWriteLine(fgYellow, &"{self.chunk.getLine(offset)}")
var opcode = OpCode(self.chunk.code[offset])
case opcode:
of simpleInstructions:
result = simpleInstruction(opcode, offset)
result = self.simpleInstruction(opcode, offset)
of constantInstructions:
result = constantInstruction(opcode, chunk, offset)
result = self.constantInstruction(opcode, offset)
of stackDoubleInstructions:
result = stackDoubleInstruction(opcode, chunk, offset)
result = self.stackDoubleInstruction(opcode, offset)
of stackTripleInstructions:
result = stackTripleInstruction(opcode, chunk, offset)
result = self.stackTripleInstruction(opcode, offset)
of argumentDoubleInstructions:
result = argumentDoubleInstruction(opcode, chunk, offset)
result = self.argumentDoubleInstruction(opcode, offset)
of argumentTripleInstructions:
result = argumentTripleInstruction(opcode, chunk, offset)
result = self.argumentTripleInstruction(opcode, offset)
of callInstructions:
result = callInstruction(opcode, chunk, offset)
result = self.callInstruction(opcode, offset)
of jumpInstructions:
result = jumpInstruction(opcode, chunk, offset)
result = self.jumpInstruction(opcode, offset)
else:
echo &"DEBUG - Unknown opcode {opcode} at index {offset}"
result = offset + 1
proc disassembleChunk*(chunk: Chunk, name: string) =
## Takes a chunk of bytecode, and prints it
echo &"==== Peon Bytecode Debugger - Chunk '{name}' ====\n"
proc parseCFIData(self: Debugger) =
var
start, stop, bottom, argc: int
fnName: string
idx = 0
size = 0
while idx < len(self.chunk.cfi):
start = int([self.chunk.cfi[idx], self.chunk.cfi[idx + 1], self.chunk.cfi[idx + 2]].fromTriple())
idx += 3
stop = int([self.chunk.cfi[idx], self.chunk.cfi[idx + 1], self.chunk.cfi[idx + 2]].fromTriple())
idx += 3
bottom = int([self.chunk.cfi[idx], self.chunk.cfi[idx + 1], self.chunk.cfi[idx + 2]].fromTriple())
idx += 3
argc = int(self.chunk.cfi[idx])
inc(idx)
size = int([self.chunk.cfi[idx], self.chunk.cfi[idx + 1]].fromDouble())
idx += 2
fnName = self.chunk.cfi[idx..<idx + size].fromBytes()
inc(idx, size)
self.cfiData.add(CFIElement(start: start, stop: stop, bottom: bottom, argc: argc, name: fnName))
proc disassembleChunk*(self: Debugger, chunk: Chunk, name: string) =
## Takes a chunk of bytecode and prints it
self.chunk = chunk
styledEcho fgBlue, &"==== Peon Bytecode Debugger - Chunk '{name}' ====\n"
var index = 0
while index < chunk.code.len:
index = disassembleInstruction(chunk, index)
self.parseCFIData()
while index < self.chunk.code.len:
index = self.disassembleInstruction(index)
echo ""
echo &"==== Debug session ended - Chunk '{name}' ===="
styledEcho fgBlue, &"==== Debug session ended - Chunk '{name}' ===="

View File

@ -12,8 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
## Utilities to convert from/to our 16-bit and 24-bit representations
## of numbers
## Utilities to handle multibyte sequences
import nimSHA2
proc toDouble*(input: int | uint | uint16): array[2, uint8] =
@ -58,4 +58,36 @@ proc fromQuad*(input: array[4, uint8]): uint =
proc fromLong*(input: array[8, uint8]): uint =
## Rebuilts the output of toQuad into
## an uint
copyMem(result.addr, unsafeAddr(input), sizeof(uint64))
copyMem(result.addr, unsafeAddr(input), sizeof(uint64))
proc toBytes*(s: string): seq[byte] =
## Converts a string into a sequence
## of bytes
for c in s:
result.add(byte(c))
proc toBytes*(s: int): array[8, uint8] =
## Converts
result = cast[array[8, uint8]](s)
proc toBytes*(d: SHA256Digest): seq[byte] =
## Converts a SHA256 hash digest to
## a sequence of bytes
for b in d:
result.add(b)
proc fromBytes*(input: seq[byte]): string =
## Converts a sequence of bytes to
## a string
for b in input:
result.add(char(b))
proc extend*[T](s: var seq[T], a: openarray[T]) =
## Extends s with the elements of a
for e in a:
s.add(e)

View File

@ -13,9 +13,10 @@
# limitations under the License.
import ../frontend/meta/errors
import ../frontend/meta/bytecode
import ../config
import multibyte
import ../frontend/compiler
import multibyte
import ../config
import strformat
import strutils
@ -36,15 +37,15 @@ type
## procedures to store
## metadata
fileHash*: string
peonVer*: tuple[major, minor, patch: int]
peonBranch*: string
commitHash*: string
version*: tuple[major, minor, patch: int]
branch*: string
commit*: string
compileDate*: int
chunk*: Chunk
proc `$`*(self: Serialized): string =
result = &"Serialized(fileHash={self.fileHash}, version={self.peonVer.major}.{self.peonVer.minor}.{self.peonVer.patch}, branch={self.peonBranch}), commitHash={self.commitHash}, date={self.compileDate}, chunk={self.chunk[]}"
result = &"Serialized(fileHash={self.fileHash}, version={self.version.major}.{self.version.minor}.{self.version.patch}, branch={self.branch}), commitHash={self.commit}, date={self.compileDate}, chunk={self.chunk[]}"
proc error(self: Serializer, message: string) =
@ -61,44 +62,17 @@ proc newSerializer*(self: Serializer = nil): Serializer =
result.chunk = nil
## Basic routines and helpers to convert various objects from and to to their byte representation
proc toBytes(self: Serializer, s: string): seq[byte] =
for c in s:
result.add(byte(c))
proc toBytes(self: Serializer, s: int): array[8, uint8] =
result = cast[array[8, uint8]](s)
proc toBytes(self: Serializer, d: SHA256Digest): seq[byte] =
for b in d:
result.add(b)
proc bytesToString(self: Serializer, input: seq[byte]): string =
for b in input:
result.add(char(b))
proc extend[T](s: var seq[T], a: openarray[T]) =
## Extends s with the elements of a
for e in a:
s.add(e)
proc writeHeaders(self: Serializer, stream: var seq[byte], file: string) =
## Writes the Peon bytecode headers in-place into a byte stream
stream.extend(self.toBytes(BYTECODE_MARKER))
stream.extend(BYTECODE_MARKER.toBytes())
stream.add(byte(PEON_VERSION.major))
stream.add(byte(PEON_VERSION.minor))
stream.add(byte(PEON_VERSION.patch))
stream.add(byte(len(PEON_BRANCH)))
stream.extend(self.toBytes(PEON_BRANCH))
stream.extend(self.toBytes(PEON_COMMIT_HASH))
stream.extend(self.toBytes(getTime().toUnixFloat().int()))
stream.extend(self.toBytes(computeSHA256(file)))
stream.extend(PEON_BRANCH.toBytes())
stream.extend(PEON_COMMIT_HASH.toBytes())
stream.extend(getTime().toUnixFloat().int().toBytes())
stream.extend(computeSHA256(file).toBytes())
proc writeLineData(self: Serializer, stream: var seq[byte]) =
@ -107,7 +81,14 @@ proc writeLineData(self: Serializer, stream: var seq[byte]) =
stream.extend(len(self.chunk.lines).toQuad())
for b in self.chunk.lines:
stream.extend(b.toTriple())
proc writeCFIData(self: Serializer, stream: var seq[byte]) =
## Writes Call Frame Information for debugging
## functions
stream.extend(len(self.chunk.cfi).toQuad())
stream.extend(self.chunk.cfi)
proc writeConstants(self: Serializer, stream: var seq[byte]) =
## Writes the constants table in-place into the
@ -128,27 +109,27 @@ proc readHeaders(self: Serializer, stream: seq[byte], serialized: Serialized): i
## Reads the bytecode headers from a given stream
## of bytes
var stream = stream
if stream[0..<len(BYTECODE_MARKER)] != self.toBytes(BYTECODE_MARKER):
if stream[0..<len(BYTECODE_MARKER)] != BYTECODE_MARKER.toBytes():
self.error("malformed bytecode marker")
result += len(BYTECODE_MARKER)
stream = stream[len(BYTECODE_MARKER)..^1]
serialized.peonVer = (major: int(stream[0]), minor: int(stream[1]), patch: int(stream[2]))
serialized.version = (major: int(stream[0]), minor: int(stream[1]), patch: int(stream[2]))
stream = stream[3..^1]
result += 3
let branchLength = stream[0]
stream = stream[1..^1]
result += 1
serialized.peonBranch = self.bytesToString(stream[0..<branchLength])
serialized.branch = stream[0..<branchLength].fromBytes()
stream = stream[branchLength..^1]
result += int(branchLength)
serialized.commitHash = self.bytesToString(stream[0..<40]).toLowerAscii()
serialized.commit = stream[0..<40].fromBytes().toLowerAscii()
stream = stream[40..^1]
result += 40
serialized.compileDate = int(fromLong([stream[0], stream[1], stream[2],
stream[3], stream[4], stream[5], stream[6], stream[7]]))
stream = stream[8..^1]
result += 8
serialized.fileHash = self.bytesToString(stream[0..<32]).toHex().toLowerAscii()
serialized.fileHash = stream[0..<32].fromBytes().toHex().toLowerAscii()
result += 32
@ -164,6 +145,17 @@ proc readLineData(self: Serializer, stream: seq[byte]): int =
stream = stream[3..^1]
proc readCFIData(self: Serializer, stream: seq[byte]): int =
## Reads Call Frame Information from a stream
## of bytes
let size = [stream[0], stream[1], stream[2], stream[3]].fromQuad()
result += 4
var stream = stream[4..^1]
for i in countup(0, int(size) - 1):
self.chunk.cfi.add(stream[i])
inc(result)
proc readConstants(self: Serializer, stream: seq[byte]): int =
## Reads the constant table from the given stream
## of bytes
@ -195,6 +187,7 @@ proc dumpBytes*(self: Serializer, chunk: Chunk, file, filename: string): seq[byt
self.chunk = chunk
self.writeHeaders(result, self.file)
self.writeLineData(result)
self.writeCFIData(result)
self.writeConstants(result)
self.writeCode(result)
@ -218,6 +211,7 @@ proc loadBytes*(self: Serializer, stream: seq[byte]): Serialized =
try:
stream = stream[self.readHeaders(stream, result)..^1]
stream = stream[self.readLineData(stream)..^1]
stream = stream[self.readCFIData(stream)..^1]
stream = stream[self.readConstants(stream)..^1]
stream = stream[self.readCode(stream)..^1]
except IndexDefect: