├── .gitignore
├── LICENSE
├── README.md
├── cmd
├── cmdconfig
│ └── config.go
├── config
│ ├── constants.go
│ ├── hosts.local.go
│ └── hosts.prod.go
├── deploy.go
├── deployer
│ ├── deployer.go
│ └── templates.go
├── root.go
├── serve.go
├── server
│ └── server.go
└── version.go
├── go.mod
├── go.sum
├── helloworld
└── main.go
├── main.go
└── splitter
├── foo.wasm
├── foo_out.wasm
└── main.go
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .DS_Store
3 | .idea/
4 | coverage.out
5 | .coverage/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2018 David Brophy
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # wasmgo
4 |
5 | The `wasmgo` command compiles Go to WASM, and serves the binary locally or deploys to the [jsgo.io](https://github.com/dave/jsgo) CDN.
6 |
7 | ### Install
8 |
9 | ```
10 | go get -u github.com/dave/wasmgo
11 | ```
12 |
13 | ### Serve command
14 |
15 | ```
16 | wasmgo serve [flags] [package]
17 | ```
18 |
19 | Serves the WASM with a local web server (default to port 8080). Refresh the browser to recompile.
20 |
21 | ### Deploy command
22 |
23 | ```
24 | wasmgo deploy [flags] [package]
25 | ```
26 |
27 | Deploys the WASM to the [jsgo.io](https://github.com/dave/jsgo) CDN.
28 |
29 | ### Global flags
30 |
31 | ```
32 | -b, --build string Build tags to pass to the go build command.
33 | -c, --command string Name of the go command. (default "go")
34 | -f, --flags string Flags to pass to the go build command.
35 | -h, --help help for wasmgo
36 | -i, --index string Specify the index page template. Variables: Script, Loader, Binary. (default "index.wasmgo.html")
37 | -o, --open Open the page in a browser. (default true)
38 | -v, --verbose Show detailed status messages.
39 | ```
40 |
41 | ### Deploy flags
42 |
43 | ```
44 | -j, --json Return all template variables as a json blob from the deploy command.
45 | -t, --template string Template defining the output returned by the deploy command. Variables: Page, Script, Loader, Binary. (default "{{ .Page }}")
46 | ```
47 |
48 | ### Serve flags
49 |
50 | ```
51 | -p, --port int Server port. (default 8080)
52 | ```
53 |
54 | ### Package
55 |
56 | Omit the package argument to use the code in the current directory.
57 |
58 | ### Examples
59 |
60 | Here's a simple hello world:
61 |
62 | ```
63 | wasmgo serve github.com/dave/wasmgo/helloworld
64 | ```
65 |
66 | The page (http://localhost:8080/) opens in a browser.
67 |
68 | Here's an amazing 2048 clone from [hajimehoshi](https://github.com/hajimehoshi):
69 |
70 | ```
71 | go get -u github.com/hajimehoshi/ebiten/examples/2048/...
72 | wasmgo deploy -b=example github.com/hajimehoshi/ebiten/examples/2048
73 | ```
74 |
75 | [The deployed page](https://jsgo.io/2893575ab26da60ef14801541b46201c9d54db13) opens in a browser.
76 |
77 | ### Index
78 |
79 | You may specify a custom index page by including `index.wasmgo.html` in your project or by using the `index`
80 | command line flag.
81 |
82 | Your index page should look something like this:
83 |
84 | ```html
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | ```
93 |
94 | ### Template variables
95 |
96 | The index page template and the `-t` flag are both Go templates with several variables available:
97 |
98 | * *Page*
99 | The URL of the page on jsgo.io (deploy command output only).
100 |
101 | * *Script*
102 | To load and execute a WASM binary, a some JS bootstap code is required. The wasmgo command uses a minified
103 | version of the [example in the official Go repo](https://github.com/golang/go/blob/master/misc/wasm/wasm_exec.js).
104 | The URL of this script is the `Script` template variable.
105 |
106 | * *Loader*
107 | The loader JS is a simple script that loads and executes the WASM binary. It's based on the [example
108 | in the official Go repo](https://github.com/golang/go/blob/master/misc/wasm/wasm_exec.html#L17-L36),
109 | but simplified to execute the program immediately instead. The URL of this script is the `Loader` template variable.
110 |
111 | * *Binary*
112 | The URL of the WASM binary file.
113 |
114 | ### Static files
115 |
116 | Unfortunately wasmgo does not host your static files. I recommend using [rawgit.com](https://rawgit.com/)
117 | to serve static files.
118 |
119 | ### Package splitting
120 |
121 | The `wasmgo deploy` can't yet split the binary output by Go package, [can you help?](https://github.com/dave/wasmgo/issues/2)
--------------------------------------------------------------------------------
/cmd/cmdconfig/config.go:
--------------------------------------------------------------------------------
1 | package cmdconfig
2 |
3 | type Config struct {
4 | Port int
5 | Index string
6 | Template string
7 | Json bool
8 | Verbose bool
9 | Open bool
10 | Command string
11 | Flags string
12 | BuildTags string
13 | Path string
14 | }
15 |
--------------------------------------------------------------------------------
/cmd/config/constants.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | const (
4 | Wasm = "wasm"
5 | Index = "index"
6 | Pkg = "pkg"
7 | )
8 |
--------------------------------------------------------------------------------
/cmd/config/hosts.local.go:
--------------------------------------------------------------------------------
1 | // +build local
2 |
3 | package config
4 |
5 | var Host = map[string]string{
6 | Wasm: "localhost:8083",
7 | Pkg: "localhost:8092",
8 | Index: "localhost:8093",
9 | }
10 |
11 | var Protocol = map[string]string{
12 | Wasm: "http",
13 | Pkg: "http",
14 | Index: "http",
15 | }
16 |
--------------------------------------------------------------------------------
/cmd/config/hosts.prod.go:
--------------------------------------------------------------------------------
1 | // +build !local
2 |
3 | package config
4 |
5 | var Host = map[string]string{
6 | Wasm: "wasm.jsgo.io",
7 | Pkg: "pkg.jsgo.io",
8 | Index: "jsgo.io",
9 | }
10 |
11 | var Protocol = map[string]string{
12 | Wasm: "https",
13 | Pkg: "https",
14 | Index: "https",
15 | }
16 |
--------------------------------------------------------------------------------
/cmd/deploy.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 | "os"
6 |
7 | "github.com/dave/wasmgo/cmd/deployer"
8 | "github.com/spf13/cobra"
9 | )
10 |
11 | func init() {
12 | deployCmd.PersistentFlags().StringVarP(&global.Template, "template", "t", "{{ .Page }}", "Template defining the output returned by the deploy command. Variables: Page, Script, Loader, Binary.")
13 | deployCmd.PersistentFlags().BoolVarP(&global.Json, "json", "j", false, "Return all template variables as a json blob from the deploy command.")
14 | rootCmd.AddCommand(deployCmd)
15 | }
16 |
17 | var deployCmd = &cobra.Command{
18 | Use: "deploy [package]",
19 | Short: "Compile and deploy",
20 | Long: "Compiles Go to WASM and deploys to the jsgo.io CDN.",
21 | Args: cobra.RangeArgs(0, 1),
22 | Run: func(cmd *cobra.Command, args []string) {
23 | if len(args) > 0 {
24 | global.Path = args[0]
25 | }
26 | d, err := deployer.New(global)
27 | if err != nil {
28 | fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error())
29 | os.Exit(1)
30 | }
31 | if err := d.Start(); err != nil {
32 | fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error())
33 | os.Exit(1)
34 | }
35 | },
36 | }
37 |
--------------------------------------------------------------------------------
/cmd/deployer/deployer.go:
--------------------------------------------------------------------------------
1 | package deployer
2 |
3 | import (
4 | "bytes"
5 | "crypto/sha1"
6 | "encoding/json"
7 | "errors"
8 | "fmt"
9 | "io"
10 | "io/ioutil"
11 | "net/http"
12 | "os"
13 | "os/exec"
14 | "path/filepath"
15 | "strings"
16 | "text/template"
17 |
18 | "github.com/dave/jsgo/assets/std"
19 | "github.com/dave/jsgo/server/servermsg"
20 | "github.com/dave/jsgo/server/wasm/messages"
21 | "github.com/dave/services/constor/constormsg"
22 | "github.com/dave/wasmgo/cmd/cmdconfig"
23 | "github.com/dave/wasmgo/cmd/config"
24 | "github.com/gorilla/websocket"
25 | "github.com/pkg/browser"
26 | )
27 |
28 | const CLIENT_VERSION = "1.0.0"
29 |
30 | func init() {
31 | servermsg.RegisterTypes()
32 | }
33 |
34 | func New(cfg *cmdconfig.Config) (*State, error) {
35 | sourceDir, err := runGoList(cfg)
36 | if err != nil {
37 | return nil, err
38 | }
39 | s := &State{cfg: cfg, dir: sourceDir}
40 | if cfg.Verbose {
41 | s.debug = os.Stdout
42 | } else {
43 | s.debug = ioutil.Discard
44 | }
45 | return s, nil
46 | }
47 |
48 | type State struct {
49 | cfg *cmdconfig.Config
50 | dir string
51 | debug io.Writer
52 | }
53 |
54 | func (d *State) Start() error {
55 |
56 | fmt.Fprintln(d.debug, "Compiling...")
57 |
58 | binaryBytes, binaryHash, err := d.Build()
59 | if err != nil {
60 | return err
61 | }
62 |
63 | files := map[messages.DeployFileType]messages.DeployFile{}
64 |
65 | files[messages.DeployFileTypeWasm] = messages.DeployFile{
66 | DeployFileKey: messages.DeployFileKey{
67 | Type: messages.DeployFileTypeWasm,
68 | Hash: fmt.Sprintf("%x", binaryHash),
69 | },
70 | Contents: binaryBytes,
71 | }
72 |
73 | binaryUrl := fmt.Sprintf("%s://%s/%x.wasm", config.Protocol[config.Pkg], config.Host[config.Pkg], binaryHash)
74 | loaderBytes, loaderHash, err := d.Loader(binaryUrl)
75 |
76 | files[messages.DeployFileTypeLoader] = messages.DeployFile{
77 | DeployFileKey: messages.DeployFileKey{
78 | Type: messages.DeployFileTypeLoader,
79 | Hash: fmt.Sprintf("%x", loaderHash),
80 | },
81 | Contents: loaderBytes,
82 | }
83 |
84 | loaderUrl := fmt.Sprintf("%s://%s/%x.js", config.Protocol[config.Pkg], config.Host[config.Pkg], loaderHash)
85 | scriptUrl := fmt.Sprintf("%s://%s/wasm_exec.%s.js", config.Protocol[config.Pkg], config.Host[config.Pkg], std.Wasm[true])
86 |
87 | indexBytes, indexHash, err := d.Index(scriptUrl, loaderUrl, binaryUrl)
88 | if err != nil {
89 | return err
90 | }
91 |
92 | files[messages.DeployFileTypeIndex] = messages.DeployFile{
93 | DeployFileKey: messages.DeployFileKey{
94 | Type: messages.DeployFileTypeIndex,
95 | Hash: fmt.Sprintf("%x", indexHash),
96 | },
97 | Contents: indexBytes,
98 | }
99 |
100 | indexUrl := fmt.Sprintf("%s://%s/%x", config.Protocol[config.Index], config.Host[config.Index], indexHash)
101 |
102 | message := messages.DeployQuery{
103 | Version: CLIENT_VERSION,
104 | Files: []messages.DeployFileKey{
105 | files[messages.DeployFileTypeWasm].DeployFileKey,
106 | files[messages.DeployFileTypeIndex].DeployFileKey,
107 | files[messages.DeployFileTypeLoader].DeployFileKey,
108 | },
109 | }
110 |
111 | fmt.Fprintln(d.debug, "Querying server...")
112 |
113 | protocol := "wss"
114 | if config.Protocol[config.Wasm] == "http" {
115 | protocol = "ws"
116 | }
117 | conn, _, err := websocket.DefaultDialer.Dial(
118 | fmt.Sprintf("%s://%s/_wasm/", protocol, config.Host[config.Wasm]),
119 | http.Header{"Origin": []string{fmt.Sprintf("%s://%s/", config.Protocol[config.Wasm], config.Host[config.Wasm])}},
120 | )
121 | if err != nil {
122 | return err
123 | }
124 |
125 | messageBytes, messageType, err := messages.Marshal(message)
126 | if err != nil {
127 | return err
128 | }
129 | if err := conn.WriteMessage(messageType, messageBytes); err != nil {
130 | return err
131 | }
132 |
133 | var response messages.DeployQueryResponse
134 | var done bool
135 | for !done {
136 | _, replyBytes, err := conn.ReadMessage()
137 | if err != nil {
138 | return err
139 | }
140 | message, err := messages.Unmarshal(replyBytes)
141 | if err != nil {
142 | return err
143 | }
144 | switch message := message.(type) {
145 | case messages.DeployQueryResponse:
146 | response = message
147 | done = true
148 | case servermsg.Queueing:
149 | // don't print
150 | case servermsg.Error:
151 | return errors.New(message.Message)
152 | case messages.DeployClientVersionNotSupported:
153 | return errors.New("this client version is not supported - try `go get -u github.com/dave/wasmgo`")
154 | default:
155 | // unexpected
156 | fmt.Fprintf(d.debug, "Unexpected message from server: %#v\n", message)
157 | }
158 | }
159 |
160 | if len(response.Required) > 0 {
161 |
162 | fmt.Fprintf(d.debug, "Files required: %d.\n", len(response.Required))
163 | fmt.Fprintln(d.debug, "Bundling required files...")
164 |
165 | var required []messages.DeployFile
166 | for _, k := range response.Required {
167 | file := files[k.Type]
168 | if file.Hash == "" {
169 | return errors.New("server requested file not found")
170 | }
171 | required = append(required, file)
172 | }
173 |
174 | payload := messages.DeployPayload{Files: required}
175 | payloadBytes, payloadType, err := messages.Marshal(payload)
176 | if err != nil {
177 | return err
178 | }
179 | if err := conn.WriteMessage(payloadType, payloadBytes); err != nil {
180 | return err
181 | }
182 |
183 | fmt.Fprintf(d.debug, "Sending payload: %dKB.\n", len(payloadBytes)/1024)
184 |
185 | done = false
186 | for !done {
187 | _, replyBytes, err := conn.ReadMessage()
188 | if err != nil {
189 | return err
190 | }
191 | message, err := messages.Unmarshal(replyBytes)
192 | if err != nil {
193 | return err
194 | }
195 | switch message := message.(type) {
196 | case messages.DeployDone:
197 | done = true
198 | case servermsg.Queueing:
199 | // don't print
200 | case servermsg.Error:
201 | return errors.New(message.Message)
202 | case constormsg.Storing:
203 | if message.Remain > 0 || message.Finished > 0 {
204 | fmt.Fprintf(d.debug, "Storing, %d to go.\n", message.Remain)
205 | }
206 | default:
207 | // unexpected
208 | fmt.Fprintf(d.debug, "Unexpected message from server: %#v\n", message)
209 | }
210 | }
211 |
212 | fmt.Fprintln(d.debug, "Sending done.")
213 |
214 | } else {
215 | fmt.Fprintln(d.debug, "No files required.")
216 | }
217 |
218 | outputVars := struct{ Page, Script, Loader, Binary string }{
219 | Page: indexUrl,
220 | Script: scriptUrl,
221 | Loader: loaderUrl,
222 | Binary: binaryUrl,
223 | }
224 |
225 | if d.cfg.Json {
226 | out, err := json.Marshal(outputVars)
227 | if err != nil {
228 | return err
229 | }
230 | fmt.Println(string(out))
231 | } else {
232 | tpl, err := template.New("main").Parse(d.cfg.Template)
233 | if err != nil {
234 | return err
235 | }
236 | tpl.Execute(os.Stdout, outputVars)
237 | fmt.Println("")
238 | }
239 |
240 | if d.cfg.Open {
241 | browser.OpenURL(indexUrl)
242 | }
243 |
244 | return nil
245 | }
246 |
247 | func (d *State) Loader(binaryUrl string) (contents, hash []byte, err error) {
248 | loaderBuf := &bytes.Buffer{}
249 | loaderSha := sha1.New()
250 | loaderVars := struct{ Binary string }{
251 | Binary: binaryUrl,
252 | }
253 | if err := loaderTemplateMin.Execute(io.MultiWriter(loaderBuf, loaderSha), loaderVars); err != nil {
254 | return nil, nil, err
255 | }
256 | return loaderBuf.Bytes(), loaderSha.Sum(nil), nil
257 | }
258 |
259 | func (d *State) Index(scriptUrl, loaderUrl, binaryUrl string) (contents, hash []byte, err error) {
260 | indexBuf := &bytes.Buffer{}
261 | indexSha := sha1.New()
262 | indexVars := struct{ Script, Loader, Binary string }{
263 | Script: scriptUrl,
264 | Loader: loaderUrl,
265 | Binary: binaryUrl,
266 | }
267 | indexTemplate := defaultIndexTemplate
268 | if d.cfg.Index != "" {
269 | indexFilename := d.cfg.Index
270 | if d.cfg.Path != "" {
271 | indexFilename = filepath.Join(d.dir, d.cfg.Index)
272 | }
273 | indexTemplateBytes, err := ioutil.ReadFile(indexFilename)
274 | if err != nil && !os.IsNotExist(err) {
275 | return nil, nil, err
276 | }
277 | if err == nil {
278 | indexTemplate, err = template.New("main").Parse(string(indexTemplateBytes))
279 | if err != nil {
280 | return nil, nil, err
281 | }
282 | }
283 | }
284 | if err := indexTemplate.Execute(io.MultiWriter(indexBuf, indexSha), indexVars); err != nil {
285 | return nil, nil, err
286 | }
287 | return indexBuf.Bytes(), indexSha.Sum(nil), nil
288 | }
289 |
290 | func (d *State) Build() (contents, hash []byte, err error) {
291 |
292 | // create a temp dir
293 | tempDir, err := ioutil.TempDir("", "")
294 | if err != nil {
295 | return nil, nil, err
296 | }
297 | defer os.RemoveAll(tempDir)
298 |
299 | fpath := filepath.Join(tempDir, "out.wasm")
300 |
301 | args := []string{"build", "-o", fpath}
302 |
303 | extraFlags := strings.Fields(d.cfg.Flags)
304 | for _, f := range extraFlags {
305 | args = append(args, f)
306 | }
307 |
308 | if d.cfg.BuildTags != "" {
309 | args = append(args, "-tags", d.cfg.BuildTags)
310 | }
311 |
312 | path := "."
313 | if d.cfg.Path != "" {
314 | path = d.cfg.Path
315 | }
316 | args = append(args, path)
317 |
318 | cmd := exec.Command(d.cfg.Command, args...)
319 | cmd.Env = os.Environ()
320 | cmd.Env = append(cmd.Env, "GOARCH=wasm")
321 | cmd.Env = append(cmd.Env, "GOOS=js")
322 | output, err := cmd.CombinedOutput()
323 | if err != nil {
324 | if strings.Contains(string(output), "unsupported GOOS/GOARCH pair js/wasm") {
325 | return nil, nil, errors.New("you need Go v1.11 to compile WASM. It looks like your default `go` command is not v1.11. Perhaps you need the -c flag to specify a custom command name - e.g. `-c=go1.11beta3`")
326 | }
327 | return nil, nil, fmt.Errorf("%v: %s", err, string(output))
328 | }
329 | if len(output) > 0 {
330 | return nil, nil, fmt.Errorf("%s", string(output))
331 | }
332 |
333 | binaryBytes, err := ioutil.ReadFile(fpath)
334 | if err != nil {
335 | return nil, nil, err
336 | }
337 | binarySha := sha1.New()
338 | if _, err := io.Copy(binarySha, bytes.NewBuffer(binaryBytes)); err != nil {
339 | return nil, nil, err
340 | }
341 |
342 | return binaryBytes, binarySha.Sum(nil), nil
343 | }
344 |
345 | func runGoList(cfg *cmdconfig.Config) (string, error) {
346 | args := []string{"list"}
347 |
348 | if cfg.BuildTags != "" {
349 | args = append(args, "-tags", cfg.BuildTags)
350 | }
351 |
352 | args = append(args, "-f", "{{.Dir}}")
353 |
354 | path := "."
355 | if cfg.Path != "" {
356 | path = cfg.Path
357 | }
358 | args = append(args, path)
359 |
360 | cmd := exec.Command(cfg.Command, args...)
361 | cmd.Env = os.Environ()
362 | cmd.Env = append(cmd.Env, "GOARCH=wasm")
363 | cmd.Env = append(cmd.Env, "GOOS=js")
364 | output, err := cmd.CombinedOutput()
365 | if err != nil {
366 | if strings.Contains(string(output), "unsupported GOOS/GOARCH pair js/wasm") {
367 | return "", errors.New("you need Go v1.11 to compile WASM. It looks like your default `go` command is not v1.11. Perhaps you need the -c flag to specify a custom command name - e.g. `-c=go1.11beta3`")
368 | }
369 | return "", fmt.Errorf("%v: %s", err, string(output))
370 | }
371 | return string(output), nil
372 | }
373 |
--------------------------------------------------------------------------------
/cmd/deployer/templates.go:
--------------------------------------------------------------------------------
1 | package deployer
2 |
3 | import "text/template"
4 |
5 | var defaultIndexTemplate = template.Must(template.New("main").Parse(`
6 |
7 |
8 |
9 |
10 |
11 | `))
12 |
13 | var loaderTemplate = template.Must(template.New("main").Parse(`if (!WebAssembly.instantiateStreaming) {
14 | WebAssembly.instantiateStreaming = async (resp, importObject) => {
15 | const source = await (await resp).arrayBuffer();
16 | return await WebAssembly.instantiate(source, importObject);
17 | };
18 | }
19 | const go = new Go();
20 | WebAssembly.instantiateStreaming(fetch("{{ .Binary }}"), go.importObject).then(result => {
21 | go.run(result.instance);
22 | });`))
23 |
24 | var loaderTemplateMin = template.Must(template.New("main").Parse(`WebAssembly.instantiateStreaming||(WebAssembly.instantiateStreaming=(async(t,a)=>{const e=await(await t).arrayBuffer();return await WebAssembly.instantiate(e,a)}));const go=new Go;WebAssembly.instantiateStreaming(fetch("{{ .Binary }}"),go.importObject).then(t=>{go.run(t.instance)});`))
25 |
--------------------------------------------------------------------------------
/cmd/root.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 | "os"
6 |
7 | "github.com/dave/wasmgo/cmd/cmdconfig"
8 | "github.com/spf13/cobra"
9 | )
10 |
11 | func init() {
12 | rootCmd.PersistentFlags().StringVarP(&global.Index, "index", "i", "index.wasmgo.html", "Specify the index page template. Variables: Script, Loader, Binary.")
13 | rootCmd.PersistentFlags().BoolVarP(&global.Verbose, "verbose", "v", false, "Show detailed status messages.")
14 | rootCmd.PersistentFlags().BoolVarP(&global.Open, "open", "o", true, "Open the page in a browser.")
15 | rootCmd.PersistentFlags().StringVarP(&global.Command, "command", "c", "go", "Name of the go command.")
16 | rootCmd.PersistentFlags().StringVarP(&global.Flags, "flags", "f", "", "Flags to pass to the go build command.")
17 | rootCmd.PersistentFlags().StringVarP(&global.BuildTags, "build", "b", "", "Build tags to pass to the go build command.")
18 | }
19 |
20 | var global = &cmdconfig.Config{}
21 |
22 | var rootCmd = &cobra.Command{
23 | Use: "wasmgo",
24 | Short: "Compile Go to WASM, test locally or deploy to jsgo.io",
25 | }
26 |
27 | func Execute() {
28 | if err := rootCmd.Execute(); err != nil {
29 | fmt.Println(err)
30 | os.Exit(1)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/cmd/serve.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 | "os"
6 |
7 | "github.com/dave/wasmgo/cmd/server"
8 | "github.com/spf13/cobra"
9 | )
10 |
11 | func init() {
12 | serveCmd.PersistentFlags().IntVarP(&global.Port, "port", "p", 8080, "Server port.")
13 | rootCmd.AddCommand(serveCmd)
14 | }
15 |
16 | var serveCmd = &cobra.Command{
17 | Use: "serve [package]",
18 | Short: "Serve locally",
19 | Long: "Starts a webserver locally, and recompiles the WASM on every page refresh, for testing and development.",
20 | Args: cobra.RangeArgs(0, 1),
21 | Run: func(cmd *cobra.Command, args []string) {
22 | if len(args) > 0 {
23 | global.Path = args[0]
24 | }
25 | if err := server.Start(global); err != nil {
26 | fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error())
27 | os.Exit(1)
28 | }
29 | },
30 | }
31 |
--------------------------------------------------------------------------------
/cmd/server/server.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io"
7 | "io/ioutil"
8 | "log"
9 | "net/http"
10 | "os"
11 | "os/signal"
12 | "strings"
13 | "syscall"
14 |
15 | "github.com/dave/wasmgo/cmd/cmdconfig"
16 | "github.com/dave/wasmgo/cmd/deployer"
17 | "github.com/pkg/browser"
18 | )
19 |
20 | func Start(cfg *cmdconfig.Config) error {
21 |
22 | var debug io.Writer
23 | if cfg.Verbose {
24 | debug = os.Stdout
25 | } else {
26 | debug = ioutil.Discard
27 | }
28 |
29 | dep, err := deployer.New(cfg)
30 | if err != nil {
31 | return err
32 | }
33 |
34 | svr := &server{cfg: cfg, dep: dep, debug: debug}
35 |
36 | s := &http.Server{Addr: fmt.Sprintf(":%d", cfg.Port), Handler: svr}
37 |
38 | go func() {
39 | fmt.Fprintf(debug, "Starting server on %s\n", s.Addr)
40 | if err := s.ListenAndServe(); err != http.ErrServerClosed {
41 | log.Fatal(err)
42 | }
43 | }()
44 |
45 | go func() {
46 | if cfg.Open {
47 | browser.OpenURL(fmt.Sprintf("http://localhost:%d/", cfg.Port))
48 | }
49 | }()
50 |
51 | // Set up graceful shutdown
52 | stop := make(chan os.Signal, 1)
53 | signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
54 |
55 | // Wait for shutdown signal
56 | <-stop
57 |
58 | fmt.Fprintln(debug, "Stopping server")
59 |
60 | return nil
61 | }
62 |
63 | type server struct {
64 | cfg *cmdconfig.Config
65 | dep *deployer.State
66 | debug io.Writer
67 | }
68 |
69 | func (s *server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
70 | switch {
71 | case strings.HasSuffix(req.RequestURI, "/favicon.ico"):
72 | // ignore
73 | case strings.HasSuffix(req.RequestURI, "/binary.wasm"):
74 | // binary
75 | contents, hash, err := s.dep.Build()
76 | if err != nil {
77 | fmt.Fprintf(os.Stderr, "%v\n", err)
78 | }
79 | w.Header().Set("Content-Type", "application/wasm")
80 | if _, err := io.Copy(w, bytes.NewReader(contents)); err != nil {
81 | fmt.Fprintf(os.Stderr, "%v\n", err)
82 | }
83 | fmt.Fprintf(s.debug, "Compiled WASM binary with hash %x\n", hash)
84 | case strings.HasSuffix(req.RequestURI, "/loader.js"):
85 | // loader js
86 | contents, _, err := s.dep.Loader("/binary.wasm")
87 | if err != nil {
88 | fmt.Fprintf(os.Stderr, "%v\n", err)
89 | }
90 | w.Header().Set("Content-Type", "application/javascript")
91 | if _, err := io.Copy(w, bytes.NewReader(contents)); err != nil {
92 | fmt.Fprintf(os.Stderr, "%v\n", err)
93 | }
94 | case strings.HasSuffix(req.RequestURI, "/script.js"):
95 | // script
96 | w.Header().Set("Content-Type", "application/javascript")
97 | if _, err := io.Copy(w, bytes.NewBufferString(WasmExec)); err != nil {
98 | fmt.Fprintf(os.Stderr, "%v\n", err)
99 | }
100 | default:
101 | // index page
102 | contents, _, err := s.dep.Index("/script.js", "/loader.js", "/binary.wasm")
103 | if err != nil {
104 | fmt.Fprintf(os.Stderr, "%v\n", err)
105 | }
106 | w.Header().Set("Content-Type", "text/html")
107 | if _, err := io.Copy(w, bytes.NewReader(contents)); err != nil {
108 | fmt.Fprintf(os.Stderr, "%v\n", err)
109 | }
110 | }
111 | }
112 |
113 | const WasmExec = `(()=>{if("undefined"!=typeof global);else if("undefined"!=typeof window)window.global=window;else{if("undefined"==typeof self)throw new Error("cannot export Go (neither global, window nor self is defined)");self.global=self}const e=global.process&&"node"===global.process.title;if(e){global.require=require,global.fs=require("fs");const e=require("crypto");global.crypto={getRandomValues(t){e.randomFillSync(t)}},global.performance={now(){const[e,t]=process.hrtime();return 1e3*e+t/1e6}};const t=require("util");global.TextEncoder=t.TextEncoder,global.TextDecoder=t.TextDecoder}else{let e="";global.fs={constants:{O_WRONLY:-1,O_RDWR:-1,O_CREAT:-1,O_TRUNC:-1,O_APPEND:-1,O_EXCL:-1},writeSync(t,n){const i=(e+=s.decode(n)).lastIndexOf("\n");return-1!=i&&(console.log(e.substr(0,i)),e=e.substr(i+1)),n.length},write(e,t,s,n,i,r){if(0!==s||n!==t.length||null!==i)throw new Error("not implemented");r(null,this.writeSync(e,t))},open(e,t,s,n){const i=new Error("not implemented");i.code="ENOSYS",n(i)},read(e,t,s,n,i,r){const o=new Error("not implemented");o.code="ENOSYS",r(o)},fsync(e,t){t(null)}}}const t=new TextEncoder("utf-8"),s=new TextDecoder("utf-8");if(global.Go=class{constructor(){this.argv=["js"],this.env={},this.exit=(e=>{0!==e&&console.warn("exit code:",e)}),this._exitPromise=new Promise(e=>{this._resolveExitPromise=e}),this._pendingEvent=null,this._scheduledTimeouts=new Map,this._nextCallbackTimeoutID=1;const e=()=>new DataView(this._inst.exports.mem.buffer),n=(t,s)=>{e().setUint32(t+0,s,!0),e().setUint32(t+4,Math.floor(s/4294967296),!0)},i=t=>{return e().getUint32(t+0,!0)+4294967296*e().getInt32(t+4,!0)},r=t=>{const s=e().getFloat64(t,!0);if(0===s)return;if(!isNaN(s))return s;const n=e().getUint32(t,!0);return this._values[n]},o=(t,s)=>{if("number"==typeof s)return isNaN(s)?(e().setUint32(t+4,2146959360,!0),void e().setUint32(t,0,!0)):0===s?(e().setUint32(t+4,2146959360,!0),void e().setUint32(t,1,!0)):void e().setFloat64(t,s,!0);switch(s){case void 0:return void e().setFloat64(t,0,!0);case null:return e().setUint32(t+4,2146959360,!0),void e().setUint32(t,2,!0);case!0:return e().setUint32(t+4,2146959360,!0),void e().setUint32(t,3,!0);case!1:return e().setUint32(t+4,2146959360,!0),void e().setUint32(t,4,!0)}let n=this._refs.get(s);void 0===n&&(n=this._values.length,this._values.push(s),this._refs.set(s,n));let i=0;switch(typeof s){case"string":i=1;break;case"symbol":i=2;break;case"function":i=3}e().setUint32(t+4,2146959360|i,!0),e().setUint32(t,n,!0)},l=e=>{const t=i(e+0),s=i(e+8);return new Uint8Array(this._inst.exports.mem.buffer,t,s)},a=e=>{const t=i(e+0),s=i(e+8),n=new Array(s);for(let e=0;e{const t=i(e+0),n=i(e+8);return s.decode(new DataView(this._inst.exports.mem.buffer,t,n))},u=Date.now()-performance.now();this.importObject={go:{"runtime.wasmExit":t=>{const s=e().getInt32(t+8,!0);this.exited=!0,delete this._inst,delete this._values,delete this._refs,this.exit(s)},"runtime.wasmWrite":t=>{const s=i(t+8),n=i(t+16),r=e().getInt32(t+24,!0);fs.writeSync(s,new Uint8Array(this._inst.exports.mem.buffer,n,r))},"runtime.nanotime":e=>{n(e+8,1e6*(u+performance.now()))},"runtime.walltime":t=>{const s=(new Date).getTime();n(t+8,s/1e3),e().setInt32(t+16,s%1e3*1e6,!0)},"runtime.scheduleTimeoutEvent":t=>{const s=this._nextCallbackTimeoutID;this._nextCallbackTimeoutID++,this._scheduledTimeouts.set(s,setTimeout(()=>{this._resume()},i(t+8)+1)),e().setInt32(t+16,s,!0)},"runtime.clearTimeoutEvent":t=>{const s=e().getInt32(t+8,!0);clearTimeout(this._scheduledTimeouts.get(s)),this._scheduledTimeouts.delete(s)},"runtime.getRandomData":e=>{crypto.getRandomValues(l(e+8))},"syscall/js.stringVal":e=>{o(e+24,c(e+8))},"syscall/js.valueGet":e=>{const t=Reflect.get(r(e+8),c(e+16));e=this._inst.exports.getsp(),o(e+32,t)},"syscall/js.valueSet":e=>{Reflect.set(r(e+8),c(e+16),r(e+32))},"syscall/js.valueIndex":e=>{o(e+24,Reflect.get(r(e+8),i(e+16)))},"syscall/js.valueSetIndex":e=>{Reflect.set(r(e+8),i(e+16),r(e+24))},"syscall/js.valueCall":t=>{try{const s=r(t+8),n=Reflect.get(s,c(t+16)),i=a(t+32),l=Reflect.apply(n,s,i);t=this._inst.exports.getsp(),o(t+56,l),e().setUint8(t+64,1)}catch(s){o(t+56,s),e().setUint8(t+64,0)}},"syscall/js.valueInvoke":t=>{try{const s=r(t+8),n=a(t+16),i=Reflect.apply(s,void 0,n);t=this._inst.exports.getsp(),o(t+40,i),e().setUint8(t+48,1)}catch(s){o(t+40,s),e().setUint8(t+48,0)}},"syscall/js.valueNew":t=>{try{const s=r(t+8),n=a(t+16),i=Reflect.construct(s,n);t=this._inst.exports.getsp(),o(t+40,i),e().setUint8(t+48,1)}catch(s){o(t+40,s),e().setUint8(t+48,0)}},"syscall/js.valueLength":e=>{n(e+16,parseInt(r(e+8).length))},"syscall/js.valuePrepareString":e=>{const s=t.encode(String(r(e+8)));o(e+16,s),n(e+24,s.length)},"syscall/js.valueLoadString":e=>{const t=r(e+8);l(e+16).set(t)},"syscall/js.valueInstanceOf":t=>{e().setUint8(t+24,r(t+8)instanceof r(t+16))},debug:e=>{console.log(e)}}}}async run(e){this._inst=e,this._values=[NaN,0,null,!0,!1,global,this._inst.exports.mem,this],this._refs=new Map,this.exited=!1;const s=new DataView(this._inst.exports.mem.buffer);let n=4096;const i=e=>{let i=n;return new Uint8Array(s.buffer,n,e.length+1).set(t.encode(e+"\0")),n+=e.length+(8-e.length%8),i},r=this.argv.length,o=[];this.argv.forEach(e=>{o.push(i(e))});const l=Object.keys(this.env).sort();o.push(l.length),l.forEach(e=>{o.push(i(` + "`" + `${e}=${this.env[e]}` + "`" + `))});const a=n;o.forEach(e=>{s.setUint32(n,e,!0),s.setUint32(n+4,0,!0),n+=8}),this._inst.exports.run(r,a),this.exited&&this._resolveExitPromise(),await this._exitPromise}_resume(){if(this.exited)throw new Error("Go program has already exited");this._inst.exports.resume(),this.exited&&this._resolveExitPromise()}_makeFuncWrapper(e){const t=this;return function(){const s={id:e,this:this,args:arguments};return t._pendingEvent=s,t._resume(),s.result}}},e){process.argv.length<3&&(process.stderr.write("usage: go_js_wasm_exec [wasm binary] [arguments]\n"),process.exit(1));const e=new Go;e.argv=process.argv.slice(2),e.env=Object.assign({TMPDIR:require("os").tmpdir()},process.env),e.exit=process.exit,WebAssembly.instantiate(fs.readFileSync(process.argv[2]),e.importObject).then(t=>(process.on("exit",t=>{0!==t||e.exited||(e._pendingEvent={id:0},e._resume())}),e.run(t.instance))).catch(e=>{throw e})}(()=>{if("undefined"!=typeof global);else if("undefined"!=typeof window)window.global=window;else{if("undefined"==typeof self)throw new Error("cannot export Go (neither global, window nor self is defined)");self.global=self}const e=global.process&&"node"===global.process.title;if(e){global.require=require,global.fs=require("fs");const e=require("crypto");global.crypto={getRandomValues(t){e.randomFillSync(t)}},global.performance={now(){const[e,t]=process.hrtime();return 1e3*e+t/1e6}};const t=require("util");global.TextEncoder=t.TextEncoder,global.TextDecoder=t.TextDecoder}else{let e="";global.fs={constants:{O_WRONLY:-1,O_RDWR:-1,O_CREAT:-1,O_TRUNC:-1,O_APPEND:-1,O_EXCL:-1},writeSync(t,n){const i=(e+=s.decode(n)).lastIndexOf("\n");return-1!=i&&(console.log(e.substr(0,i)),e=e.substr(i+1)),n.length},write(e,t,s,n,i,r){if(0!==s||n!==t.length||null!==i)throw new Error("not implemented");r(null,this.writeSync(e,t))},open(e,t,s,n){const i=new Error("not implemented");i.code="ENOSYS",n(i)},read(e,t,s,n,i,r){const o=new Error("not implemented");o.code="ENOSYS",r(o)},fsync(e,t){t(null)}}}const t=new TextEncoder("utf-8"),s=new TextDecoder("utf-8");if(global.Go=class{constructor(){this.argv=["js"],this.env={},this.exit=(e=>{0!==e&&console.warn("exit code:",e)}),this._exitPromise=new Promise(e=>{this._resolveExitPromise=e}),this._pendingEvent=null,this._scheduledTimeouts=new Map,this._nextCallbackTimeoutID=1;const e=()=>new DataView(this._inst.exports.mem.buffer),n=(t,s)=>{e().setUint32(t+0,s,!0),e().setUint32(t+4,Math.floor(s/4294967296),!0)},i=t=>{return e().getUint32(t+0,!0)+4294967296*e().getInt32(t+4,!0)},r=t=>{const s=e().getFloat64(t,!0);if(0===s)return;if(!isNaN(s))return s;const n=e().getUint32(t,!0);return this._values[n]},o=(t,s)=>{if("number"==typeof s)return isNaN(s)?(e().setUint32(t+4,2146959360,!0),void e().setUint32(t,0,!0)):0===s?(e().setUint32(t+4,2146959360,!0),void e().setUint32(t,1,!0)):void e().setFloat64(t,s,!0);switch(s){case void 0:return void e().setFloat64(t,0,!0);case null:return e().setUint32(t+4,2146959360,!0),void e().setUint32(t,2,!0);case!0:return e().setUint32(t+4,2146959360,!0),void e().setUint32(t,3,!0);case!1:return e().setUint32(t+4,2146959360,!0),void e().setUint32(t,4,!0)}let n=this._refs.get(s);void 0===n&&(n=this._values.length,this._values.push(s),this._refs.set(s,n));let i=0;switch(typeof s){case"string":i=1;break;case"symbol":i=2;break;case"function":i=3}e().setUint32(t+4,2146959360|i,!0),e().setUint32(t,n,!0)},l=e=>{const t=i(e+0),s=i(e+8);return new Uint8Array(this._inst.exports.mem.buffer,t,s)},a=e=>{const t=i(e+0),s=i(e+8),n=new Array(s);for(let e=0;e{const t=i(e+0),n=i(e+8);return s.decode(new DataView(this._inst.exports.mem.buffer,t,n))},u=Date.now()-performance.now();this.importObject={go:{"runtime.wasmExit":t=>{const s=e().getInt32(t+8,!0);this.exited=!0,delete this._inst,delete this._values,delete this._refs,this.exit(s)},"runtime.wasmWrite":t=>{const s=i(t+8),n=i(t+16),r=e().getInt32(t+24,!0);fs.writeSync(s,new Uint8Array(this._inst.exports.mem.buffer,n,r))},"runtime.nanotime":e=>{n(e+8,1e6*(u+performance.now()))},"runtime.walltime":t=>{const s=(new Date).getTime();n(t+8,s/1e3),e().setInt32(t+16,s%1e3*1e6,!0)},"runtime.scheduleTimeoutEvent":t=>{const s=this._nextCallbackTimeoutID;this._nextCallbackTimeoutID++,this._scheduledTimeouts.set(s,setTimeout(()=>{this._resume()},i(t+8)+1)),e().setInt32(t+16,s,!0)},"runtime.clearTimeoutEvent":t=>{const s=e().getInt32(t+8,!0);clearTimeout(this._scheduledTimeouts.get(s)),this._scheduledTimeouts.delete(s)},"runtime.getRandomData":e=>{crypto.getRandomValues(l(e+8))},"syscall/js.stringVal":e=>{o(e+24,c(e+8))},"syscall/js.valueGet":e=>{const t=Reflect.get(r(e+8),c(e+16));e=this._inst.exports.getsp(),o(e+32,t)},"syscall/js.valueSet":e=>{Reflect.set(r(e+8),c(e+16),r(e+32))},"syscall/js.valueIndex":e=>{o(e+24,Reflect.get(r(e+8),i(e+16)))},"syscall/js.valueSetIndex":e=>{Reflect.set(r(e+8),i(e+16),r(e+24))},"syscall/js.valueCall":t=>{try{const s=r(t+8),n=Reflect.get(s,c(t+16)),i=a(t+32),l=Reflect.apply(n,s,i);t=this._inst.exports.getsp(),o(t+56,l),e().setUint8(t+64,1)}catch(s){o(t+56,s),e().setUint8(t+64,0)}},"syscall/js.valueInvoke":t=>{try{const s=r(t+8),n=a(t+16),i=Reflect.apply(s,void 0,n);t=this._inst.exports.getsp(),o(t+40,i),e().setUint8(t+48,1)}catch(s){o(t+40,s),e().setUint8(t+48,0)}},"syscall/js.valueNew":t=>{try{const s=r(t+8),n=a(t+16),i=Reflect.construct(s,n);t=this._inst.exports.getsp(),o(t+40,i),e().setUint8(t+48,1)}catch(s){o(t+40,s),e().setUint8(t+48,0)}},"syscall/js.valueLength":e=>{n(e+16,parseInt(r(e+8).length))},"syscall/js.valuePrepareString":e=>{const s=t.encode(String(r(e+8)));o(e+16,s),n(e+24,s.length)},"syscall/js.valueLoadString":e=>{const t=r(e+8);l(e+16).set(t)},"syscall/js.valueInstanceOf":t=>{e().setUint8(t+24,r(t+8)instanceof r(t+16))},debug:e=>{console.log(e)}}}}async run(e){this._inst=e,this._values=[NaN,0,null,!0,!1,global,this._inst.exports.mem,this],this._refs=new Map,this.exited=!1;const s=new DataView(this._inst.exports.mem.buffer);let n=4096;const i=e=>{let i=n;return new Uint8Array(s.buffer,n,e.length+1).set(t.encode(e+"\0")),n+=e.length+(8-e.length%8),i},r=this.argv.length,o=[];this.argv.forEach(e=>{o.push(i(e))});const l=Object.keys(this.env).sort();o.push(l.length),l.forEach(e=>{o.push(i(` + "`" + `${e}=${this.env[e]}` + "`" + `))});const a=n;o.forEach(e=>{s.setUint32(n,e,!0),s.setUint32(n+4,0,!0),n+=8}),this._inst.exports.run(r,a),this.exited&&this._resolveExitPromise(),await this._exitPromise}_resume(){if(this.exited)throw new Error("Go program has already exited");this._inst.exports.resume(),this.exited&&this._resolveExitPromise()}_makeFuncWrapper(e){const t=this;return function(){const s={id:e,this:this,args:arguments};return t._pendingEvent=s,t._resume(),s.result}}},e){process.argv.length<3&&(process.stderr.write("usage: go_js_wasm_exec [wasm binary] [arguments]\n"),process.exit(1));const e=new Go;e.argv=process.argv.slice(2),e.env=Object.assign({TMPDIR:require("os").tmpdir()},process.env),e.exit=process.exit,WebAssembly.instantiate(fs.readFileSync(process.argv[2]),e.importObject).then(t=>(process.on("exit",t=>{0!==t||e.exited||(e._pendingEvent={id:0},e._resume())}),e.run(t.instance))).catch(e=>{throw e})}})()})();`
114 |
--------------------------------------------------------------------------------
/cmd/version.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/dave/wasmgo/cmd/deployer"
7 | "github.com/spf13/cobra"
8 | )
9 |
10 | func init() {
11 | rootCmd.AddCommand(versionCmd)
12 | }
13 |
14 | var versionCmd = &cobra.Command{
15 | Use: "version",
16 | Short: "Show client version number",
17 | Run: func(cmd *cobra.Command, args []string) {
18 | fmt.Println(deployer.CLIENT_VERSION)
19 | },
20 | }
21 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/dave/wasmgo
2 |
3 | require (
4 | github.com/dave/jsgo v0.0.2
5 | github.com/dave/services v0.1.0
6 | github.com/gorilla/websocket v1.4.0
7 | github.com/hajimehoshi/ebiten v1.8.3 // indirect
8 | github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4
9 | github.com/spf13/cobra v0.0.3
10 | )
11 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
3 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
4 | git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
5 | git.apache.org/thrift.git v0.0.0-20181225175352-087d88108d34/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
6 | github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
7 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
8 | github.com/apex/log v1.1.0/go.mod h1:yA770aXIDQrhVOIGurT/pVdfCpSq1GQV/auzMN5fzvY=
9 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
10 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
11 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
12 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
13 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
14 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
15 | github.com/dave/blast v0.0.0-20180301095328-f3afebf2d24c/go.mod h1:ymzNd2UFvluyHXI17RZXTgRP7u7jXv7F8VmezJcwQxw=
16 | github.com/dave/frizz v0.0.0-20181022080000-c1df23557613/go.mod h1:4OWEbOWLqSRWl4PWGG4d74sMxEmEB6bQH1/DzBNdyQo=
17 | github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg=
18 | github.com/dave/jsgo v0.0.2 h1:33EHuEx5169DEp2kyoDmqo+Sz1uMMKnGZmRLlZ+pt5k=
19 | github.com/dave/jsgo v0.0.2/go.mod h1:3bhGOAa+LE1DFyxEaQZLTZbV9vmbuApV/+P8tsnL8To=
20 | github.com/dave/patsy v0.0.0-20170606133301-2245ba804d71/go.mod h1:QoL10ED5VDNgf52lD3oZY9WU2Ey6UwXvuLuc65u1pVk=
21 | github.com/dave/play v0.0.0-20180927083150-0d1bd3827742/go.mod h1:9wEYNMuYsdXgAvEAerBHrNahvYmCd+OnKWOPcIClozc=
22 | github.com/dave/services v0.1.0 h1:7isGzpZHJWmOYTV+Pn3f6gpQUmrveJqsQpAkH0HXFbU=
23 | github.com/dave/services v0.1.0/go.mod h1:H/RSVtLEC67SK6QAevsdWJgKMcE0fRhJmgXxEqBA/IA=
24 | github.com/dave/stablegob v1.0.0/go.mod h1:YSkxg4P8gwXEcrk/LN4tj9379lOKCKgj+j5TNV7jRG8=
25 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
26 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
27 | github.com/emirpasic/gods v1.9.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
28 | github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
29 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
30 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
31 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
32 | github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
33 | github.com/go-gl/gl v0.0.0-20180407155706-68e253793080/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
34 | github.com/go-gl/glfw v0.0.0-20181008143348-547915429f42/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
35 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
36 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
37 | github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
38 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
39 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
40 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
41 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
42 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
43 | github.com/googleapis/gax-go v2.0.2+incompatible h1:silFMLAnr330+NRuag/VjIGF7TLp/LBrV2CJKFLWEww=
44 | github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
45 | github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
46 | github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
47 | github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
48 | github.com/gopherjs/gopherwasm v0.1.1/go.mod h1:kx4n9a+MzHH0BJJhvlsQ65hqLFXDO/m256AsaDPQ+/4=
49 | github.com/gopherjs/gopherwasm v1.0.0/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI=
50 | github.com/gopherjs/gopherwasm v1.0.1/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI=
51 | github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
52 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
53 | github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
54 | github.com/grpc-ecosystem/grpc-gateway v1.6.3/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
55 | github.com/hajimehoshi/bitmapfont v1.1.1/go.mod h1:Hamfxgney7tDSmVOSDh2AWzoDH70OaC+P24zc02Gum4=
56 | github.com/hajimehoshi/ebiten v1.8.3 h1:cK7KIbXCaRgyedIWedeGauTXqeBmqhK5D5MytLkIOwg=
57 | github.com/hajimehoshi/ebiten v1.8.3/go.mod h1:0TBS/ZihfKJ83OJdgMoyeT+C9jJSOv+oIypUePnVS74=
58 | github.com/hajimehoshi/go-mp3 v0.1.1/go.mod h1:4i+c5pDNKDrxl1iu9iG90/+fhP37lio6gNhjCx9WBJw=
59 | github.com/hajimehoshi/oto v0.1.1/go.mod h1:hUiLWeBQnbDu4pZsAhOnGqMI1ZGibS6e2qhQdfpwz04=
60 | github.com/hajimehoshi/oto v0.2.1/go.mod h1:0ZepxT+2KLDrCm1gdkKBCQCxr+8fgQqoh0I7g+kr040=
61 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
62 | github.com/jakecoffman/cp v0.1.0/go.mod h1:a3xPx9N8RyFAACD644t2dj/nK4SuLg1v+jL61m2yVo4=
63 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
64 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
65 | github.com/jfreymuth/oggvorbis v1.0.0/go.mod h1:abe6F9QRjuU9l+2jek3gj46lu40N4qlYxh2grqkLEDM=
66 | github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0=
67 | github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
68 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
69 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
70 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
71 | github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
72 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
73 | github.com/leemcloughlin/gofarmhash v0.0.0-20160919192320-0a055c5b87a8/go.mod h1:f59bwMArqO7YmZZv21lKDV0fwP4N/vJZtL1/jv8wgaY=
74 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
75 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
76 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
77 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
78 | github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
79 | github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
80 | github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
81 | github.com/openzipkin/zipkin-go v0.1.3/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
82 | github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
83 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
84 | github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98=
85 | github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
86 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
87 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
88 | github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
89 | github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
90 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
91 | github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
92 | github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
93 | github.com/prometheus/common v0.0.0-20181218105931-67670fe90761/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
94 | github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
95 | github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
96 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
97 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
98 | github.com/shurcooL/httpfs v0.0.0-20181222201310-74dc9339e414/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
99 | github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
100 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
101 | github.com/spf13/afero v1.2.0/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
102 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
103 | github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
104 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
105 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
106 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
107 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
108 | github.com/spf13/viper v1.3.1/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
109 | github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
110 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
111 | github.com/theckman/go-flock v0.6.0/go.mod h1:kjuth3y9VJ2aNlkNEO99G/8lp9fMIKaGyBmh84IBheM=
112 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
113 | github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
114 | github.com/xanzy/ssh-agent v0.2.0/go.mod h1:0NyE30eGUDliuLEHJgYte/zncp2zdTStcOnWhgSqHD8=
115 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
116 | go.opencensus.io v0.18.0 h1:Mk5rgZcggtbvtAun5aJzAtjKKN/t0R3jJPlWILlv938=
117 | go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
118 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
119 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
120 | golang.org/x/exp v0.0.0-20180710024300-14dda7b62fcd/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
121 | golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
122 | golang.org/x/image v0.0.0-20180926015637-991ec62608f3/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
123 | golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
124 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
125 | golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
126 | golang.org/x/mobile v0.0.0-20180806140643-507816974b79/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
127 | golang.org/x/mobile v0.0.0-20180907224111-0ff817254b04/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
128 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
129 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
130 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
131 | golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
132 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
133 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis=
134 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
135 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
136 | golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890 h1:uESlIz09WIHT2I+pasSXcpLYqYK8wHcdCetU3VuMBJE=
137 | golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
138 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
139 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
140 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
141 | golang.org/x/sys v0.0.0-20180806082429-34b17bdb4300/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
142 | golang.org/x/sys v0.0.0-20180814072032-4e1fef560951/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
143 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
144 | golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
145 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
146 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
147 | golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
148 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
149 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
150 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
151 | golang.org/x/tools v0.0.0-20180928181343-b3c0be4c978b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
152 | golang.org/x/tools v0.0.0-20181221235234-d00ac6d27372/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
153 | google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
154 | google.golang.org/api v0.0.0-20181221000618-65a46cafb132 h1:SLcC5l+3o5vwvXAbdm936WwLkHteUZpo1RULZD7YvQ4=
155 | google.golang.org/api v0.0.0-20181221000618-65a46cafb132/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
156 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
157 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
158 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
159 | google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
160 | google.golang.org/genproto v0.0.0-20181221175505-bd9b4fb69e2f h1:eT3B0O2ghdSPzjAOznr3oOLyN1HFeYUncYl7FRwg4VI=
161 | google.golang.org/genproto v0.0.0-20181221175505-bd9b4fb69e2f/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
162 | google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
163 | google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
164 | google.golang.org/grpc v1.17.0 h1:TRJYBgMclJvGYn2rIMjj+h9KtMt5r1Ij7ODVRIZkwhk=
165 | google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
166 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
167 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
168 | gopkg.in/src-d/go-billy-siva.v4 v4.2.2/go.mod h1:4wKeCzOCSsdyFeM5+58M6ObU6FM+lZT12p7zm7A+9n0=
169 | gopkg.in/src-d/go-billy.v4 v4.2.1/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk=
170 | gopkg.in/src-d/go-billy.v4 v4.3.0 h1:KtlZ4c1OWbIs4jCv5ZXrTqG8EQocr0g/d4DjNg70aek=
171 | gopkg.in/src-d/go-billy.v4 v4.3.0/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk=
172 | gopkg.in/src-d/go-git-fixtures.v3 v3.1.1/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
173 | gopkg.in/src-d/go-git-fixtures.v3 v3.3.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
174 | gopkg.in/src-d/go-git.v4 v4.8.1/go.mod h1:Vtut8izDyrM8BUVQnzJ+YvmNcem2J89EmfZYCkLokZk=
175 | gopkg.in/src-d/go-siva.v1 v1.3.0/go.mod h1:tk1jnIXawd/PTlRNWdr5V5lC0PttNJmu1fv7wt7IZlw=
176 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
177 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
178 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
179 | honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
180 | honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
181 |
--------------------------------------------------------------------------------
/helloworld/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "syscall/js"
4 |
5 | func main() {
6 | js.Global().Get("document").Call("getElementsByTagName", "body").Index(0).Set("innerHTML", "Hello, World!")
7 | }
8 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "github.com/dave/wasmgo/cmd"
4 |
5 | func main() {
6 | cmd.Execute()
7 | }
8 |
--------------------------------------------------------------------------------
/splitter/foo.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dave/wasmgo/e8e181b4ddaf9a699bed6b75362abc9614e64c13/splitter/foo.wasm
--------------------------------------------------------------------------------
/splitter/foo_out.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dave/wasmgo/e8e181b4ddaf9a699bed6b75362abc9614e64c13/splitter/foo_out.wasm
--------------------------------------------------------------------------------
/splitter/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "flag"
6 | "fmt"
7 | "log"
8 | "os"
9 | "path/filepath"
10 | "strings"
11 |
12 | "github.com/dustin/go-humanize"
13 | "github.com/go-interpreter/wagon/disasm"
14 | "github.com/go-interpreter/wagon/wasm"
15 | "github.com/go-interpreter/wagon/wasm/operators"
16 | )
17 |
18 | func main() {
19 | flag.Parse()
20 | //if err := split(flag.Arg(0)); err != nil {
21 | if err := split("./splitter/foo.wasm"); err != nil {
22 | log.Fatal(err)
23 | }
24 | }
25 |
26 | func split(path string) error {
27 | f, err := os.Open(path)
28 | if err != nil {
29 | return err
30 | }
31 | m, err := wasm.DecodeModule(f)
32 | f.Close()
33 | if err != nil {
34 | return fmt.Errorf("cannot decode module: %v", err)
35 | }
36 |
37 | if err := splitPackages(m, filepath.Dir(path), []string{
38 | "runtime.",
39 | "runtime_",
40 | "callRet",
41 | "memeqbody", "cmpbody", "memcmp", "memchr",
42 | "time.now",
43 | "sync.event",
44 | "internal_bytealg",
45 | "internal_cpu",
46 | }); err != nil {
47 | return err
48 | }
49 |
50 | ext := filepath.Ext(path)
51 | f, err = os.Create(strings.TrimSuffix(path, ext) + "_out" + ext)
52 | if err != nil {
53 | return err
54 | }
55 | defer f.Close()
56 |
57 | return wasm.EncodeModule(f, m)
58 | }
59 |
60 | func splitPackages(bin *wasm.Module, dir string, prefixes []string) error {
61 | fmt.Println("section sizes:")
62 | for _, s := range bin.Sections {
63 | raw := s.GetRawSection()
64 | size := len(raw.Bytes)
65 | fmt.Printf("%T %10v: %8v\n", s, raw.ID, humanize.Bytes(uint64(size)))
66 | switch s := s.(type) {
67 | case *wasm.SectionFunctions:
68 | fmt.Println(len(s.Types))
69 | case *wasm.SectionCode:
70 | fmt.Println(len(s.Bodies))
71 | for _, v := range s.Bodies {
72 | fmt.Println(len(v.Locals))
73 | }
74 | case *wasm.SectionElements:
75 | //for _, v := range s.Entries {
76 | // fmt.Println(v.)
77 | //}
78 | case *wasm.SectionData:
79 | for _, v := range s.Entries {
80 | fmt.Println(v.Index, len(v.Data))
81 | }
82 | case *wasm.SectionCustom:
83 |
84 | }
85 | }
86 | for k, v := range bin.Customs {
87 | fmt.Printf("%d: %#v\n", k, v.Name)
88 | }
89 |
90 | sp, err := NewSplitter(bin)
91 | if err != nil {
92 | return err
93 | }
94 | m, err := sp.SplitByPrefix(prefixes)
95 | if err != nil {
96 | return err
97 | }
98 | _ = m
99 |
100 | return nil
101 | }
102 |
103 | func NewSplitter(mod *wasm.Module) (*Splitter, error) {
104 | sp := &Splitter{mod: mod}
105 | if err := sp.decodeNames(); err != nil {
106 | return nil, err
107 | }
108 | sp.countImported()
109 | if err := sp.buildFuncTable(); err != nil {
110 | return nil, err
111 | }
112 | sp.statsCallIndirect()
113 | return sp, nil
114 | }
115 |
116 | type Splitter struct {
117 | mod *wasm.Module
118 | funcs wasm.NameMap // function names; indexes are in a function index space (with funcsImp offset)
119 | funcsImp int // number of imported functions
120 | funcTable []int // global table with function indexes
121 |
122 | bodies map[int][]disasm.Instr
123 | }
124 |
125 | func findInstr(code []disasm.Instr, typ byte) int {
126 | for i, op := range code {
127 | if op.Op.Code == typ {
128 | return i
129 | }
130 | }
131 | return -1
132 | }
133 |
134 | func findInstrRev(code []disasm.Instr, typ byte) int {
135 | for i := len(code) - 1; i >= 0; i-- {
136 | op := code[i]
137 | if op.Op.Code == typ {
138 | return i
139 | }
140 | }
141 | return -1
142 | }
143 |
144 | func sumCallStubs(instr []disasm.Instr) int {
145 | var sum int
146 | for {
147 | // find call_indirect
148 | ci := findInstr(instr, operators.CallIndirect)
149 | if ci < 0 {
150 | ci = findInstr(instr, operators.Call)
151 | }
152 | if ci < 0 {
153 | return sum
154 | }
155 | // find SP -= 8
156 | sp := findInstrRev(instr[:ci], operators.I32Sub)
157 | if sp < 0 || instr[sp-1].Op.Code != operators.I32Const || instr[sp-1].Immediates[0].(int32) != 8 {
158 | instr = instr[ci+1:]
159 | continue
160 | }
161 | sp -= 2
162 | expr := instr[sp : ci+1]
163 |
164 | data, err := disasm.Assemble(expr)
165 | if err != nil {
166 | panic(err)
167 | }
168 | sum += len(data) - 2
169 | instr = instr[ci+1:]
170 | }
171 | }
172 |
173 | func sumReturnStubs(instr []disasm.Instr) int {
174 | var sum int
175 | codes := []byte{
176 | operators.I32Const,
177 |
178 | operators.SetGlobal,
179 | operators.I32Add,
180 | operators.I32Const,
181 | operators.GetGlobal,
182 |
183 | operators.SetGlobal,
184 | operators.I32Load16u,
185 | operators.GetGlobal,
186 |
187 | operators.SetGlobal,
188 | operators.I32Load16u,
189 | operators.GetGlobal,
190 | }
191 |
192 | opt := []byte{
193 | operators.SetGlobal,
194 | operators.I32Add,
195 | operators.I32Const,
196 | operators.GetGlobal,
197 | }
198 | loop:
199 | for {
200 | // find return
201 | ci := findInstr(instr, operators.Return)
202 | if ci < 0 {
203 | return sum
204 | }
205 | next := func() {
206 | instr = instr[ci+1:]
207 | }
208 | // check previous ops
209 | for i, opc := range codes {
210 | op := instr[ci-1-i]
211 | if op.Op.Code != opc {
212 | next()
213 | continue loop
214 | }
215 | }
216 | end := ci - len(codes)
217 | ok := true
218 | for i, opc := range opt {
219 | ind := end - 1 - i
220 | if ind < 0 {
221 | ok = false
222 | break
223 | }
224 | op := instr[ind]
225 | if op.Op.Code != opc {
226 | ok = false
227 | break
228 | }
229 | }
230 | if ok {
231 | end -= len(opt)
232 | }
233 |
234 | expr := instr[end : ci+1]
235 |
236 | data, err := disasm.Assemble(expr)
237 | if err != nil {
238 | panic(err)
239 | }
240 | sum += len(data) - 2
241 | next()
242 | }
243 | }
244 |
245 | func (sp *Splitter) statsCallIndirect() {
246 | save := 0
247 | for ind, name := range sp.funcs {
248 | instr, err := sp.disassemble(int(ind))
249 | if err != nil {
250 | log.Println(name, err)
251 | continue // we only gathering stats here
252 | }
253 | save += sumCallStubs(instr)
254 | save += sumReturnStubs(instr)
255 | }
256 | log.Println("wrapping to a function will save:", save, humanize.Bytes(uint64(save)))
257 | }
258 |
259 | func (sp *Splitter) decodeNames() error {
260 | sec := sp.mod.Custom(wasm.CustomSectionName)
261 | if sec == nil {
262 | return fmt.Errorf("cannot find names section")
263 | }
264 | var names wasm.NameSection
265 | if err := names.UnmarshalWASM(bytes.NewReader(sec.Data)); err != nil {
266 | return fmt.Errorf("cannot decode names section: %v", err)
267 | }
268 | sub, err := names.Decode(wasm.NameFunction)
269 | if err != nil {
270 | return err
271 | } else if sub == nil {
272 | return fmt.Errorf("no function names")
273 | }
274 | sp.funcs = sub.(*wasm.FunctionNames).Names
275 | return nil
276 | }
277 |
278 | func (sp *Splitter) countImported() {
279 | if sp.mod.Import == nil {
280 | return
281 | }
282 | var n int
283 | for _, imp := range sp.mod.Import.Entries {
284 | if _, ok := imp.Type.(wasm.FuncImport); ok {
285 | n++
286 | }
287 | }
288 | sp.funcsImp = n
289 | }
290 |
291 | func (sp *Splitter) buildFuncTable() error {
292 | var (
293 | tbl *wasm.Table
294 | ind int
295 | )
296 | for i, t := range sp.mod.Table.Entries {
297 | if t.ElementType == wasm.ElemTypeAnyFunc {
298 | ind, tbl = i, &t
299 | break
300 | }
301 | }
302 | if tbl == nil {
303 | return nil
304 | }
305 | table := make([]int, tbl.Limits.Initial)
306 | for _, e := range sp.mod.Elements.Entries {
307 | if int(e.Index) != ind {
308 | continue
309 | }
310 | stack, err := evalCode(e.Offset)
311 | if err != nil {
312 | return fmt.Errorf("cannot evaluate table offset: %v", err)
313 | }
314 | off := stack[0]
315 | for i, v := range e.Elems {
316 | table[int(off)+i] = int(v)
317 | }
318 | }
319 | sp.funcTable = table
320 | return nil
321 | }
322 |
323 | func (sp *Splitter) importFuncName(i int) string {
324 | ind := 0
325 | for _, e := range sp.mod.Import.Entries {
326 | _, ok := e.Type.(wasm.FuncImport)
327 | if !ok {
328 | continue
329 | }
330 | if ind == i {
331 | return e.ModuleName + "." + e.FieldName
332 | }
333 | ind++
334 | }
335 | return ""
336 | }
337 |
338 | func (sp *Splitter) funcName(i int) string {
339 | name, ok := sp.funcs[uint32(i)]
340 | if !ok && sp.isImported(i) {
341 | name = sp.importFuncName(i)
342 | }
343 | return name
344 | }
345 |
346 | func (sp *Splitter) funcNameRel(i int) string {
347 | i += sp.funcsImp
348 | return sp.funcName(i)
349 | }
350 |
351 | func (sp *Splitter) lookupFuncTable(i int) int {
352 | return sp.funcTable[i]
353 | }
354 |
355 | func (sp *Splitter) SplitByPrefix(prefixes []string) (*wasm.Module, error) {
356 | // indexes are in a function index space
357 | var funcs []int
358 | for ind, name := range sp.funcs {
359 | if hasAnyPrefix(name, prefixes) {
360 | funcs = append(funcs, int(ind))
361 | }
362 | }
363 | return sp.SplitFunctions(funcs)
364 | }
365 |
366 | func (sp *Splitter) isImported(fnc int) bool {
367 | return fnc < sp.funcsImp
368 | }
369 | func (sp *Splitter) toFuncTable(fnc int) int {
370 | return fnc - sp.funcsImp
371 | }
372 | func (sp *Splitter) toFuncSpace(fnc int) int {
373 | return fnc + sp.funcsImp
374 | }
375 | func (sp *Splitter) codeOf(fnc int) ([]byte, error) {
376 | if sp.isImported(fnc) {
377 | return nil, fmt.Errorf("attempting to disassemble imported function")
378 | }
379 | fnc = sp.toFuncTable(fnc)
380 | if fnc >= len(sp.mod.Code.Bodies) {
381 | return nil, fmt.Errorf("function index out of bounds")
382 | }
383 | b := sp.mod.Code.Bodies[fnc]
384 | return b.Code, nil
385 | }
386 | func (sp *Splitter) disassemble(fnc int) ([]disasm.Instr, error) {
387 | if instr, ok := sp.bodies[fnc]; ok {
388 | return instr, nil
389 | }
390 | code, err := sp.codeOf(fnc)
391 | if err != nil {
392 | return nil, err
393 | }
394 | d, err := disasm.DisassembleRaw(code)
395 | if err != nil {
396 | return nil, err
397 | }
398 | if sp.bodies == nil {
399 | sp.bodies = make(map[int][]disasm.Instr)
400 | }
401 | sp.bodies[fnc] = d
402 | return d, nil
403 | }
404 |
405 | func (sp *Splitter) SplitFunctions(funcs []int) (*wasm.Module, error) {
406 | job := sp.newSplitJob(funcs)
407 | if err := job.ValidateFuncs(); err != nil {
408 | return nil, err
409 | }
410 | return nil, fmt.Errorf("TODO: split")
411 | }
412 |
413 | func (sp *Splitter) newSplitJob(funcs []int) *splitJob {
414 | splitFunc := make(map[int]struct{}, len(funcs))
415 | for _, ind := range funcs {
416 | splitFunc[ind] = struct{}{}
417 | }
418 | return &splitJob{sp: sp, split: splitFunc, funcs: funcs}
419 | }
420 |
421 | type splitJob struct {
422 | sp *Splitter
423 | split map[int]struct{}
424 | funcs []int
425 | }
426 |
427 | func (s *splitJob) ValidateFuncs() error {
428 | for _, fnc := range s.funcs {
429 | if s.sp.isImported(fnc) {
430 | return fmt.Errorf("attempting to split imported function")
431 | }
432 | d, err := s.sp.disassemble(fnc)
433 | if err != nil {
434 | return err
435 | }
436 | for oi, op := range d {
437 | var callee int
438 | switch op.Op.Code {
439 | case operators.Call:
440 | callee = int(op.Immediates[0].(uint32))
441 | case operators.CallIndirect:
442 | n, err := backEvalN(d[:oi], 1)
443 | if err != nil {
444 | return fmt.Errorf("cannot eval an indirrect call target from '%v': %v", s.sp.funcName(fnc), err)
445 | } else if n < 0 {
446 | return fmt.Errorf("cannot eval an indirrect call target from '%v'", s.sp.funcName(fnc))
447 | }
448 | expr := d[n:oi]
449 | log.Println("indirect call:", s.sp.funcName(fnc), expr)
450 | stack, err := eval(expr)
451 | if err != nil {
452 | return fmt.Errorf("cannot eval an indirrect call target from '%v': %v", s.sp.funcName(fnc), err)
453 | }
454 | ind := int(stack[0])
455 | callee = s.sp.lookupFuncTable(ind)
456 | log.Printf("indirect call: '%v' -> '%v' (%d = %d)",
457 | s.sp.funcName(fnc), s.sp.funcName(callee), ind, callee)
458 | default:
459 | continue
460 | }
461 | if s.sp.isImported(callee) {
462 | continue // call of imported function
463 | }
464 | if _, ok := s.split[callee]; !ok {
465 | return fmt.Errorf("cannot split: external call to '%v'", s.sp.funcName(callee))
466 | }
467 | }
468 | }
469 | return nil
470 | }
471 |
472 | var stackVars = map[string]int{
473 | "i32.shr_u": 2,
474 | "i32.const": 0,
475 | "i64.const": 0,
476 | "i32.store": 1,
477 | "i64.store": 1,
478 | "i32.wrap/i64": 1,
479 | "set_global": 1,
480 | }
481 |
482 | func backEvalN(instr []disasm.Instr, req int) (int, error) {
483 | i := len(instr) - 1
484 | for ; i >= 0; i-- {
485 | op := instr[i]
486 | log.Println(req, op.Immediates, op.Op.Name, op.Op.Args, op.Op.Returns)
487 | st, ok := stackVars[op.Op.Name]
488 | if !ok {
489 | return 0, fmt.Errorf("unsupported op: %v", op.Op.Name)
490 | }
491 | req += st
492 | if op.Op.Returns != 0 && op.Op.Returns != wasm.ValueType(wasm.BlockTypeEmpty) {
493 | req--
494 | }
495 | if req == 0 {
496 | return i, nil
497 | }
498 | }
499 | return -1, nil
500 | }
501 |
502 | func eval(instr []disasm.Instr) ([]uint64, error) {
503 | var stack []uint64
504 | push := func(v uint64) {
505 | stack = append(stack, v)
506 | }
507 | pop := func() uint64 {
508 | i := len(stack) - 1
509 | v := stack[i]
510 | stack = stack[:i]
511 | return v
512 | }
513 | for i, op := range instr {
514 | switch op.Op.Code {
515 | case operators.I32Const:
516 | v := op.Immediates[0].(int32)
517 | push(uint64(v))
518 | case operators.I32WrapI64:
519 | push(uint64(uint32(pop())))
520 | case operators.I32ShrU:
521 | v2 := uint32(pop())
522 | v1 := uint32(pop())
523 | push(uint64(v1 >> v2))
524 | case operators.SetGlobal:
525 | _ = pop()
526 | // do nothing
527 | case operators.End:
528 | if i != len(instr)-1 {
529 | return nil, fmt.Errorf("unexpected end")
530 | }
531 | default:
532 | return nil, fmt.Errorf("unsupported eval operation: %v", op.Op.Name)
533 | }
534 | }
535 | return stack, nil
536 | }
537 |
538 | func evalCode(code []byte) ([]uint64, error) {
539 | instr, err := disasm.DisassembleRaw(code)
540 | if err != nil {
541 | return nil, err
542 | }
543 | return eval(instr)
544 | }
545 |
546 | func hasAnyPrefix(s string, prefixes []string) bool {
547 | for _, pref := range prefixes {
548 | if strings.HasPrefix(s, pref) {
549 | return true
550 | }
551 | }
552 | return false
553 | }
554 |
--------------------------------------------------------------------------------