Giter Club home page Giter Club logo

jankscripten's Introduction

Introduction

jankscripten is a compiler and runtime system that targets WebAssembly and supports multiple front-end languages:

  • JankyScript is a language with JavaScript syntax. It supports higher-order functions and a standard library that is similar to JavaScript. The compiler translates JankyScript to NotWasm by performing type inference and closure conversion.

  • NotWasm is an explicitly-typed lower-level language that does not support higher-order functions. However, it does support:

    • Garbage collection: at the moment, a simple mark-and-sweep collector with a stop-and-copy region for floating point numbers.

    • String values: Worth mentioning, since WebAssembly does not support strings natively.

    • Any-typed values (a.k.a. type dynamic): a failed downcast crashes the program with an unrecoverable error (i.e., a WebAssembly trap).

    • Monotonic dynamic objects with prototype inheritance: The fields of these objects are of type Any, and new fields may be added dynamically. However, these objects do not support field deletion. Field lookup in dynamic objects is optimized using inline caching.

    • Hash tables and arrays: these store Any-typed values.

    • Explicit closures: Although NotWasm does not support higher-order functions, it has a representation for closures (i.e., code + environment pointer) which is designed to fit into Any-typed values.

    • ML-style mutable references

JankyScript is not JavaScript. It makes several simplifying assumptions, which it inherits from NotWasm. However, as long as you’re working with “sane” JavaScript, e.g., JavaScript generated by a compiler from a saner programming language, you can use JankyScript. With more effort, you can instead use NotWasm as an intermediate language for a compiler that targets WebAssembly.

Prerequisites and Building

Prerequites

jankscripten is written in Rust, and uses the Rust WebAssembly toolchain. It depends on Rust packages that link to libssl on Linux, and it relies on the Z3 SMT Solver. Finally, the test suite relies on Node. Follow these steps to install the prerequisites:

  1. Install the Rust toolchain. We require Rust 1.51.0 (released March 2021). More recent versions of Rust changed the ABI of the WebAssembly backend. We will fix this soon. After installing Rust, run the following commands:

    rustup toolchain add 1.51.0
    rustup default 1.51.0
    
  2. Install Node. We require Node 11 or higher.

  3. Install the Rust WebAssembly toolchain:

    rustup target add wasm32-unknown-unknown
    
  4. Install the Wasm-Bindgen CLI, to allow Rust unit tests to run in WebAssembly:

    cargo install wasm-bindgen-cli
    
  5. On Ubuntu Linux, install libssl and pkg-config:

    sudo apt-get install libssl-dev pkg-config
    
  6. Install the Z3 library. On Ubuntu Linux:

    sudo apt-get install libz3-dev
    
Building
  1. Build the jankscripten compiler:

    cargo build
    
  2. Build the jankscripten runtime[1]:

    (cd runtime && cargo build)
    
  3. Build the integration testing tool:

    (cd integration_tests && npm install)
    
Testing
cargo test
(cd runtime && cargo test) # Runs tests using WebAssembly
(cd integration_tests && npx jest)

Running

To compile filename.ext to WebAssembly:

./bin/jankscripten compile filename.ext

NOTE: The supported extensions are .js and .notwasm.

To run a compiled WebAssembly program with the jankscripten runtime:

./bin/run-node filename.wasm

Debugging

To debug or profile a compiled WebAssembly program:

node --inspect-brk bin/run FILENAME.wasm

The Chrome debugger uses source maps correctly to show the original Rust code. You can use Visual Studio Code or Edge, but source maps do not appear to work correctly. See the Node Debugging Guide for more information.

NotWasm Guide

NotWasm is a somewhat low-level language that compiles to WebAssembly. It supports several data structures that WebAssembly does not natively support. It also supports garbage collection (presently, a simple mark-and-sweep collector). NotWasm programs are linked to a runtime system, which is written in Rust and compiled to WebAssembly.

NotWasm programs use the following concrete syntax for types:

Result Types
R := T Result of type T
void
Types
T := any Unknown type (occupies 64-bits)
i32
f64
bool
str
Array
DynObject
HT
( T ,..., T**) ->** R
clos ( T ,..., T**) ->** R
Ref(T)
env

NotWasm programs use a psuedo-ANF: atoms do no not alter control-flow or allocate memory, expressions may allocate memory but do not alter control-flow, and statements function bodies that may alter the control-flow of the program.

Primitives

Programs may reference primitive operations that are defined in the NotWasm runtime system. These operations are imported at the top of stdlib.notwasm, which is in the root of the repository.

Atoms

Atoms use the following concrete syntax:

bop := + Integer addition
-
*
>
<
>=
<=
==
===
+.
-.
*.
/.
b := true false
n := ... Integer literals
f := ... Floating point literals
s := "..." String literals
a := b
n
f
s
null
x
@ prima( a1 ... an)
*a:T
a1**.**s
a1 bop a2
Expresssions

Expressions have the following concrete syntax:

e := a Atom
! prime( x1 ... xn)
xf( x1 ... xn)
xf!( xe,x1 ... xn)
a**.**x = a
Statements

Statements have the following concrete syntax:

