Skip to content

Commit 4d32658

Browse files
ndmitchellfacebook-github-bot
authored andcommitted
Add a WASM/JS example (#129)
Summary: Based on the code in #109 (comment), with a few minor changes to make it work with stable and work for types that aren't string. Plus a slightly nicer HTML page where you can edit the input. ![image](https://github.com/user-attachments/assets/cc5a1f42-8e25-4284-9d9c-9ccffdeab318) Credit for most of the code goes to aschleck. Pull Request resolved: #129 Reviewed By: stepancheg Differential Revision: D61774902 Pulled By: ndmitchell fbshipit-source-id: 4babe21de199f194be3405cd7e22b543a3e9169d
1 parent 52b9f76 commit 4d32658

File tree

8 files changed

+174
-0
lines changed

8 files changed

+174
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
/target/
22
Cargo.lock
3+
*.wasm

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ members = [
77
"starlark",
88
"starlark_bin",
99
"starlark_derive",
10+
"starlark_js_example",
1011
"starlark_lsp",
1112
"starlark_map",
1213
"starlark_syntax",

starlark/src/environment/modules.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,7 @@ impl Module {
517517
self.docstring.replace(Some(docstring));
518518
}
519519

520+
#[cfg(not(target_arch = "wasm32"))]
520521
pub(crate) fn add_eval_duration(&self, duration: Duration) {
521522
self.eval_duration.set(self.eval_duration.get() + duration);
522523
}

starlark/src/eval.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub(crate) mod soft_error;
2525

2626
use std::collections::HashMap;
2727
use std::mem;
28+
#[cfg(not(target_arch = "wasm32"))]
2829
use std::time::Instant;
2930

3031
use dupe::Dupe;
@@ -62,6 +63,7 @@ impl<'v, 'a, 'e> Evaluator<'v, 'a, 'e> {
6263
/// Evaluate an [`AstModule`] with this [`Evaluator`], modifying the in-scope
6364
/// [`Module`](crate::environment::Module) as appropriate.
6465
pub fn eval_module(&mut self, ast: AstModule, globals: &Globals) -> crate::Result<Value<'v>> {
66+
#[cfg(not(target_arch = "wasm32"))]
6567
let start = Instant::now();
6668

6769
let (codemap, statement, dialect, typecheck) = ast.into_parts();
@@ -135,6 +137,7 @@ impl<'v, 'a, 'e> Evaluator<'v, 'a, 'e> {
135137

136138
self.module_def_info = old_def_info;
137139

140+
#[cfg(not(target_arch = "wasm32"))]
138141
self.module_env.add_eval_duration(start.elapsed());
139142

140143
// Return the result of evaluation

starlark_js_example/Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
description = "Example of running starlark-rust interpreter in browser"
3+
edition = "2021"
4+
name = "starlark_js_example"
5+
publish = false
6+
version = "0.0.0"
7+
8+
[dependencies]
9+
starlark = { path = "../starlark", version = "0.12.0" }
10+
11+
[lib]
12+
crate-type = ["cdylib", "rlib"]

starlark_js_example/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Starlark JS
2+
3+
This directory contains an example project making use of Starlark
4+
WebAssembly/WASM. To try it:
5+
6+
```
7+
rustup target add wasm32-unknown-unknown
8+
cargo build --target wasm32-unknown-unknown --release
9+
cp ../target/wasm32-unknown-unknown/release/starlark_js.wasm .
10+
python -m http.server
11+
```
12+
13+
Then visit [http://localhost:8000](http://localhost:8000).

starlark_js_example/index.html

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
4+
<head>
5+
<meta charset="utf-8" />
6+
<title>Starlark evaluator</title>
7+
<style type="text/css">
8+
body {
9+
font-family: sans-serif;
10+
}
11+
12+
textarea {
13+
width: calc(100% - 20px);
14+
height: 20em;
15+
box-sizing: border-box;
16+
margin: 5px;
17+
border-radius: 3px;
18+
padding: 5px;
19+
border-color: lightgray;
20+
}
21+
</style>
22+
</head>
23+
24+
<body>
25+
<script>
26+
function run() {
27+
WebAssembly.instantiateStreaming(fetch("starlark_js.wasm"), {}).then(({ instance }) => {
28+
const readString = (offset) => {
29+
const memory = instance.exports.memory.buffer;
30+
const length = new Uint32Array(memory, offset, 1)[0];
31+
const characters = new Uint8Array(memory, offset + 4, length);
32+
return new TextDecoder().decode(characters);
33+
};
34+
35+
const readU8 = (offset) => {
36+
return new Uint8Array(instance.exports.memory.buffer, offset, 1)[0];
37+
};
38+
39+
const writeString = (s) => {
40+
const encoded = new TextEncoder().encode(s.trim());
41+
const offset = instance.exports.allocation(4 + encoded.byteLength);
42+
// TODO(april): this probably isn't guaranteed to be 4-byte aligned? Might need to fix.
43+
const memory = instance.exports.memory.buffer;
44+
const uint32s = new Uint32Array(memory, offset, 1);
45+
uint32s[0] = encoded.byteLength;
46+
const uint8s = new Uint8Array(memory, offset + 4, encoded.byteLength);
47+
uint8s.set(encoded);
48+
return offset;
49+
};
50+
51+
const content = document.getElementById("input").value;
52+
const offset = instance.exports.evaluate(writeString(content));
53+
const ok = readU8(offset) != 0;
54+
const result = readString(offset + 4);
55+
const output = document.getElementById("output");
56+
output.value = (ok ? "" : "ERROR\n") + result;
57+
});
58+
}
59+
60+
window.addEventListener("load", function () {
61+
document.getElementById("input").addEventListener("input", run, false);
62+
run()
63+
})
64+
</script>
65+
<h1>Starlark evaluator</h1>
66+
<p>
67+
Using <a href="https://github.com/facebook/starlark-rust">starlark-rust</a> compiled to web assembly.
68+
Change the input to see it update.
69+
</p>
70+
<textarea id="input">
71+
def hello(name):
72+
return "hello " + name
73+
74+
hello("friend")</textarea>
75+
<textarea id="output" readonly="readonly" style="background-color: lightgray;">
76+
</textarea>
77+
</body>
78+
79+
</html>

starlark_js_example/src/lib.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2019 The Starlark in Rust Authors.
3+
* Copyright (c) Facebook, Inc. and its affiliates.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
use std::mem;
19+
use std::slice;
20+
use std::str;
21+
22+
use starlark::environment::Globals;
23+
use starlark::environment::Module;
24+
use starlark::eval::Evaluator;
25+
use starlark::syntax::AstModule;
26+
use starlark::syntax::Dialect;
27+
use starlark::values::Value;
28+
29+
#[no_mangle]
30+
pub extern "C" fn allocation(n: usize) -> *mut u8 {
31+
mem::ManuallyDrop::new(Vec::with_capacity(n)).as_mut_ptr()
32+
}
33+
34+
#[no_mangle]
35+
pub unsafe extern "C" fn evaluate(s: *const u8) -> *mut u8 {
36+
let length = u32::from_le_bytes(*(s as *const [u8; 4])) as usize;
37+
let input = slice::from_raw_parts(s.offset(4), length);
38+
let output = evaluate_buffers(input);
39+
mem::ManuallyDrop::new(output).as_mut_ptr()
40+
}
41+
42+
fn evaluate_buffers(input: &[u8]) -> Vec<u8> {
43+
let contents = str::from_utf8(input).unwrap();
44+
let result = evaluate_starlark(contents);
45+
let success = result.is_ok();
46+
let message = result.unwrap_or_else(|e| e.into_anyhow().to_string());
47+
let len = message.len();
48+
let mut buffer = Vec::with_capacity(len + 8);
49+
buffer.push(if success { 1 } else { 0 });
50+
buffer.extend(vec![0; 3]);
51+
buffer.extend_from_slice(&(len as u32).to_le_bytes());
52+
buffer.extend_from_slice(message.as_bytes());
53+
buffer
54+
}
55+
56+
fn evaluate_starlark(content: &str) -> Result<String, starlark::Error> {
57+
let ast: AstModule =
58+
AstModule::parse("hello_world.star", content.to_owned(), &Dialect::Standard)?;
59+
let globals = Globals::standard();
60+
let module: Module = Module::new();
61+
let mut eval: Evaluator = Evaluator::new(&module);
62+
let res: Value = eval.eval_module(ast, &globals)?;
63+
Ok(res.to_string())
64+
}

0 commit comments

Comments
 (0)