Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wasm support #37

Merged
merged 12 commits into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,18 @@ go.work.sum

.golangci.yml
gorepl
grol

.DS_Store
__*
# *_string.go
dist/
wasm/wasm_exec.js
wasm/wasm_exec.html
wasm/grol.wasm
wasm/test.wasm
wasm/grol_tiny.wasm
grol
grol.tiny
grol.wasm
grol_tiny.wasm
wasm/grol_tiny.wasm
38 changes: 35 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,39 @@ GEN:=object/type_string.go parser/priority_string.go token/type_string.go

grol: Makefile *.go */*.go $(GEN)
CGO_ENABLED=0 go build -trimpath -ldflags="-w -s" -tags "$(GO_BUILD_TAGS)" .
ls -l grol
ls -lh grol

tinygo: Makefile *.go */*.go $(GEN) wasm/wasm_exec.js wasm/wasm_exec.html
CGO_ENABLED=0 tinygo build -o grol.tiny -tags "$(GO_BUILD_TAGS)" .
strip grol.tiny
ls -lh grol.tiny

wasm: Makefile *.go */*.go $(GEN) wasm/wasm_exec.js wasm/wasm_exec.html
# GOOS=wasip1 GOARCH=wasm go build -o grol.wasm -trimpath -ldflags="-w -s" -tags "$(GO_BUILD_TAGS)" .
GOOS=js GOARCH=wasm go build -o wasm/grol.wasm -trimpath -ldflags="-w -s" -tags "$(GO_BUILD_TAGS)" ./wasm
# GOOS=wasip1 GOARCH=wasm tinygo build -target=wasi -no-debug -o grol_tiny.wasm -tags "$(GO_BUILD_TAGS)" .
# Tiny go generates errors https://github.com/tinygo-org/tinygo/issues/1140
# GOOS=js GOARCH=wasm tinygo build -no-debug -o wasm/test.wasm -tags "$(GO_BUILD_TAGS)" ./wasm
-ls -lh wasm/*.wasm
-pkill wasm
go run ./wasm ./wasm &
sleep 3
open http://localhost:8080/

GIT_TAG:=$(shell git describe --tags --abbrev=0)
# used to copy to site a release version
wasm-release: Makefile *.go */*.go $(GEN) wasm/wasm_exec.js wasm/wasm_exec.html
@echo "Building wasm release GIT_TAG=$(GIT_TAG)"
GOOS=js GOARCH=wasm go install -trimpath -ldflags="-w -s" -tags "$(GO_BUILD_TAGS)" grol.io/grol/wasm@$(GIT_TAG)
mv "$(shell go env GOPATH)/bin/js_wasm/wasm" wasm/grol.wasm
ls -lh wasm/*.wasm

wasm/wasm_exec.js: Makefile
# cp "$(shell tinygo env TINYGOROOT)/targets/wasm_exec.js" ./wasm/
cp "$(shell tinygo env GOROOT)/misc/wasm/wasm_exec.js" ./wasm/

wasm/wasm_exec.html:
cp "$(shell go env GOROOT)/misc/wasm/wasm_exec.html" ./wasm/

test: grol
CGO_ENABLED=0 go test -tags $(GO_BUILD_TAGS) ./...
Expand All @@ -34,7 +66,7 @@ token/type_string.go: token/token.go


clean:
rm -f grol */*_string.go
rm -f grol */*_string.go *.wasm wasm/*.wasm wasm/wasm_exec.html wasm/wasm_exec.js

build: grol

Expand All @@ -44,4 +76,4 @@ lint: .golangci.yml
.golangci.yml: Makefile
curl -fsS -o .golangci.yml https://raw.githubusercontent.com/fortio/workflows/main/golangci.yml

.PHONY: all lint generate test clean run build
.PHONY: all lint generate test clean run build wasm tinygo failing-tests wasm-release
2 changes: 2 additions & 0 deletions apply.gr
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ apply = func(f, a) {
apply(func(x) {2*x}, a)

// ^^^ [2, 6, 10, 14]

log("should be reached, won't")
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ go 1.22.5

require (
fortio.org/cli v1.7.0
fortio.org/log v1.14.0
fortio.org/log v1.15.0
github.com/google/go-cmp v0.6.0 // only for tests
)

require (
fortio.org/struct2env v0.4.1 // indirect
fortio.org/version v1.0.4 // indirect
github.com/kortschak/goroutine v1.1.2 // indirect
golang.org/x/crypto/x509roots/fallback v0.0.0-20240626151235-a6a393ffd658 // indirect; not actually used with our build tags
)
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
fortio.org/cli v1.7.0 h1:w+uXZLGi4t3Vn/BvbeMuSw84Z1pvNPG9HqeGfpP68cc=
fortio.org/cli v1.7.0/go.mod h1:s4vxWz7P7T4cYOWdMF0NA693Nu1gK9OW4KoDj54/Do4=
fortio.org/log v1.14.0 h1:ZkIc3Qqwfs9Dd931k07YzoC+bqCpJKEjVlZwxgXW3Nw=
fortio.org/log v1.14.0/go.mod h1:1tnXMqd5rZAgvSeHJkD2xXpyXRBzdeXtKLZuzNLIwtA=
fortio.org/log v1.15.0 h1:DRbZzgZH4av3ZPz6yIcvBwMy4NLH8a5iznRXXEegvJQ=
fortio.org/log v1.15.0/go.mod h1:t58Spg9njjymvRioh5F6qKGSupEsnMjXLGWIS1i3khE=
fortio.org/struct2env v0.4.1 h1:rJludAMO5eBvpWplWEQNqoVDFZr4RWMQX7RUapgZyc0=
fortio.org/struct2env v0.4.1/go.mod h1:lENUe70UwA1zDUCX+8AsO663QCFqYaprk5lnPhjD410=
fortio.org/version v1.0.4 h1:FWUMpJ+hVTNc4RhvvOJzb0xesrlRmG/a+D6bjbQ4+5U=
fortio.org/version v1.0.4/go.mod h1:2JQp9Ax+tm6QKiGuzR5nJY63kFeANcgrZ0osoQFDVm0=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/kortschak/goroutine v1.1.2 h1:lhllcCuERxMIK5cYr8yohZZScL1na+JM5JYPRclWjck=
github.com/kortschak/goroutine v1.1.2/go.mod h1:zKpXs1FWN/6mXasDQzfl7g0LrGFIOiA6cLs9eXKyaMY=
golang.org/x/crypto/x509roots/fallback v0.0.0-20240626151235-a6a393ffd658 h1:i7K6wQLN/0oxF7FT3tKkfMCstxoT4VGG36YIB9ZKLzI=
golang.org/x/crypto/x509roots/fallback v0.0.0-20240626151235-a6a393ffd658/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8=
11 changes: 11 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main

import (
"flag"
"fmt"
"os"

"fortio.org/cli"
Expand All @@ -12,17 +13,27 @@ import (
)

func main() {
commandFlag := flag.String("c", "", "command/inline script to run instead of interactive mode")
showParse := flag.Bool("parse", false, "show parse tree")
showEval := flag.Bool("eval", true, "show eval results")
sharedState := flag.Bool("shared-state", false, "All files share same interpreter state (default is new state for each)")
cli.ArgsHelp = "*.gr files to interpret or no arg for stdin repl..."
cli.MaxArgs = -1
cli.Main()
log.Printf("grol %s - welcome!", cli.LongVersion)
options := repl.Options{
ShowParse: *showParse,
ShowEval: *showEval,
}
nArgs := len(flag.Args())
if *commandFlag != "" {
res, errs := repl.EvalString(*commandFlag)
if len(errs) > 0 {
log.Errf("Errors: %v", errs)
}
fmt.Println(res)
return
}
if nArgs == 0 {
repl.Interactive(os.Stdin, os.Stdout, options)
return
Expand Down
16 changes: 16 additions & 0 deletions pi.gr
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
f = func(n, fac, dfac, exp, N) {
// log("Call n=", n, fac, dfac, exp)
if (n>N) {
[fac, dfac, exp]
} else {
dfac = 1.*dfac*(2*n - 1)
exp = exp * 2
fac = fac * n
f(n+1, fac, dfac, exp, N)
}
}
N = 100
r = f(1,1.,1.,1.,N)
// log("r", r)
approx = r[0] * r[2] / r[1]
approx * approx / N
11 changes: 11 additions & 0 deletions pi2.gr
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Γ(x + 1/2) ~ Γ(x)x^(1/2) = (x-1)!√x
// Γ(x + 1/2) = (2x - 1)!! * 2^-x * √π
f=func(i,n, prod) {
//log(i, prod)
if (i==n+1) {
return 1./(prod*prod*n)
}
f(i+1, n, prod * ( 1 - 1./(2*i)))
}
n=200000
f(1,n,1)
1 change: 1 addition & 0 deletions repl/repl.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ func Interactive(in io.Reader, out io.Writer, options Options) {
return
}
l := prev + scanner.Text()
// errors are already logged and this is the only case that can get contNeeded (EOL instead of EOF mode)
contNeeded, _ := EvalOne(s, macroState, l, out, options)
if contNeeded {
prev = l + "\n"
Expand Down
4 changes: 2 additions & 2 deletions tags.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Make go install fail if wrong tags are set

//go:build cgo || !no_net || !no_json
// +build cgo !no_net !no_json
//go:build (cgo && !tinygo) || !no_net || !no_json
// +build cgo,!tinygo !no_net !no_json

package main

Expand Down
20 changes: 20 additions & 0 deletions wasm/dev_server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//go:build !wasm
// +build !wasm

// Not to be used for anything but localhost testing
package main

import (
"fmt"
"log"
"net/http"
"os"
)

func main() {
port := ":8080"
path := os.Args[1]
fmt.Println("Serving", path, "on", port)
fs := http.FileServer(http.Dir(path))
log.Fatalf("%v", http.ListenAndServe(port, fs)) //nolint:gosec // just a test server
}
111 changes: 111 additions & 0 deletions wasm/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<!doctype html>
<html>

<head>
<meta charset="utf-8">
<title>Grol</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}

.container {
display: flex;
flex-direction: column;
gap: 10px;
}

textarea, label, div {
font-size: 14px;
width: 100%;
box-sizing: border-box;
}

button {
align-self: flex-start;
}

label {
margin-bottom: 5px;
}

.error-textarea {
color: red;
}
</style>
</head>

<body>
<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;
WebAssembly.instantiateStreaming(fetch("grol.wasm"), go.importObject).then((result) => {
mod = result.module;
inst = result.instance;
document.getElementById("runButton").disabled = false;
}).catch((err) => {
console.error(err);
});
function resizeTextarea(textarea) {
textarea.style.height = 'auto';
textarea.style.height = (textarea.scrollHeight) + 'px';
}

async function run() {
try {
// console.clear();
console.log('In run')
go.run(inst);
var input = document.getElementById('input').value
// Call the grol function with the input
var output = grol(input);
console.log('Eval done:');
console.log(output);
// Write the result to the output textarea
document.getElementById('output').value = output.result;
document.getElementById('errors').value = output.errors.join("\n");
document.getElementById('version').innerHTML = "GROL " + grolVersion;
resizeTextarea(document.getElementById('output'));
resizeTextarea(document.getElementById('errors'));
} catch (e) {
console.error(e);
} finally {
inst = await WebAssembly.instantiate(mod, go.importObject); // reset instance
}
}
document.addEventListener('DOMContentLoaded', (event) => {
document.getElementById('input').addEventListener('keydown', function (e) {
if (e.key === 'Enter') {
run();
}
});
});

</script>
<div>
<label for="input">Enter your GROL code here:</label>
<textarea id="input" rows="12" cols="80"></textarea>
</div>
<div>
Hit enter or click <button onClick="run();" id="runButton" disabled>Run</button>
</div>
<div>
<label for="output">Result:</label>
<textarea id="output" rows="2" cols="80"></textarea>
</div>
<div>
<label for="errors">Errors:</label>
<textarea id="errors" rows="1" cols="80" class="error-textarea"></textarea>
</div>
<div id="version">GROL</div>
</body>

</html>
45 changes: 45 additions & 0 deletions wasm/wasm_main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//go:build wasm
// +build wasm

/*
Web assembly main for grol, exposing grol (repl.EvalString for now) to JS
*/

package main

import (
"syscall/js"

"fortio.org/cli"
"fortio.org/log"
"fortio.org/version"
"grol.io/grol/repl"
)

func jsEval(this js.Value, args []js.Value) interface{} {
if len(args) != 1 {
return "ERROR: number of arguments doesn't match"
}
input := args[0].String()
res, errs := repl.EvalString(input)
result := make(map[string]any)
result["result"] = res
// transfer errors to []any (!)
anyErrs := make([]any, len(errs))
for i, v := range errs {
anyErrs[i] = v
}
result["errors"] = anyErrs
return result
}

func main() {
cli.Main() // just to get version etc
_, grolVersion, _ := version.FromBuildInfoPath("grol.io/grol")
log.Infof("Grol wasm main %s", grolVersion)
done := make(chan struct{}, 0)
global := js.Global()
global.Set("grol", js.FuncOf(jsEval))
global.Set("grolVersion", js.ValueOf(grolVersion))
<-done
}