blk := { s1 ... sn}
s := var x**:T=** e**;** Declare a variable of type T
x = e**;**
**if (**a ) blk else blk
loop blk
return a**;**
break l**;**
*x= e**;**
Programs

A program is a list of global variables, followed by a list of functions. Global variables have the following concrete syntax:

var x**:T=** a**;**

Functions have the following concrete syntax:

function xf( x1**:T1 ... xn:Tn):R **blk

There must be a function called main that received no arguments and does not return a result.

  1. This is a separate step because it targets WebAssembly and not native code

jankscripten's People

Contributors

mwaldrich avatar arjunguha avatar cosinep avatar mgree avatar jchammond avatar dependabot[bot] avatar

Stargazers

Jasvir Nagra avatar Rachit Nigam avatar

Watchers

 avatar  avatar Yangtian Zi avatar Matthew Guthrie avatar John Gouwar avatar  avatar  avatar

Forkers

dhil

jankscripten's Issues

The visitor used to desugar JavaScript does not support mutating ancestors of the parent node

Although the values of a node are &mut, when it is further up the stack, that &mut is behind a &Context, thus is immutable. The technique that we used for blocks -- where the node accumulates a list of patches that we later apply -- will work in general. Here is an example using a binary tree:

use std::cell::Cell;

pub enum BinTree {
    Leaf,
    Node(Box<BinTree>, usize, Box<BinTree>)
}

pub struct Replaceable<'a, T> {
    value: &'a mut T,
    new_value: Cell<Option<T>>
}

impl<'a, T> Replaceable<'a, T> {
    fn new(value: &'a mut T) -> Replaceable<'a, T> {
        Replaceable {
            value: value,
            new_value: Cell::new(None)
        }
    }

    fn get(&self) -> &T {
        self.value
    }

    fn set(&self, value: T) { 
        self.new_value.set(Some(value));
    }

    fn update(&mut self)  {
        if let Some(x) = self.new_value.take() {
            *self.value = x;
        }
    }
}

pub enum Context1<'a> {
    Left(&'a Replaceable<'a, usize>, &'a Replaceable<'a, BinTree>),
    Right(&'a Replaceable<'a, BinTree>, &'a Replaceable<'a, usize>)
}

pub enum Context<'a> {
    Nil,
    Cons(Context1<'a>, &'a Context<'a>)
}

pub trait Visitor {
    fn enter(&mut self, _node: &mut BinTree, _context: &Context) {
        // Do nothing by default
    }

    fn exit(&mut self, _node: &mut BinTree, _context: &Context) {
        // Do nothing by default
    }
}

pub fn visit_rec<'a, 'b>(tree: &'a mut BinTree, cxt: &'a Context<'a>, visitor: &mut impl Visitor) {
    visitor.enter(tree, cxt);
    match tree {
        BinTree::Leaf => { },
        BinTree::Node(lhs, value, rhs) => {
                  
                let mut value_cell = Replaceable::new(value);
                let mut rhs_cell = Replaceable::new(rhs.as_mut());
                let lhs_cxt = Context::Cons(Context1::Left(&value_cell, &rhs_cell), cxt);
                visit_rec(lhs, &lhs_cxt, visitor);
                value_cell.update();
                rhs_cell.update();
                
                let mut value_cell = Replaceable::new(value);
                let mut lhs_cell = Replaceable::new(lhs.as_mut());
                let mut rhs_cxt = Context::Cons(Context1::Right(&lhs_cell, &value_cell), cxt);
                visit_rec(rhs, &mut rhs_cxt, visitor);
                value_cell.update();
                lhs_cell.update();
        }
    }
    visitor.exit(tree, cxt);
}


pub fn visit(tree: & mut BinTree, visitor: &mut impl Visitor) {
    let mut cxt = Context::Nil;
    visit_rec(tree, &mut cxt, visitor);
}

Consolidate desugar testing

Right now, we have tests for desugaring scattered across a number of modules. We should instead do the following:

  • Ensure that all desugaring passes are called by the main desugar function
  • Ensure that all tests use desugar_okay
  • Ensure that all tests end with an expression that is likely to return the wrong value if desugaring goes wrong.
  • Collect all tests in a single module.

GotoWasm: condition on false branches missing

The false branch of an if statement needs a guard as well:

S[[if (e) s1 else s2]] = 
  if ((!inGoto && e) || (inGoto && gotoTarget ∊ L[[s1]])) S[[s1]]
  else (!inGoto || (inGoto && gotoTarget ∊ L[[s2]])) S[[s2]]

GotoWasm: loop guard

Consider the case when the goto target is inside a loop. Given a while loop:

while (atom) block, we can rewrite atom using ideas similar to what we have done for if-statements.

NotWasm has an unguarded loop, so we would need to wrap the whole loop in an if-statement.

Correctly handle `var`, `let`, and `const`

Currently, all 3 ways of declaring a variable are handled like var inside jankscripten. This stemmed from a parser bug in the old parser, and I had to replicate the bug in the new parser in order to get the tests to pass.

I'm not sure if we already decided to support let or const, but I don't think there's any machinery implemented for them already. We might be able to get away with treating const like var. let might be trickier to ignore, since it has different scoping rules than var, which some of our programs may rely on.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.