Navigation Menu

Skip to content

Commit

Permalink
Implement ternery conditional operator;
Browse files Browse the repository at this point in the history
Implement integer wrap around behavior in comprehensive VM
  • Loading branch information
coder-mike committed May 9, 2020
1 parent a5dfd3b commit 590b7d3
Show file tree
Hide file tree
Showing 54 changed files with 4,104 additions and 2,733 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Expand Up @@ -24,6 +24,7 @@
"Conseq",
"DIDs",
"Decr",
"Microvium",
"Typeof",
"UNIQUED",
"UNOP",
Expand All @@ -32,10 +33,10 @@
"ccitt",
"clearscreen",
"fastpriorityqueue",
"Microvium",
"microvm",
"mvms",
"napi",
"nullish",
"semispace",
"subdir",
"uncallable"
Expand Down
2 changes: 1 addition & 1 deletion binding.gyp
Expand Up @@ -4,7 +4,7 @@
'target_name': 'native-vm',
'sources': [
'native-vm-bindings/index.cc',
'native-vm-bindings/MicroviumClass.cc',
'native-vm-bindings/NativeVM.cc',
'native-vm-bindings/Value.cc',
'native-vm-bindings/misc.cc',
'native-vm-bindings/WeakRef.cc',
Expand Down
3 changes: 2 additions & 1 deletion lib/native-vm.ts
Expand Up @@ -19,12 +19,13 @@ export type HostFunction = (object: Value, args: Value[]) => Value;
export type ResolveImport = (hostFunctionID: vm_HostFunctionID) => HostFunction;
export type CoverageCallback = (id: number, mode: CoverageCaseMode, indexInTable: number, tableSize: number, line: number) => void;

export const NativeVM = addon.Microvium as NativeVMClass;
export const NativeVM = addon.NativeVM as NativeVMClass;

export interface NativeVMClass {
new (snapshotBytecode: Buffer, resolveImport: ResolveImport): NativeVM;
// Used for code coverage analysis
setCoverageCallback(callback: CoverageCallback | undefined): void;
readonly MVM_PORT_INT32_OVERFLOW_CHECKS: boolean;
}

export interface NativeVM {
Expand Down
2 changes: 1 addition & 1 deletion lib/runtime-types.ts
Expand Up @@ -79,7 +79,7 @@ export enum TeTypeCode {
TC_REF_NONE = 0x0,

TC_REF_INT32 = 0x1, // 32-bit signed integer
TC_REF_DOUBLE = 0x2, // 64-bit float
TC_REF_FLOAT64 = 0x2, // 64-bit float

/**
* UTF8-encoded string that may or may not be unique.
Expand Down
2 changes: 1 addition & 1 deletion lib/snapshot-info.ts
Expand Up @@ -267,7 +267,7 @@ export function encodeSnapshot(snapshot: SnapshotInfo, generateDebugHTML: boolea
if (Object.is(value.value, -0)) return vm_TeWellKnownValues.VM_VALUE_NEG_ZERO;
if (isSInt14(value.value)) return value.value & 0x3FFF;
if (isSInt32(value.value)) return allocateLargePrimitive(TeTypeCode.TC_REF_INT32, b => b.append(value.value, 'Int32', formats.sInt32LERow));
return allocateLargePrimitive(TeTypeCode.TC_REF_DOUBLE, b => b.append(value.value, 'Double', formats.doubleLERow));
return allocateLargePrimitive(TeTypeCode.TC_REF_FLOAT64, b => b.append(value.value, 'Double', formats.doubleLERow));
};
case 'StringValue': return getString(value.value);
case 'FunctionValue': {
Expand Down
41 changes: 34 additions & 7 deletions lib/src-to-il.ts
Expand Up @@ -103,6 +103,10 @@ interface Cursor {
commentNext?: string[];
}

function moveCursor(cur: Cursor, toLocation: Cursor): void {
Object.assign(cur, toLocation);
}

export function compileScript(filename: string, scriptText: string, globals: string[]): IL.Unit {
let file: B.File;
try {
Expand Down Expand Up @@ -529,7 +533,7 @@ export function compileForStatement(cur: Cursor, statement: B.ForStatement): voi
if (!statement.update) return unexpected();
compileExpression(bodyCur, statement.update);
addOp(bodyCur, 'Pop', countOperand(1)); // Expression result not used
const [terminateBlock] = createBlock(bodyCur, bodyCur.stackDepth, bodyCur.scope);
const [terminateBlock, terminateBlockCur] = createBlock(bodyCur, bodyCur.stackDepth, bodyCur.scope);

// Jump into loop from initializer
addOp(cur, 'Jump', labelOfBlock(loopBlock));
Expand All @@ -540,7 +544,7 @@ export function compileForStatement(cur: Cursor, statement: B.ForStatement): voi
// Loop back at end of body
addOp(bodyCur, 'Jump', labelOfBlock(loopBlock));

cur.block = terminateBlock;
moveCursor(cur, terminateBlockCur);
scope.endScope();
}

Expand All @@ -553,7 +557,7 @@ export function compileWhileStatement(cur: Cursor, statement: B.WhileStatement):
const [exitBlock, exitCur] = createBlock(cur, cur.stackDepth, cur.scope);
addOp(testCur, 'Branch', labelOfBlock(bodyBlock), labelOfBlock(exitBlock));
addOp(bodyCur, 'Jump', labelOfBlock(testBlock));
cur.block = exitBlock;
moveCursor(cur, exitCur);
}

export function compileDoWhileStatement(cur: Cursor, statement: B.DoWhileStatement): void {
Expand All @@ -563,7 +567,7 @@ export function compileDoWhileStatement(cur: Cursor, statement: B.DoWhileStateme
const [after, afterCur] = createBlock(bodyCur, cur.stackDepth, cur.scope);
addOp(cur, 'Jump', labelOfBlock(body));
addOp(bodyCur, 'Branch', labelOfBlock(body), labelOfBlock(after));
cur.block = afterCur.block;
moveCursor(cur, afterCur);
}

export function compileBlockStatement(cur: Cursor, statement: B.BlockStatement): void {
Expand Down Expand Up @@ -592,7 +596,7 @@ export function compileIfStatement(cur: Cursor, statement: B.IfStatement): void
addOp(cur, 'Branch', labelOfBlock(consequent), labelOfBlock(alternate));
addOp(consequentCur, 'Jump', labelOfBlock(after));
addOp(alternateCur, 'Jump', labelOfBlock(after));
cur.block = afterCur.block;
moveCursor(cur, afterCur);
} else {
// Expression leaves the test result at the top of the stack
compileExpression(cur, statement.test);
Expand All @@ -604,7 +608,7 @@ export function compileIfStatement(cur: Cursor, statement: B.IfStatement): void

addOp(cur, 'Branch', labelOfBlock(consequent), labelOfBlock(after));
addOp(consequentCur, 'Jump', labelOfBlock(after));
cur.block = afterCur.block;
moveCursor(cur, afterCur);
}
}

Expand Down Expand Up @@ -833,10 +837,33 @@ export function compileExpression(cur: Cursor, expression: B.Expression) {
case 'MemberExpression': return compileMemberExpression(cur, expression);
case 'ArrayExpression': return compileArrayExpression(cur, expression);
case 'ObjectExpression': return compileObjectExpression(cur, expression);
case 'ConditionalExpression': return compileConditionalExpression(cur, expression);
default: return compileError(cur, `Expression of type "${expression.type}" not supported.`);
}
}

export function compileConditionalExpression(cur: Cursor, expression: B.ConditionalExpression) {
// Expression leaves the test result at the top of the stack
compileExpression(cur, expression.test);

// The -1 is because the branch instruction pops a value off the stack
const [consequent, consequentCur] = createBlock(cur, cur.stackDepth - 1, cur.scope);
compileExpression(consequentCur, expression.consequent);

const [alternate, alternateCur] = createBlock(cur, cur.stackDepth - 1, cur.scope);
compileExpression(alternateCur, expression.alternate);

// The stack depth is the same as when we have the "test" result on the stack,
// because the consequent and alternate paths both pop the test and push the
// result.
const [after, afterCur] = createBlock(cur, cur.stackDepth, cur.scope);

addOp(cur, 'Branch', labelOfBlock(consequent), labelOfBlock(alternate));
addOp(consequentCur, 'Jump', labelOfBlock(after));
addOp(alternateCur, 'Jump', labelOfBlock(after));
moveCursor(cur, afterCur);
}

export function compileArrayExpression(cur: Cursor, expression: B.ArrayExpression) {
addOp(cur, 'ArrayNew');
for (const element of expression.elements) {
Expand Down Expand Up @@ -945,7 +972,7 @@ export function compileLogicalExpression(cur: Cursor, expression: B.LogicalExpre
// Short circuit || -- if left is truthy, result is left, else result is right
addOp(cur, 'Branch', labelOfBlock(endBlock), labelOfBlock(rightBlock));
}
cur.block = endCur.block;
moveCursor(cur, endCur);
cur.stackDepth = endCur.stackDepth;
} else if (expression.operator === '??') {
return notImplemented();
Expand Down
3 changes: 3 additions & 0 deletions lib/virtual-machine-types.ts
Expand Up @@ -44,6 +44,9 @@ export interface ExternalFrame {
export interface VirtualMachineOptions {
// Function called before every operation
trace?: (operation: IL.Operation) => void;
// If set to false, numeric operations on 32-bit signed integers will result
// in 32-bit signed integer results, except for division
overflowChecks?: boolean;
}

export interface GlobalDefinitions {
Expand Down
7 changes: 6 additions & 1 deletion lib/virtual-machine.ts
Expand Up @@ -9,6 +9,7 @@ import deepFreeze from 'deep-freeze';
import { Snapshot } from './snapshot';
import { EventEmitter } from 'events';
import { SynchronousWebSocketServer } from './synchronous-ws-server';
import { isSInt32 } from './runtime-types';
export * from "./virtual-machine-types";

interface DebuggerInstrumentationState {
Expand Down Expand Up @@ -366,7 +367,7 @@ export class VirtualMachine {
instr.debugServer.send({ type: 'from-app:stop-on-step' });
instr.executionState = 'paused';
}

console.log('paused bc of entry:', pauseBecauseOfEntry);
while (instr.executionState === 'paused') {
console.log('Before waiting for message');
Expand Down Expand Up @@ -738,6 +739,10 @@ export class VirtualMachine {
case '^': result = leftNum ^ rightNum; break;
default: return assertUnreachable(op);
}
// Overflow checking changes the semantics of the language
if (this.opts.overflowChecks === false && op !== '/' && isSInt32(leftNum) && isSInt32(rightNum)) {
result = result | 0;
}
this.pushNumber(result);
break;
}
Expand Down
@@ -1,26 +1,27 @@
#include <map>
#include <napi.h>
#include "MicroviumClass.hh"
#include "NativeVM.hh"
#include "misc.hh"

using namespace VM;

Napi::FunctionReference Microvium::constructor;
Napi::FunctionReference Microvium::coverageCallback;
Napi::FunctionReference NativeVM::constructor;
Napi::FunctionReference NativeVM::coverageCallback;

void Microvium::Init(Napi::Env env, Napi::Object exports) {
Napi::Function ctr = DefineClass(env, "Microvium", {
Microvium::InstanceMethod("resolveExport", &Microvium::resolveExport),
Microvium::InstanceMethod("call", &Microvium::call),
Microvium::InstanceAccessor("undefined", &Microvium::getUndefined, nullptr),
Microvium::StaticMethod("setCoverageCallback", &Microvium::setCoverageCallback)
void NativeVM::Init(Napi::Env env, Napi::Object exports) {
Napi::Function ctr = DefineClass(env, "NativeVM", {
NativeVM::InstanceMethod("resolveExport", &NativeVM::resolveExport),
NativeVM::InstanceMethod("call", &NativeVM::call),
NativeVM::InstanceAccessor("undefined", &NativeVM::getUndefined, nullptr),
NativeVM::StaticMethod("setCoverageCallback", &NativeVM::setCoverageCallback),
NativeVM::StaticValue("MVM_PORT_INT32_OVERFLOW_CHECKS", Napi::Boolean::New(env, MVM_PORT_INT32_OVERFLOW_CHECKS)),
});
constructor = Napi::Persistent(ctr);
exports.Set(Napi::String::New(env, "Microvium"), ctr);
exports.Set(Napi::String::New(env, "NativeVM"), ctr);
constructor.SuppressDestruct();
}

Microvium::Microvium(const Napi::CallbackInfo& info) : ObjectWrap(info), vm(nullptr)
NativeVM::NativeVM(const Napi::CallbackInfo& info) : ObjectWrap(info), vm(nullptr)
{
Napi::Env env = info.Env();
if (info.Length() < 2) {
Expand Down Expand Up @@ -48,7 +49,7 @@ Microvium::Microvium(const Napi::CallbackInfo& info) : ObjectWrap(info), vm(null

this->resolveImport.Reset(info[1].As<Napi::Function>(), 1);

mvm_TeError err = mvm_restore(&this->vm, this->bytecode, bytecodeLength, this, Microvium::resolveImportHandler);
mvm_TeError err = mvm_restore(&this->vm, this->bytecode, bytecodeLength, this, NativeVM::resolveImportHandler);
if (err != MVM_E_SUCCESS) {
if (this->error) {
std::unique_ptr<Napi::Error> err(std::move(this->error));
Expand All @@ -60,11 +61,11 @@ Microvium::Microvium(const Napi::CallbackInfo& info) : ObjectWrap(info), vm(null
}
}

Napi::Value Microvium::getUndefined(const Napi::CallbackInfo& info) {
Napi::Value NativeVM::getUndefined(const Napi::CallbackInfo& info) {
return VM::Value::wrap(vm, mvm_undefined);
}

Napi::Value Microvium::call(const Napi::CallbackInfo& info) {
Napi::Value NativeVM::call(const Napi::CallbackInfo& info) {
mvm_TeError err;
auto env = info.Env();

Expand All @@ -76,15 +77,15 @@ Napi::Value Microvium::call(const Napi::CallbackInfo& info) {

auto funcArg = info[0];
if (!VM::Value::isVMValue(funcArg)) {
Napi::TypeError::New(env, "Expected first argument to be a Microvium `Value`")
Napi::TypeError::New(env, "Expected first argument to be a NativeVM `Value`")
.ThrowAsJavaScriptException();
return env.Undefined();
}
mvm_Value funcArgVMValue = VM::Value::unwrap(funcArg);

auto argsArg = info[1];
if (!argsArg.IsArray()) {
Napi::TypeError::New(env, "Expected second argument to be an array of Microvium `Value`s")
Napi::TypeError::New(env, "Expected second argument to be an array of NativeVM `Value`s")
.ThrowAsJavaScriptException();
return env.Undefined();
}
Expand All @@ -94,7 +95,7 @@ Napi::Value Microvium::call(const Napi::CallbackInfo& info) {
for (uint32_t i = 0; i < argsLength; i++) {
auto argsItem = argsArray.Get(i);
if (!VM::Value::isVMValue(argsItem)) { // TODO(low): Test arguments
Napi::TypeError::New(env, "Expected second argument to be an array of Microvium `Value`s")
Napi::TypeError::New(env, "Expected second argument to be an array of NativeVM `Value`s")
.ThrowAsJavaScriptException();
return env.Undefined();
}
Expand All @@ -120,15 +121,15 @@ Napi::Value Microvium::call(const Napi::CallbackInfo& info) {
return VM::Value::wrap(vm, result);
}

Microvium::~Microvium() {
NativeVM::~NativeVM() {
if (this->vm) {
mvm_free(this->vm);
this->vm = nullptr;
}
}

mvm_TeError Microvium::resolveImportHandler(mvm_HostFunctionID hostFunctionID, void* context, mvm_TfHostFunction* out_hostFunction) {
Microvium* self = (Microvium*)context;
mvm_TeError NativeVM::resolveImportHandler(mvm_HostFunctionID hostFunctionID, void* context, mvm_TfHostFunction* out_hostFunction) {
NativeVM* self = (NativeVM*)context;
try {
auto env = self->resolveImport.Env();
auto global = env.Global();
Expand All @@ -147,7 +148,7 @@ mvm_TeError Microvium::resolveImportHandler(mvm_HostFunctionID hostFunctionID, v
self->importTable[hostFunctionID] = Napi::Persistent(hostFunction);

// All host calls go through a common handler
*out_hostFunction = &Microvium::hostFunctionHandler;
*out_hostFunction = &NativeVM::hostFunctionHandler;

return MVM_E_SUCCESS;
}
Expand All @@ -160,8 +161,8 @@ mvm_TeError Microvium::resolveImportHandler(mvm_HostFunctionID hostFunctionID, v
}
}

mvm_TeError Microvium::hostFunctionHandler(mvm_VM* vm, mvm_HostFunctionID hostFunctionID, mvm_Value* result, mvm_Value* args, uint8_t argCount) {
Microvium* self = (Microvium*)mvm_getContext(vm);
mvm_TeError NativeVM::hostFunctionHandler(mvm_VM* vm, mvm_HostFunctionID hostFunctionID, mvm_Value* result, mvm_Value* args, uint8_t argCount) {
NativeVM* self = (NativeVM*)mvm_getContext(vm);
auto handlerIter = self->importTable.find(hostFunctionID);
if (handlerIter == self->importTable.end()) {
// This should never happen because the bytecode should resolve all its
Expand Down Expand Up @@ -190,7 +191,7 @@ mvm_TeError Microvium::hostFunctionHandler(mvm_VM* vm, mvm_HostFunctionID hostFu
return MVM_E_SUCCESS; // TODO(high): Error handling -- catch exceptions?
}

Napi::Value Microvium::resolveExport(const Napi::CallbackInfo& info) {
Napi::Value NativeVM::resolveExport(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();

if (info.Length() < 1) {
Expand Down Expand Up @@ -226,7 +227,7 @@ Napi::Value Microvium::resolveExport(const Napi::CallbackInfo& info) {
return VM::Value::wrap(vm, result);
}

void Microvium::setCoverageCallback(const Napi::CallbackInfo& info) {
void NativeVM::setCoverageCallback(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();

if (info.Length() < 1) {
Expand Down Expand Up @@ -255,12 +256,12 @@ void Microvium::setCoverageCallback(const Napi::CallbackInfo& info) {

// Called by VM
extern "C" void codeCoverage(int id, int mode, int indexInTable, int tableSize, int line) {
if (!Microvium::coverageCallback) return;
if (!NativeVM::coverageCallback) return;

try {
auto env = Microvium::coverageCallback.Env();
auto env = NativeVM::coverageCallback.Env();
auto global = env.Global();
Microvium::coverageCallback.Call(global, {
NativeVM::coverageCallback.Call(global, {
Napi::Number::New(env, id),
Napi::Number::New(env, mode),
Napi::Number::New(env, indexInTable),
Expand Down
Expand Up @@ -8,11 +8,11 @@

namespace VM {

class Microvium: public Napi::ObjectWrap<Microvium> {
class NativeVM: public Napi::ObjectWrap<NativeVM> {
public:
static void Init(Napi::Env env, Napi::Object exports);
Microvium(const Napi::CallbackInfo&);
~Microvium();
NativeVM(const Napi::CallbackInfo&);
~NativeVM();

Napi::Value resolveExport(const Napi::CallbackInfo&);
Napi::Value getUndefined(const Napi::CallbackInfo&);
Expand Down

0 comments on commit 590b7d3

Please sign in to comment.