From 2b11825f9805424582d58c8c1e1b3c85b3950f2e Mon Sep 17 00:00:00 2001 From: Arjun Guha Date: Fri, 16 Feb 2018 09:57:00 -0500 Subject: [PATCH] Hacks --- stopify-compiler-cloud-function/test.js | 11 -- stopify-compiler-cloud-function/ts/index.ts | 111 +++++++++++++++++- .../ts/testServer.ts | 2 +- .../src/callcc/boxAssignables.ts | 2 +- stopify-continuations/src/callcc/callcc.ts | 2 +- .../src/callcc/delimitTopLevel.ts | 2 +- stopify-continuations/src/types.ts | 3 +- stopify-static-website/ts/languages.ts | 59 ++++++++-- stopify-static-website/ts/stopify.tsx | 18 +-- .../server/ts/pyjsFast.ts | 22 ++++ .../server/ts/server.ts | 2 + stopify/src/stopify/stopifyCallCC.ts | 20 +--- 12 files changed, 191 insertions(+), 63 deletions(-) delete mode 100644 stopify-compiler-cloud-function/test.js create mode 100644 stopify-third-party-compiler-container/server/ts/pyjsFast.ts diff --git a/stopify-compiler-cloud-function/test.js b/stopify-compiler-cloud-function/test.js deleted file mode 100644 index e615c1ef..00000000 --- a/stopify-compiler-cloud-function/test.js +++ /dev/null @@ -1,11 +0,0 @@ -const PubSub = require('@google-cloud/pubsub'); -const pubsub = PubSub(); -const topic = pubsub.topic('compile-scalajs-response'); - -topic.createSubscription('stopify-js-receiver').then(subscription => { - -subscription[0].on('message', msg => { - msg.ack(); - console.log(msg.data.toString()); -}); -}); diff --git a/stopify-compiler-cloud-function/ts/index.ts b/stopify-compiler-cloud-function/ts/index.ts index b1a66620..f84a8b36 100644 --- a/stopify-compiler-cloud-function/ts/index.ts +++ b/stopify-compiler-cloud-function/ts/index.ts @@ -104,7 +104,8 @@ genericCompiler('pyjs', `${thirdPartyCompilers}/pyjs`, { es: 'sane', hofs: 'builtin', jsArgs: 'faithful', - requireRuntime: false + requireRuntime: false, + eval: false }); genericCompiler('emscripten', `${thirdPartyCompilers}/emscripten`, { @@ -115,7 +116,8 @@ genericCompiler('emscripten', `${thirdPartyCompilers}/emscripten`, { es: 'sane', hofs: 'builtin', jsArgs: 'simple', - requireRuntime: false + requireRuntime: false, + eval: false }); genericCompiler('bucklescript', `${thirdPartyCompilers}/bucklescript`, { @@ -126,7 +128,8 @@ genericCompiler('bucklescript', `${thirdPartyCompilers}/bucklescript`, { es: 'sane', hofs: 'builtin', jsArgs: 'simple', - requireRuntime: false + requireRuntime: false, + eval: false }); genericCompiler('scalajs', `${thirdPartyCompilers}/scalajs`, { @@ -137,7 +140,8 @@ genericCompiler('scalajs', `${thirdPartyCompilers}/scalajs`, { es: 'sane', hofs: 'builtin', jsArgs: 'simple', - requireRuntime: false + requireRuntime: false, + eval: false }); genericCompiler('clojurescript', `${thirdPartyCompilers}/clojurescript`, { @@ -148,7 +152,8 @@ genericCompiler('clojurescript', `${thirdPartyCompilers}/clojurescript`, { es: 'sane', hofs: 'builtin', jsArgs: 'simple', - requireRuntime: false + requireRuntime: false, + eval: false }); genericCompiler('dart2js', `${thirdPartyCompilers}/dart2js`, { @@ -159,5 +164,99 @@ genericCompiler('dart2js', `${thirdPartyCompilers}/dart2js`, { es: 'sane', hofs: 'builtin', jsArgs: 'simple', - requireRuntime: false + requireRuntime: false, + eval: false +}); + +stopify.post('/pyjs-fast', bodyParser.text({ type: '*/*' }), async (req, resp) => { + try { + resp.set('Access-Control-Allow-Origin', '*'); + resp.set('Access-Control-Allow-Methods', 'POST'); + + const { filename, exists } = await checkCache('pyjs-fast', req.body); + if (exists) { + return resp.send(filename); + } + console.info(`Compiling PyJS (fast) program (${req.body.length} bytes)`); + const url = `${thirdPartyCompilers}/pyjs-fast`; + const jsCode = await request.post(url, { headers, body: req.body }); + console.info(`Stopifying program (${jsCode.length} bytes)`); + const dir = await tmpDir(); + try { + const jsPath = `${dir}/original.js`; + await fs.writeFile(jsPath, jsCode + '\npygwtOnLoad();'); + const stopifiedJsCode = await stopifyCompiler.stopify(jsPath, { + compileFunction: 'module', + getters: false, + debug: false, + captureMethod: 'lazy', + newMethod: 'wrapper', + es: 'sane', + hofs: 'builtin', + jsArgs: 'faithful', + requireRuntime: false, + eval: false + }); + const prelude = await fs.readFile(__dirname + '/../pyjs_prelude.lazy.wrapper.faithful.js'); + await bucket.file(filename).save(prelude + stopifiedJsCode + ` + $__R.delimit(function () { + $S.onEnd(); + });` + ); + return resp.send(filename); + } + finally { + await fs.remove(dir); + } + } + catch (exn) { + resp.statusCode = 503; + const reason = + (exn.name === 'StatusCodeError' ? exn.response.body : exn).toString(); + console.error(`Error: ${reason}`); + return resp.send(reason.toString()); + } +}); + +stopify.post('/js', bodyParser.text({ type: '*/*' }), async (req, resp) => { + try { + resp.set('Access-Control-Allow-Origin', '*'); + resp.set('Access-Control-Allow-Methods', 'POST'); + + const { filename, exists } = await checkCache('js', req.body); + if (exists) { + return resp.send(filename); + } + console.info(`Compiling JavaScript program (${req.body.length} bytes)`); + const jsCode = req.body; + const dir = await tmpDir(); + try { + const jsPath = `${dir}/original.js`; + await fs.writeFile(jsPath, jsCode); + const stopifiedJsCode = await stopifyCompiler.stopify(jsPath, { + compileFunction: false, + getters: false, + debug: true, + captureMethod: 'lazy', + newMethod: 'wrapper', + es: 'sane', + hofs: 'builtin', + jsArgs: 'faithful', + requireRuntime: false, + eval: false + }); + await bucket.file(filename).save(stopifiedJsCode); + return resp.send(filename); + } + finally { + await fs.remove(dir); + } + } + catch (exn) { + resp.statusCode = 503; + const reason = + (exn.name === 'StatusCodeError' ? exn.response.body : exn).toString(); + console.error(`Error: ${reason}`); + return resp.send(reason.toString()); + } }); diff --git a/stopify-compiler-cloud-function/ts/testServer.ts b/stopify-compiler-cloud-function/ts/testServer.ts index e2e5c070..a2006c0b 100644 --- a/stopify-compiler-cloud-function/ts/testServer.ts +++ b/stopify-compiler-cloud-function/ts/testServer.ts @@ -3,5 +3,5 @@ import * as index from './index'; const app = express(); app.use(index.stopifyTesting); -app.listen(8080, '0.0.0.0'); +app.listen(8081, '0.0.0.0'); diff --git a/stopify-continuations/src/callcc/boxAssignables.ts b/stopify-continuations/src/callcc/boxAssignables.ts index f7d4c25e..6318c8d0 100644 --- a/stopify-continuations/src/callcc/boxAssignables.ts +++ b/stopify-continuations/src/callcc/boxAssignables.ts @@ -218,7 +218,7 @@ const visitor = { // topFunction. It shouldn't be boxed since we want to preserve its // signature. if (vars.includes(path.node.id.name) && - !(state.opts.compileFunction && (path.node).topFunction)) { + !(state.opts.compileFunction === true && (path.node).topFunction)) { const fun = t.functionExpression( fastFreshId.fresh('fun'), path.node.params, diff --git a/stopify-continuations/src/callcc/callcc.ts b/stopify-continuations/src/callcc/callcc.ts index 31f17598..fec646b7 100644 --- a/stopify-continuations/src/callcc/callcc.ts +++ b/stopify-continuations/src/callcc/callcc.ts @@ -96,7 +96,7 @@ const visitor: Visitor = { path.stop(); let toShift; - if (opts.compileFunction) { + if (opts.compileFunction === true) { if (t.isFunctionDeclaration(path.node.body[0])) { toShift = (path.node.body[0]).body.body } diff --git a/stopify-continuations/src/callcc/delimitTopLevel.ts b/stopify-continuations/src/callcc/delimitTopLevel.ts index 544618d8..827902de 100644 --- a/stopify-continuations/src/callcc/delimitTopLevel.ts +++ b/stopify-continuations/src/callcc/delimitTopLevel.ts @@ -52,7 +52,7 @@ const visitor = { // leave intact } else { - if (state.opts.compileFunction && (body[i]).topFunction) { + if (state.opts.compileFunction !== false && (body[i]).topFunction) { } else { body[i] = delimitStmt(body[i]); diff --git a/stopify-continuations/src/types.ts b/stopify-continuations/src/types.ts index 9c392a7a..d92a69cf 100644 --- a/stopify-continuations/src/types.ts +++ b/stopify-continuations/src/types.ts @@ -1,8 +1,9 @@ export type CaptureMethod = 'eager' | 'retval' | 'lazy' | 'original' | 'fudge'; export type HandleNew = 'direct' | 'wrapper' +export type CompileFunction = boolean | 'module'; export interface CompilerOpts { - compileFunction?: boolean, + compileFunction?: CompileFunction, getters: boolean, debug: boolean, captureMethod: CaptureMethod, diff --git a/stopify-static-website/ts/languages.ts b/stopify-static-website/ts/languages.ts index 108db9ea..d8ee9d3a 100644 --- a/stopify-static-website/ts/languages.ts +++ b/stopify-static-website/ts/languages.ts @@ -1,3 +1,5 @@ +import * as stopifyCompiler from 'stopify'; + export interface Language { compileUrl: string, defaultCode: string @@ -7,6 +9,31 @@ export interface Language { const compilerBase = 'https://us-central1-arjun-umass.cloudfunctions.net/stopify'; +export function runtimeOpts(name: string): stopifyCompiler.Opts { + if (name === 'pyjs' || name === 'js') { + return { + filename: '', + estimator: 'reservoir', + yieldInterval: 100, + timePerElapsed: 1, + resampleInterval: 1, + variance: false, + env: 'chrome', + stop: undefined + }; + } + + return { + filename: '', + estimator: 'countdown', + yieldInterval: 1, + timePerElapsed: 1, + resampleInterval: 1, + variance: false, + env: 'chrome', + stop: undefined + }; +} export const langs: { [name: string]: Language } = { 'Dart': { @@ -86,16 +113,28 @@ object Runner extends JSApp { stepSupported: false, aceMode: 'python', defaultCode: - `def run_forever(): - i = 0 - while(True): - if i > 10000: - i = 0 - i += 1 - print i + `def fib(n): + print "fib(" + str(n) + ")" + if n == 0 or n == 1: + return 1 + return fib(n-1) + fib(n-2) -run_forever() - `, - compileUrl: `${compilerBase}/pyjs` +print (fib(15))`, + compileUrl: `${compilerBase}/pyjs-fast` + }, + JavaScript: { + stepSupported: true, + aceMode: 'js', + defaultCode: + `function fib(n) { + console.log('fib(' + n + ')'); + if (n === 0 || n === 1) { + return 1; } + return fib(n-1) + fib(n-2); } + +fib(15);`, + compileUrl: `${compilerBase}/js` + }, +}; diff --git a/stopify-static-website/ts/stopify.tsx b/stopify-static-website/ts/stopify.tsx index c3e97628..96b5b7c4 100644 --- a/stopify-static-website/ts/stopify.tsx +++ b/stopify-static-website/ts/stopify.tsx @@ -4,7 +4,7 @@ import AceEditor from 'react-ace'; import { StopifyAce } from './StopifyAce'; import * as browser from 'detect-browser' import * as ace from 'brace'; -import { langs } from './languages'; +import { langs, runtimeOpts } from './languages'; import * as stopifyCompiler from 'stopify'; @@ -40,6 +40,7 @@ class MultilingualStopifyEditor extends React.Component<{}, {language: string}>
  • this.setState({ language: 'Dart' })}>Dart
  • this.setState({ language: 'Python' })}>Python
  • +
  • this.setState({ language: 'JavaScript' })}>JavaScript
  • this.setState({ language: 'Scala' })}>Scala
  • this.setState({ language: 'OCaml' })}>OCaml
  • this.setState({ language: 'C++' })}>C++
  • @@ -141,16 +142,7 @@ class StopifyEditor extends React.Component<{ language: string }, StopifyEditorS } })) .then(path => { - const opts: stopifyCompiler.Opts = { - filename: path, - estimator: 'countdown', - yieldInterval: 1, - timePerElapsed: 1, - resampleInterval: 1, - variance: false, - env: browser.name as any, - stop: undefined - }; + const opts = runtimeOpts(this.state.language); this.setState({ rhs: { type: 'iframe', url: './container.html', opts: opts, path: path } }); @@ -249,8 +241,8 @@ class StopifyEditor extends React.Component<{ language: string }, StopifyEditorS else { // The "key" in the iframe is unique and forces a full reload. rhs = ; } return
    diff --git a/stopify-third-party-compiler-container/server/ts/pyjsFast.ts b/stopify-third-party-compiler-container/server/ts/pyjsFast.ts new file mode 100644 index 00000000..6f57a194 --- /dev/null +++ b/stopify-third-party-compiler-container/server/ts/pyjsFast.ts @@ -0,0 +1,22 @@ +import * as fs from 'fs-extra'; +import * as path from 'path'; +import * as request from 'request-promise-native'; +import { tmpDir, exec } from './misc'; + +/** + * Compiles a Python module using PyJS, but does not link it to the PyJS + * runtime system. The produced module is called 'main' and the PyJS runtime + * must be written to load 'main'. + * + * @param code body of the python module + */ +export async function compile(code: string): Promise { + const dir = await tmpDir(); + try { + await fs.writeFile(`${dir}/main.py`, code); + return await exec(`pyjscompile main.py`, dir); + } + finally { + await fs.remove(dir); + } +} diff --git a/stopify-third-party-compiler-container/server/ts/server.ts b/stopify-third-party-compiler-container/server/ts/server.ts index 04212591..7a80eb06 100644 --- a/stopify-third-party-compiler-container/server/ts/server.ts +++ b/stopify-third-party-compiler-container/server/ts/server.ts @@ -7,6 +7,7 @@ import * as bucklescript from './bucklescript'; import * as clojurescript from './clojurescript'; import * as scalajs from './scalajs'; import * as dart2js from './dart2js'; +import * as pyjsFast from './pyjsFast'; export const app = express(); app.use(morgan('short')); @@ -31,5 +32,6 @@ compiler('/bucklescript', bucklescript.compile); compiler('/clojurescript', clojurescript.compile); compiler('/scalajs', scalajs.compile); compiler('/dart2js', dart2js.compile); +compiler('/pyjs-fast', pyjsFast.compile); app.listen(8080); \ No newline at end of file diff --git a/stopify/src/stopify/stopifyCallCC.ts b/stopify/src/stopify/stopifyCallCC.ts index 2acef218..600f0584 100644 --- a/stopify/src/stopify/stopifyCallCC.ts +++ b/stopify/src/stopify/stopifyCallCC.ts @@ -5,18 +5,6 @@ import suspendStop from './suspendStop'; import suspendStep from './suspendStep'; import { timeSlow } from '../generic'; -const allowed = [ - "Object", - "exports", - "require", - "console", - "global", - "window", - "document", - "setTimeout", - "captureCC" -]; - export const visitor: Visitor = { Program(path: NodePath, state) { const opts: callcc.CompilerOpts = state.opts; @@ -40,10 +28,6 @@ export const visitor: Visitor = { callcc.fastFreshId.init(path); const plugs: any[] = []; - // Cleanup globals when not running in `func` compile mode - if (!state.opts.compileFunction) { - plugs.push([callcc.cleanupGlobals, { allowed }]) - } timeSlow('hygiene, etc.', () => callcc.transformFromAst(path, [ @@ -60,7 +44,7 @@ export const visitor: Visitor = { callcc.fastFreshId.cleanup() - if (opts.compileFunction) { + if (opts.compileFunction === true || opts.compileFunction === 'module') { // Do nothing } else if (opts.requireRuntime) { @@ -90,7 +74,7 @@ export const visitor: Visitor = { [t.identifier('$__R')]))])); } - if (!opts.compileFunction) { + if (opts.compileFunction === false) { path.node.body.push( t.expressionStatement( t.callExpression(