├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── cmd └── gowasm2csharp │ └── main.go ├── example ├── binding │ ├── binding.csproj │ ├── main.cs │ ├── main.go │ └── run.sh ├── goroutine │ ├── goroutine.csproj │ ├── main.cs │ ├── main.go │ └── run.sh ├── helloworld │ ├── helloworld.csproj │ ├── main.cs │ ├── main.go │ └── run.sh └── monogame │ ├── Content │ └── Content.mgcb │ ├── GoGame.cs │ ├── Program.cs │ ├── main.go │ ├── monogame.csproj │ ├── monogame.sln │ └── run.sh ├── go.mod ├── go.sum ├── gowasm2csharp ├── bits.go ├── generate.go ├── import.go ├── inst.go ├── js.go ├── mem.go └── ops.go ├── internal └── stackvar │ ├── stackvars.go │ └── stackvars_test.go └── test ├── binding ├── binding.csproj ├── binding_test.go ├── main.cs └── run.sh └── stdlib ├── main.cs ├── run.sh └── stdlib.csproj /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | test: 12 | name: Test 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - uses: actions/setup-go@v1 17 | with: 18 | go-version: 1.14 19 | id: go 20 | - uses: actions/checkout@v2 21 | - uses: actions/setup-dotnet@v1 22 | with: 23 | dotnet-version: '3.1.102' 24 | 25 | - name: Test stdlib 26 | working-directory: test/stdlib 27 | run: | 28 | ./run.sh fmt -test.v -test.run=^Test 29 | ./run.sh math -test.v -test.run=^Test 30 | ./run.sh math/bits -test.v -test.run=^Test 31 | ./run.sh runtime/internal/atomic -test.v -test.run=^Test 32 | ./run.sh runtime/internal/math -test.v -test.run=^Test 33 | ./run.sh runtime/internal/sys -test.v -test.run=^Test 34 | ./run.sh sort -test.v -test.run=^Test 35 | ./run.sh sync -test.v -test.run=^Test 36 | ./run.sh sync/atomic -test.v -test.run=^Test 37 | 38 | - name: Test binding 39 | working-directory: test/binding 40 | run: ./run.sh 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.wasm 3 | .DS_Store 4 | .vs 5 | autogen/ 6 | bin/ 7 | obj/ 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go2dotnet 2 | 3 | A converter from Go to .NET (C#) 4 | 5 | ## Example 6 | 7 | ```sh 8 | cd example/helloworld 9 | ./run.sh 10 | ``` 11 | 12 | ## How does this work? 13 | 14 | This tool analyses a Wasm file compiled from Go files, and generates C# files based on the Wasm file. 15 | 16 | ## Calling C# code from Go 17 | 18 | You can use `syscall/js`. You can access C# world via `js.Global().Get(".net").Get("Something.That.Can.Be.Reached.By.Reflection")`. 19 | 20 | See `example/binding` for actual usages. 21 | 22 | ## TODO 23 | 24 | * Improving compiling speed by reducing C# files 25 | * `net/http` 26 | * `os` 27 | -------------------------------------------------------------------------------- /cmd/gowasm2csharp/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "log" 8 | "os" 9 | 10 | "github.com/pkg/profile" 11 | 12 | "github.com/hajimehoshi/go2dotnet/gowasm2csharp" 13 | ) 14 | 15 | var ( 16 | flagOut = flag.String("out", ".", "Output directory") 17 | flagWasm = flag.String("wasm", "", "WebAssembly file generated by Go") 18 | flagNamespace = flag.String("namespace", "", "Namespace") 19 | flagProfile = flag.Bool("profile", false, "Take profiles") 20 | ) 21 | 22 | func main() { 23 | flag.Parse() 24 | if *flagProfile { 25 | defer profile.Start().Stop() 26 | } 27 | 28 | if err := os.MkdirAll(*flagOut, 0755); err != nil { 29 | log.Fatal(err) 30 | } 31 | if err := gowasm2csharp.Generate(*flagOut, *flagWasm, *flagNamespace); err != nil { 32 | log.Fatal(err) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /example/binding/binding.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | true 7 | 8 | 9 | 10 | 11 | Go2DotNet.Example.Binding.AutoGen.MemInitData 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /example/binding/main.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | #pragma warning disable 649 4 | 5 | using System; 6 | using Go2DotNet.Example.Binding.AutoGen; 7 | 8 | namespace Go2DotNet.Example.Binding 9 | { 10 | public class Test 11 | { 12 | internal static void CallTwice(IInvokable a) 13 | { 14 | a.Invoke(null); 15 | a.Invoke(null); 16 | } 17 | } 18 | 19 | class Program 20 | { 21 | static void Main(string[] args) 22 | { 23 | Go go = new Go(); 24 | go.Run(args); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example/binding/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | // +build example 4 | 5 | package main 6 | 7 | import ( 8 | "syscall/js" 9 | ) 10 | 11 | func main() { 12 | t := js.Global().Get(".net").Get("Go2DotNet.Example.Binding.Test") 13 | f := js.FuncOf(func(this js.Value, args []js.Value) interface{} { 14 | println("Hi") 15 | return nil 16 | }) 17 | defer f.Release() 18 | t.Call("CallTwice", f) 19 | } 20 | -------------------------------------------------------------------------------- /example/binding/run.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | env GOOS=js GOARCH=wasm go build -tags example -o binding.wasm -trimpath . 3 | rm -rf autogen 4 | go run ../../cmd/gowasm2csharp -out autogen -wasm binding.wasm -namespace Go2DotNet.Example.Binding.AutoGen 5 | dotnet run . 6 | -------------------------------------------------------------------------------- /example/goroutine/goroutine.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | true 7 | 8 | 9 | 10 | 11 | Go2DotNet.Example.Goroutine.AutoGen.MemInitData 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /example/goroutine/main.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | using System; 4 | using Go2DotNet.Example.Goroutine.AutoGen; 5 | 6 | namespace Go2DotNet.Example.Goroutine 7 | { 8 | class Program 9 | { 10 | static void Main(string[] args) 11 | { 12 | Go go = new Go(); 13 | go.Run(args); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /example/goroutine/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | // +build example 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "time" 10 | ) 11 | 12 | func main() { 13 | ch1 := make(chan string) 14 | ch2 := make(chan string) 15 | ch3 := make(chan struct{}) 16 | 17 | go func() { 18 | for { 19 | time.Sleep(100 * time.Millisecond) 20 | ch1 <- "A" 21 | } 22 | }() 23 | go func() { 24 | for { 25 | time.Sleep(200 * time.Millisecond) 26 | ch2 <- "B" 27 | } 28 | }() 29 | go func() { 30 | for { 31 | time.Sleep(3 * time.Second) 32 | close(ch3) 33 | } 34 | }() 35 | 36 | loop: 37 | for { 38 | select { 39 | case str := <-ch1: 40 | fmt.Println(str) 41 | case str := <-ch2: 42 | fmt.Println(str) 43 | case <-ch3: 44 | fmt.Println("Done") 45 | break loop 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /example/goroutine/run.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | env GOOS=js GOARCH=wasm go build -tags example -o goroutine.wasm -trimpath . 3 | rm -rf autogen 4 | go run ../../cmd/gowasm2csharp -out autogen -wasm goroutine.wasm -namespace Go2DotNet.Example.Goroutine.AutoGen 5 | dotnet run . 6 | -------------------------------------------------------------------------------- /example/helloworld/helloworld.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | true 7 | 8 | 9 | 10 | 11 | Go2DotNet.Example.HelloWorld.AutoGen.MemInitData 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /example/helloworld/main.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | using System; 4 | using Go2DotNet.Example.HelloWorld.AutoGen; 5 | 6 | namespace Go2DotNet.Example.HelloWorld 7 | { 8 | class Program 9 | { 10 | static void Main(string[] args) 11 | { 12 | Go go = new Go(); 13 | go.Run(args); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /example/helloworld/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | // +build example 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | ) 10 | 11 | func main() { 12 | fmt.Println("Hello, World!") 13 | } 14 | -------------------------------------------------------------------------------- /example/helloworld/run.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | env GOOS=js GOARCH=wasm go build -tags example -o helloworld.wasm -trimpath . 3 | rm -rf autogen 4 | go run ../../cmd/gowasm2csharp -out autogen -wasm helloworld.wasm -namespace Go2DotNet.Example.HelloWorld.AutoGen 5 | dotnet run . 6 | -------------------------------------------------------------------------------- /example/monogame/Content/Content.mgcb: -------------------------------------------------------------------------------- 1 | 2 | #----------------------------- Global Properties ----------------------------# 3 | 4 | /outputDir:bin/$(Platform) 5 | /intermediateDir:obj/$(Platform) 6 | /platform:DesktopGL 7 | /config: 8 | /profile:Reach 9 | /compress:False 10 | 11 | #-------------------------------- References --------------------------------# 12 | 13 | 14 | #---------------------------------- Content ---------------------------------# 15 | 16 | -------------------------------------------------------------------------------- /example/monogame/GoGame.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | using Microsoft.Xna.Framework; 4 | using Microsoft.Xna.Framework.Graphics; 5 | using Microsoft.Xna.Framework.Input; 6 | using Go2DotNet.Example.MonoGame.AutoGen; 7 | 8 | namespace Go2DotNet.Example.MonoGame 9 | { 10 | static class GoGameRunner 11 | { 12 | public static void Run(IInvokable onUpdate, IInvokable onDraw) 13 | { 14 | using var g = new GoGame(onUpdate, onDraw); 15 | g.Run(); 16 | } 17 | } 18 | 19 | public class GoGame : Game 20 | { 21 | GraphicsDeviceManager graphics; 22 | SpriteBatch spriteBatch; 23 | IInvokable onUpdate; 24 | IInvokable onDraw; 25 | 26 | public GoGame(IInvokable onUpdate, IInvokable onDraw) 27 | { 28 | this.onUpdate = onUpdate; 29 | this.onDraw = onDraw; 30 | graphics = new GraphicsDeviceManager(this); 31 | Content.RootDirectory = "Content"; 32 | IsMouseVisible = true; 33 | } 34 | 35 | protected override void Initialize() 36 | { 37 | // TODO: Add your initialization logic here 38 | 39 | base.Initialize(); 40 | } 41 | 42 | protected override void LoadContent() 43 | { 44 | spriteBatch = new SpriteBatch(GraphicsDevice); 45 | 46 | // TODO: use this.Content to load your game content here 47 | } 48 | 49 | protected override void Update(GameTime gameTime) 50 | { 51 | if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) 52 | Exit(); 53 | 54 | this.onUpdate.Invoke(null); 55 | 56 | base.Update(gameTime); 57 | } 58 | 59 | protected override void Draw(GameTime gameTime) 60 | { 61 | int v = (int)(double)this.onDraw.Invoke(null); 62 | GraphicsDevice.Clear(new Color(v, v, v)); 63 | 64 | base.Draw(gameTime); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /example/monogame/Program.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | using System; 4 | using Go2DotNet.Example.MonoGame.AutoGen; 5 | 6 | namespace Go2DotNet.Example.MonoGame 7 | { 8 | public static class Program 9 | { 10 | [STAThread] 11 | static void Main() 12 | { 13 | Go go = new Go(); 14 | go.Run(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/monogame/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | // +build example 4 | 5 | package main 6 | 7 | import ( 8 | "syscall/js" 9 | ) 10 | 11 | type game struct { 12 | counter int 13 | } 14 | 15 | func (g *game) update() { 16 | g.counter += 2 17 | if g.counter >= 256 { 18 | g.counter = 0 19 | } 20 | } 21 | 22 | func (g *game) draw() int { 23 | return g.counter 24 | } 25 | 26 | func main() { 27 | g := &game{} 28 | 29 | updatef := js.FuncOf(func(this js.Value, args []js.Value) interface{} { 30 | g.update() 31 | return nil 32 | }) 33 | defer updatef.Release() 34 | 35 | drawf := js.FuncOf(func(this js.Value, args []js.Value) interface{} { 36 | return g.draw() 37 | }) 38 | defer drawf.Release() 39 | 40 | js.Global().Get(".net").Get("Go2DotNet.Example.MonoGame.GoGameRunner").Call("Run", updatef, drawf) 41 | } 42 | -------------------------------------------------------------------------------- /example/monogame/monogame.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | true 10 | 11 | 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Go2DotNet.Example.MonoGame.AutoGen.MemInitData 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /example/monogame/monogame.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "monogame", "monogame.csproj", "{7DB9B0FE-3F26-4144-88BC-39F50E5FAD7C}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Any CPU = Debug|Any CPU 9 | Release|Any CPU = Release|Any CPU 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {7DB9B0FE-3F26-4144-88BC-39F50E5FAD7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 13 | {7DB9B0FE-3F26-4144-88BC-39F50E5FAD7C}.Debug|Any CPU.Build.0 = Debug|Any CPU 14 | {7DB9B0FE-3F26-4144-88BC-39F50E5FAD7C}.Release|Any CPU.ActiveCfg = Release|Any CPU 15 | {7DB9B0FE-3F26-4144-88BC-39F50E5FAD7C}.Release|Any CPU.Build.0 = Release|Any CPU 16 | EndGlobalSection 17 | EndGlobal 18 | -------------------------------------------------------------------------------- /example/monogame/run.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | env GOOS=js GOARCH=wasm go build -tags example -o monogame.wasm -trimpath . 3 | rm -rf autogen 4 | go run ../../cmd/gowasm2csharp -out autogen -wasm monogame.wasm -namespace Go2DotNet.Example.MonoGame.AutoGen 5 | dotnet run . 6 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hajimehoshi/go2dotnet 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/go-interpreter/wagon v0.6.1-0.20200226200811-4ca95707c808 7 | github.com/pkg/profile v1.4.0 8 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a 9 | golang.org/x/sys v0.0.0-20190412213103-97732733099d // indirect 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= 2 | github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= 3 | github.com/go-interpreter/wagon v0.6.1-0.20200226200811-4ca95707c808 h1:DK/r87dK66orWskGaYpM/0csKs1uOtwve5tX0z/ZljI= 4 | github.com/go-interpreter/wagon v0.6.1-0.20200226200811-4ca95707c808/go.mod h1:lQUozviuTS6v7A2HXs6L0Wc72Apl9+mKsGLsJOHYiME= 5 | github.com/pkg/profile v1.4.0 h1:uCmaf4vVbWAOZz36k1hrQD7ijGRzLwaME8Am/7a4jZI= 6 | github.com/pkg/profile v1.4.0/go.mod h1:NWz/XGvpEW1FyYQ7fCx4dqYBLlfTcE+A9FLAkNKqjFE= 7 | github.com/twitchyliquid64/golang-asm v0.0.0-20190126203739-365674df15fc h1:RTUQlKzoZZVG3umWNzOYeFecQLIh+dbxXvJp1zPQJTI= 8 | github.com/twitchyliquid64/golang-asm v0.0.0-20190126203739-365674df15fc/go.mod h1:NoCfSFWosfqMqmmD7hApkirIK9ozpHjxRnRxs1l413A= 9 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= 10 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 11 | golang.org/x/sys v0.0.0-20190306220234-b354f8bf4d9e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 12 | golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= 13 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 14 | -------------------------------------------------------------------------------- /gowasm2csharp/bits.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package gowasm2csharp 4 | 5 | import ( 6 | "os" 7 | "path/filepath" 8 | "text/template" 9 | ) 10 | 11 | func writeBitsCS(dir string, namespace string) error { 12 | f, err := os.Create(filepath.Join(dir, "Bits.cs")) 13 | if err != nil { 14 | return err 15 | } 16 | defer f.Close() 17 | 18 | if err := bitsTmpl.Execute(f, struct { 19 | Namespace string 20 | }{ 21 | Namespace: namespace, 22 | }); err != nil { 23 | return err 24 | } 25 | return nil 26 | } 27 | 28 | var bitsTmpl = template.Must(template.New("Bits.cs").Parse(`// Code generated by go2dotnet. DO NOT EDIT. 29 | 30 | namespace {{.Namespace}} 31 | { 32 | // The implementation is copied from the Go standard package math/bits, which is under BSD-style license. 33 | static class Bits 34 | { 35 | public static int LeadingZeros(uint x) 36 | { 37 | return 32 - Len(x); 38 | } 39 | 40 | public static int LeadingZeros(ulong x) 41 | { 42 | return 64 - Len(x); 43 | } 44 | 45 | public static int TailingZeros(uint x) 46 | { 47 | if (x == 0) 48 | { 49 | return 32; 50 | } 51 | return (int)deBruijn32tab[(x&-x)*deBruijn32>>(32-5)]; 52 | } 53 | 54 | public static int TailingZeros(ulong x) 55 | { 56 | if (x == 0) 57 | { 58 | return 64; 59 | } 60 | return (int)deBruijn64tab[(x&(ulong)(-(long)x))*deBruijn64>>(64-6)]; 61 | } 62 | 63 | public static int OnesCount(uint x) 64 | { 65 | return (int)(pop8tab[x>>24] + pop8tab[x>>16&0xff] + pop8tab[x>>8&0xff] + pop8tab[x&0xff]); 66 | } 67 | 68 | public static int OnesCount(ulong x) 69 | { 70 | const ulong m0 = 0x5555555555555555ul; 71 | const ulong m1 = 0x3333333333333333ul; 72 | const ulong m2 = 0x0f0f0f0f0f0f0f0ful; 73 | const ulong m = 0xfffffffffffffffful; 74 | 75 | x = ((x>>1)&(m0&m)) + (x&(m0&m)); 76 | x = ((x>>2)&(m1&m)) + (x&(m1&m)); 77 | x = ((x>>4) + x) & (m2 & m); 78 | x += x >> 8; 79 | x += x >> 16; 80 | x += x >> 32; 81 | return (int)(x) & ((1<<7) - 1); 82 | } 83 | 84 | public static uint RotateLeft(uint x, int k) 85 | { 86 | const int n = 32; 87 | int s = k & (n - 1); 88 | return x<>(n-s); 89 | } 90 | 91 | public static ulong RotateLeft(ulong x, int k) 92 | { 93 | const int n = 64; 94 | int s = k & (n - 1); 95 | return x<>(n-s); 96 | } 97 | 98 | private static int Len(uint x) 99 | { 100 | int n = 0; 101 | if (x >= 1<<16) 102 | { 103 | x >>= 16; 104 | n = 16; 105 | } 106 | if (x >= 1<<8) 107 | { 108 | x >>= 8; 109 | n += 8; 110 | } 111 | return n + (int)len8tab[x]; 112 | } 113 | 114 | private static int Len(ulong x) 115 | { 116 | int n = 0; 117 | if (x >= 1ul<<32) 118 | { 119 | x >>= 32; 120 | n = 32; 121 | } 122 | if (x >= 1ul<<16) 123 | { 124 | x >>= 16; 125 | n += 16; 126 | } 127 | if (x >= 1ul<<8) 128 | { 129 | x >>= 8; 130 | n += 8; 131 | } 132 | return n + (int)len8tab[x]; 133 | } 134 | 135 | static byte[] pop8tab = new byte[] { 136 | 0x00, 0x01, 0x01, 0x02, 0x01, 0x02, 0x02, 0x03, 0x01, 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x04, 137 | 0x01, 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x04, 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, 138 | 0x01, 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x04, 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, 139 | 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, 140 | 0x01, 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x04, 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, 141 | 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, 142 | 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, 143 | 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, 0x04, 0x05, 0x05, 0x06, 0x05, 0x06, 0x06, 0x07, 144 | 0x01, 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x04, 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, 145 | 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, 146 | 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, 147 | 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, 0x04, 0x05, 0x05, 0x06, 0x05, 0x06, 0x06, 0x07, 148 | 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, 149 | 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, 0x04, 0x05, 0x05, 0x06, 0x05, 0x06, 0x06, 0x07, 150 | 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, 0x04, 0x05, 0x05, 0x06, 0x05, 0x06, 0x06, 0x07, 151 | 0x04, 0x05, 0x05, 0x06, 0x05, 0x06, 0x06, 0x07, 0x05, 0x06, 0x06, 0x07, 0x06, 0x07, 0x07, 0x08, 152 | }; 153 | 154 | static byte[] len8tab = new byte[] { 155 | 0x00, 0x01, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 156 | 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 157 | 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 158 | 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 159 | 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 160 | 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 161 | 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 162 | 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 163 | 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 164 | 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 165 | 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 166 | 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 167 | 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 168 | 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 169 | 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 170 | 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 171 | }; 172 | 173 | const uint deBruijn32 = 0x077CB531; 174 | 175 | static byte[] deBruijn32tab = new byte[] { 176 | 0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 177 | 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9, 178 | }; 179 | 180 | const ulong deBruijn64 = 0x03f79d71b4ca8b09; 181 | 182 | static byte[] deBruijn64tab = new byte[] { 183 | 0, 1, 56, 2, 57, 49, 28, 3, 61, 58, 42, 50, 38, 29, 17, 4, 184 | 62, 47, 59, 36, 45, 43, 51, 22, 53, 39, 33, 30, 24, 18, 12, 5, 185 | 63, 55, 48, 27, 60, 41, 37, 16, 46, 35, 44, 21, 52, 32, 23, 11, 186 | 54, 26, 40, 15, 34, 20, 31, 10, 25, 14, 19, 9, 13, 8, 7, 6, 187 | }; 188 | } 189 | } 190 | `)) 191 | -------------------------------------------------------------------------------- /gowasm2csharp/generate.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package gowasm2csharp 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | "text/template" 12 | 13 | "github.com/go-interpreter/wagon/wasm" 14 | "golang.org/x/sync/errgroup" 15 | ) 16 | 17 | func identifierFromString(str string) string { 18 | var ident string 19 | for _, r := range []rune(str) { 20 | if r > 0xff { 21 | panic("identifiers cannot include non-Latin1 characters") 22 | } 23 | if '0' <= r && r <= '9' { 24 | ident += string(r) 25 | continue 26 | } 27 | if 'a' <= r && r <= 'z' { 28 | ident += string(r) 29 | continue 30 | } 31 | if 'A' <= r && r <= 'Z' { 32 | ident += string(r) 33 | continue 34 | } 35 | ident += fmt.Sprintf("_%02x", r) 36 | } 37 | if len(ident) > 512 { 38 | ident = ident[:511] 39 | } 40 | return ident 41 | } 42 | 43 | type wasmFunc struct { 44 | Mod *wasm.Module 45 | Funcs []*wasmFunc 46 | Types []*wasmType 47 | Type *wasmType 48 | Wasm wasm.Function 49 | Index int 50 | Import bool 51 | BodyStr string 52 | } 53 | 54 | func (f *wasmFunc) Identifier() string { 55 | return identifierFromString(f.Wasm.Name) 56 | } 57 | 58 | var funcTmpl = template.Must(template.New("func").Parse(`// OriginalName: {{.OriginalName}} 59 | // Index: {{.Index}} 60 | {{if .WithBody}}{{if .Public}}public{{else}}private{{end}} {{end}}{{.ReturnType}} {{.Name}}({{.Args}}){{if .WithBody}} 61 | { 62 | {{range .Locals}} {{.}} 63 | {{end}}{{if .Locals}} 64 | {{end}}{{range .Body}}{{.}} 65 | {{end}}}{{else}};{{end}}`)) 66 | 67 | func wasmTypeToReturnType(v wasm.ValueType) returnType { 68 | switch v { 69 | case wasm.ValueTypeI32: 70 | return returnTypeI32 71 | case wasm.ValueTypeI64: 72 | return returnTypeI64 73 | case wasm.ValueTypeF32: 74 | return returnTypeF32 75 | case wasm.ValueTypeF64: 76 | return returnTypeF64 77 | default: 78 | panic("not reached") 79 | } 80 | } 81 | 82 | func (f *wasmFunc) CSharp(indent string, public bool, withBody bool) (string, error) { 83 | var retType returnType 84 | switch ts := f.Wasm.Sig.ReturnTypes; len(ts) { 85 | case 0: 86 | retType = returnTypeVoid 87 | case 1: 88 | retType = wasmTypeToReturnType(ts[0]) 89 | default: 90 | return "", fmt.Errorf("the number of return values must be 0 or 1 but %d", len(ts)) 91 | } 92 | 93 | var args []string 94 | for i, t := range f.Wasm.Sig.ParamTypes { 95 | args = append(args, fmt.Sprintf("%s local%d", wasmTypeToReturnType(t).CSharp(), i)) 96 | } 97 | 98 | var locals []string 99 | var body []string 100 | if withBody { 101 | if f.BodyStr != "" { 102 | body = strings.Split(f.BodyStr, "\n") 103 | } else if f.Wasm.Body != nil { 104 | var idx int 105 | for _, e := range f.Wasm.Body.Locals { 106 | for i := 0; i < int(e.Count); i++ { 107 | locals = append(locals, fmt.Sprintf("%s local%d = 0;", wasmTypeToReturnType(e.Type).CSharp(), idx+len(f.Wasm.Sig.ParamTypes))) 108 | idx++ 109 | } 110 | } 111 | var err error 112 | body, err = f.bodyToCSharp() 113 | if err != nil { 114 | return "", err 115 | } 116 | } else { 117 | body = []string{" throw new NotImplementedException();"} 118 | } 119 | } 120 | 121 | var buf bytes.Buffer 122 | if err := funcTmpl.Execute(&buf, struct { 123 | OriginalName string 124 | Name string 125 | Index int 126 | ReturnType string 127 | Args string 128 | Locals []string 129 | Body []string 130 | Public bool 131 | WithBody bool 132 | }{ 133 | OriginalName: f.Wasm.Name, 134 | Name: identifierFromString(f.Wasm.Name), 135 | Index: f.Index, 136 | ReturnType: retType.CSharp(), 137 | Args: strings.Join(args, ", "), 138 | Locals: locals, 139 | Body: body, 140 | Public: public, 141 | WithBody: withBody, 142 | }); err != nil { 143 | return "", err 144 | } 145 | 146 | // Add indentations 147 | var lines []string 148 | for _, line := range strings.Split(buf.String(), "\n") { 149 | lines = append(lines, indent+line) 150 | } 151 | return strings.Join(lines, "\n") + "\n", nil 152 | } 153 | 154 | type wasmExport struct { 155 | Funcs []*wasmFunc 156 | Index int 157 | Name string 158 | } 159 | 160 | func (e *wasmExport) CSharp(indent string) (string, error) { 161 | f := e.Funcs[e.Index] 162 | 163 | var ret string 164 | var retType returnType 165 | switch ts := f.Wasm.Sig.ReturnTypes; len(ts) { 166 | case 0: 167 | retType = returnTypeVoid 168 | case 1: 169 | ret = "return " 170 | retType = wasmTypeToReturnType(ts[0]) 171 | default: 172 | return "", fmt.Errorf("the number of return values must be 0 or 1 but %d", len(ts)) 173 | } 174 | 175 | var args []string 176 | var argsToPass []string 177 | for i, t := range f.Wasm.Sig.ParamTypes { 178 | args = append(args, fmt.Sprintf("%s arg%d", wasmTypeToReturnType(t).CSharp(), i)) 179 | argsToPass = append(argsToPass, fmt.Sprintf("arg%d", i)) 180 | } 181 | 182 | str := fmt.Sprintf(`public %s %s(%s) 183 | { 184 | %s%s(%s); 185 | } 186 | `, retType.CSharp(), e.Name, strings.Join(args, ", "), ret, identifierFromString(f.Wasm.Name), strings.Join(argsToPass, ", ")) 187 | 188 | lines := strings.Split(str, "\n") 189 | for i := range lines { 190 | lines[i] = indent + lines[i] 191 | } 192 | return strings.Join(lines, "\n"), nil 193 | } 194 | 195 | type wasmGlobal struct { 196 | Type wasm.ValueType 197 | Index int 198 | Init int 199 | } 200 | 201 | func (g *wasmGlobal) CSharp(indent string) string { 202 | return fmt.Sprintf("%sprivate %s global%d = %d;", indent, wasmTypeToReturnType(g.Type).CSharp(), g.Index, g.Init) 203 | } 204 | 205 | type wasmType struct { 206 | Sig *wasm.FunctionSig 207 | Index int 208 | } 209 | 210 | func (t *wasmType) CSharp(indent string) (string, error) { 211 | var retType returnType 212 | switch ts := t.Sig.ReturnTypes; len(ts) { 213 | case 0: 214 | retType = returnTypeVoid 215 | case 1: 216 | retType = wasmTypeToReturnType(ts[0]) 217 | default: 218 | return "", fmt.Errorf("the number of return values must be 0 or 1 but %d", len(ts)) 219 | } 220 | 221 | var args []string 222 | for i, t := range t.Sig.ParamTypes { 223 | args = append(args, fmt.Sprintf("%s arg%d", wasmTypeToReturnType(t).CSharp(), i)) 224 | } 225 | 226 | return fmt.Sprintf("%sprivate delegate %s Type%d(%s);", indent, retType.CSharp(), t.Index, strings.Join(args, ", ")), nil 227 | } 228 | 229 | func Generate(outDir string, wasmFile string, namespace string) error { 230 | f, err := os.Open(wasmFile) 231 | if err != nil { 232 | return err 233 | } 234 | defer f.Close() 235 | 236 | mod, err := wasm.DecodeModule(f) 237 | if err != nil { 238 | return err 239 | } 240 | 241 | var types []*wasmType 242 | for i, e := range mod.Types.Entries { 243 | e := e 244 | types = append(types, &wasmType{ 245 | Sig: &e, 246 | Index: i, 247 | }) 248 | } 249 | 250 | var ifs []*wasmFunc 251 | for i, e := range mod.Import.Entries { 252 | name := e.FieldName 253 | ifs = append(ifs, &wasmFunc{ 254 | Type: types[e.Type.(wasm.FuncImport).Type], 255 | Wasm: wasm.Function{ 256 | Sig: types[e.Type.(wasm.FuncImport).Type].Sig, 257 | Name: name, 258 | }, 259 | Index: i, 260 | Import: true, 261 | BodyStr: importFuncBodies[name], 262 | }) 263 | } 264 | 265 | // There is a bug that signature and body are shifted (go-interpreter/wagon#190). 266 | var names wasm.NameMap 267 | if c := mod.Custom(wasm.CustomSectionName); c != nil { 268 | var nsec wasm.NameSection 269 | if err := nsec.UnmarshalWASM(bytes.NewReader(c.Data)); err != nil { 270 | return err 271 | } 272 | if len(nsec.Types[wasm.NameFunction]) > 0 { 273 | sub, err := nsec.Decode(wasm.NameFunction) 274 | if err != nil { 275 | return err 276 | } 277 | names = sub.(*wasm.FunctionNames).Names 278 | } 279 | } 280 | var fs []*wasmFunc 281 | for i, t := range mod.Function.Types { 282 | name := names[uint32(i+len(mod.Import.Entries))] 283 | body := mod.Code.Bodies[i] 284 | fs = append(fs, &wasmFunc{ 285 | Type: types[t], 286 | Wasm: wasm.Function{ 287 | Sig: types[t].Sig, 288 | Body: &body, 289 | Name: name, 290 | }, 291 | Index: i + len(mod.Import.Entries), 292 | }) 293 | } 294 | 295 | var exports []*wasmExport 296 | for _, e := range mod.Export.Entries { 297 | switch e.Kind { 298 | case wasm.ExternalFunction: 299 | exports = append(exports, &wasmExport{ 300 | Index: int(e.Index), 301 | Name: e.FieldStr, 302 | }) 303 | case wasm.ExternalMemory: 304 | // Ignore 305 | default: 306 | return fmt.Errorf("export type %d is not implemented", e.Kind) 307 | } 308 | } 309 | 310 | allfs := append(ifs, fs...) 311 | for _, e := range exports { 312 | e.Funcs = allfs 313 | } 314 | for _, f := range ifs { 315 | f.Mod = mod 316 | f.Funcs = allfs 317 | f.Types = types 318 | } 319 | for _, f := range fs { 320 | f.Mod = mod 321 | f.Funcs = allfs 322 | f.Types = types 323 | } 324 | 325 | var globals []*wasmGlobal 326 | for i, e := range mod.Global.Globals { 327 | // TODO: Consider mutability. 328 | // TODO: Use e.Type.Init. 329 | globals = append(globals, &wasmGlobal{ 330 | Type: e.Type.Type, 331 | Index: i, 332 | Init: 0, 333 | }) 334 | } 335 | 336 | if mod.Start != nil { 337 | return fmt.Errorf("start section must be nil but not") 338 | } 339 | 340 | tables := make([][]uint32, len(mod.Table.Entries)) 341 | for _, e := range mod.Elements.Entries { 342 | v, err := mod.ExecInitExpr(e.Offset) 343 | if err != nil { 344 | return err 345 | } 346 | offset := v.(int32) 347 | if diff := int(offset) + int(len(e.Elems)) - int(len(tables[e.Index])); diff > 0 { 348 | tables[e.Index] = append(tables[e.Index], make([]uint32, diff)...) 349 | } 350 | copy(tables[e.Index][offset:], e.Elems) 351 | } 352 | 353 | var data []wasmData 354 | for _, e := range mod.Data.Entries { 355 | offset, err := mod.ExecInitExpr(e.Offset) 356 | if err != nil { 357 | return err 358 | } 359 | data = append(data, wasmData{ 360 | Offset: int(offset.(int32)), 361 | Data: e.Data, 362 | }) 363 | } 364 | 365 | var g errgroup.Group 366 | g.Go(func() error { 367 | out, err := os.Create(filepath.Join(outDir, "Go.cs")) 368 | if err != nil { 369 | return err 370 | } 371 | defer out.Close() 372 | 373 | if err := goTmpl.Execute(out, struct { 374 | Namespace string 375 | ImportFuncs []*wasmFunc 376 | }{ 377 | Namespace: namespace, 378 | ImportFuncs: ifs, 379 | }); err != nil { 380 | return err 381 | } 382 | return nil 383 | }) 384 | g.Go(func() error { 385 | return writeBitsCS(outDir, namespace) 386 | }) 387 | g.Go(func() error { 388 | return writeJSCS(outDir, namespace) 389 | }) 390 | g.Go(func() error { 391 | return writeInstCS(outDir, namespace, ifs, fs, exports, globals, types, tables) 392 | }) 393 | g.Go(func() error { 394 | return writeMemInitData(outDir, data) 395 | }) 396 | g.Go(func() error { 397 | return writeMemCS(outDir, namespace, int(mod.Memory.Entries[0].Limits.Initial)) 398 | }) 399 | 400 | if err := g.Wait(); err != nil { 401 | return err 402 | } 403 | 404 | return nil 405 | } 406 | 407 | var goTmpl = template.Must(template.New("Go.cs").Parse(`// Code generated by go2dotnet. DO NOT EDIT. 408 | 409 | using System; 410 | using System.Collections.Concurrent; 411 | using System.Collections.Generic; 412 | using System.Diagnostics; 413 | using System.Security.Cryptography; 414 | using System.Text; 415 | using System.Timers; 416 | 417 | namespace {{.Namespace}} 418 | { 419 | internal interface IImport 420 | { 421 | {{- range $value := .ImportFuncs}} 422 | {{$value.CSharp " " false false}}{{end}} 423 | } 424 | 425 | public class Go 426 | { 427 | class Import : IImport 428 | { 429 | internal Import(Go go) 430 | { 431 | this.go = go; 432 | } 433 | {{range $value := .ImportFuncs}} 434 | {{$value.CSharp " " true true}}{{end}} 435 | private Go go; 436 | } 437 | 438 | private class JSValues : JSObject.IValues 439 | { 440 | public JSValues(Go go) 441 | { 442 | this.go = go; 443 | } 444 | 445 | public object Get(string key) 446 | { 447 | switch (key) 448 | { 449 | case "_makeFuncWrapper": 450 | return new JSObject((object self, object[] args) => 451 | { 452 | return this.go.MakeFuncWrapper((int)JSObject.ToDouble(args[0])); 453 | }); 454 | case "_pendingEvent": 455 | return this.go.pendingEvent; 456 | } 457 | throw new KeyNotFoundException(key); 458 | } 459 | 460 | public void Set(string key, object value) 461 | { 462 | switch (key) 463 | { 464 | case "_pendingEvent": 465 | this.go.pendingEvent = (JSObject)value; 466 | return; 467 | } 468 | throw new KeyNotFoundException(key); 469 | } 470 | 471 | public void Remove(string key) 472 | { 473 | throw new NotImplementedException(); 474 | } 475 | 476 | private Go go; 477 | } 478 | 479 | public Go() 480 | { 481 | this.import = new Import(this); 482 | this.taskQueue = new BlockingCollection(new ConcurrentQueue()); 483 | } 484 | 485 | internal object LoadValue(int addr) 486 | { 487 | double f = this.mem.LoadFloat64(addr); 488 | if (f == 0) 489 | { 490 | return JSObject.Undefined; 491 | } 492 | if (!double.IsNaN(f)) 493 | { 494 | return f; 495 | } 496 | int id = (int)this.mem.LoadUint32(addr); 497 | return this.values[id]; 498 | } 499 | 500 | internal void StoreValue(int addr, object v) 501 | { 502 | const int NaNHead = 0x7FF80000; 503 | double? d = JSObject.ToDouble(v); 504 | if (d.HasValue) 505 | { 506 | if (double.IsNaN(d.Value)) 507 | { 508 | this.mem.StoreInt32(addr + 4, NaNHead); 509 | this.mem.StoreInt32(addr, 0); 510 | return; 511 | } 512 | if (d.Value == 0) 513 | { 514 | this.mem.StoreInt32(addr + 4, NaNHead); 515 | this.mem.StoreInt32(addr, 1); 516 | return; 517 | } 518 | this.mem.StoreFloat64(addr, d.Value); 519 | return; 520 | } 521 | if (v == JSObject.Undefined) 522 | { 523 | this.mem.StoreFloat64(addr, 0); 524 | return; 525 | } 526 | switch (v) 527 | { 528 | case null: 529 | this.mem.StoreInt32(addr + 4, NaNHead); 530 | this.mem.StoreInt32(addr, 2); 531 | return; 532 | case true: 533 | this.mem.StoreInt32(addr + 4, NaNHead); 534 | this.mem.StoreInt32(addr, 3); 535 | return; 536 | case false: 537 | this.mem.StoreInt32(addr + 4, NaNHead); 538 | this.mem.StoreInt32(addr, 4); 539 | return; 540 | } 541 | int id = 0; 542 | if (this.ids.ContainsKey(v)) 543 | { 544 | id = this.ids[v]; 545 | } 546 | else 547 | { 548 | if (this.idPool.Count > 0) 549 | { 550 | id = this.idPool.Pop(); 551 | } 552 | else 553 | { 554 | id = this.values.Count; 555 | } 556 | this.values[id] = v; 557 | this.goRefCounts[id] = 0; 558 | this.ids[v] = id; 559 | } 560 | this.goRefCounts[id]++; 561 | int typeFlag = 1; 562 | if (v is string) 563 | { 564 | typeFlag = 2; 565 | // There is no counterpart for Symbol in C#, then typeFlag = 3 is not used. 566 | } 567 | else if (v is JSObject && ((JSObject)v).IsFunction) 568 | { 569 | typeFlag = 4; 570 | } 571 | this.mem.StoreInt32(addr + 4, NaNHead | typeFlag); 572 | this.mem.StoreInt32(addr, id); 573 | } 574 | 575 | internal object[] LoadSliceOfValues(int addr) 576 | { 577 | var array = (int)this.mem.LoadInt64(addr); 578 | var len = (int)this.mem.LoadInt64(addr + 8); 579 | var a = new object[len]; 580 | for (int i = 0; i < len; i++) 581 | { 582 | a[i] = this.LoadValue(array + i * 8); 583 | } 584 | return a; 585 | } 586 | 587 | public void Run() 588 | { 589 | Run(new string[0]); 590 | } 591 | 592 | public void Run(string[] args) 593 | { 594 | this.debugWriter = new Writer(Console.Error); 595 | this.stopwatch = Stopwatch.StartNew(); 596 | this.mem = new Mem(); 597 | this.inst = new Inst(this.mem, this.import); 598 | this.values = new Dictionary 599 | { 600 | {0, double.NaN}, 601 | {1, 0.0}, 602 | {2, null}, 603 | {3, true}, 604 | {4, false}, 605 | {5, JSObject.Global}, 606 | {6, JSObject.Go(new JSValues(this))}, 607 | }; 608 | this.goRefCounts = new Dictionary(); 609 | this.ids = new Dictionary(); 610 | this.idPool = new Stack(); 611 | this.exited = false; 612 | 613 | int offset = 4096; 614 | Func strPtr = (string str) => { 615 | int ptr = offset; 616 | byte[] bytes = Encoding.UTF8.GetBytes(str + '\0'); 617 | this.mem.StoreBytes(offset, bytes); 618 | offset += bytes.Length; 619 | if (offset % 8 != 0) 620 | { 621 | offset += 8 - (offset % 8); 622 | } 623 | return ptr; 624 | }; 625 | 626 | // 'js' is requried as the first argument. 627 | if (args.Length == 0) 628 | { 629 | args = new string[] { "js" }; 630 | } 631 | else 632 | { 633 | args[0] = "js"; 634 | } 635 | int argc = args.Length; 636 | List argvPtrs = new List(); 637 | foreach (string arg in args) 638 | { 639 | argvPtrs.Add(strPtr(arg)); 640 | } 641 | argvPtrs.Add(0); 642 | // TODO: Add environment variables. 643 | argvPtrs.Add(0); 644 | 645 | int argv = offset; 646 | foreach (int ptr in argvPtrs) 647 | { 648 | this.mem.StoreInt32(offset, ptr); 649 | this.mem.StoreInt32(offset + 4, 0); 650 | offset += 8; 651 | } 652 | 653 | this.inst.run(argc, argv); 654 | for (;;) 655 | { 656 | if (this.exited) 657 | { 658 | return; 659 | } 660 | var task = this.taskQueue.Take(); 661 | if (task != null) 662 | { 663 | task(); 664 | } 665 | } 666 | } 667 | 668 | private void Exit(int code) 669 | { 670 | if (code != 0) 671 | { 672 | Console.Error.WriteLine($"exit code: {code}"); 673 | } 674 | } 675 | 676 | private void Resume() 677 | { 678 | if (this.exited) 679 | { 680 | throw new Exception("Go program has already exited"); 681 | } 682 | this.inst.resume(); 683 | // Post a null task and procceed the loop. 684 | this.taskQueue.Add(null); 685 | } 686 | 687 | private JSObject MakeFuncWrapper(int id) 688 | { 689 | return new JSObject((object self, object[] args) => 690 | { 691 | var evt = new JSObject(new Dictionary() 692 | { 693 | {"id", id}, 694 | {"this", self}, 695 | {"args", args ?? new object[0]}, 696 | }); 697 | this.pendingEvent = evt; 698 | this.Resume(); 699 | return JSObject.ReflectGet(evt, "result"); 700 | }); 701 | } 702 | 703 | private void DebugWrite(IEnumerable bytes) 704 | { 705 | this.debugWriter.Write(bytes); 706 | } 707 | 708 | private long PreciseNowInNanoseconds() 709 | { 710 | return this.stopwatch.ElapsedTicks * nanosecPerTick; 711 | } 712 | 713 | private double UnixNowInMilliseconds() 714 | { 715 | return (DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalMilliseconds; 716 | } 717 | 718 | private int SetTimeout(double interval) 719 | { 720 | var id = this.nextCallbackTimeoutId; 721 | this.nextCallbackTimeoutId++; 722 | 723 | Timer timer = new Timer(interval); 724 | timer.Elapsed += (sender, e) => { 725 | this.taskQueue.Add(() => { 726 | this.Resume(); 727 | while (this.scheduledTimeouts.ContainsKey(id)) 728 | { 729 | // for some reason Go failed to register the timeout event, log and try again 730 | // (temporary workaround for https://github.com/golang/go/issues/28975) 731 | this.Resume(); 732 | } 733 | }); 734 | }; 735 | timer.AutoReset = false; 736 | timer.Start(); 737 | 738 | this.scheduledTimeouts[id] = timer; 739 | 740 | return id; 741 | } 742 | 743 | private void ClearTimeout(int id) 744 | { 745 | if (this.scheduledTimeouts.ContainsKey(id)) 746 | { 747 | this.scheduledTimeouts[id].Stop(); 748 | } 749 | this.scheduledTimeouts.Remove(id); 750 | } 751 | 752 | private byte[] GetRandomBytes(int length) 753 | { 754 | var bytes = new byte[length]; 755 | this.rngCsp.GetBytes(bytes); 756 | return bytes; 757 | } 758 | 759 | private static long nanosecPerTick = (1_000_000_000L) / Stopwatch.Frequency; 760 | 761 | private Import import; 762 | 763 | private Writer debugWriter; 764 | private Stopwatch stopwatch; 765 | 766 | private JSObject pendingEvent; 767 | private Dictionary scheduledTimeouts = new Dictionary(); 768 | private int nextCallbackTimeoutId = 1; 769 | private Inst inst; 770 | private Mem mem; 771 | private Dictionary values; 772 | private Dictionary goRefCounts; 773 | private Dictionary ids; 774 | private Stack idPool; 775 | private bool exited; 776 | private RNGCryptoServiceProvider rngCsp = new RNGCryptoServiceProvider(); 777 | 778 | private BlockingCollection taskQueue; 779 | } 780 | } 781 | `)) 782 | -------------------------------------------------------------------------------- /gowasm2csharp/import.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package gowasm2csharp 4 | 5 | var importFuncBodies = map[string]string{ 6 | // func wasmExit(code int32) 7 | "runtime.wasmExit": ` var code = go.mem.LoadInt32(local0 + 8); 8 | go.exited = true; 9 | go.inst = null; 10 | go.values = null; 11 | go.goRefCounts = null; 12 | go.ids = null; 13 | go.idPool = null; 14 | go.Exit(code);`, 15 | 16 | // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) 17 | "runtime.wasmWrite": ` var fd = go.mem.LoadInt64(local0 + 8); 18 | if (fd != 1 && fd != 2) 19 | { 20 | throw new NotImplementedException($"fd for runtime.wasmWrite must be 1 or 2 but {fd}"); 21 | } 22 | var p = go.mem.LoadInt64(local0 + 16); 23 | var n = go.mem.LoadInt32(local0 + 24); 24 | 25 | // Note that runtime.wasmWrite is used only for print/println so far. 26 | // Write the buffer to the standard output regardless of fd. 27 | go.DebugWrite(go.mem.LoadSliceDirectly(p, n));`, 28 | 29 | // func resetMemoryDataView() 30 | "runtime.resetMemoryDataView": ` // Do nothing.`, 31 | 32 | // func nanotime1() int64 33 | "runtime.nanotime1": ` go.mem.StoreInt64(local0 + 8, go.PreciseNowInNanoseconds());`, 34 | 35 | // func walltime1() (sec int64, nsec int32) 36 | "runtime.walltime1": ` var now = go.UnixNowInMilliseconds(); 37 | go.mem.StoreInt64(local0 + 8, (long)(now / 1000)); 38 | go.mem.StoreInt32(local0 + 16, (int)((now % 1000) * 1_000_000));`, 39 | 40 | // func scheduleTimeoutEvent(delay int64) int32 41 | "runtime.scheduleTimeoutEvent": ` var interval = go.mem.LoadInt64(local0 + 8); 42 | var id = go.SetTimeout((double)interval); 43 | go.mem.StoreInt32(local0 + 16, id);`, 44 | 45 | // func clearTimeoutEvent(id int32) 46 | "runtime.clearTimeoutEvent": ` var id = go.mem.LoadInt32(local0 + 8); 47 | go.ClearTimeout(id);`, 48 | 49 | // func getRandomData(r []byte) 50 | "runtime.getRandomData": ` var slice = go.mem.LoadSlice(local0 + 8); 51 | var bytes = go.GetRandomBytes(slice.Count); 52 | for (int i = 0; i < slice.Count; i++) { 53 | slice.Array[slice.Offset + i] = bytes[i]; 54 | }`, 55 | 56 | // func finalizeRef(v ref) 57 | "syscall/js.finalizeRef": ` int id = (int)go.mem.LoadUint32(local0 + 8); 58 | go.goRefCounts[id]--; 59 | if (go.goRefCounts[id] == 0) 60 | { 61 | var v = go.values[id]; 62 | go.values[id] = null; 63 | go.ids.Remove(v); 64 | go.idPool.Push(id); 65 | }`, 66 | 67 | // func stringVal(value string) ref 68 | "syscall/js.stringVal": ` go.StoreValue(local0 + 24, go.mem.LoadString(local0 + 8));`, 69 | 70 | // func valueGet(v ref, p string) ref 71 | "syscall/js.valueGet": ` var result = JSObject.ReflectGet(go.LoadValue(local0 + 8), go.mem.LoadString(local0 + 16)); 72 | local0 = go.inst.getsp(); 73 | go.StoreValue(local0 + 32, result);`, 74 | 75 | // func valueSet(v ref, p string, x ref) 76 | "syscall/js.valueSet": ` JSObject.ReflectSet(go.LoadValue(local0 + 8), go.mem.LoadString(local0 + 16), go.LoadValue(local0 + 32));`, 77 | 78 | // func valueDelete(v ref, p string) 79 | "syscall/js.valueDelete": ` JSObject.ReflectDelete(go.LoadValue(local0 + 8), go.mem.LoadString(local0 + 16));`, 80 | 81 | // func valueIndex(v ref, i int) ref 82 | "syscall/js.valueIndex": ` go.StoreValue(local0 + 24, JSObject.ReflectGet(go.LoadValue(local0 + 8), go.mem.LoadInt64(local0 + 16).ToString()));`, 83 | 84 | // valueSetIndex(v ref, i int, x ref) 85 | "syscall/js.valueSetIndex": ` JSObject.ReflectSet(go.LoadValue(local0 + 8), go.mem.LoadInt64(local0 + 16).ToString(), go.LoadValue(local0 + 24));`, 86 | 87 | // func valueCall(v ref, m string, args []ref) (ref, bool) 88 | "syscall/js.valueCall": ` var v = go.LoadValue(local0 + 8); 89 | var m = JSObject.ReflectGet(v, go.mem.LoadString(local0 + 16)); 90 | var args = go.LoadSliceOfValues(local0 + 32); 91 | var result = JSObject.ReflectApply(m, v, args); 92 | local0 = go.inst.getsp(); 93 | go.StoreValue(local0 + 56, result); 94 | go.mem.StoreInt8(local0 + 64, 1);`, 95 | 96 | // func valueInvoke(v ref, args []ref) (ref, bool) 97 | "syscall/js.valueInvoke": ` var v = go.LoadValue(local0 + 8); 98 | var args = go.LoadSliceOfValues(local0 + 16); 99 | var result = JSObject.ReflectApply(v, JSObject.Undefined, args); 100 | local0 = go.inst.getsp(); 101 | go.StoreValue(local0 + 40, result); 102 | go.mem.StoreInt8(local0 + 48, 1);`, 103 | 104 | // func valueNew(v ref, args []ref) (ref, bool) 105 | "syscall/js.valueNew": ` var v = go.LoadValue(local0 + 8); 106 | var args = go.LoadSliceOfValues(local0 + 16); 107 | var result = JSObject.ReflectConstruct(v, args); 108 | if (result != null) 109 | { 110 | local0 = go.inst.getsp(); 111 | go.StoreValue(local0 + 40, result); 112 | go.mem.StoreInt8(local0 + 48, 1); 113 | } 114 | else 115 | { 116 | go.StoreValue(local0 + 40, null); 117 | go.mem.StoreInt8(local0 + 48, 0); 118 | }`, 119 | 120 | // func valueLength(v ref) int 121 | "syscall/js.valueLength": ` go.mem.StoreInt64(local0 + 16, ((Array)go.LoadValue(local0 + 8)).Length);`, 122 | 123 | // valuePrepareString(v ref) (ref, int) 124 | "syscall/js.valuePrepareString": ` byte[] str = Encoding.UTF8.GetBytes(go.LoadValue(local0 + 8).ToString()); 125 | go.StoreValue(local0 + 16, str); 126 | go.mem.StoreInt64(local0 + 24, str.Length);`, 127 | 128 | // valueLoadString(v ref, b []byte) 129 | "syscall/js.valueLoadString": ` byte[] src = (byte[])go.LoadValue(local0 + 8); 130 | var dst = go.mem.LoadSlice(local0 + 16); 131 | int len = Math.Min(dst.Count, src.Length); 132 | for (int i = 0; i < len; i++) 133 | { 134 | dst.Array[dst.Offset + i] = src[i]; 135 | }`, 136 | 137 | /*// func valueInstanceOf(v ref, t ref) bool 138 | "syscall/js.valueInstanceOf": (sp) => { 139 | this.mem.setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16)); 140 | },*/ 141 | 142 | // func copyBytesToGo(dst []byte, src ref) (int, bool) 143 | "syscall/js.copyBytesToGo": ` var dst = go.mem.LoadSlice(local0 + 8); 144 | var src = go.LoadValue(local0 + 32); 145 | if (!(src is byte[])) 146 | { 147 | go.mem.StoreInt8(local0 + 48, 0); 148 | return; 149 | } 150 | var srcbs = (byte[])src; 151 | for (int i = 0; i < dst.Count; i++) 152 | { 153 | dst.Array[dst.Offset + i] = srcbs[i]; 154 | } 155 | go.mem.StoreInt64(local0 + 40, (long)dst.Count); 156 | go.mem.StoreInt8(local0 + 48, 1);`, 157 | 158 | // func copyBytesToJS(dst ref, src []byte) (int, bool) 159 | "syscall/js.copyBytesToJS": ` var dst = go.LoadValue(local0 + 8); 160 | var src = go.mem.LoadSlice(local0 + 16); 161 | if (!(dst is byte[])) 162 | { 163 | go.mem.StoreInt8(local0 + 48, 0); 164 | return; 165 | } 166 | var dstbs = (byte[])dst; 167 | for (int i = 0; i < dstbs.Length; i++) 168 | { 169 | dstbs[i] = src.Array[src.Offset + i]; 170 | } 171 | go.mem.StoreInt64(local0 + 40, (long)dstbs.Length); 172 | go.mem.StoreInt8(local0 + 48, 1);`, 173 | 174 | "debug": ` Console.WriteLine(local0);`, 175 | } 176 | -------------------------------------------------------------------------------- /gowasm2csharp/inst.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package gowasm2csharp 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "path/filepath" 9 | "text/template" 10 | 11 | "golang.org/x/sync/errgroup" 12 | ) 13 | 14 | func min(a, b int) int { 15 | if a < b { 16 | return a 17 | } 18 | return b 19 | } 20 | 21 | func writeInstCS(dir string, namespace string, importFuncs, funcs []*wasmFunc, exports []*wasmExport, globals []*wasmGlobal, types []*wasmType, tables [][]uint32) error { 22 | const groupSize = 64 23 | 24 | var g errgroup.Group 25 | for i := 0; i < (len(funcs)-1)/groupSize+1; i++ { 26 | i := i 27 | fs := funcs[groupSize*i : min(groupSize*(i+1), len(funcs))] 28 | g.Go(func() error { 29 | f, err := os.Create(filepath.Join(dir, fmt.Sprintf("Inst.Funcs%d.cs", i))) 30 | if err != nil { 31 | return err 32 | } 33 | defer f.Close() 34 | 35 | if err := instTmpl.Execute(f, struct { 36 | Namespace string 37 | Funcs []*wasmFunc 38 | }{ 39 | Namespace: namespace, 40 | Funcs: fs, 41 | }); err != nil { 42 | return err 43 | } 44 | return nil 45 | }) 46 | } 47 | g.Go(func() error { 48 | f, err := os.Create(filepath.Join(dir, "Inst.Exports.cs")) 49 | if err != nil { 50 | return err 51 | } 52 | defer f.Close() 53 | 54 | if err := instExportsTmpl.Execute(f, struct { 55 | Namespace string 56 | Exports []*wasmExport 57 | }{ 58 | Namespace: namespace, 59 | Exports: exports, 60 | }); err != nil { 61 | return err 62 | } 63 | return nil 64 | }) 65 | g.Go(func() error { 66 | f, err := os.Create(filepath.Join(dir, "Inst.Init.cs")) 67 | if err != nil { 68 | return err 69 | } 70 | defer f.Close() 71 | 72 | if err := instInitTmpl.Execute(f, struct { 73 | Namespace string 74 | ImportFuncs []*wasmFunc 75 | Funcs []*wasmFunc 76 | Types []*wasmType 77 | Tables [][]uint32 78 | Globals []*wasmGlobal 79 | }{ 80 | Namespace: namespace, 81 | ImportFuncs: importFuncs, 82 | Funcs: funcs, 83 | Types: types, 84 | Tables: tables, 85 | Globals: globals, 86 | }); err != nil { 87 | return err 88 | } 89 | return nil 90 | }) 91 | if err := g.Wait(); err != nil { 92 | return err 93 | } 94 | return nil 95 | } 96 | 97 | var instTmpl = template.Must(template.New("Inst.cs").Parse(`// Code generated by go2dotnet. DO NOT EDIT. 98 | 99 | #pragma warning disable 162 // unreachable code 100 | #pragma warning disable 164 // label 101 | #pragma warning disable 219 // unused local variables 102 | #pragma warning disable 1718 // comparison made to same variable 103 | 104 | using System; 105 | using System.Diagnostics; 106 | 107 | namespace {{.Namespace}} 108 | { 109 | sealed partial class Inst 110 | { 111 | {{range $value := .Funcs}}{{$value.CSharp " " false true}} 112 | {{end}} } 113 | } 114 | `)) 115 | 116 | var instExportsTmpl = template.Must(template.New("Inst.Exports.cs").Parse(`// Code generated by go2dotnet. DO NOT EDIT. 117 | 118 | namespace {{.Namespace}} 119 | { 120 | sealed partial class Inst 121 | { 122 | {{range $value := .Exports}}{{$value.CSharp " "}} 123 | {{end}} } 124 | } 125 | `)) 126 | 127 | var instInitTmpl = template.Must(template.New("Inst.Init.cs").Parse(`// Code generated by go2dotnet. DO NOT EDIT. 128 | 129 | namespace {{.Namespace}} 130 | { 131 | sealed partial class Inst 132 | { 133 | private static float CopySign(float x, float y) 134 | { 135 | const uint sign = 1u << 31; 136 | unsafe 137 | { 138 | uint r = (*(uint*)(&x) & ~sign) | (*(uint*)(&y) & sign); 139 | return *(float*)(&r); 140 | } 141 | } 142 | 143 | private static double CopySign(double x, double y) 144 | { 145 | const ulong sign = 1ul << 63; 146 | unsafe 147 | { 148 | ulong r = (*(ulong*)(&x) & ~sign) | (*(ulong*)(&y) & sign); 149 | return *(double*)(&r); 150 | } 151 | } 152 | 153 | public Inst(Mem mem, IImport import) 154 | { 155 | initializeFuncs_(); 156 | mem_ = mem; 157 | import_ = import; 158 | } 159 | 160 | private void initializeFuncs_() 161 | { 162 | funcs_ = new object[] { 163 | {{range $value := .ImportFuncs}} null, 164 | {{end}}{{range $value := .Funcs}} (Type{{.Type.Index}})({{.Identifier}}), 165 | {{end}} }; 166 | } 167 | 168 | {{range $value := .Types}}{{$value.CSharp " "}} 169 | {{end}} 170 | private static readonly uint[][] table_ = { 171 | {{range $value := .Tables}} new uint[] { {{- range $value2 := $value}}{{$value2}}, {{end}}}, 172 | {{end}} }; 173 | 174 | {{range $value := .Globals}}{{$value.CSharp " "}} 175 | {{end}} 176 | private object[] funcs_; 177 | private Mem mem_; 178 | private IImport import_; 179 | } 180 | } 181 | `)) 182 | -------------------------------------------------------------------------------- /gowasm2csharp/js.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package gowasm2csharp 4 | 5 | import ( 6 | "os" 7 | "path/filepath" 8 | "text/template" 9 | ) 10 | 11 | func writeJSCS(dir string, namespace string) error { 12 | f, err := os.Create(filepath.Join(dir, "JS.cs")) 13 | if err != nil { 14 | return err 15 | } 16 | defer f.Close() 17 | 18 | if err := jsTmpl.Execute(f, struct { 19 | Namespace string 20 | }{ 21 | Namespace: namespace, 22 | }); err != nil { 23 | return err 24 | } 25 | return nil 26 | } 27 | 28 | var jsTmpl = template.Must(template.New("JS.cs").Parse(`// Code generated by go2dotnet. DO NOT EDIT. 29 | 30 | using System; 31 | using System.Collections.Generic; 32 | using System.IO; 33 | using System.Reflection; 34 | using System.Security.Cryptography; 35 | using System.Text; 36 | 37 | namespace {{.Namespace}} 38 | { 39 | sealed class Writer 40 | { 41 | public Writer(TextWriter writer) 42 | { 43 | this.writer = writer; 44 | } 45 | 46 | public void Write(IEnumerable bytes) 47 | { 48 | this.buf.AddRange(bytes); 49 | while (this.buf.Contains((byte)'\n')) 50 | { 51 | var idx = this.buf.IndexOf((byte)'\n'); 52 | var str = Encoding.UTF8.GetString(this.buf.GetRange(0, idx).ToArray()); 53 | this.writer.WriteLine(str); 54 | this.buf.RemoveRange(0, idx+1); 55 | } 56 | } 57 | 58 | private TextWriter writer; 59 | private List buf = new List(); 60 | } 61 | 62 | public delegate object JSFunc(object self, object[] args); 63 | 64 | public interface IInvokable 65 | { 66 | object Invoke(object[] args); 67 | } 68 | 69 | sealed class JSObject : IInvokable 70 | { 71 | public interface IValues 72 | { 73 | object Get(string key); 74 | void Set(string key, object value); 75 | void Remove(string key); 76 | } 77 | 78 | private class DictionaryValues : IValues 79 | { 80 | public DictionaryValues(Dictionary dict) 81 | { 82 | this.dict = dict; 83 | } 84 | 85 | public object Get(string key) 86 | { 87 | if (!this.dict.ContainsKey(key)) 88 | { 89 | return null; 90 | } 91 | return this.dict[key]; 92 | } 93 | 94 | public void Set(string key, object value) 95 | { 96 | this.dict[key] = value; 97 | } 98 | 99 | public void Remove(string key) 100 | { 101 | this.dict.Remove(key); 102 | } 103 | 104 | private Dictionary dict; 105 | } 106 | 107 | private class DotNetRootValues : IValues 108 | { 109 | public object Get(string key) 110 | { 111 | Type type = Type.GetType(key); 112 | if (type == null) 113 | { 114 | return null; 115 | } 116 | return new JSObject(key, new DotNetTypeValues(type), (object self, object[] args) => 117 | { 118 | BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; 119 | object inst = Activator.CreateInstance(type, flags, null, args, null, null); 120 | return new JSObject(key, new DotNetInstanceValues(inst)); 121 | }, true); 122 | } 123 | 124 | public void Set(string key, object value) 125 | { 126 | throw new Exception($"setting ${key} on a DotNetRootValue is forbidden"); 127 | } 128 | 129 | public void Remove(string key) 130 | { 131 | throw new Exception($"removing ${key} on a DotNetRootValue is forbidden"); 132 | } 133 | } 134 | 135 | private class DotNetTypeValues : IValues 136 | { 137 | public DotNetTypeValues(Type type) 138 | { 139 | this.type = type; 140 | } 141 | 142 | public object Get(string key) 143 | { 144 | BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static; 145 | FieldInfo field = this.type.GetField(key, flags); 146 | if (field != null) 147 | { 148 | return WrapObjectIfNeeded(field.GetValue(null)); 149 | } 150 | PropertyInfo prop = this.type.GetProperty(key, flags); 151 | if (prop != null) 152 | { 153 | return WrapObjectIfNeeded(prop.GetValue(null)); 154 | } 155 | try 156 | { 157 | MethodInfo method = this.type.GetMethod(key, flags); 158 | if (method != null) 159 | { 160 | return new JSObject(key, null, (object self, object[] args) => { 161 | return WrapObjectIfNeeded(method.Invoke(null, args)); 162 | }, false); 163 | } 164 | } 165 | catch (AmbiguousMatchException e) 166 | { 167 | Console.Error.WriteLine($"Method {key} is ambiguous due to overloads."); 168 | throw e; 169 | } 170 | return null; 171 | } 172 | 173 | public void Set(string key, object value) 174 | { 175 | BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static; 176 | FieldInfo field = this.type.GetField(key, flags); 177 | if (field != null) 178 | { 179 | field.SetValue(null, UnwrapObjectIfNeeded(value)); 180 | return; 181 | } 182 | PropertyInfo prop = this.type.GetProperty(key, flags); 183 | if (prop != null) 184 | { 185 | prop.SetValue(null, UnwrapObjectIfNeeded(value)); 186 | return; 187 | } 188 | throw new Exception($"setting {key} on {type} is forbidden"); 189 | } 190 | 191 | public void Remove(string key) 192 | { 193 | throw new Exception($"removing ${key} on a DotNetTypeValue is forbidden"); 194 | } 195 | 196 | private Type type; 197 | } 198 | 199 | private class DotNetInstanceValues : IValues 200 | { 201 | public DotNetInstanceValues(object obj) 202 | { 203 | this.obj = obj; 204 | } 205 | 206 | public object InternalObject 207 | { 208 | get { return this.obj; } 209 | } 210 | 211 | public object Get(string key) 212 | { 213 | BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; 214 | FieldInfo field = this.obj.GetType().GetField(key, flags); 215 | if (field != null) 216 | { 217 | return WrapObjectIfNeeded(field.GetValue(this.obj)); 218 | } 219 | PropertyInfo prop = this.obj.GetType().GetProperty(key, flags); 220 | if (prop != null) 221 | { 222 | return WrapObjectIfNeeded(prop.GetValue(this.obj)); 223 | } 224 | try 225 | { 226 | MethodInfo method = this.obj.GetType().GetMethod(key, flags); 227 | if (method != null) 228 | { 229 | return new JSObject(key, null, (object self, object[] args) => { 230 | return WrapObjectIfNeeded(method.Invoke(this.obj, args)); 231 | }, false); 232 | } 233 | } 234 | catch (AmbiguousMatchException e) 235 | { 236 | Console.Error.WriteLine($"Method {key} is ambiguous due to overloads."); 237 | throw e; 238 | } 239 | return null; 240 | } 241 | 242 | public void Set(string key, object value) 243 | { 244 | BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; 245 | FieldInfo field = this.obj.GetType().GetField(key, flags); 246 | if (field != null) 247 | { 248 | field.SetValue(this.obj, UnwrapObjectIfNeeded(value)); 249 | return; 250 | } 251 | PropertyInfo prop = this.obj.GetType().GetProperty(key, flags); 252 | if (prop != null) 253 | { 254 | prop.SetValue(this.obj, UnwrapObjectIfNeeded(value)); 255 | return; 256 | } 257 | throw new Exception($"setting {key} on {obj} is forbidden"); 258 | } 259 | 260 | public void Remove(string key) 261 | { 262 | throw new Exception($"removing ${key} on a DotNetInstanceValue is forbidden"); 263 | } 264 | 265 | private object obj; 266 | } 267 | 268 | private class FS 269 | { 270 | public FS() 271 | { 272 | this.stdout = new Writer(Console.Out); 273 | this.stderr = new Writer(Console.Error); 274 | } 275 | 276 | public object Write(object self, object[] args) 277 | { 278 | var fd = (int)ToDouble(args[0]); 279 | var buf = (byte[])args[1]; 280 | var offset = (int)ToDouble(args[2]); 281 | var length = (int)ToDouble(args[3]); 282 | var position = args[4]; 283 | var callback = args[5]; 284 | if (offset != 0 || length != buf.Length) 285 | { 286 | ReflectApply(callback, null, new object[] { Enosys("write") }); 287 | return null; 288 | } 289 | if (position != null) 290 | { 291 | ReflectApply(callback, null, new object[] { Enosys("write") }); 292 | return null; 293 | } 294 | switch (fd) 295 | { 296 | case 1: 297 | this.stdout.Write(buf); 298 | break; 299 | case 2: 300 | this.stderr.Write(buf); 301 | break; 302 | default: 303 | ReflectApply(callback, null, new object[] { Enosys("write") }); 304 | return null; 305 | } 306 | ReflectApply(callback, null, new object[] { null, buf.Length }); 307 | return null; 308 | } 309 | 310 | private Writer stdout; 311 | private Writer stderr; 312 | } 313 | 314 | public static double? ToDouble(object value) 315 | { 316 | if (value == null) 317 | { 318 | return null; 319 | } 320 | 321 | switch (Type.GetTypeCode(value.GetType())) 322 | { 323 | case TypeCode.SByte: 324 | return (double)(sbyte)value; 325 | case TypeCode.Byte: 326 | return (double)(byte)value; 327 | case TypeCode.Int16: 328 | return (double)(short)value; 329 | case TypeCode.UInt16: 330 | return (double)(ushort)value; 331 | case TypeCode.Int32: 332 | return (double)(int)value; 333 | case TypeCode.UInt32: 334 | return (double)(uint)value; 335 | case TypeCode.Int64: 336 | return (double)(long)value; 337 | case TypeCode.UInt64: 338 | return (double)(ulong)value; 339 | case TypeCode.Single: 340 | return (double)(float)value; 341 | case TypeCode.Double: 342 | return (double)(double)value; 343 | case TypeCode.Decimal: 344 | return (double)(decimal)value; 345 | } 346 | return null; 347 | } 348 | 349 | private static object WrapObjectIfNeeded(object value) 350 | { 351 | if (value == null) 352 | { 353 | return null; 354 | } 355 | 356 | switch (Type.GetTypeCode(value.GetType())) 357 | { 358 | case TypeCode.SByte: 359 | case TypeCode.Byte: 360 | case TypeCode.Int16: 361 | case TypeCode.UInt16: 362 | case TypeCode.Int32: 363 | case TypeCode.UInt32: 364 | case TypeCode.Int64: 365 | case TypeCode.UInt64: 366 | case TypeCode.Single: 367 | case TypeCode.Double: 368 | case TypeCode.Decimal: 369 | case TypeCode.Boolean: 370 | case TypeCode.String: 371 | return value; 372 | } 373 | if (value is byte[]) 374 | { 375 | return value; 376 | } 377 | return new JSObject(new DotNetInstanceValues(value)); 378 | } 379 | 380 | private static object UnwrapObjectIfNeeded(object value) 381 | { 382 | if (value == null) 383 | { 384 | return value; 385 | } 386 | 387 | if (value is JSObject) 388 | { 389 | JSObject jsobj = (JSObject)value; 390 | if (jsobj.values is DotNetInstanceValues) 391 | { 392 | return ((DotNetInstanceValues)jsobj.values).InternalObject; 393 | } 394 | } 395 | return value; 396 | } 397 | 398 | private static object[] UnwrapObjectsIfNeeded(object[] values) 399 | { 400 | if (values == null) 401 | { 402 | return null; 403 | } 404 | 405 | object[] result = new object[values.Length]; 406 | for (int i = 0; i < values.Length; i++) 407 | { 408 | result[i] = UnwrapObjectIfNeeded(values[i]); 409 | } 410 | return result; 411 | } 412 | 413 | public static JSObject Undefined = new JSObject("undefined"); 414 | public static JSObject Global; 415 | 416 | static JSObject() 417 | { 418 | var rngCsp = new RNGCryptoServiceProvider(); 419 | 420 | JSObject arr = new JSObject("Array"); 421 | JSObject obj = new JSObject("Object"); 422 | JSObject u8 = new JSObject("Uint8Array", null, (object self, object[] args) => 423 | { 424 | if (args.Length == 0) 425 | { 426 | return new byte[0]; 427 | } 428 | if (args.Length == 1) 429 | { 430 | var len = args[0]; 431 | if (len is double) 432 | { 433 | return new byte[(int)(double)len]; 434 | } 435 | throw new NotImplementedException($"new Uint8Array({args[0]}) is not implemented"); 436 | } 437 | throw new NotImplementedException($"new Uint8Array with {args.Length} args is not implemented"); 438 | }, true); 439 | 440 | JSObject crypto = new JSObject("crypto", new Dictionary() 441 | { 442 | {"getRandomValues", new JSObject((object self, object[] args) => 443 | { 444 | var bs = (byte[])args[0]; 445 | rngCsp.GetBytes(bs); 446 | return bs; 447 | })}, 448 | }); 449 | 450 | JSObject console = new JSObject("console", new Dictionary() 451 | { 452 | {"error", new JSObject((object self, object[] args) => 453 | { 454 | Console.Error.WriteLine(string.Join(" ", args)); 455 | return null; 456 | })}, 457 | {"debug", new JSObject((object self, object[] args) => 458 | { 459 | Console.Error.WriteLine(string.Join(" ", args)); 460 | return null; 461 | })}, 462 | {"info", new JSObject((object self, object[] args) => 463 | { 464 | Console.Out.WriteLine(string.Join(" ", args)); 465 | return null; 466 | })}, 467 | {"log", new JSObject((object self, object[] args) => 468 | { 469 | Console.Out.WriteLine(string.Join(" ", args)); 470 | return null; 471 | })}, 472 | {"warn", new JSObject((object self, object[] args) => 473 | { 474 | Console.Error.WriteLine(string.Join(" ", args)); 475 | return null; 476 | })}, 477 | }); 478 | 479 | JSObject fetch = new JSObject((object self, object[] args) => 480 | { 481 | // TODO: Implement this. 482 | return null; 483 | }); 484 | 485 | FS fsimpl = new FS(); 486 | JSObject fs = new JSObject("fs", new Dictionary() 487 | { 488 | {"constants", new JSObject(new Dictionary() 489 | { 490 | {"O_WRONLY", -1}, 491 | {"O_RDWR", -1}, 492 | {"O_CREAT", -1}, 493 | {"O_TRUNC", -1}, 494 | {"O_APPEND", -1}, 495 | {"O_EXCL", -1}, 496 | })}, 497 | {"write", new JSObject(fsimpl.Write)}, 498 | }); 499 | JSObject process = new JSObject("process", new Dictionary() 500 | { 501 | {"pid", -1}, 502 | {"ppid", -1}, 503 | }); 504 | 505 | Global = new JSObject("global", new Dictionary() 506 | { 507 | {"Array", arr}, 508 | {"Object", obj}, 509 | {"Uint8Array", u8}, 510 | {"console", console}, 511 | {"crypto", crypto}, 512 | {"fetch", fetch}, 513 | {"fs", fs}, 514 | {"process", process}, 515 | {".net", new JSObject(".net", new DotNetRootValues())}, 516 | }); 517 | } 518 | 519 | public static JSObject Go(IValues values) 520 | { 521 | return new JSObject("go", values); 522 | } 523 | 524 | public static JSObject Enosys(string name) 525 | { 526 | return new JSObject(new Dictionary() 527 | { 528 | {"message", $"{name} not implemented"}, 529 | {"code", "ENOSYS"}, 530 | }); 531 | } 532 | 533 | public static object ReflectGet(object target, string key) 534 | { 535 | if (target == Undefined) 536 | { 537 | throw new Exception($"get on undefined (key: {key}) is forbidden"); 538 | } 539 | if (target == null) 540 | { 541 | throw new Exception($"get on null (key: {key}) is forbidden"); 542 | } 543 | if (target is JSObject) 544 | { 545 | return ((JSObject)target).Get(key); 546 | } 547 | if (target is object[]) 548 | { 549 | int idx = 0; 550 | if (int.TryParse(key, out idx)) 551 | { 552 | object[] arr = (object[])target; 553 | return arr[idx]; 554 | } 555 | } 556 | throw new Exception($"{target}.{key} not found"); 557 | } 558 | 559 | public static void ReflectSet(object target, string key, object value) 560 | { 561 | if (target == Undefined) 562 | { 563 | throw new Exception($"set on undefined (key: {key}) is forbidden"); 564 | } 565 | if (target == null) 566 | { 567 | throw new Exception($"set on null (key: {key}) is forbidden"); 568 | } 569 | if (target is JSObject) 570 | { 571 | ((JSObject)target).Set(key, value); 572 | return; 573 | } 574 | throw new Exception($"{target}.{key} cannot be set"); 575 | } 576 | 577 | public static void ReflectDelete(object target, string key) 578 | { 579 | if (target == Undefined) 580 | { 581 | throw new Exception($"delete on undefined is forbidden"); 582 | } 583 | if (target == null) 584 | { 585 | throw new Exception($"delete on null is forbidden"); 586 | } 587 | if (target is JSObject) 588 | { 589 | ((JSObject)target).Delete(key); 590 | return; 591 | } 592 | throw new Exception($"{target}.{key} cannot be deleted"); 593 | } 594 | 595 | public static object ReflectConstruct(object target, object[] args) 596 | { 597 | if (target == Undefined) 598 | { 599 | throw new Exception($"new on undefined is forbidden"); 600 | } 601 | if (target == null) 602 | { 603 | throw new Exception($"new on null is forbidden"); 604 | } 605 | if (target is JSObject) 606 | { 607 | var t = (JSObject)target; 608 | if (!t.ctor) 609 | { 610 | throw new Exception($"{t} is not a constructor"); 611 | } 612 | return t.fn(t, UnwrapObjectsIfNeeded(args)); 613 | } 614 | throw new NotImplementedException($"new {target}({args}) cannot be called"); 615 | } 616 | 617 | public static object ReflectApply(object target, object self, object[] args) 618 | { 619 | if (target == Undefined) 620 | { 621 | throw new Exception($"apply on undefined is forbidden"); 622 | } 623 | if (target == null) 624 | { 625 | throw new Exception($"apply on null is forbidden"); 626 | } 627 | if (target is JSObject) 628 | { 629 | var t = (JSObject)target; 630 | if (t.ctor) 631 | { 632 | throw new Exception($"{t} is a constructor"); 633 | } 634 | return t.fn(self, UnwrapObjectsIfNeeded(args)); 635 | } 636 | throw new NotImplementedException($"{target}({args}) cannot be called"); 637 | } 638 | 639 | public JSObject(string name) 640 | : this("", null, null, false) 641 | { 642 | } 643 | 644 | public JSObject(Dictionary values) 645 | : this("", new DictionaryValues(values), null, false) 646 | { 647 | } 648 | 649 | public JSObject(IValues values) 650 | : this("", values, null, false) 651 | { 652 | } 653 | 654 | public JSObject(string name, IValues values) 655 | : this(name, values, null, false) 656 | { 657 | } 658 | 659 | public JSObject(string name, Dictionary values) 660 | : this(name, new DictionaryValues(values), null, false) 661 | { 662 | } 663 | 664 | public JSObject(JSFunc fn) 665 | : this("", null, fn, false) 666 | { 667 | } 668 | 669 | public JSObject(string name, IValues values, JSFunc fn, bool ctor) 670 | { 671 | const string defaultName = "(JSObject)"; 672 | 673 | this.name = name; 674 | if (this.name == "") 675 | { 676 | this.name = defaultName; 677 | } 678 | this.values = values; 679 | this.fn = fn; 680 | this.ctor = ctor; 681 | } 682 | 683 | public bool IsFunction 684 | { 685 | get { return this.fn != null; } 686 | } 687 | 688 | public object Get(string key) 689 | { 690 | if (this.values != null) 691 | { 692 | return this.values.Get(key); 693 | } 694 | throw new Exception($"{this}.{key} not found"); 695 | } 696 | 697 | public void Set(string key, object value) 698 | { 699 | if (this.values == null) 700 | { 701 | this.values = new DictionaryValues(new Dictionary()); 702 | } 703 | this.values.Set(key, value); 704 | } 705 | 706 | public void Delete(string key) 707 | { 708 | if (this.values == null) 709 | { 710 | return; 711 | } 712 | this.values.Remove(key); 713 | } 714 | 715 | public object Invoke(object[] args) 716 | { 717 | if (this.fn == null) 718 | { 719 | throw new Exception($"{this} is not invokable since ${this} is not a function"); 720 | } 721 | if (this.ctor) 722 | { 723 | throw new Exception($"{this} is not invokable since ${this} is a constructor"); 724 | } 725 | return this.fn(null, UnwrapObjectsIfNeeded(args)); 726 | } 727 | 728 | public override string ToString() 729 | { 730 | return this.name; 731 | } 732 | 733 | private IValues values; 734 | private string name; 735 | private JSFunc fn; 736 | private bool ctor = false; 737 | } 738 | } 739 | `)) 740 | -------------------------------------------------------------------------------- /gowasm2csharp/mem.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package gowasm2csharp 4 | 5 | import ( 6 | "os" 7 | "path/filepath" 8 | "text/template" 9 | ) 10 | 11 | type wasmData struct { 12 | Offset int 13 | Data []byte 14 | } 15 | 16 | func writeMemInitData(dir string, data []wasmData) error { 17 | f, err := os.Create(filepath.Join(dir, "MemInitData")) 18 | if err != nil { 19 | return err 20 | } 21 | defer f.Close() 22 | 23 | for _, d := range data { 24 | if _, err := f.WriteAt(d.Data, int64(d.Offset)); err != nil { 25 | return err 26 | } 27 | } 28 | return nil 29 | } 30 | 31 | func writeMemCS(dir string, namespace string, initPageNum int) error { 32 | f, err := os.Create(filepath.Join(dir, "Mem.cs")) 33 | if err != nil { 34 | return err 35 | } 36 | defer f.Close() 37 | 38 | if err := memTmpl.Execute(f, struct { 39 | Namespace string 40 | InitPageNum int 41 | Data []wasmData 42 | }{ 43 | Namespace: namespace, 44 | InitPageNum: initPageNum, 45 | }); err != nil { 46 | return err 47 | } 48 | return nil 49 | } 50 | 51 | var memTmpl = template.Must(template.New("Mem.cs").Parse(`// Code generated by go2dotnet. DO NOT EDIT. 52 | 53 | using System; 54 | using System.IO; 55 | using System.Reflection; 56 | using System.Text; 57 | 58 | namespace {{.Namespace}} 59 | { 60 | sealed class Mem 61 | { 62 | const int PageSize = 64 * 1024; 63 | 64 | private static void ReadFull(byte[] dst, Stream stream) 65 | { 66 | int offset = 0; 67 | int read = 0; 68 | while ((read = stream.Read(dst, offset, dst.Length - offset)) > 0) 69 | { 70 | offset += read; 71 | } 72 | } 73 | 74 | public Mem() 75 | { 76 | this.bytes = new byte[{{.InitPageNum}} * PageSize]; 77 | 78 | Assembly asm = Assembly.GetExecutingAssembly(); 79 | Stream stream = asm.GetManifestResourceStream("{{.Namespace}}.MemInitData"); 80 | if (stream == null) 81 | { 82 | Console.Error.WriteLine("MemInitData must be embedded but not found."); 83 | Console.Error.WriteLine("Please add these lines under the in the csproj file:"); 84 | Console.Error.WriteLine(""); 85 | Console.Error.WriteLine(" "); 86 | Console.Error.WriteLine(" "); 87 | Console.Error.WriteLine(" {{.Namespace}}.MemInitData"); 88 | Console.Error.WriteLine(" "); 89 | Console.Error.WriteLine(" "); 90 | Environment.Exit(1); 91 | return; 92 | } 93 | ReadFull(this.bytes, stream); 94 | } 95 | 96 | internal int Size 97 | { 98 | get 99 | { 100 | return this.bytes.Length / PageSize; 101 | } 102 | } 103 | 104 | internal int Grow(int delta) 105 | { 106 | var prevSize = this.Size; 107 | Array.Resize(ref this.bytes, (prevSize + delta) * PageSize); 108 | return prevSize; 109 | } 110 | 111 | internal sbyte LoadInt8(int addr) 112 | { 113 | return (sbyte)this.bytes[addr]; 114 | } 115 | 116 | internal byte LoadUint8(int addr) 117 | { 118 | return this.bytes[addr]; 119 | } 120 | 121 | internal short LoadInt16(int addr) 122 | { 123 | unsafe 124 | { 125 | fixed (byte* ptr = &this.bytes[addr]) 126 | { 127 | return *(short*)ptr; 128 | } 129 | } 130 | } 131 | 132 | internal ushort LoadUint16(int addr) 133 | { 134 | unsafe 135 | { 136 | fixed (byte* ptr = &this.bytes[addr]) 137 | { 138 | return *(ushort*)ptr; 139 | } 140 | } 141 | } 142 | 143 | internal int LoadInt32(int addr) 144 | { 145 | unsafe 146 | { 147 | fixed (byte* ptr = &this.bytes[addr]) 148 | { 149 | return *(int*)ptr; 150 | } 151 | } 152 | } 153 | 154 | internal uint LoadUint32(int addr) 155 | { 156 | unsafe 157 | { 158 | fixed (byte* ptr = &this.bytes[addr]) 159 | { 160 | return *(uint*)ptr; 161 | } 162 | } 163 | } 164 | 165 | internal long LoadInt64(int addr) 166 | { 167 | unsafe 168 | { 169 | fixed (byte* ptr = &this.bytes[addr]) 170 | { 171 | return *(long*)ptr; 172 | } 173 | } 174 | } 175 | 176 | internal float LoadFloat32(int addr) 177 | { 178 | unsafe 179 | { 180 | fixed (byte* ptr = &this.bytes[addr]) 181 | { 182 | return *(float*)ptr; 183 | } 184 | } 185 | } 186 | 187 | internal double LoadFloat64(int addr) 188 | { 189 | unsafe 190 | { 191 | fixed (byte* ptr = &this.bytes[addr]) 192 | { 193 | return *(double*)ptr; 194 | } 195 | } 196 | } 197 | 198 | internal void StoreInt8(int addr, sbyte val) 199 | { 200 | this.bytes[addr] = (byte)val; 201 | } 202 | 203 | internal void StoreInt16(int addr, short val) 204 | { 205 | unsafe 206 | { 207 | fixed (byte* ptr = &this.bytes[addr]) 208 | { 209 | *(short*)ptr = val; 210 | } 211 | } 212 | } 213 | 214 | internal void StoreInt32(int addr, int val) 215 | { 216 | unsafe 217 | { 218 | fixed (byte* ptr = &this.bytes[addr]) 219 | { 220 | *(int*)ptr = val; 221 | } 222 | } 223 | } 224 | 225 | internal void StoreInt64(int addr, long val) 226 | { 227 | unsafe 228 | { 229 | fixed (byte* ptr = &this.bytes[addr]) 230 | { 231 | *(long*)ptr = val; 232 | } 233 | } 234 | } 235 | 236 | internal void StoreFloat32(int addr, float val) 237 | { 238 | unsafe 239 | { 240 | fixed (byte* ptr = &this.bytes[addr]) 241 | { 242 | *(float*)ptr = val; 243 | } 244 | } 245 | } 246 | 247 | internal void StoreFloat64(int addr, double val) 248 | { 249 | unsafe 250 | { 251 | fixed (byte* ptr = &this.bytes[addr]) 252 | { 253 | *(double*)ptr = val; 254 | } 255 | } 256 | } 257 | 258 | internal void StoreBytes(int addr, byte[] bytes) 259 | { 260 | Array.Copy(bytes, 0, this.bytes, addr, bytes.Length); 261 | } 262 | 263 | internal ArraySegment LoadSlice(int addr) 264 | { 265 | var array = this.LoadInt64(addr); 266 | var len = this.LoadInt64(addr + 8); 267 | return new ArraySegment(this.bytes, (int)array, (int)len); 268 | } 269 | 270 | internal ArraySegment LoadSliceDirectly(long array, int len) 271 | { 272 | return new ArraySegment(this.bytes, (int)array, len); 273 | } 274 | 275 | internal string LoadString(int addr) 276 | { 277 | var saddr = this.LoadInt64(addr); 278 | var len = this.LoadInt64(addr + 8); 279 | return Encoding.UTF8.GetString(this.bytes, (int)saddr, (int)len); 280 | } 281 | 282 | private byte[] bytes; 283 | } 284 | } 285 | `)) 286 | -------------------------------------------------------------------------------- /gowasm2csharp/ops.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package gowasm2csharp 4 | 5 | import ( 6 | "fmt" 7 | "math" 8 | "os" 9 | "runtime/debug" 10 | "strings" 11 | 12 | "github.com/go-interpreter/wagon/disasm" 13 | "github.com/go-interpreter/wagon/wasm" 14 | "github.com/go-interpreter/wagon/wasm/operators" 15 | 16 | "github.com/hajimehoshi/go2dotnet/internal/stackvar" 17 | ) 18 | 19 | type returnType int 20 | 21 | const ( 22 | returnTypeVoid returnType = iota 23 | returnTypeI32 24 | returnTypeI64 25 | returnTypeF32 26 | returnTypeF64 27 | ) 28 | 29 | func (r returnType) CSharp() string { 30 | switch r { 31 | case returnTypeVoid: 32 | return "void" 33 | case returnTypeI32: 34 | return "int" 35 | case returnTypeI64: 36 | return "long" 37 | case returnTypeF32: 38 | return "float" 39 | case returnTypeF64: 40 | return "double" 41 | default: 42 | panic("not reached") 43 | } 44 | } 45 | 46 | type stack struct { 47 | newIdx int 48 | stack []int 49 | } 50 | 51 | func (s *stack) Push() int { 52 | idx := s.newIdx 53 | s.stack = append(s.stack, idx) 54 | s.newIdx++ 55 | return idx 56 | } 57 | 58 | func (s *stack) Pop() int { 59 | idx := s.stack[len(s.stack)-1] 60 | s.stack = s.stack[:len(s.stack)-1] 61 | return idx 62 | } 63 | 64 | func (s *stack) Peep() int { 65 | return s.stack[len(s.stack)-1] 66 | } 67 | 68 | func (s *stack) PeepLevel(level int) (int, bool) { 69 | if len(s.stack) > level { 70 | return s.stack[len(s.stack)-1-level], true 71 | } 72 | return 0, false 73 | } 74 | 75 | func (s *stack) Len() int { 76 | return len(s.stack) 77 | } 78 | 79 | type blockType int 80 | 81 | const ( 82 | blockTypeBlock blockType = iota 83 | blockTypeLoop 84 | blockTypeIf 85 | ) 86 | 87 | type blockStack struct { 88 | types []blockType 89 | rets []string 90 | stackvars []*stackvar.StackVars 91 | s stack 92 | tmpindent int 93 | } 94 | 95 | func (b *blockStack) UnindentTemporarily() { 96 | b.tmpindent-- 97 | } 98 | 99 | func (b *blockStack) IndentTemporarily() { 100 | b.tmpindent++ 101 | } 102 | 103 | func (b *blockStack) varName(idx int) string { 104 | if b.s.Len() > 0 { 105 | return fmt.Sprintf("stack%d_%d", b.s.Peep(), idx) 106 | } 107 | return fmt.Sprintf("stack%d", idx) 108 | } 109 | 110 | func (b *blockStack) Push(btype blockType, ret string) int { 111 | b.types = append(b.types, btype) 112 | b.rets = append(b.rets, ret) 113 | b.stackvars = append(b.stackvars, &stackvar.StackVars{ 114 | VarName: b.varName, 115 | }) 116 | return b.s.Push() 117 | } 118 | 119 | func (b *blockStack) Pop() (int, blockType, string) { 120 | btype := b.types[len(b.types)-1] 121 | ret := b.rets[len(b.rets)-1] 122 | 123 | b.types = b.types[:len(b.types)-1] 124 | b.rets = b.rets[:len(b.rets)-1] 125 | b.stackvars = b.stackvars[:len(b.stackvars)-1] 126 | return b.s.Pop(), btype, ret 127 | } 128 | 129 | func (b *blockStack) Peep() (int, blockType, string) { 130 | return b.s.Peep(), b.types[len(b.types)-1], b.rets[len(b.rets)-1] 131 | } 132 | 133 | func (b *blockStack) PeepLevel(level int) (int, blockType, bool) { 134 | l, ok := b.s.PeepLevel(level) 135 | var t blockType 136 | if ok { 137 | t = b.types[len(b.types)-1-level] 138 | } 139 | return l, t, ok 140 | } 141 | 142 | func (b *blockStack) Len() int { 143 | return b.s.Len() 144 | } 145 | 146 | func (b *blockStack) IndentLevel() int { 147 | l := 0 148 | for _, t := range b.types { 149 | if t == blockTypeIf { 150 | l++ 151 | } 152 | } 153 | l += b.tmpindent 154 | return l 155 | } 156 | 157 | func (b *blockStack) PushLhs() string { 158 | if b.stackvars == nil { 159 | b.stackvars = []*stackvar.StackVars{ 160 | &stackvar.StackVars{ 161 | VarName: b.varName, 162 | }, 163 | } 164 | } 165 | return b.stackvars[len(b.stackvars)-1].PushLhs() 166 | } 167 | 168 | func (b *blockStack) PushStackVar(expr string) { 169 | if b.stackvars == nil { 170 | b.stackvars = []*stackvar.StackVars{ 171 | &stackvar.StackVars{ 172 | VarName: b.varName, 173 | }, 174 | } 175 | } 176 | b.stackvars[len(b.stackvars)-1].Push(expr) 177 | } 178 | 179 | func (b *blockStack) PopStackVar() string { 180 | return b.stackvars[len(b.stackvars)-1].Pop() 181 | } 182 | 183 | func (b *blockStack) PeepStackVar() ([]string, string) { 184 | return b.stackvars[len(b.stackvars)-1].Peep() 185 | } 186 | 187 | func (b *blockStack) Empty() bool { 188 | if len(b.stackvars) == 0 { 189 | return true 190 | } 191 | return b.stackvars[len(b.stackvars)-1].Empty() 192 | } 193 | 194 | func (f *wasmFunc) bodyToCSharp() ([]string, error) { 195 | defer func() { 196 | if err := recover(); err != nil { 197 | fmt.Fprintln(os.Stderr, err) 198 | debug.PrintStack() 199 | panic(err) 200 | } 201 | }() 202 | 203 | sig := f.Wasm.Sig 204 | funcs := f.Funcs 205 | types := f.Types 206 | 207 | dis, err := disasm.NewDisassembly(f.Wasm, f.Mod) 208 | if err != nil { 209 | return nil, err 210 | } 211 | 212 | var body []string 213 | blockStack := &blockStack{} 214 | var tmpidx int 215 | 216 | appendBody := func(str string, args ...interface{}) { 217 | if len(args) > 0 { 218 | str = fmt.Sprintf(str, args...) 219 | } 220 | level := blockStack.IndentLevel() + 1 221 | if strings.HasSuffix(str, ":;") { 222 | level-- 223 | } 224 | indent := strings.Repeat(" ", level) 225 | body = append(body, indent+str) 226 | } 227 | 228 | gotoOrReturn := func(level int) string { 229 | if l, _, ok := blockStack.PeepLevel(level); ok { 230 | return fmt.Sprintf("goto label%d;", l) 231 | } 232 | switch len(sig.ReturnTypes) { 233 | case 0: 234 | return "return;" 235 | default: 236 | // TODO: Should this be PopStackVar? 237 | ls, v := blockStack.PeepStackVar() 238 | for _, l := range ls { 239 | appendBody(l) 240 | } 241 | return fmt.Sprintf("return %s;", v) 242 | } 243 | } 244 | 245 | for _, instr := range dis.Code { 246 | switch instr.Op.Code { 247 | case operators.Unreachable: 248 | appendBody(`Debug.Fail("not reached");`) 249 | case operators.Nop: 250 | // Do nothing 251 | case operators.Block: 252 | var ret string 253 | if t := instr.Immediates[0]; t != wasm.BlockTypeEmpty { 254 | t := wasmTypeToReturnType(wasm.ValueType(t.(wasm.BlockType))) 255 | ret = blockStack.PushLhs() 256 | appendBody("%s %s;", t.CSharp(), ret) 257 | } 258 | blockStack.Push(blockTypeBlock, ret) 259 | case operators.Loop: 260 | var ret string 261 | if t := instr.Immediates[0]; t != wasm.BlockTypeEmpty { 262 | t := wasmTypeToReturnType(wasm.ValueType(t.(wasm.BlockType))) 263 | ret = blockStack.PushLhs() 264 | appendBody("%s %s;", t.CSharp(), ret) 265 | } 266 | l := blockStack.Push(blockTypeLoop, ret) 267 | appendBody("label%d:;", l) 268 | case operators.If: 269 | cond := blockStack.PopStackVar() 270 | var ret string 271 | if t := instr.Immediates[0]; t != wasm.BlockTypeEmpty { 272 | t := wasmTypeToReturnType(wasm.ValueType(t.(wasm.BlockType))) 273 | ret = blockStack.PushLhs() 274 | appendBody("%s %s;", t.CSharp(), ret) 275 | } 276 | appendBody("if ((%s) != 0)", cond) 277 | appendBody("{") 278 | blockStack.Push(blockTypeIf, ret) 279 | case operators.Else: 280 | if _, _, ret := blockStack.Peep(); ret != "" { 281 | appendBody("%s = (%s);", ret, blockStack.PopStackVar()) 282 | } 283 | blockStack.UnindentTemporarily() 284 | appendBody("}") 285 | appendBody("else") 286 | appendBody("{") 287 | blockStack.IndentTemporarily() 288 | case operators.End: 289 | if _, btype, ret := blockStack.Peep(); btype != blockTypeLoop && ret != "" { 290 | idx := blockStack.PopStackVar() 291 | appendBody("%s = %s;", ret, idx) 292 | } 293 | idx, btype, _ := blockStack.Pop() 294 | if btype == blockTypeIf { 295 | appendBody("}") 296 | } 297 | if btype != blockTypeLoop { 298 | appendBody("label%d:;", idx) 299 | } 300 | case operators.Br: 301 | if _, _, ret := blockStack.Peep(); ret != "" { 302 | return nil, fmt.Errorf("br with a returning value is not implemented yet") 303 | } 304 | level := instr.Immediates[0].(uint32) 305 | appendBody(gotoOrReturn(int(level))) 306 | case operators.BrIf: 307 | if _, _, ret := blockStack.Peep(); ret != "" { 308 | return nil, fmt.Errorf("br_if with a returning value is not implemented yet") 309 | } 310 | level := instr.Immediates[0].(uint32) 311 | appendBody("if ((%s) != 0)", blockStack.PopStackVar()) 312 | appendBody("{") 313 | blockStack.IndentTemporarily() 314 | appendBody(gotoOrReturn(int(level))) 315 | blockStack.UnindentTemporarily() 316 | appendBody("}") 317 | case operators.BrTable: 318 | if _, _, ret := blockStack.Peep(); ret != "" { 319 | return nil, fmt.Errorf("br_table with a returning value is not implemented yet") 320 | } 321 | appendBody("switch (%s)", blockStack.PopStackVar()) 322 | appendBody("{") 323 | len := int(instr.Immediates[0].(uint32)) 324 | for i := 0; i < len; i++ { 325 | level := int(instr.Immediates[1+i].(uint32)) 326 | appendBody("case %d: %s", i, gotoOrReturn(int(level))) 327 | } 328 | level := int(instr.Immediates[len+1].(uint32)) 329 | appendBody("default: %s", gotoOrReturn(int(level))) 330 | appendBody("}") 331 | case operators.Return: 332 | switch len(sig.ReturnTypes) { 333 | case 0: 334 | appendBody("return;") 335 | default: 336 | appendBody("return %s;", blockStack.PopStackVar()) 337 | } 338 | 339 | case operators.Call: 340 | f := funcs[instr.Immediates[0].(uint32)] 341 | 342 | args := make([]string, len(f.Wasm.Sig.ParamTypes)) 343 | for i := range f.Wasm.Sig.ParamTypes { 344 | args[len(f.Wasm.Sig.ParamTypes)-i-1] = fmt.Sprintf("(%s)", blockStack.PopStackVar()) 345 | } 346 | 347 | var ret string 348 | if len(f.Wasm.Sig.ReturnTypes) > 0 { 349 | ret = fmt.Sprintf("var %s = ", blockStack.PushLhs()) 350 | } 351 | 352 | var imp string 353 | if f.Import { 354 | imp = "import_." 355 | } 356 | appendBody("%s%s%s(%s);", ret, imp, identifierFromString(f.Wasm.Name), strings.Join(args, ", ")) 357 | case operators.CallIndirect: 358 | idx := blockStack.PopStackVar() 359 | typeid := instr.Immediates[0].(uint32) 360 | t := types[typeid] 361 | 362 | args := make([]string, len(t.Sig.ParamTypes)) 363 | for i := range t.Sig.ParamTypes { 364 | args[len(t.Sig.ParamTypes)-i-1] = fmt.Sprintf("(%s)", blockStack.PopStackVar()) 365 | } 366 | 367 | var ret string 368 | if len(t.Sig.ReturnTypes) > 0 { 369 | ret = fmt.Sprintf("var %s = ", blockStack.PushLhs()) 370 | } 371 | 372 | appendBody("%s((Type%d)(funcs_[table_[0][%s]]))(%s);", ret, typeid, idx, strings.Join(args, ", ")) 373 | 374 | case operators.Drop: 375 | blockStack.PopStackVar() 376 | case operators.Select: 377 | cond := blockStack.PopStackVar() 378 | arg1 := blockStack.PopStackVar() 379 | arg0 := blockStack.PopStackVar() 380 | blockStack.PushStackVar(fmt.Sprintf("((%s) != 0) ? (%s) : (%s)", cond, arg0, arg1)) 381 | 382 | case operators.GetLocal: 383 | // Copy the local variable here because local variables can be modified later. 384 | appendBody("var %s = local%d;", blockStack.PushLhs(), instr.Immediates[0]) 385 | case operators.SetLocal: 386 | lhs := fmt.Sprintf("local%d", instr.Immediates[0]) 387 | v := blockStack.PopStackVar() 388 | if lhs != v { 389 | appendBody("%s = (%s);", lhs, v) 390 | } 391 | case operators.TeeLocal: 392 | ls, v := blockStack.PeepStackVar() 393 | for _, l := range ls { 394 | appendBody(l) 395 | } 396 | lhs := fmt.Sprintf("local%d", instr.Immediates[0]) 397 | if lhs != v { 398 | appendBody("%s = (%s);", lhs, v) 399 | } 400 | case operators.GetGlobal: 401 | // Copy the global variable here because global variables can be modified later. 402 | appendBody("var %s = global%d;", blockStack.PushLhs(), instr.Immediates[0]) 403 | case operators.SetGlobal: 404 | appendBody("global%d = (%s);", instr.Immediates[0], blockStack.PopStackVar()) 405 | 406 | case operators.I32Load: 407 | offset := instr.Immediates[1].(uint32) 408 | addr := blockStack.PopStackVar() 409 | appendBody("var %s = mem_.LoadInt32((%s) + %d);", blockStack.PushLhs(), addr, offset) 410 | case operators.I64Load: 411 | offset := instr.Immediates[1].(uint32) 412 | addr := blockStack.PopStackVar() 413 | appendBody("var %s = mem_.LoadInt64((%s) + %d);", blockStack.PushLhs(), addr, offset) 414 | case operators.F32Load: 415 | offset := instr.Immediates[1].(uint32) 416 | addr := blockStack.PopStackVar() 417 | appendBody("var %s = mem_.LoadFloat32((%s) + %d);", blockStack.PushLhs(), addr, offset) 418 | case operators.F64Load: 419 | offset := instr.Immediates[1].(uint32) 420 | addr := blockStack.PopStackVar() 421 | appendBody("var %s = mem_.LoadFloat64((%s) + %d);", blockStack.PushLhs(), addr, offset) 422 | case operators.I32Load8s: 423 | offset := instr.Immediates[1].(uint32) 424 | addr := blockStack.PopStackVar() 425 | appendBody("var %s = (int)mem_.LoadInt8((%s) + %d);", blockStack.PushLhs(), addr, offset) 426 | case operators.I32Load8u: 427 | offset := instr.Immediates[1].(uint32) 428 | addr := blockStack.PopStackVar() 429 | appendBody("var %s = (int)mem_.LoadUint8((%s) + %d);", blockStack.PushLhs(), addr, offset) 430 | case operators.I32Load16s: 431 | offset := instr.Immediates[1].(uint32) 432 | addr := blockStack.PopStackVar() 433 | appendBody("var %s = (int)mem_.LoadInt16((%s) + %d);", blockStack.PushLhs(), addr, offset) 434 | case operators.I32Load16u: 435 | offset := instr.Immediates[1].(uint32) 436 | addr := blockStack.PopStackVar() 437 | appendBody("var %s = (int)mem_.LoadUint16((%s) + %d);", blockStack.PushLhs(), addr, offset) 438 | case operators.I64Load8s: 439 | offset := instr.Immediates[1].(uint32) 440 | addr := blockStack.PopStackVar() 441 | appendBody("var %s = (long)mem_.LoadInt8((%s) + %d);", blockStack.PushLhs(), addr, offset) 442 | case operators.I64Load8u: 443 | offset := instr.Immediates[1].(uint32) 444 | addr := blockStack.PopStackVar() 445 | appendBody("var %s = (long)mem_.LoadUint8((%s) + %d);", blockStack.PushLhs(), addr, offset) 446 | case operators.I64Load16s: 447 | offset := instr.Immediates[1].(uint32) 448 | addr := blockStack.PopStackVar() 449 | appendBody("var %s = (long)mem_.LoadInt16((%s) + %d);", blockStack.PushLhs(), addr, offset) 450 | case operators.I64Load16u: 451 | offset := instr.Immediates[1].(uint32) 452 | addr := blockStack.PopStackVar() 453 | appendBody("var %s = (long)mem_.LoadUint16((%s) + %d);", blockStack.PushLhs(), addr, offset) 454 | case operators.I64Load32s: 455 | offset := instr.Immediates[1].(uint32) 456 | addr := blockStack.PopStackVar() 457 | appendBody("var %s = (long)mem_.LoadInt32((%s) + %d);", blockStack.PushLhs(), addr, offset) 458 | case operators.I64Load32u: 459 | offset := instr.Immediates[1].(uint32) 460 | addr := blockStack.PopStackVar() 461 | appendBody("var %s = (long)mem_.LoadUint32((%s) + %d);", blockStack.PushLhs(), addr, offset) 462 | 463 | case operators.I32Store: 464 | offset := instr.Immediates[1].(uint32) 465 | idx := blockStack.PopStackVar() 466 | addr := blockStack.PopStackVar() 467 | appendBody("mem_.StoreInt32((%s) + %d, %s);", addr, offset, idx) 468 | case operators.I64Store: 469 | offset := instr.Immediates[1].(uint32) 470 | idx := blockStack.PopStackVar() 471 | addr := blockStack.PopStackVar() 472 | appendBody("mem_.StoreInt64((%s) + %d, %s);", addr, offset, idx) 473 | case operators.F32Store: 474 | offset := instr.Immediates[1].(uint32) 475 | idx := blockStack.PopStackVar() 476 | addr := blockStack.PopStackVar() 477 | appendBody("mem_.StoreFloat32((%s) + %d, %s);", addr, offset, idx) 478 | case operators.F64Store: 479 | offset := instr.Immediates[1].(uint32) 480 | idx := blockStack.PopStackVar() 481 | addr := blockStack.PopStackVar() 482 | appendBody("mem_.StoreFloat64((%s) + %d, %s);", addr, offset, idx) 483 | case operators.I32Store8: 484 | offset := instr.Immediates[1].(uint32) 485 | idx := blockStack.PopStackVar() 486 | addr := blockStack.PopStackVar() 487 | appendBody("mem_.StoreInt8((%s) + %d, unchecked((sbyte)(%s)));", addr, offset, idx) 488 | case operators.I32Store16: 489 | offset := instr.Immediates[1].(uint32) 490 | idx := blockStack.PopStackVar() 491 | addr := blockStack.PopStackVar() 492 | appendBody("mem_.StoreInt16((%s) + %d, unchecked((short)(%s)));", addr, offset, idx) 493 | case operators.I64Store8: 494 | offset := instr.Immediates[1].(uint32) 495 | idx := blockStack.PopStackVar() 496 | addr := blockStack.PopStackVar() 497 | appendBody("mem_.StoreInt8((%s) + %d, unchecked((sbyte)(%s)));", addr, offset, idx) 498 | case operators.I64Store16: 499 | offset := instr.Immediates[1].(uint32) 500 | idx := blockStack.PopStackVar() 501 | addr := blockStack.PopStackVar() 502 | appendBody("mem_.StoreInt16((%s) + %d, unchecked((short)(%s)));", addr, offset, idx) 503 | case operators.I64Store32: 504 | offset := instr.Immediates[1].(uint32) 505 | idx := blockStack.PopStackVar() 506 | addr := blockStack.PopStackVar() 507 | appendBody("mem_.StoreInt32((%s) + %d, unchecked((int)(%s)));", addr, offset, idx) 508 | 509 | case operators.CurrentMemory: 510 | blockStack.PushStackVar("mem_.Size") 511 | case operators.GrowMemory: 512 | delta := blockStack.PopStackVar() 513 | // As Grow has side effects, call PushLhs instead of PushStackVar. 514 | v := blockStack.PushLhs() 515 | appendBody("var %s = mem_.Grow(%s);", v, delta) 516 | 517 | case operators.I32Const: 518 | blockStack.PushStackVar(fmt.Sprintf("%d", instr.Immediates[0])) 519 | case operators.I64Const: 520 | blockStack.PushStackVar(fmt.Sprintf("%dL", instr.Immediates[0])) 521 | case operators.F32Const: 522 | if v := instr.Immediates[0].(float32); v == 0 { 523 | blockStack.PushStackVar("0.0f"); 524 | } else { 525 | va := blockStack.PushLhs() 526 | bits := math.Float32bits(v) 527 | appendBody("uint tmp%d = %d; // %f", tmpidx, bits, v) 528 | appendBody("float %s;", va) 529 | appendBody("unsafe { %s = *(float*)(&tmp%d); };", va, tmpidx) 530 | tmpidx++ 531 | } 532 | case operators.F64Const: 533 | if v := instr.Immediates[0].(float64); v == 0 { 534 | blockStack.PushStackVar("0.0"); 535 | } else { 536 | va := blockStack.PushLhs() 537 | bits := math.Float64bits(v) 538 | appendBody("ulong tmp%d = %dUL; // %f", tmpidx, bits, v) 539 | appendBody("double %s;", va) 540 | appendBody("unsafe { %s = *(double*)(&tmp%d); };", va, tmpidx) 541 | tmpidx++ 542 | } 543 | 544 | case operators.I32Eqz: 545 | arg := blockStack.PopStackVar() 546 | blockStack.PushStackVar(fmt.Sprintf("((%s) == 0) ? 1 : 0", arg)) 547 | case operators.I32Eq: 548 | arg1 := blockStack.PopStackVar() 549 | arg0 := blockStack.PopStackVar() 550 | blockStack.PushStackVar(fmt.Sprintf("((%s) == (%s)) ? 1 : 0", arg0, arg1)) 551 | case operators.I32Ne: 552 | arg1 := blockStack.PopStackVar() 553 | arg0 := blockStack.PopStackVar() 554 | blockStack.PushStackVar(fmt.Sprintf("((%s) != (%s)) ? 1 : 0", arg0, arg1)) 555 | case operators.I32LtS: 556 | arg1 := blockStack.PopStackVar() 557 | arg0 := blockStack.PopStackVar() 558 | blockStack.PushStackVar(fmt.Sprintf("((%s) < (%s)) ? 1 : 0", arg0, arg1)) 559 | case operators.I32LtU: 560 | arg1 := blockStack.PopStackVar() 561 | arg0 := blockStack.PopStackVar() 562 | blockStack.PushStackVar(fmt.Sprintf("(unchecked((uint)(%s)) < unchecked((uint)(%s))) ? 1 : 0", arg0, arg1)) 563 | case operators.I32GtS: 564 | arg1 := blockStack.PopStackVar() 565 | arg0 := blockStack.PopStackVar() 566 | blockStack.PushStackVar(fmt.Sprintf("((%s) > (%s)) ? 1 : 0", arg0, arg1)) 567 | case operators.I32GtU: 568 | arg1 := blockStack.PopStackVar() 569 | arg0 := blockStack.PopStackVar() 570 | blockStack.PushStackVar(fmt.Sprintf("(unchecked((uint)(%s)) > unchecked((uint)(%s))) ? 1 : 0", arg0, arg1)) 571 | case operators.I32LeS: 572 | arg1 := blockStack.PopStackVar() 573 | arg0 := blockStack.PopStackVar() 574 | blockStack.PushStackVar(fmt.Sprintf("((%s) <= (%s)) ? 1 : 0", arg0, arg1)) 575 | case operators.I32LeU: 576 | arg1 := blockStack.PopStackVar() 577 | arg0 := blockStack.PopStackVar() 578 | blockStack.PushStackVar(fmt.Sprintf("(unchecked((uint)(%s)) <= unchecked((uint)(%s))) ? 1 : 0", arg0, arg1)) 579 | case operators.I32GeS: 580 | arg1 := blockStack.PopStackVar() 581 | arg0 := blockStack.PopStackVar() 582 | blockStack.PushStackVar(fmt.Sprintf("((%s) >= (%s)) ? 1 : 0", arg0, arg1)) 583 | case operators.I32GeU: 584 | arg1 := blockStack.PopStackVar() 585 | arg0 := blockStack.PopStackVar() 586 | blockStack.PushStackVar(fmt.Sprintf("(unchecked((uint)(%s)) >= unchecked((uint)(%s))) ? 1 : 0", arg0, arg1)) 587 | case operators.I64Eqz: 588 | arg := blockStack.PopStackVar() 589 | blockStack.PushStackVar(fmt.Sprintf("((%s) == 0) ? 1 : 0", arg)) 590 | case operators.I64Eq: 591 | arg1 := blockStack.PopStackVar() 592 | arg0 := blockStack.PopStackVar() 593 | blockStack.PushStackVar(fmt.Sprintf("((%s) == (%s)) ? 1 : 0", arg0, arg1)) 594 | case operators.I64Ne: 595 | arg1 := blockStack.PopStackVar() 596 | arg0 := blockStack.PopStackVar() 597 | blockStack.PushStackVar(fmt.Sprintf("((%s) != (%s)) ? 1 : 0", arg0, arg1)) 598 | case operators.I64LtS: 599 | arg1 := blockStack.PopStackVar() 600 | arg0 := blockStack.PopStackVar() 601 | blockStack.PushStackVar(fmt.Sprintf("((%s) < (%s)) ? 1 : 0", arg0, arg1)) 602 | case operators.I64LtU: 603 | arg1 := blockStack.PopStackVar() 604 | arg0 := blockStack.PopStackVar() 605 | blockStack.PushStackVar(fmt.Sprintf("(unchecked((ulong)(%s)) < unchecked((ulong)(%s))) ? 1 : 0", arg0, arg1)) 606 | case operators.I64GtS: 607 | arg1 := blockStack.PopStackVar() 608 | arg0 := blockStack.PopStackVar() 609 | blockStack.PushStackVar(fmt.Sprintf("((%s) > (%s)) ? 1 : 0", arg0, arg1)) 610 | case operators.I64GtU: 611 | arg1 := blockStack.PopStackVar() 612 | arg0 := blockStack.PopStackVar() 613 | blockStack.PushStackVar(fmt.Sprintf("(unchecked((ulong)(%s)) > unchecked((ulong)(%s))) ? 1 : 0", arg0, arg1)) 614 | case operators.I64LeS: 615 | arg1 := blockStack.PopStackVar() 616 | arg0 := blockStack.PopStackVar() 617 | blockStack.PushStackVar(fmt.Sprintf("((%s) <= (%s)) ? 1 : 0", arg0, arg1)) 618 | case operators.I64LeU: 619 | arg1 := blockStack.PopStackVar() 620 | arg0 := blockStack.PopStackVar() 621 | blockStack.PushStackVar(fmt.Sprintf("(unchecked((ulong)(%s)) <= unchecked((ulong)(%s))) ? 1 : 0", arg0, arg1)) 622 | case operators.I64GeS: 623 | arg1 := blockStack.PopStackVar() 624 | arg0 := blockStack.PopStackVar() 625 | blockStack.PushStackVar(fmt.Sprintf("((%s) >= (%s)) ? 1 : 0", arg0, arg1)) 626 | case operators.I64GeU: 627 | arg1 := blockStack.PopStackVar() 628 | arg0 := blockStack.PopStackVar() 629 | blockStack.PushStackVar(fmt.Sprintf("(unchecked((ulong)(%s)) >= unchecked((ulong)(%s))) ? 1 : 0", arg0, arg1)) 630 | case operators.F32Eq: 631 | arg1 := blockStack.PopStackVar() 632 | arg0 := blockStack.PopStackVar() 633 | blockStack.PushStackVar(fmt.Sprintf("((%s) == (%s)) ? 1 : 0", arg0, arg1)) 634 | case operators.F32Ne: 635 | arg1 := blockStack.PopStackVar() 636 | arg0 := blockStack.PopStackVar() 637 | blockStack.PushStackVar(fmt.Sprintf("((%s) != (%s)) ? 1 : 0", arg0, arg1)) 638 | case operators.F32Lt: 639 | arg1 := blockStack.PopStackVar() 640 | arg0 := blockStack.PopStackVar() 641 | blockStack.PushStackVar(fmt.Sprintf("((%s) < (%s)) ? 1 : 0", arg0, arg1)) 642 | case operators.F32Gt: 643 | arg1 := blockStack.PopStackVar() 644 | arg0 := blockStack.PopStackVar() 645 | blockStack.PushStackVar(fmt.Sprintf("((%s) > (%s)) ? 1 : 0", arg0, arg1)) 646 | case operators.F32Le: 647 | arg1 := blockStack.PopStackVar() 648 | arg0 := blockStack.PopStackVar() 649 | blockStack.PushStackVar(fmt.Sprintf("((%s) <= (%s)) ? 1 : 0", arg0, arg1)) 650 | case operators.F32Ge: 651 | arg1 := blockStack.PopStackVar() 652 | arg0 := blockStack.PopStackVar() 653 | blockStack.PushStackVar(fmt.Sprintf("((%s) >= (%s)) ? 1 : 0", arg0, arg1)) 654 | case operators.F64Eq: 655 | arg1 := blockStack.PopStackVar() 656 | arg0 := blockStack.PopStackVar() 657 | blockStack.PushStackVar(fmt.Sprintf("((%s) == (%s)) ? 1 : 0", arg0, arg1)) 658 | case operators.F64Ne: 659 | arg1 := blockStack.PopStackVar() 660 | arg0 := blockStack.PopStackVar() 661 | blockStack.PushStackVar(fmt.Sprintf("((%s) != (%s)) ? 1 : 0", arg0, arg1)) 662 | case operators.F64Lt: 663 | arg1 := blockStack.PopStackVar() 664 | arg0 := blockStack.PopStackVar() 665 | blockStack.PushStackVar(fmt.Sprintf("((%s) < (%s)) ? 1 : 0", arg0, arg1)) 666 | case operators.F64Gt: 667 | arg1 := blockStack.PopStackVar() 668 | arg0 := blockStack.PopStackVar() 669 | blockStack.PushStackVar(fmt.Sprintf("((%s) > (%s)) ? 1 : 0", arg0, arg1)) 670 | case operators.F64Le: 671 | arg1 := blockStack.PopStackVar() 672 | arg0 := blockStack.PopStackVar() 673 | blockStack.PushStackVar(fmt.Sprintf("((%s) <= (%s)) ? 1 : 0", arg0, arg1)) 674 | case operators.F64Ge: 675 | arg1 := blockStack.PopStackVar() 676 | arg0 := blockStack.PopStackVar() 677 | blockStack.PushStackVar(fmt.Sprintf("((%s) >= (%s)) ? 1 : 0", arg0, arg1)) 678 | 679 | case operators.I32Clz: 680 | arg := blockStack.PopStackVar() 681 | blockStack.PushStackVar(fmt.Sprintf("Bits.LeadingZeros(unchecked((uint)(%s)))", arg)) 682 | case operators.I32Ctz: 683 | arg := blockStack.PopStackVar() 684 | blockStack.PushStackVar(fmt.Sprintf("Bits.TailingZeros(unchecked((uint)(%s)))", arg)) 685 | case operators.I32Popcnt: 686 | arg := blockStack.PopStackVar() 687 | blockStack.PushStackVar(fmt.Sprintf("Bits.OnesCount(unchecked((uint)(%s)))", arg)) 688 | case operators.I32Add: 689 | arg1 := blockStack.PopStackVar() 690 | arg0 := blockStack.PopStackVar() 691 | blockStack.PushStackVar(fmt.Sprintf("(%s) + (%s)", arg0, arg1)) 692 | case operators.I32Sub: 693 | arg1 := blockStack.PopStackVar() 694 | arg0 := blockStack.PopStackVar() 695 | blockStack.PushStackVar(fmt.Sprintf("(%s) - (%s)", arg0, arg1)) 696 | case operators.I32Mul: 697 | arg1 := blockStack.PopStackVar() 698 | arg0 := blockStack.PopStackVar() 699 | blockStack.PushStackVar(fmt.Sprintf("(%s) * (%s)", arg0, arg1)) 700 | case operators.I32DivS: 701 | arg1 := blockStack.PopStackVar() 702 | arg0 := blockStack.PopStackVar() 703 | blockStack.PushStackVar(fmt.Sprintf("(%s) / (%s)", arg0, arg1)) 704 | case operators.I32DivU: 705 | arg1 := blockStack.PopStackVar() 706 | arg0 := blockStack.PopStackVar() 707 | blockStack.PushStackVar(fmt.Sprintf("(int)(unchecked((uint)(%s)) / unchecked((uint)(%s)))", arg0, arg1)) 708 | case operators.I32RemS: 709 | arg1 := blockStack.PopStackVar() 710 | arg0 := blockStack.PopStackVar() 711 | blockStack.PushStackVar(fmt.Sprintf("(%s) %% (%s)", arg0, arg1)) 712 | case operators.I32RemU: 713 | arg1 := blockStack.PopStackVar() 714 | arg0 := blockStack.PopStackVar() 715 | blockStack.PushStackVar(fmt.Sprintf("(int)(unchecked((uint)(%s)) %% unchecked((uint)(%s)))", arg0, arg1)) 716 | case operators.I32And: 717 | arg1 := blockStack.PopStackVar() 718 | arg0 := blockStack.PopStackVar() 719 | blockStack.PushStackVar(fmt.Sprintf("(%s) & (%s)", arg0, arg1)) 720 | case operators.I32Or: 721 | arg1 := blockStack.PopStackVar() 722 | arg0 := blockStack.PopStackVar() 723 | blockStack.PushStackVar(fmt.Sprintf("(%s) | (%s)", arg0, arg1)) 724 | case operators.I32Xor: 725 | arg1 := blockStack.PopStackVar() 726 | arg0 := blockStack.PopStackVar() 727 | blockStack.PushStackVar(fmt.Sprintf("(%s) ^ (%s)", arg0, arg1)) 728 | case operators.I32Shl: 729 | arg1 := blockStack.PopStackVar() 730 | arg0 := blockStack.PopStackVar() 731 | blockStack.PushStackVar(fmt.Sprintf("(%s) << (%s)", arg0, arg1)) 732 | case operators.I32ShrS: 733 | arg1 := blockStack.PopStackVar() 734 | arg0 := blockStack.PopStackVar() 735 | blockStack.PushStackVar(fmt.Sprintf("(%s) >> (%s)", arg0, arg1)) 736 | case operators.I32ShrU: 737 | arg1 := blockStack.PopStackVar() 738 | arg0 := blockStack.PopStackVar() 739 | blockStack.PushStackVar(fmt.Sprintf("(int)(unchecked((uint)(%s)) >> (%s))", arg0, arg1)) 740 | case operators.I32Rotl: 741 | arg1 := blockStack.PopStackVar() 742 | arg0 := blockStack.PopStackVar() 743 | blockStack.PushStackVar(fmt.Sprintf("(int)Bits.RotateLeft(unchecked((uint)(%s)), unchecked((int)(%s)))", arg0, arg1)) 744 | case operators.I32Rotr: 745 | arg1 := blockStack.PopStackVar() 746 | arg0 := blockStack.PopStackVar() 747 | blockStack.PushStackVar(fmt.Sprintf("(int)Bits.RotateLeft(unchecked((uint)(%s)), -unchecked((int)(%s)))", arg0, arg1)) 748 | case operators.I64Clz: 749 | arg := blockStack.PopStackVar() 750 | blockStack.PushStackVar(fmt.Sprintf("(long)Bits.LeadingZeros(unchecked((ulong)(%s)))", arg)) 751 | case operators.I64Ctz: 752 | arg := blockStack.PopStackVar() 753 | blockStack.PushStackVar(fmt.Sprintf("(long)Bits.TailingZeros(unchecked((ulong)(%s)))", arg)) 754 | case operators.I64Popcnt: 755 | arg := blockStack.PopStackVar() 756 | blockStack.PushStackVar(fmt.Sprintf("(long)Bits.OnesCount(unchecked((ulong)(%s)))", arg)) 757 | case operators.I64Add: 758 | arg1 := blockStack.PopStackVar() 759 | arg0 := blockStack.PopStackVar() 760 | blockStack.PushStackVar(fmt.Sprintf("(%s) + (%s)", arg0, arg1)) 761 | case operators.I64Sub: 762 | arg1 := blockStack.PopStackVar() 763 | arg0 := blockStack.PopStackVar() 764 | blockStack.PushStackVar(fmt.Sprintf("(%s) - (%s)", arg0, arg1)) 765 | case operators.I64Mul: 766 | arg1 := blockStack.PopStackVar() 767 | arg0 := blockStack.PopStackVar() 768 | blockStack.PushStackVar(fmt.Sprintf("(%s) * (%s)", arg0, arg1)) 769 | case operators.I64DivS: 770 | arg1 := blockStack.PopStackVar() 771 | arg0 := blockStack.PopStackVar() 772 | blockStack.PushStackVar(fmt.Sprintf("(%s) / (%s)", arg0, arg1)) 773 | case operators.I64DivU: 774 | arg1 := blockStack.PopStackVar() 775 | arg0 := blockStack.PopStackVar() 776 | blockStack.PushStackVar(fmt.Sprintf("(long)(unchecked((ulong)(%s)) / unchecked((ulong)(%s)))", arg0, arg1)) 777 | case operators.I64RemS: 778 | arg1 := blockStack.PopStackVar() 779 | arg0 := blockStack.PopStackVar() 780 | blockStack.PushStackVar(fmt.Sprintf("(%s) %% (%s)", arg0, arg1)) 781 | case operators.I64RemU: 782 | arg1 := blockStack.PopStackVar() 783 | arg0 := blockStack.PopStackVar() 784 | blockStack.PushStackVar(fmt.Sprintf("(long)(unchecked((ulong)(%s)) %% unchecked((ulong)(%s)))", arg0, arg1)) 785 | case operators.I64And: 786 | arg1 := blockStack.PopStackVar() 787 | arg0 := blockStack.PopStackVar() 788 | blockStack.PushStackVar(fmt.Sprintf("(%s) & (%s)", arg0, arg1)) 789 | case operators.I64Or: 790 | arg1 := blockStack.PopStackVar() 791 | arg0 := blockStack.PopStackVar() 792 | blockStack.PushStackVar(fmt.Sprintf("(%s) | (%s)", arg0, arg1)) 793 | case operators.I64Xor: 794 | arg1 := blockStack.PopStackVar() 795 | arg0 := blockStack.PopStackVar() 796 | blockStack.PushStackVar(fmt.Sprintf("(%s) ^ (%s)", arg0, arg1)) 797 | case operators.I64Shl: 798 | arg1 := blockStack.PopStackVar() 799 | arg0 := blockStack.PopStackVar() 800 | blockStack.PushStackVar(fmt.Sprintf("(%s) << unchecked((int)(%s))", arg0, arg1)) 801 | case operators.I64ShrS: 802 | arg1 := blockStack.PopStackVar() 803 | arg0 := blockStack.PopStackVar() 804 | blockStack.PushStackVar(fmt.Sprintf("(%s) >> unchecked((int)(%s))", arg0, arg1)) 805 | case operators.I64ShrU: 806 | arg1 := blockStack.PopStackVar() 807 | arg0 := blockStack.PopStackVar() 808 | blockStack.PushStackVar(fmt.Sprintf("(long)(unchecked((ulong)(%s)) >> unchecked((int)(%s)))", arg0, arg1)) 809 | case operators.I64Rotl: 810 | arg1 := blockStack.PopStackVar() 811 | arg0 := blockStack.PopStackVar() 812 | blockStack.PushStackVar(fmt.Sprintf("(long)Bits.RotateLeft(unchecked((ulong)(%s)), unchecked((int)(%s)))", arg0, arg1)) 813 | case operators.I64Rotr: 814 | arg1 := blockStack.PopStackVar() 815 | arg0 := blockStack.PopStackVar() 816 | blockStack.PushStackVar(fmt.Sprintf("(long)Bits.RotateLeft(unchecked((ulong)(%s)), -unchecked((int)(%s)))", arg0, arg1)) 817 | case operators.F32Abs: 818 | blockStack.PushStackVar(fmt.Sprintf("Math.Abs(%s)", blockStack.PopStackVar())) 819 | case operators.F32Neg: 820 | blockStack.PushStackVar(fmt.Sprintf("-(%s)", blockStack.PopStackVar())) 821 | case operators.F32Ceil: 822 | blockStack.PushStackVar(fmt.Sprintf("Math.Ceiling(%s)", blockStack.PopStackVar())) 823 | case operators.F32Floor: 824 | blockStack.PushStackVar(fmt.Sprintf("Math.Floor(%s)", blockStack.PopStackVar())) 825 | case operators.F32Trunc: 826 | blockStack.PushStackVar(fmt.Sprintf("Math.Truncate(%s)", blockStack.PopStackVar())) 827 | case operators.F32Nearest: 828 | blockStack.PushStackVar(fmt.Sprintf("Math.Round(%s)", blockStack.PopStackVar())) 829 | case operators.F32Sqrt: 830 | blockStack.PushStackVar(fmt.Sprintf("Math.Sqrt(%s)", blockStack.PopStackVar())) 831 | case operators.F32Add: 832 | arg1 := blockStack.PopStackVar() 833 | arg0 := blockStack.PopStackVar() 834 | blockStack.PushStackVar(fmt.Sprintf("(%s) + (%s)", arg0, arg1)) 835 | case operators.F32Sub: 836 | arg1 := blockStack.PopStackVar() 837 | arg0 := blockStack.PopStackVar() 838 | blockStack.PushStackVar(fmt.Sprintf("(%s) - (%s)", arg0, arg1)) 839 | case operators.F32Mul: 840 | arg1 := blockStack.PopStackVar() 841 | arg0 := blockStack.PopStackVar() 842 | blockStack.PushStackVar(fmt.Sprintf("(%s) * (%s)", arg0, arg1)) 843 | case operators.F32Div: 844 | arg1 := blockStack.PopStackVar() 845 | arg0 := blockStack.PopStackVar() 846 | blockStack.PushStackVar(fmt.Sprintf("(%s) / (%s)", arg0, arg1)) 847 | case operators.F32Min: 848 | arg1 := blockStack.PopStackVar() 849 | arg0 := blockStack.PopStackVar() 850 | blockStack.PushStackVar(fmt.Sprintf("Math.Min((%s), (%s))", arg0, arg1)) 851 | case operators.F32Max: 852 | arg1 := blockStack.PopStackVar() 853 | arg0 := blockStack.PopStackVar() 854 | blockStack.PushStackVar(fmt.Sprintf("Math.Max((%s), (%s))", arg0, arg1)) 855 | case operators.F32Copysign: 856 | arg1 := blockStack.PopStackVar() 857 | arg0 := blockStack.PopStackVar() 858 | blockStack.PushStackVar(fmt.Sprintf("CopySign((%s), (%s))", arg0, arg1)) 859 | case operators.F64Abs: 860 | blockStack.PushStackVar(fmt.Sprintf("Math.Abs(%s)", blockStack.PopStackVar())) 861 | case operators.F64Neg: 862 | blockStack.PushStackVar(fmt.Sprintf("-(%s)", blockStack.PopStackVar())) 863 | case operators.F64Ceil: 864 | blockStack.PushStackVar(fmt.Sprintf("Math.Ceiling(%s)", blockStack.PopStackVar())) 865 | case operators.F64Floor: 866 | blockStack.PushStackVar(fmt.Sprintf("Math.Floor(%s)", blockStack.PopStackVar())) 867 | case operators.F64Trunc: 868 | blockStack.PushStackVar(fmt.Sprintf("Math.Truncate(%s)", blockStack.PopStackVar())) 869 | case operators.F64Nearest: 870 | blockStack.PushStackVar(fmt.Sprintf("Math.Round(%s)", blockStack.PopStackVar())) 871 | case operators.F64Sqrt: 872 | blockStack.PushStackVar(fmt.Sprintf("Math.Sqrt(%s)", blockStack.PopStackVar())) 873 | case operators.F64Add: 874 | arg1 := blockStack.PopStackVar() 875 | arg0 := blockStack.PopStackVar() 876 | blockStack.PushStackVar(fmt.Sprintf("(%s) + (%s)", arg0, arg1)) 877 | case operators.F64Sub: 878 | arg1 := blockStack.PopStackVar() 879 | arg0 := blockStack.PopStackVar() 880 | blockStack.PushStackVar(fmt.Sprintf("(%s) - (%s)", arg0, arg1)) 881 | case operators.F64Mul: 882 | arg1 := blockStack.PopStackVar() 883 | arg0 := blockStack.PopStackVar() 884 | blockStack.PushStackVar(fmt.Sprintf("(%s) * (%s)", arg0, arg1)) 885 | case operators.F64Div: 886 | arg1 := blockStack.PopStackVar() 887 | arg0 := blockStack.PopStackVar() 888 | blockStack.PushStackVar(fmt.Sprintf("(%s) / (%s)", arg0, arg1)) 889 | case operators.F64Min: 890 | arg1 := blockStack.PopStackVar() 891 | arg0 := blockStack.PopStackVar() 892 | blockStack.PushStackVar(fmt.Sprintf("Math.Min((%s), (%s))", arg0, arg1)) 893 | case operators.F64Max: 894 | arg1 := blockStack.PopStackVar() 895 | arg0 := blockStack.PopStackVar() 896 | blockStack.PushStackVar(fmt.Sprintf("Math.Max((%s), (%s))", arg0, arg1)) 897 | case operators.F64Copysign: 898 | arg1 := blockStack.PopStackVar() 899 | arg0 := blockStack.PopStackVar() 900 | blockStack.PushStackVar(fmt.Sprintf("CopySign((%s), (%s))", arg0, arg1)) 901 | 902 | case operators.I32WrapI64: 903 | blockStack.PushStackVar(fmt.Sprintf("unchecked((int)(%s))", blockStack.PopStackVar())) 904 | case operators.I32TruncSF32: 905 | blockStack.PushStackVar(fmt.Sprintf("(int)Math.Truncate(%s)", blockStack.PopStackVar())) 906 | case operators.I32TruncUF32: 907 | blockStack.PushStackVar(fmt.Sprintf("(int)((uint)Math.Truncate(%s))", blockStack.PopStackVar())) 908 | case operators.I32TruncSF64: 909 | blockStack.PushStackVar(fmt.Sprintf("(int)Math.Truncate(%s)", blockStack.PopStackVar())) 910 | case operators.I32TruncUF64: 911 | blockStack.PushStackVar(fmt.Sprintf("(int)((uint)Math.Truncate(%s))", blockStack.PopStackVar())) 912 | case operators.I64ExtendSI32: 913 | blockStack.PushStackVar(fmt.Sprintf("(long)(%s)", blockStack.PopStackVar())) 914 | case operators.I64ExtendUI32: 915 | blockStack.PushStackVar(fmt.Sprintf("(long)(unchecked((uint)(%s)))", blockStack.PopStackVar())) 916 | case operators.I64TruncSF32: 917 | blockStack.PushStackVar(fmt.Sprintf("(long)Math.Truncate(%s)", blockStack.PopStackVar())) 918 | case operators.I64TruncUF32: 919 | blockStack.PushStackVar(fmt.Sprintf("(long)((ulong)Math.Truncate(%s))", blockStack.PopStackVar())) 920 | case operators.I64TruncSF64: 921 | blockStack.PushStackVar(fmt.Sprintf("(long)Math.Truncate(%s)", blockStack.PopStackVar())) 922 | case operators.I64TruncUF64: 923 | blockStack.PushStackVar(fmt.Sprintf("(long)((ulong)Math.Truncate(%s))", blockStack.PopStackVar())) 924 | case operators.F32ConvertSI32: 925 | blockStack.PushStackVar(fmt.Sprintf("(float)(%s)", blockStack.PopStackVar())) 926 | case operators.F32ConvertUI32: 927 | blockStack.PushStackVar(fmt.Sprintf("(float)(unchecked((uint)(%s)))", blockStack.PopStackVar())) 928 | case operators.F32ConvertSI64: 929 | blockStack.PushStackVar(fmt.Sprintf("(float)(%s)", blockStack.PopStackVar())) 930 | case operators.F32ConvertUI64: 931 | blockStack.PushStackVar(fmt.Sprintf("(float)(unchecked((ulong)(%s)))", blockStack.PopStackVar())) 932 | case operators.F32DemoteF64: 933 | blockStack.PushStackVar(fmt.Sprintf("(float)(%s)", blockStack.PopStackVar())) 934 | case operators.F64ConvertSI32: 935 | blockStack.PushStackVar(fmt.Sprintf("(double)(%s)", blockStack.PopStackVar())) 936 | case operators.F64ConvertUI32: 937 | blockStack.PushStackVar(fmt.Sprintf("(double)(unchecked((uint)(%s)))", blockStack.PopStackVar())) 938 | case operators.F64ConvertSI64: 939 | blockStack.PushStackVar(fmt.Sprintf("(double)(%s)", blockStack.PopStackVar())) 940 | case operators.F64ConvertUI64: 941 | blockStack.PushStackVar(fmt.Sprintf("(double)(unchecked((ulong)(%s)))", blockStack.PopStackVar())) 942 | case operators.F64PromoteF32: 943 | blockStack.PushStackVar(fmt.Sprintf("(double)(%s)", blockStack.PopStackVar())) 944 | 945 | case operators.I32ReinterpretF32: 946 | return nil, fmt.Errorf("I32ReinterpretF32 is not implemented yet") 947 | case operators.I64ReinterpretF64: 948 | return nil, fmt.Errorf("I64ReinterpretF64 is not implemented yet") 949 | case operators.F32ReinterpretI32: 950 | return nil, fmt.Errorf("F32ReinterpretI32 is not implemented yet") 951 | case operators.F64ReinterpretI64: 952 | return nil, fmt.Errorf("F64ReinterpretI64 is not implemented yet") 953 | 954 | default: 955 | return nil, fmt.Errorf("unexpected operator: %v", instr.Op) 956 | } 957 | } 958 | switch len(sig.ReturnTypes) { 959 | case 0: 960 | // Do nothing. 961 | case 1: 962 | if !blockStack.Empty() && dis.Code[len(dis.Code)-1].Op.Code != operators.Unreachable { 963 | if len(body) == 0 || !strings.HasPrefix(strings.TrimSpace(body[len(body)-1]), "return ") { 964 | appendBody(`return %s;`, blockStack.PopStackVar()) 965 | } 966 | } else { 967 | // Throwing an exception might prevent optimization. Use assertion here. 968 | appendBody(`Debug.Fail("not reached");`) 969 | appendBody(`return 0;`) 970 | } 971 | default: 972 | return nil, fmt.Errorf("unexpected num of return types: %d", len(sig.ReturnTypes)) 973 | } 974 | 975 | return body, nil 976 | } 977 | -------------------------------------------------------------------------------- /internal/stackvar/stackvars.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package stackvar 4 | 5 | import ( 6 | "fmt" 7 | ) 8 | 9 | type StackVars struct { 10 | VarName func(idx int) string 11 | 12 | exprs []string 13 | idx int 14 | peeped bool 15 | } 16 | 17 | func (s *StackVars) PushLhs() string { 18 | n := s.VarName(s.idx) 19 | s.Push(n) 20 | s.idx++ 21 | return n 22 | } 23 | 24 | func (s *StackVars) Push(expr string) { 25 | s.peeped = false 26 | s.exprs = append(s.exprs, expr) 27 | } 28 | 29 | func (s *StackVars) Pop() string { 30 | s.peeped = false 31 | l := s.exprs[len(s.exprs)-1] 32 | s.exprs = s.exprs[:len(s.exprs)-1] 33 | return l 34 | } 35 | 36 | func (s *StackVars) Peep() ([]string, string) { 37 | if s.peeped { 38 | return nil, s.exprs[len(s.exprs)-1] 39 | } 40 | 41 | l := s.Pop() 42 | n := s.PushLhs() 43 | s.peeped = true 44 | return []string{fmt.Sprintf("var %s = (%s);", n, l)}, n 45 | } 46 | 47 | func (s *StackVars) Empty() bool { 48 | return len(s.exprs) == 0 49 | } 50 | -------------------------------------------------------------------------------- /internal/stackvar/stackvars_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package stackvar_test 4 | 5 | import ( 6 | "fmt" 7 | "strings" 8 | "testing" 9 | 10 | . "github.com/hajimehoshi/go2dotnet/internal/stackvar" 11 | ) 12 | 13 | func TestPushPop(t *testing.T) { 14 | s := StackVars{ 15 | VarName: func(idx int) string { 16 | return fmt.Sprintf("stack%d", idx) 17 | }, 18 | } 19 | s.Push("foo") 20 | s.Push("bar") 21 | if got, want := s.Pop(), "bar"; got != want { 22 | t.Errorf("got: %v, want: %v", got, want) 23 | } 24 | if got, want := s.Pop(), "foo"; got != want { 25 | t.Errorf("got: %v, want: %v", got, want) 26 | } 27 | } 28 | 29 | func TestPeep(t *testing.T) { 30 | s := StackVars{ 31 | VarName: func(idx int) string { 32 | return fmt.Sprintf("stack%d", idx) 33 | }, 34 | } 35 | s.Push("foo") 36 | s.Push("bar") 37 | 38 | ls, v := s.Peep() 39 | if got, want := strings.Join(ls, "\n"), "var stack0 = (bar);"; got != want { 40 | t.Errorf("got: %v, want: %v", got, want) 41 | } 42 | if got, want := v, "stack0"; got != want { 43 | t.Errorf("got: %v, want: %v", got, want) 44 | } 45 | 46 | ls, v = s.Peep() 47 | if got, want := strings.Join(ls, "\n"), ""; got != want { 48 | t.Errorf("got: %v, want: %v", got, want) 49 | } 50 | if got, want := v, "stack0"; got != want { 51 | t.Errorf("got: %v, want: %v", got, want) 52 | } 53 | 54 | if got, want := s.Pop(), "stack0"; got != want { 55 | t.Errorf("got: %v, want: %v", got, want) 56 | } 57 | 58 | ls, v = s.Peep() 59 | if got, want := strings.Join(ls, "\n"), "var stack1 = (foo);"; got != want { 60 | t.Errorf("got: %v, want: %v", got, want) 61 | } 62 | if got, want := v, "stack1"; got != want { 63 | t.Errorf("got: %v, want: %v", got, want) 64 | } 65 | 66 | ls, v = s.Peep() 67 | if got, want := strings.Join(ls, "\n"), ""; got != want { 68 | t.Errorf("got: %v, want: %v", got, want) 69 | } 70 | if got, want := v, "stack1"; got != want { 71 | t.Errorf("got: %v, want: %v", got, want) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /test/binding/binding.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | true 7 | 8 | 9 | 10 | 11 | Go2DotNet.Test.Binding.AutoGen.MemInitData 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/binding/binding_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package main 4 | 5 | import ( 6 | "bytes" 7 | "syscall/js" 8 | "testing" 9 | ) 10 | 11 | func TestStatic(t *testing.T) { 12 | cls := js.Global().Get(".net").Get("Go2DotNet.Test.Binding.Testing") 13 | 14 | cls.Set("StaticField", 1) 15 | cls.Set("StaticProperty", 2) 16 | if got, want := cls.Get("StaticField").Int(), 1; got != want { 17 | t.Errorf("got: %v, want: %v", got, want) 18 | } 19 | if got, want := cls.Get("StaticProperty").Int(), 2; got != want { 20 | t.Errorf("got: %v, want: %v", got, want) 21 | } 22 | 23 | if got, want := cls.Call("StaticMethod", "Hello from Go").String(), "Hello from Go and C#"; got != want { 24 | t.Errorf("got: %v, want: %v", got, want) 25 | } 26 | } 27 | 28 | func TestInstance(t *testing.T) { 29 | cls := js.Global().Get(".net").Get("Go2DotNet.Test.Binding.Testing") 30 | inst := cls.New("foo", 3) 31 | 32 | inst.Set("InstanceField", 4) 33 | inst.Set("InstanceProperty", 5) 34 | if got, want := inst.Get("InstanceField").Int(), 4; got != want { 35 | t.Errorf("got: %v, want: %v", got, want) 36 | } 37 | if got, want := inst.Get("InstanceProperty").Int(), 5; got != want { 38 | t.Errorf("got: %v, want: %v", got, want) 39 | } 40 | 41 | if got, want := inst.Call("InstanceMethod", "bar").String(), "str: foo, num: 3, arg: bar"; got != want { 42 | t.Errorf("got: %v, want: %v", got, want) 43 | } 44 | } 45 | 46 | func TestFunc(t *testing.T) { 47 | cls := js.Global().Get(".net").Get("Go2DotNet.Test.Binding.Testing") 48 | inst := cls.New("", 0) 49 | 50 | f := js.FuncOf(func(this js.Value, args []js.Value) interface{} { 51 | return args[0].String() + "+go" 52 | }) 53 | defer f.Release() 54 | if got, want := inst.Call("InvokeGo", f, "arg").String(), "arg+go"; got != want { 55 | t.Errorf("got: %v, want: %v", got, want) 56 | } 57 | } 58 | 59 | func TestFuncNoArgs(t *testing.T) { 60 | cls := js.Global().Get(".net").Get("Go2DotNet.Test.Binding.Testing") 61 | inst := cls.New("", 0) 62 | 63 | a := 0 64 | f := js.FuncOf(func(this js.Value, args []js.Value) interface{} { 65 | a = 1 66 | return nil 67 | }) 68 | defer f.Release() 69 | inst.Call("InvokeGoWithoutArgs", f) 70 | if got, want := a, 1; got != want { 71 | t.Errorf("got: %v, want: %v", got, want) 72 | } 73 | } 74 | 75 | func TestFuncReturningZero(t *testing.T) { 76 | cls := js.Global().Get(".net").Get("Go2DotNet.Test.Binding.Testing") 77 | inst := cls.New("", 0) 78 | 79 | f := js.FuncOf(func(this js.Value, args []js.Value) interface{} { 80 | return 0 81 | }) 82 | defer f.Release() 83 | if got, want := inst.Call("InvokeGoAndReturnDouble", f).Int(), 0; got != want { 84 | t.Errorf("got: %v, want: %v", got, want) 85 | } 86 | } 87 | 88 | func TestCopyBytes(t *testing.T) { 89 | cls := js.Global().Get(".net").Get("Go2DotNet.Test.Binding.Testing") 90 | inst := cls.New("", 0) 91 | 92 | bs := []byte{1, 2, 3} 93 | arr := js.Global().Get("Uint8Array").New(len(bs)) 94 | if got, want := js.CopyBytesToJS(arr, bs), 3; got != want { 95 | t.Errorf("got: %v, want: %v", got, want) 96 | } 97 | arr2 := inst.Call("DoubleBytes", arr) 98 | bs2 := make([]byte, arr2.Length()) 99 | if got, want := js.CopyBytesToGo(bs2, arr2), 3; got != want { 100 | t.Errorf("got: %v, want: %v", got, want) 101 | } 102 | if got, want := bs2, []byte{2, 4, 6}; !bytes.Equal(got, want) { 103 | t.Errorf("got: %v, want: %v", got, want) 104 | } 105 | } 106 | 107 | func TestReturnAndPassInstance(t *testing.T) { 108 | cls := js.Global().Get(".net").Get("Go2DotNet.Test.Binding.Testing") 109 | if got, want := cls.Call("StaticMethodToReturnInstance", "foo", 1).Call("InstanceMethod", "bar").String(), "str: foo, num: 1, arg: bar"; got != want { 110 | t.Errorf("got: %v, want: %v", got, want) 111 | } 112 | 113 | inst := cls.New("foo", 1) 114 | inst2 := inst.Call("Clone") 115 | 116 | if got, want := inst2.Call("InstanceMethod", "bar").String(), "str: foo, num: 1, arg: bar"; got != want { 117 | t.Errorf("got: %v, want: %v", got, want) 118 | } 119 | 120 | inst3 := cls.New("bar", 2) 121 | inst3.Call("CopyFrom", inst) 122 | if got, want := inst3.Call("InstanceMethod", "bar").String(), "str: foo, num: 1, arg: bar"; got != want { 123 | t.Errorf("got: %v, want: %v", got, want) 124 | } 125 | 126 | inst3.Set("InstanceObjectProperty", inst) 127 | inst = inst3.Get("InstanceObjectProperty") 128 | if got, want := inst.Call("InstanceMethod", "bar").String(), "str: foo, num: 1, arg: bar"; got != want { 129 | t.Errorf("got: %v, want: %v", got, want) 130 | } 131 | 132 | // TODO: Add tests fields and properties to treat instances. 133 | } 134 | 135 | func TestPredefined(t *testing.T) { 136 | cls := js.Global().Get(".net").Get("Go2DotNet.Test.Binding.Testing") 137 | if got, want := cls.Call("Bool", true).Bool(), true; got != want { 138 | t.Errorf("got: %v, want: %v", got, want) 139 | } 140 | if got, want := cls.Call("Bool", false).Bool(), false; got != want { 141 | t.Errorf("got: %v, want: %v", got, want) 142 | } 143 | if got, want := cls.Call("Null").IsNull(), true; got != want { 144 | t.Errorf("got: %v, want: %v", got, want) 145 | } 146 | if got, want := cls.Call("NaN").IsNaN(), true; got != want { 147 | t.Errorf("got: %v, want: %v", got, want) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /test/binding/main.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | #pragma warning disable 649 4 | 5 | using System; 6 | using Go2DotNet.Test.Binding.AutoGen; 7 | 8 | namespace Go2DotNet.Test.Binding 9 | { 10 | public class Testing 11 | { 12 | // All numerics from Go are treated as double due to syscall/js. 13 | internal static double StaticField; 14 | 15 | internal static double StaticProperty 16 | { 17 | get { return staticProperty; } 18 | set { staticProperty = value; } 19 | } 20 | static double staticProperty; 21 | 22 | internal static string StaticMethod(string arg) 23 | { 24 | return arg + " and C#"; 25 | } 26 | 27 | internal static Testing StaticMethodToReturnInstance(string str, double num) 28 | { 29 | return new Testing(str, num); 30 | } 31 | 32 | internal static bool Bool(bool val) 33 | { 34 | return val; 35 | } 36 | 37 | internal static object Null() 38 | { 39 | return null; 40 | } 41 | 42 | internal static double NaN() 43 | { 44 | return double.NaN; 45 | } 46 | 47 | public Testing(string str, double num) 48 | { 49 | this.str = str; 50 | this.num = num; 51 | } 52 | 53 | internal double InstanceField; 54 | 55 | internal double InstanceProperty 56 | { 57 | get { return instanceProperty; } 58 | set { instanceProperty = value; } 59 | } 60 | private double instanceProperty; 61 | 62 | internal string InstanceMethod(string arg) 63 | { 64 | return $"str: {this.str}, num: {this.num}, arg: {arg}"; 65 | } 66 | 67 | internal object InvokeGo(IInvokable a, string arg) 68 | { 69 | return a.Invoke(new object[] { arg }); 70 | } 71 | 72 | internal object InvokeGoWithoutArgs(IInvokable a) 73 | { 74 | return a.Invoke(null); 75 | } 76 | 77 | internal double InvokeGoAndReturnDouble(IInvokable a) 78 | { 79 | return (double)a.Invoke(null); 80 | } 81 | 82 | internal byte[] DoubleBytes(byte[] bytes) 83 | { 84 | byte[] newBytes = new byte[bytes.Length]; 85 | for (int i = 0; i < bytes.Length; i++) 86 | { 87 | newBytes[i] = (byte)(bytes[i] * 2); 88 | } 89 | return newBytes; 90 | } 91 | 92 | internal Testing InstanceObjectProperty { get; set; } 93 | 94 | internal Testing Clone() 95 | { 96 | return new Testing(this.str, this.num); 97 | } 98 | 99 | internal void CopyFrom(Testing rhs) 100 | { 101 | this.str = rhs.str; 102 | this.num = rhs.num; 103 | } 104 | 105 | private string str; 106 | private double num; 107 | } 108 | 109 | class Program 110 | { 111 | static void Main(string[] args) 112 | { 113 | Go go = new Go(); 114 | go.Run(args); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /test/binding/run.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | env GOOS=js GOARCH=wasm go test -c -o binding.wasm -trimpath . 3 | rm -rf autogen 4 | go run ../../cmd/gowasm2csharp -out autogen -wasm binding.wasm -namespace Go2DotNet.Test.Binding.AutoGen 5 | dotnet run . -- -test.v 6 | -------------------------------------------------------------------------------- /test/stdlib/main.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | using System; 4 | using Go2DotNet.Test.StdLib.AutoGen; 5 | 6 | namespace Go2DotNet.Test.StdLib 7 | { 8 | class Program 9 | { 10 | static void Main(string[] args) 11 | { 12 | Go go = new Go(); 13 | go.Run(args); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/stdlib/run.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | echo "# Test $1" 3 | env GOOS=js GOARCH=wasm go test -c -o test.wasm -trimpath $1 4 | rm -rf autogen 5 | go run ../../cmd/gowasm2csharp -out autogen -wasm test.wasm -namespace Go2DotNet.Test.StdLib.AutoGen 6 | shift 7 | dotnet run -c Release . -- $* 8 | -------------------------------------------------------------------------------- /test/stdlib/stdlib.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | true 7 | 8 | 9 | 10 | 11 | Go2DotNet.Test.StdLib.AutoGen.MemInitData 12 | 13 | 14 | 15 | 16 | --------------------------------------------------------------------------------