├── .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 | --------------------------------------------------------------------------------