Skip to content

Commit

Permalink
Support wasm arch (#59)
Browse files Browse the repository at this point in the history
* WIP wasm

Signed-off-by: James Hamlin <[email protected]>

* WASM REPL

Signed-off-by: James Hamlin <[email protected]>

* Add cli history in browser

Signed-off-by: James Hamlin <[email protected]>

* Make bins

Signed-off-by: James Hamlin <[email protected]>

* Remove unused wasm files

Signed-off-by: James Hamlin <[email protected]>

* Ignore glj.wasm in doc dir

Signed-off-by: James Hamlin <[email protected]>

* glj main with/without wasm

Signed-off-by: James Hamlin <[email protected]>

---------

Signed-off-by: James Hamlin <[email protected]>
  • Loading branch information
jfhamlin authored Nov 24, 2023
1 parent 4d4670b commit 46f928d
Show file tree
Hide file tree
Showing 19 changed files with 16,809 additions and 31 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bin
doc/repl/glj.wasm
16 changes: 14 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@ STDLIB_TARGETS := $(addprefix pkg/stdlib/glojure/,$(STDLIB:.clj=.glj))
TEST_FILES := $(shell find ./test -name '*.glj')
TEST_TARGETS := $(addsuffix .test,$(TEST_FILES))

GOPLATFORMS := darwin_arm64 darwin_amd64 linux_arm64 linux_amd64 windows
GOPLATFORMS := darwin_arm64 darwin_amd64 linux_arm64 linux_amd64 windows_amd64 windows_arm js_wasm
GLJIMPORTS=$(foreach platform,$(GOPLATFORMS),pkg/gen/gljimports/gljimports_$(platform).go)
# wasm should have .wasm suffix; others should not
BINS=$(foreach platform,$(GOPLATFORMS),bin/$(platform)/glj$(if $(findstring wasm,$(platform)),.wasm,))

# eventually, support multiple minor versions
GO_VERSION := 1.19.3
GO_CMD := go$(GO_VERSION)

.PHONY: all
all: gocmd $(STDLIB_TARGETS) generate $(GLJIMPORTS)
all: gocmd $(STDLIB_TARGETS) generate $(GLJIMPORTS) $(BINS)

.PHONY: gocmd
gocmd:
Expand All @@ -38,6 +40,16 @@ pkg/stdlib/glojure/%.glj: scripts/rewrite-core/originals/%.clj scripts/rewrite-c
@mkdir -p $(dir $@)
@scripts/rewrite-core/run.sh $< > $@

bin/%/glj: $(wildcard ./cmd/glj/*.go) $(wildcard ./pkg/**/*.go) $(wildcard ./internal/**/*.go)
@echo "Building $@"
@mkdir -p $(dir $@)
@scripts/build-glj.sh $@ $*

bin/%/glj.wasm: $(wildcard ./cmd/glj/*.go) $(wildcard ./pkg/**/*.go) $(wildcard ./internal/**/*.go)
@echo "Building $@"
@mkdir -p $(dir $@)
@scripts/build-glj.sh $@ $*

.PHONY: vet
vet:
@go vet ./...
Expand Down
2 changes: 2 additions & 0 deletions cmd/glj/main.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build !wasm

package main

import (
Expand Down
15 changes: 15 additions & 0 deletions cmd/glj/main_wasm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//go:build wasm

package main

import (
"os"

// Bootstrap the runtime
_ "github.com/glojurelang/glojure/pkg/glj"
"github.com/glojurelang/glojure/pkg/gljmain"
)

func main() {
gljmain.Main(os.Args[1:])
}
167 changes: 167 additions & 0 deletions doc/repl/wasm_exec.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Go wasm</title>
<style>
#replContainer {
background-color: black;
color: white;

border: 1px solid #ddd;
border-radius: 15px;

padding: 10px;
margin: 10px 0;
font-family: monospace;

overflow-y: scroll;
height: 500px;
width: 800px;
}
#inputLine {
background-color: black;
color: white;

font-family: monospace;

border: none;
outline: none;
width: auto;
}
span {
white-space: pre;
}
</style>
</head>

<body>
<div id="replContainer"
onclick="document.getElementById('inputLine').focus()">
<div id="output">
<span id="inputLine" contenteditable="true" autocomplete="off" spellcheck="false"></span>
</div>
</div>

<!--
Add the following polyfill for Microsoft Edge 17/18 support:
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/encoding.min.js"></script>
(see https://caniuse.com/#feat=textencoder)
-->
<script src="wasm_exec.js"></script>
<script>
if (!WebAssembly.instantiateStreaming) { // polyfill
WebAssembly.instantiateStreaming = async (resp, importObject) => {
const source = await (await resp).arrayBuffer();
return await WebAssembly.instantiate(source, importObject);
};
}

const go = new Go();
let mod, inst;

async function run() {
console.clear();

const decoder = new TextDecoder("utf-8");
globalThis.fs.writeSync = function(fd, buf) {
const output = document.getElementById("output");
const text = decoder.decode(buf);

const span = document.createElement("span");
span.innerText = text;
// place span just before the input line
inputLine = document.getElementById("inputLine");
output.insertBefore(span, inputLine);

// scroll to bottom
inputLine.scrollIntoView();

return buf.length;
};

let pendingRead = null;
const originalRead = globalThis.fs.read;
const encoder = new TextEncoder();
globalThis.fs.read = function(fd, buffer, offset, length, position, callback) {
if (fd !== 0) {
return originalRead(fd, buffer, offset, length, position, callback);
}
if (pendingRead) {
throw new Error("multiple reads");
}
pendingRead = { buffer, offset, length, position, callback };
};

{
const history = [];
let historyIndex = 0;
let currentLine = "";
document.getElementById('inputLine').addEventListener('keydown', function(event) {
if (event.key === 'Enter') {
event.preventDefault();
let input = this.innerText;
this.innerText = "";
processInput(input);
if (input !== "") {
history.push(input);
}
historyIndex = history.length;
currentLine = "";
} else if (event.key === 'ArrowUp' || (event.ctrlKey && event.key === 'p')) {
event.preventDefault();
if (historyIndex > 0) {
if (historyIndex === history.length) {
currentLine = this.innerText;
}
historyIndex--;
this.innerText = history[historyIndex];
}
} else if (event.key === 'ArrowDown' || (event.ctrlKey && event.key === 'n')) {
event.preventDefault();
if (historyIndex < history.length) {
historyIndex++;
if (historyIndex === history.length) {
this.innerText = currentLine;
} else {
this.innerText = history[historyIndex];
}
}
}
});

function processInput(input) {
let output = document.getElementById('output');
// add a span to the output with the text and a newline
const span = document.createElement("span");
span.innerText = input + "\n";
output.insertBefore(span, document.getElementById("inputLine"));

if (pendingRead) {
const { buffer, offset, length, position, callback } = pendingRead;
pendingRead = null;
const view = encoder.encode(input + "\n");
// copy the data into the buffer
buffer.set(view, offset);
callback(null, view.length);
}
}
}

console.log("Running");
await go.run(inst);
console.log("Finished");
inst = await WebAssembly.instantiate(mod, go.importObject); // reset instance
}

WebAssembly.instantiateStreaming(fetch("glj.wasm"), go.importObject).then((result) => {
mod = result.module;
inst = result.instance;
run();
}).catch((err) => {
console.error(err);
});

</script>
</body>
</html>
Loading

0 comments on commit 46f928d

Please sign in to comment.