nan boxing
This commit is contained in:
parent
6961c91468
commit
2765daa55b
11
compiler.nim
11
compiler.nim
|
@ -7,6 +7,7 @@ import options
|
|||
import scanner
|
||||
import chunk
|
||||
import value
|
||||
import bitops # needed for value
|
||||
import config
|
||||
|
||||
type
|
||||
|
@ -351,7 +352,7 @@ proc expression(comp: Compiler) =
|
|||
|
||||
proc number(comp: Compiler) =
|
||||
# assume the number is already advanced through
|
||||
let value = comp.previous.text.parseFloat.toNdValue
|
||||
let value = comp.previous.text.parseFloat.fromFloat()
|
||||
comp.writeConstant(value)
|
||||
when debugCompiler:
|
||||
debugEcho &"Written constant (type: {value.ndType}, str repr: {$value}) to chunk"
|
||||
|
@ -374,7 +375,7 @@ proc expNil(comp: Compiler) =
|
|||
tkNil.genRule(expNil, nop, pcNone)
|
||||
|
||||
proc expString(comp: Compiler) =
|
||||
let value = comp.previous.text[1..^2].toNdValue()
|
||||
let value = comp.previous.text[1..^2].fromNimString()
|
||||
comp.writeConstant(value)
|
||||
when debugCompiler:
|
||||
debugEcho &"Written constant (type: {value.ndType}, str repr: {$value}) to chunk"
|
||||
|
@ -409,7 +410,7 @@ proc variable(comp: Compiler) =
|
|||
setOp = opSetLocal
|
||||
else:
|
||||
# global
|
||||
arg = comp.chunk.addConstant(name.toNdValue)
|
||||
arg = comp.chunk.addConstant(name.fromNimString())
|
||||
|
||||
if comp.match(tkEqual):
|
||||
# assignment (global/local)
|
||||
|
@ -648,7 +649,7 @@ proc parseFunct(comp: Compiler) =
|
|||
dec comp.stackIndex # the previous end scope did not put anything on the stack, it is jumped over
|
||||
|
||||
# get ndvalue functions:
|
||||
let ntFunct = newNdFunction(functII)
|
||||
let ntFunct = functII.fromFunct()
|
||||
|
||||
# end of function declaration:
|
||||
comp.patchJump(jumpOverBody)
|
||||
|
@ -707,7 +708,7 @@ proc parseVariable(comp: Compiler, msg: string): int =
|
|||
0 # index to the constant is irrelevant if the var is local
|
||||
else:
|
||||
# global
|
||||
comp.chunk.addConstant(name.toNdValue())
|
||||
comp.chunk.addConstant(name.fromNimString())
|
||||
|
||||
proc defineVariable(comp: Compiler, index: int) =
|
||||
## Generate code that moves the variable on the stack
|
||||
|
|
212
value.nim
212
value.nim
|
@ -1,84 +1,131 @@
|
|||
import strformat
|
||||
import bitops
|
||||
|
||||
import types/ndstring
|
||||
import types/stringutils
|
||||
|
||||
type
|
||||
NdType* = enum
|
||||
ntNil, ntBool, ntFloat, ntString,
|
||||
ntFunct,
|
||||
|
||||
type
|
||||
NdValue* = object
|
||||
case ndType*: NdType:
|
||||
of ntNil:
|
||||
discard
|
||||
of ntBool:
|
||||
boolValue*: bool
|
||||
of ntFloat:
|
||||
floatValue*: float64
|
||||
of ntString:
|
||||
stringValue*: NdString
|
||||
of ntFunct:
|
||||
entryII*: int # entry instruction index
|
||||
NdValue* = uint
|
||||
|
||||
NatReturn* = object
|
||||
ok*: bool
|
||||
msg*: string
|
||||
|
||||
# NaN boxing constants
|
||||
|
||||
# see https://craftinginterpreters.com/optimization.html
|
||||
# bit layout:
|
||||
# bit 63 is unused (can be used for more types in the future)
|
||||
# bits 62-50 are all 1 if it's not a float
|
||||
# bits 49-48 determine type:
|
||||
# 00 -> nil or bool (singletons)
|
||||
# 01 -> string
|
||||
# 10 -> funct
|
||||
|
||||
const qNan* = 0x7ffc000000000000'u
|
||||
|
||||
const ndNil* = 0x7ffc000000000001'u
|
||||
const ndTrue* = 0x7ffc000000000002'u
|
||||
const ndFalse* = 0x7ffc000000000003'u
|
||||
|
||||
# 0111 1111 1111 11*01* 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
|
||||
const tagString* = 0x7ffd000000000000'u
|
||||
|
||||
# 0111 1111 1111 11*10* 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
|
||||
const tagFunct* = 0x7ffe000000000000'u
|
||||
|
||||
const mask48* = 0xffff000000000000'u
|
||||
|
||||
# converters
|
||||
|
||||
template isNil*(val: NdValue): bool =
|
||||
val == ndNil
|
||||
|
||||
template isBool*(val: NdValue): bool =
|
||||
(val.bitor(1'u)) == ndFalse
|
||||
|
||||
template isFloat*(val: NdValue): bool =
|
||||
val.bitand(qNan) != qNan
|
||||
|
||||
template isString*(val: NdValue): bool =
|
||||
val.bitand(mask48) == tagString
|
||||
|
||||
template isFunct*(val: NdValue): bool =
|
||||
val.bitand(mask48) == tagFunct
|
||||
|
||||
# these assume that the type has been previously determined
|
||||
template asBool*(val: NdValue): bool =
|
||||
val == ndTrue
|
||||
|
||||
template asFloat*(val: NdValue): float =
|
||||
cast[float64](val)
|
||||
|
||||
template asString*(val: NdValue): NdString =
|
||||
cast[NdString](val.bitand(mask48.bitnot()))
|
||||
|
||||
template asFunct*(val: NdValue): int =
|
||||
(val.bitand(mask48.bitnot())).int
|
||||
|
||||
template fromNil*(): NdValue =
|
||||
ndNil
|
||||
|
||||
template fromBool*(val: bool): NdValue =
|
||||
if val: ndTrue else: ndFalse
|
||||
|
||||
template fromFloat*(val: float): NdValue =
|
||||
cast[NdValue](val)
|
||||
|
||||
template fromNdString*(val: NdString): NdValue =
|
||||
cast[uint](val).bitor(tagString)
|
||||
|
||||
template fromNimString*(sval: string): NdValue =
|
||||
fromNdString(newString(sval))
|
||||
|
||||
template fromFunct*(val: int): NdValue =
|
||||
cast[uint](val).bitor(tagFunct)
|
||||
|
||||
|
||||
# KON VALUE HELPERS, MUST BE DEFINED FOR EVERY KONVALUE
|
||||
|
||||
proc `$`*(val: NdValue): string =
|
||||
case val.ndType:
|
||||
of ntFloat:
|
||||
return $val.floatValue
|
||||
of ntBool:
|
||||
return $val.boolValue
|
||||
of ntNil:
|
||||
return "nil"
|
||||
of ntString:
|
||||
return $val.stringValue
|
||||
of ntFunct:
|
||||
return &"Function object: {val.entryII}"
|
||||
if val.isNil():
|
||||
return "nil"
|
||||
elif val == ndTrue:
|
||||
return "true"
|
||||
elif val == ndFalse:
|
||||
return "false"
|
||||
elif val.isFloat():
|
||||
return $val.asFloat()
|
||||
elif val.isFunct():
|
||||
return &"Function object {val.asFunct()}"
|
||||
elif val.isString():
|
||||
return $val.asString()
|
||||
|
||||
proc isFalsey*(val: NdValue): bool =
|
||||
val.ndType in {ntNil} or (val.ndType == ntBool and not val.boolValue)
|
||||
val == ndNil or val == ndFalse
|
||||
|
||||
template isTruthy*(val: NdValue): bool =
|
||||
not isFalsey(val)
|
||||
not val.isFalsey()
|
||||
|
||||
proc equal*(val, right: NdValue): bool =
|
||||
if val.ndType != right.ndType:
|
||||
false
|
||||
if val.isFloat() and right.isFloat():
|
||||
val.asFloat() == right.asFloat()
|
||||
else:
|
||||
case val.ndType:
|
||||
of ntFloat:
|
||||
val.floatValue == right.floatValue
|
||||
of ntBool:
|
||||
val.boolValue == right.boolValue
|
||||
of ntNil:
|
||||
true
|
||||
of ntString:
|
||||
# TODO this was meant for nim strings, not ndStrings, FIXME!
|
||||
val.stringValue == right.stringValue
|
||||
of ntFunct:
|
||||
val.entryII == right.entryII
|
||||
val == right
|
||||
|
||||
# NIM VALUE TO KON VALUE WRAPPERS
|
||||
|
||||
proc toNdValue*(val: float): NdValue =
|
||||
NdValue(ndType: ntFloat, floatValue: val)
|
||||
|
||||
proc toNdValue*(val: bool): NdValue =
|
||||
NdValue(ndType: ntBool, boolValue: val)
|
||||
|
||||
proc toNdValue*(val: string): NdValue =
|
||||
NdValue(ndType: ntString, stringValue: val.newString())
|
||||
|
||||
proc newNdFunction*(ii: int): NdValue =
|
||||
NdValue(ndType: ntFunct, entryII: ii)
|
||||
|
||||
proc toNdValue*: NdValue =
|
||||
NdValue(ndType: ntNil)
|
||||
proc friendlyType*(val: NdValue): string =
|
||||
if val == ndNil:
|
||||
"nil"
|
||||
elif val.isBool():
|
||||
"bool"
|
||||
elif val.isFloat():
|
||||
"number"
|
||||
elif val.isString():
|
||||
"string"
|
||||
elif val.isFunct():
|
||||
"function"
|
||||
else:
|
||||
"unknown"
|
||||
|
||||
# NatReturn misc
|
||||
|
||||
|
@ -88,55 +135,54 @@ proc natError*(msg: string): NatReturn {.inline.} =
|
|||
const natOk* = NatReturn(ok: true)
|
||||
|
||||
# OPERATIONS
|
||||
# NOTE: these operations can return ktTypeError with a message if types are invalid
|
||||
|
||||
proc negate*(val: var NdValue): NatReturn {.inline.} =
|
||||
if (val.ndType != ntFloat):
|
||||
if not val.isFloat():
|
||||
return natError("Operand must be a number.")
|
||||
else:
|
||||
val.floatValue = -val.floatValue
|
||||
val = fromFloat(-val.asFloat())
|
||||
return natOk
|
||||
|
||||
proc add*(val: var NdValue, right: NdValue): NatReturn {.inline.} =
|
||||
if val.ndType == ntFloat and right.ndType == ntFloat:
|
||||
val.floatValue += right.floatValue
|
||||
elif val.ndType == ntString and right.ndType == ntString:
|
||||
val.stringValue = val.stringValue & right.stringValue
|
||||
if val.isFloat() and right.isFloat():
|
||||
val = fromFloat(val.asFloat() + right.asFloat())
|
||||
elif val.isString() and right.isString():
|
||||
val = fromNdString(val.asString() & val.asString())
|
||||
else:
|
||||
return natError(&"Attempt to add types {val.ndType} and {right.ndType}.")
|
||||
return natError(&"Attempt to add types {val.friendlyType()} and {right.friendlyType()}.")
|
||||
return natOk
|
||||
|
||||
proc subtract*(val: var NdValue, right: NdValue): NatReturn {.inline.} =
|
||||
if val.ndType == ntFloat and right.ndType == ntFloat:
|
||||
val.floatValue -= right.floatValue
|
||||
if val.isFloat() and right.isFloat():
|
||||
val = fromFloat(val.asFloat() - right.asFloat())
|
||||
else:
|
||||
return natError(&"Attempt to subtract types {val.ndType} and {right.ndType}.")
|
||||
return natError(&"Attempt to subtract types {val.friendlyType()} and {right.friendlyType()}.")
|
||||
return natOk
|
||||
|
||||
proc multiply*(val: var NdValue, right: NdValue): NatReturn {.inline.} =
|
||||
if val.ndType == ntFloat and right.ndType == ntFloat:
|
||||
val.floatValue *= right.floatValue
|
||||
if val.isFloat() and right.isFloat():
|
||||
val = fromFloat(val.asFloat() * right.asFloat())
|
||||
else:
|
||||
return natError(&"Attempt to multiply types {val.ndType} and {right.ndType}.")
|
||||
return natError(&"Attempt to multiply types {val.friendlyType()} and {right.friendlyType()}.")
|
||||
return natOk
|
||||
|
||||
proc divide*(val: var NdValue, right: NdValue): NatReturn {.inline.} =
|
||||
if val.ndType == ntFloat and right.ndType == ntFloat:
|
||||
val.floatValue /= right.floatValue
|
||||
if val.isFloat() and right.isFloat():
|
||||
val = fromFloat(val.asFloat() / right.asFloat())
|
||||
else:
|
||||
return natError(&"Attempt to divide types {val.ndType} and {right.ndType}.")
|
||||
return natError(&"Attempt to divide types {val.friendlyType()} and {right.friendlyType()}.")
|
||||
return natOk
|
||||
|
||||
proc less*(val: var NdValue, right: NdValue): NatReturn {.inline.} =
|
||||
if val.ndType == ntFloat and right.ndType == ntFloat:
|
||||
val = toNdValue(val.floatValue < right.floatValue)
|
||||
if val.isFloat() and right.isFloat():
|
||||
val = fromBool(val.asFloat() < right.asFloat())
|
||||
else:
|
||||
return natError(&"Attempt to compare types {val.ndType} and {right.ndType}.")
|
||||
return natError(&"Attempt to compare types {val.friendlyType()} and {right.friendlyType()}.")
|
||||
return natOk
|
||||
|
||||
proc greater*(val: var NdValue, right: NdValue): NatReturn {.inline.} =
|
||||
if val.ndType == ntFloat and right.ndType == ntFloat:
|
||||
val = toNdValue(val.floatValue > right.floatValue)
|
||||
if val.isFloat() and right.isFloat():
|
||||
val = fromBool(val.asFloat() > right.asFloat())
|
||||
else:
|
||||
return natError(&"Attempt to compare types {val.ndType} and {right.ndType}.")
|
||||
return natError(&"Attempt to compare types {val.friendlyType()} and {right.friendlyType()}.")
|
||||
return natOk
|
||||
|
|
27
vm.nim
27
vm.nim
|
@ -2,6 +2,8 @@ import strformat
|
|||
import tables
|
||||
|
||||
import value
|
||||
import bitops # needed for value's templates
|
||||
|
||||
import chunk
|
||||
import config
|
||||
import pointerutils
|
||||
|
@ -10,6 +12,7 @@ import types/stack
|
|||
import types/ndstring
|
||||
import types/stringutils
|
||||
|
||||
|
||||
when profileInstructions:
|
||||
import times
|
||||
import std/monotimes
|
||||
|
@ -140,15 +143,15 @@ proc run*(chunk: Chunk): InterpretResult =
|
|||
else:
|
||||
ip = frames.pop().returnIp # remove frame that's over
|
||||
of opTrue:
|
||||
stack.add(toNdValue(true))
|
||||
stack.add(fromBool(true))
|
||||
of opFalse:
|
||||
stack.add(toNdValue(false))
|
||||
stack.add(fromBool(false))
|
||||
of opNil:
|
||||
stack.add(toNdValue())
|
||||
of opNot:
|
||||
stack.add(toNdValue(stack.pop().isFalsey))
|
||||
stack.add(fromNil())
|
||||
of opNot: # TODO hayago += optimization
|
||||
stack.add(fromBool(stack.pop().isFalsey()))
|
||||
of opEqual:
|
||||
stack.add(toNdValue(stack.pop().equal(stack.pop())))
|
||||
stack.add(fromBool(stack.pop().equal(stack.pop())))
|
||||
of opLess:
|
||||
let right = stack.pop()
|
||||
let res = stack.peek().less(right)
|
||||
|
@ -164,21 +167,21 @@ proc run*(chunk: Chunk): InterpretResult =
|
|||
of opPrint:
|
||||
echo $stack.peek()
|
||||
of opDefineGlobal:
|
||||
let name = $readConstant().stringValue
|
||||
let name = $readConstant().asString()
|
||||
if not globals.hasKey(name):
|
||||
globals[name] = stack.pop()
|
||||
else:
|
||||
runtimeError("Attempt to redefine an existing global variable.")
|
||||
break
|
||||
of opGetGlobal:
|
||||
let name = $readConstant().stringValue
|
||||
let name = $readConstant().asString()
|
||||
if globals.hasKey(name):
|
||||
stack.add(globals[name])
|
||||
else:
|
||||
runtimeError(&"Undefined global variable {name}.")
|
||||
break
|
||||
of opSetGlobal:
|
||||
let name = $readConstant().stringValue
|
||||
let name = $readConstant().asString()
|
||||
if globals.hasKey(name):
|
||||
globals[name] = stack.peek()
|
||||
else:
|
||||
|
@ -218,16 +221,16 @@ proc run*(chunk: Chunk): InterpretResult =
|
|||
# ... <ret val> <arg1> <arg2> <arg3>
|
||||
let argcount = readUI8()
|
||||
let funct = stack.getIndexNeg(argcount)
|
||||
if funct.ndType != ntFunct:
|
||||
if not funct.isFunct():
|
||||
runtimeError("Attempt to call a non-funct (a defunct?).") # here is a bad defunct joke
|
||||
break
|
||||
|
||||
stack.setIndexNeg(argcount, toNdValue()) # replace the function with nil: this is the return value slot
|
||||
stack.setIndexNeg(argcount, fromNil()) # replace the function with nil: this is the return value slot
|
||||
|
||||
# create new frame
|
||||
frames.add(Frame(stackBottom: stack.high - argcount, returnIp: ip))
|
||||
|
||||
ip = chunk.code[0].unsafeAddr.padd(funct.entryII) # jump to the entry point
|
||||
ip = chunk.code[0].unsafeAddr.padd(funct.asFunct()) # jump to the entry point
|
||||
|
||||
when profileInstructions:
|
||||
durations[ins] += getMonoTime() - startTime
|
||||
|
|
Loading…
Reference in New Issue