├── .gitignore ├── .gitmodules ├── .travis.yml ├── LICENSE ├── README.md ├── WasmMeta ├── Meta │ ├── Heap.cs │ ├── Module.cs │ └── Test.cs ├── Properties │ └── AssemblyInfo.cs └── WasmMeta.csproj ├── WasmSExprEmitter.sln ├── WasmSExprEmitter ├── Analyzer.cs ├── AssemblyEmitter.cs ├── AstEmitter.cs ├── CustomNodes.cs ├── EmitterFactory.cs ├── FunctionTransformer.cs ├── NullAssemblyEmitter.cs ├── NullAstEmitter.cs ├── Util.cs └── WasmSExprEmitter.csproj ├── ilwasm.jsilconfig ├── linux-build.sh ├── runtest-d8.sh ├── runtests.py ├── third_party └── tests │ ├── BlendFunc.cs │ ├── CountUp.cs │ ├── FannkuchRedux.cs │ ├── Goto.cs │ ├── Gradient.cs │ ├── NBody.cs │ ├── Puzzle.cs │ ├── Raytracer.cs │ ├── Sieve.cs │ ├── Strcat.cs │ ├── Strings.cs │ ├── lib │ ├── Stdout.cs │ └── Targa.cs │ └── llvm_license.txt └── windows-build.bat /.gitignore: -------------------------------------------------------------------------------- 1 | *.suo 2 | obj 3 | bin 4 | output 5 | third_party/tests/*.exe 6 | third_party/tests/*.dll 7 | third_party/tests/*.xml 8 | third_party/tests/*.pdb 9 | third_party/tests/*.mdb 10 | *.user 11 | last-test-run.log -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/JSIL"] 2 | path = third_party/JSIL 3 | url = https://github.com/sq/JSIL 4 | [submodule "wasm-spec"] 5 | path = third_party/wasm-spec 6 | url = https://github.com/kg/spec.git 7 | branch = stdio_write 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | 3 | install: 4 | - git submodule update --init --recursive # really? REALLY? 5 | - export NODEJS=$(dirname `nvm which $NODEJS_VERSION`) 6 | - export FSHARPINSTALLDIR=$(dirname /third_party/JSIL/packages/FSharp.Compiler.Tools.4.0.0.1/tools/) 7 | - nuget restore ./third_party/JSIL/JSIL.sln 8 | - nuget install NUnit.Runners -Version 2.6.4 -OutputDirectory ./third_party/JSIL/packages 9 | - nuget install FSharp.Compiler.Tools -Version 4.0.0.1 -OutputDirectory ./third_party/JSIL/packages 10 | - ./third_party/JSIL/linux-build.sh 11 | - ./linux-build.sh 12 | - ./third_party/wasm-spec/ml-proto/runtests.py --build 13 | 14 | script: 15 | - ./runtests.py -------------------------------------------------------------------------------- /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 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ilwasm 2 | Maps a subset of [.NET CIL](https://en.wikipedia.org/wiki/Common_Intermediate_Language) to WebAssembly S-expressions. Implemented as a custom code generator backend for the [JSIL](https://github.com/sq/JSIL) CIL -> JS compiler. 3 | 4 | ## Building on Windows 5 | Make sure you have Visual Studio 2015 (the free Community edition is fine). Build JSIL (```third_party/JSIL/JSIL.sln```), then build WasmSExprEmitter & WasmMeta (```WasmSExprEmitter.sln```). 6 | 7 | ## Building on Linux 8 | A recent version of Mono is required for C#6 support and NuGet compatibility. If you don't have a recent version of Mono installed, run ```third_party/JSIL/install-mono.sh``` once to download & build from source, then do ```source third_party/JSIL/activate-mono.sh``` to load it into your current environment. 9 | 10 | Build JSIL (```third_party/JSIL/linux-build.sh```), then build WasmSExprEmitter & WasmMeta (```linux-build.sh```). 11 | 12 | ## Writing tests 13 | Tests are placed in the ```third_party/tests``` directory as .cs files. A test has a single entry point (typically ```Program.Main```) that accepts no arguments and has no return value. 14 | 15 | The entry point's body must contain only calls to APIs from ```Wasm.Test```, like ```SetHeapSize``` or ```AssertEq```. Constants are fine but locals, branching or other behavior are not allowed in the entry point, as it is converted into top-level wasm assertions. 16 | 17 | For more sophisticated tests, ```AssertHeapEq``` and ```AssertHeapEqFile``` can be used to assert against the contents of the wasm heap. ```AssertHeapEqFile``` will produce the actual contents of the heap as a file in the ```output``` directory during test runs, for debugging. 18 | 19 | ### Exports 20 | 21 | A test can export static methods with the Wasm.Module.Export attribute, like so: 22 | ```csharp 23 | [Export] 24 | public static void Exported () {} 25 | [Export("customName")] 26 | public static void AlsoExported () {} 27 | ``` 28 | Exported methods can be invoked by name in the entry point by using the ```Test.AssertEq``` and ```Test.Invoke``` APIs. 29 | 30 | ### Heap 31 | 32 | Tests requiring access to the heap must first reserve it using the ```Test.SetHeapSize``` API. To read/write the heap use the fields of the ```Wasm.Heap``` class, like so: 33 | ```csharp 34 | Wasm.Heap.I32[offset] = value; 35 | value = Wasm.Heap.U8[@base, offset]; 36 | ``` 37 | 38 | The heap is not accessible externally (i.e. in the entry point) so to perform assertions against heap data, you must expose the heap via an exported function. 39 | 40 | You can get the base of the heap as a pointer using the Base property of the heap fields, i.e. ```Wasm.Heap.I32.Base```. 41 | 42 | ### Fields and properties 43 | 44 | Static fields of supported types work. To access a field's value from outside, export accessor methods, or simply define it as an exported property: 45 | ```csharp 46 | [Export] 47 | public static int Property { get; set; } 48 | ``` 49 | 50 | ### Types 51 | 52 | Currently only integral and floating-point types are supported. Functions and variables of other types will be ignored (or outright rejected) by ilwasm. 53 | 54 | ### Future features 55 | 56 | * Struct fields 57 | * Struct locals 58 | * ref/out parameters 59 | -------------------------------------------------------------------------------- /WasmMeta/Meta/Heap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.IO; 8 | using JSIL.Meta; 9 | 10 | namespace Wasm { 11 | [JSExternal] 12 | public unsafe static class Heap { 13 | internal struct State { 14 | internal readonly GCHandle BufferPin; 15 | internal readonly byte[] Buffer; 16 | internal readonly byte* pBuffer; 17 | 18 | public State (int size) { 19 | Buffer = new byte[size]; 20 | BufferPin = GCHandle.Alloc(Buffer, GCHandleType.Pinned); 21 | pBuffer = (byte *)BufferPin.AddrOfPinnedObject(); 22 | } 23 | 24 | public void Dispose () { 25 | if (BufferPin.IsAllocated) 26 | BufferPin.Free(); 27 | } 28 | } 29 | 30 | // FIXME: Put this somewhere else 31 | private static Stream Stdout; 32 | 33 | private static State Allocated; 34 | public static readonly HeapU8 U8 = new HeapU8(); 35 | public static readonly HeapI32 I32 = new HeapI32(); 36 | 37 | public static void SetHeapSize (int size) { 38 | Allocated.Dispose(); 39 | Allocated = new State(size); 40 | } 41 | 42 | internal static byte* Access (int desiredOffset) { 43 | if (Allocated.Buffer == null) 44 | throw new InvalidOperationException("Heap not allocated"); 45 | else if (desiredOffset >= Allocated.Buffer.Length) 46 | throw new Exception(string.Format("Attempted to access offset {0} but heap size is {1}", desiredOffset, Allocated.Buffer.Length)); 47 | 48 | return Allocated.pBuffer; 49 | } 50 | 51 | private static IEnumerable GetHeapRange (int offset, int count) { 52 | var indices = Enumerable.Range(0, count); 53 | var bytes = (from i in indices select U8[offset, i]); 54 | return bytes; 55 | } 56 | 57 | public static void SetStdout (string filename) { 58 | if (Stdout != null) 59 | throw new Exception("Stdout already open"); 60 | 61 | Directory.CreateDirectory(Path.Combine("output", "cs-data")); 62 | // FIXME: Leak 63 | Stdout = File.OpenWrite(Path.Combine("output", "cs-data", filename)); 64 | } 65 | 66 | public static void Write (int offset, int count) { 67 | if (Stdout == null) 68 | throw new Exception("No stdout open"); 69 | 70 | var bytes = GetHeapRange(offset, count).ToArray(); 71 | Stdout.Write(bytes, 0, count); 72 | } 73 | } 74 | 75 | [JSExternal] 76 | [JSImmutable] 77 | public unsafe class HeapI32 { 78 | public int* Base { 79 | get { 80 | return (int*)Heap.Access(0); 81 | } 82 | } 83 | 84 | public int this [int offset] { 85 | get { 86 | int* ints = (int*)Heap.Access(offset * 4); 87 | return ints[offset]; 88 | } 89 | 90 | set { 91 | int* ints = (int*)Heap.Access(offset * 4); 92 | ints[offset] = value; 93 | } 94 | } 95 | 96 | public int this [int @base, int offset] { 97 | get { 98 | return this[@base + offset]; 99 | } 100 | set { 101 | this[@base + offset] = value; 102 | } 103 | } 104 | } 105 | 106 | [JSExternal] 107 | [JSImmutable] 108 | public unsafe class HeapU8 { 109 | public byte* Base { 110 | get { 111 | return (byte*)Heap.Access(0); 112 | } 113 | } 114 | 115 | public byte this [int offset] { 116 | get { 117 | var bytes = Heap.Access(offset); 118 | return bytes[offset]; 119 | } 120 | 121 | set { 122 | var bytes = Heap.Access(offset); 123 | bytes[offset] = value; 124 | } 125 | } 126 | 127 | public byte this [int @base, int offset] { 128 | get { 129 | return this[@base + offset]; 130 | } 131 | set { 132 | this[@base + offset] = value; 133 | } 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /WasmMeta/Meta/Module.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Wasm.Module { 8 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)] 9 | public class ExportAttribute : Attribute { 10 | public readonly string Name; 11 | 12 | public ExportAttribute (string name = null) { 13 | Name = name; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /WasmMeta/Meta/Test.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using JSIL.Meta; 8 | 9 | namespace Wasm { 10 | [JSExternal] 11 | public static class Test { 12 | public static readonly bool QuietMode; 13 | 14 | private static bool HeaderPrinted; 15 | 16 | static Test () { 17 | var args = Environment.GetCommandLineArgs(); 18 | QuietMode = args.Contains("--quiet"); 19 | } 20 | 21 | [JSIgnore] 22 | private class ExportTable { 23 | private static readonly Dictionary Cache = 24 | new Dictionary(); 25 | 26 | private readonly Dictionary Exports = 27 | new Dictionary(StringComparer.InvariantCultureIgnoreCase); 28 | 29 | private ExportTable (Assembly assembly) { 30 | var flags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; 31 | foreach (var type in assembly.GetTypes()) { 32 | var staticMethods = type.GetMethods(flags); 33 | var staticProperties = type.GetProperties(flags); 34 | 35 | foreach (var prop in staticProperties) { 36 | var exportAttr = prop.GetCustomAttribute(); 37 | if (exportAttr == null) 38 | continue; 39 | 40 | var name = exportAttr.Name ?? prop.Name; 41 | 42 | if (prop.GetMethod != null) 43 | Exports.Add("get_" + name, prop.GetMethod); 44 | if (prop.SetMethod != null) 45 | Exports.Add("set_" + name, prop.SetMethod); 46 | } 47 | 48 | foreach (var method in staticMethods) { 49 | var exportAttr = method.GetCustomAttribute(); 50 | if (exportAttr == null) 51 | continue; 52 | 53 | var name = exportAttr.Name ?? method.Name; 54 | Exports.Add(name, method); 55 | } 56 | } 57 | } 58 | 59 | public MethodInfo this [string name] { 60 | get { 61 | MethodInfo result; 62 | if (!Exports.TryGetValue(name, out result)) 63 | throw new ArgumentException("No export named '" + name + "'", "name"); 64 | 65 | return result; 66 | } 67 | } 68 | 69 | public static ExportTable GetExports (Assembly assembly) { 70 | ExportTable result; 71 | 72 | if (!Cache.TryGetValue(assembly, out result)) 73 | Cache.Add(assembly, result = new ExportTable(assembly)); 74 | 75 | return result; 76 | } 77 | } 78 | 79 | private static void PrintHeader (Assembly assembly) { 80 | if (HeaderPrinted) 81 | return; 82 | 83 | HeaderPrinted = true; 84 | var testName = System.IO.Path.GetFileNameWithoutExtension(assembly.Location); 85 | Console.WriteLine("// {0}", testName); 86 | } 87 | 88 | public static void AssertReturn (object expected, string exportedFunctionName, params object[] values) { 89 | var assembly = Assembly.GetCallingAssembly(); 90 | var exports = ExportTable.GetExports(assembly); 91 | var export = exports[exportedFunctionName]; 92 | object result; 93 | 94 | try { 95 | result = export.Invoke(null, values); 96 | } catch (Exception exc) { 97 | throw new Exception(string.Format("Invoke('{0}', ...) failed ", exportedFunctionName), exc); 98 | } 99 | 100 | var expectedText = Convert.ToString(expected); 101 | var actualText = Convert.ToString(result); 102 | 103 | var passed = (expectedText == actualText); 104 | if (!passed) 105 | Environment.ExitCode = 1; 106 | 107 | if (QuietMode && passed) 108 | return; 109 | 110 | PrintHeader(assembly); 111 | Console.WriteLine( 112 | "(invoke \"{1}\" {2}){0}" + 113 | "-> {3} '{4}' == '{5}'", 114 | Environment.NewLine, 115 | exportedFunctionName, 116 | string.Join(" ", values), 117 | passed 118 | ? "pass" 119 | : "fail", 120 | expectedText, 121 | actualText 122 | ); 123 | } 124 | 125 | public static void Invoke (string exportedFunctionName, params object[] values) { 126 | var assembly = Assembly.GetCallingAssembly(); 127 | var exports = ExportTable.GetExports(assembly); 128 | var export = exports[exportedFunctionName]; 129 | object result; 130 | 131 | try { 132 | result = export.Invoke(null, values); 133 | } catch (Exception exc) { 134 | throw new Exception(string.Format("Invoke('{0}', ...) failed ", exportedFunctionName), exc); 135 | } 136 | 137 | if (QuietMode && (export.ReturnType.FullName == "System.Void")) 138 | return; 139 | 140 | PrintHeader(assembly); 141 | if (export.ReturnType.Name != "Void") 142 | Console.WriteLine( 143 | "(invoke \"{1}\" {2}){0}" + 144 | "-> {3}", 145 | Environment.NewLine, 146 | exportedFunctionName, 147 | string.Join(" ", values), 148 | result 149 | ); 150 | else 151 | Console.WriteLine( 152 | "(invoke \"{0}\" {1})", 153 | exportedFunctionName, 154 | string.Join(" ", values) 155 | ); 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /WasmMeta/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("WasmMeta")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("WasmMeta")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("5395b317-3b75-4f1b-9d89-1ba35d2fd33b")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /WasmMeta/WasmMeta.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {5395B317-3B75-4F1B-9D89-1BA35D2FD33B} 8 | Library 9 | Properties 10 | WasmMeta 11 | WasmMeta 12 | v4.5 13 | 512 14 | bin\ 15 | true 16 | 17 | 18 | true 19 | full 20 | false 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | ..\third_party\JSIL\bin\JSIL.Meta.dll 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 62 | -------------------------------------------------------------------------------- /WasmSExprEmitter.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.23107.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WasmSExprEmitter", "WasmSExprEmitter\WasmSExprEmitter.csproj", "{36B188A1-1C32-4895-BF73-CAE32CBC91AC}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WasmMeta", "WasmMeta\WasmMeta.csproj", "{5395B317-3B75-4F1B-9D89-1BA35D2FD33B}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {36B188A1-1C32-4895-BF73-CAE32CBC91AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {36B188A1-1C32-4895-BF73-CAE32CBC91AC}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {36B188A1-1C32-4895-BF73-CAE32CBC91AC}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {36B188A1-1C32-4895-BF73-CAE32CBC91AC}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {5395B317-3B75-4F1B-9D89-1BA35D2FD33B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {5395B317-3B75-4F1B-9D89-1BA35D2FD33B}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {5395B317-3B75-4F1B-9D89-1BA35D2FD33B}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {5395B317-3B75-4F1B-9D89-1BA35D2FD33B}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /WasmSExprEmitter/Analyzer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using JSIL; 7 | using JSIL.Compiler.Extensibility; 8 | using JSIL.Internal; 9 | using Mono.Cecil; 10 | 11 | namespace WasmSExprEmitter { 12 | public class Analyzer : IAnalyzer { 13 | private readonly HashSet PreviousWarnings = new HashSet(); 14 | 15 | public string SettingsKey { 16 | get { 17 | return "WasmSExpr"; 18 | } 19 | } 20 | 21 | public void Analyze (AssemblyTranslator translator, AssemblyDefinition[] assemblies, TypeInfoProvider typeInfoProvider) { 22 | } 23 | 24 | public void SetConfiguration (IDictionary analyzerSettings) { 25 | } 26 | 27 | private static Tuple Skip(bool cond, string reason = null) { 28 | return new Tuple(cond, reason); 29 | } 30 | 31 | public static Tuple ShouldSkipMember (MemberReference member) { 32 | var fr = member as FieldReference; 33 | var mr = member as MethodReference; 34 | 35 | if (fr != null) 36 | return Skip(WasmUtil.PickTypeKeyword(fr.FieldType) == null, "Unsupported field type"); 37 | 38 | if (mr != null) 39 | return Skip(WasmUtil.PickTypeKeyword(mr.ReturnType) == null, "Unsupported return type"); 40 | 41 | return Skip(false); 42 | } 43 | 44 | private bool ShouldSilence (AssemblyTranslator translator, MemberReference member) { 45 | var typeInfo = translator.TypeInfoProvider.GetTypeInformation(member.DeclaringType); 46 | if (typeInfo.IsIgnored || typeInfo.IsExternal) 47 | return true; 48 | 49 | var asm = member.DeclaringType.Module.Assembly; 50 | if (translator.IsStubbed(asm) || translator.IsIgnored(asm)) 51 | return true; 52 | 53 | return false; 54 | } 55 | 56 | private void Warn (AssemblyTranslator translator, MemberReference member, string reason) { 57 | if (ShouldSilence(translator, member)) 58 | return; 59 | 60 | if (PreviousWarnings.Contains(member.FullName)) 61 | return; 62 | 63 | PreviousWarnings.Add(member.FullName); 64 | Console.WriteLine("// Skipping {0}: {1}", member.Name, reason); 65 | } 66 | 67 | bool IAnalyzer.ShouldSkipMember (AssemblyTranslator translator, MemberReference member) { 68 | var result = ShouldSkipMember(member); 69 | 70 | if (result.Item1) 71 | Warn(translator, member, result.Item2); 72 | 73 | return result.Item1; 74 | } 75 | 76 | public IEnumerable FunctionTransformers { 77 | get { 78 | yield return new FunctionTransformer(); 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /WasmSExprEmitter/AssemblyEmitter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using Mono.Cecil; 6 | using JSIL; 7 | using JSIL.Ast; 8 | using JSIL.Compiler.Extensibility; 9 | using JSIL.Internal; 10 | using JSIL.Translator; 11 | using ICSharpCode.Decompiler; 12 | using JSIL.Transforms; 13 | 14 | namespace WasmSExprEmitter { 15 | class WasmSExprAssemblyEmitter : IAssemblyEmitter { 16 | private struct StringTableEntry { 17 | public byte[] Bytes; 18 | public int Offset; 19 | public int SizeBytes; 20 | 21 | public StringTableEntry (WasmSExprAssemblyEmitter emitter, string text) { 22 | Bytes = Encoding.UTF8.GetBytes(text); 23 | SizeBytes = Bytes.Length + 8; 24 | Offset = emitter.ReserveHeapSpace(SizeBytes); 25 | } 26 | } 27 | 28 | private struct FieldTableEntry { 29 | public int Offset; 30 | public FieldDefinition Field; 31 | 32 | public FieldTableEntry (int offset, FieldDefinition field) { 33 | Offset = offset; 34 | Field = field; 35 | } 36 | } 37 | 38 | private enum PrecedingType { 39 | None, 40 | Function, 41 | Export, 42 | Global, 43 | Memory, 44 | TopLevel 45 | } 46 | 47 | private IAstEmitter EntryPointAstEmitter; 48 | 49 | public readonly AssemblyTranslator Translator; 50 | public readonly AssemblyDefinition Assembly; 51 | public readonly JavascriptFormatter Formatter; 52 | 53 | private readonly Dictionary FieldTable = new Dictionary(); 54 | private readonly Dictionary StringTable = new Dictionary(); 55 | // FIXME: Ick 56 | public readonly List MethodTable = new List(); 57 | 58 | private int AssignedHeapSize = 0; 59 | private bool NeedStaticInit = false; 60 | 61 | private readonly HashSet TypesToStaticInitialize = new HashSet(); 62 | 63 | private PrecedingType Preceding; 64 | 65 | 66 | public WasmSExprAssemblyEmitter (AssemblyTranslator translator, AssemblyDefinition assembly, JavascriptFormatter formatter) { 67 | Translator = translator; 68 | Assembly = assembly; 69 | Formatter = formatter; 70 | } 71 | 72 | private int ReserveHeapSpace (int size) { 73 | var result = AssignedHeapSize; 74 | AssignedHeapSize += Math.Max(8, size); 75 | return result; 76 | } 77 | 78 | public int? GetFieldOffset (FieldDefinition fd) { 79 | var key = fd.FullName; 80 | 81 | FieldTableEntry result; 82 | if (!FieldTable.TryGetValue(key, out result)) { 83 | var size = WasmUtil.SizeOfType(fd.FieldType); 84 | 85 | // HACK HACK HACK 86 | int offset = fd.IsStatic 87 | ? (int)ReserveHeapSpace(size) 88 | : WasmUtil.OffsetOfField(fd); 89 | 90 | result = new FieldTableEntry(offset, fd); 91 | FieldTable.Add(key, result); 92 | } 93 | 94 | return result.Offset; 95 | } 96 | 97 | public int GetStringOffset (string str) { 98 | // HACK 99 | if (str == null) 100 | str = ""; 101 | 102 | StringTableEntry result; 103 | if (!StringTable.TryGetValue(str, out result)) { 104 | result = new StringTableEntry(this, str); 105 | StringTable.Add(str, result); 106 | } 107 | 108 | return result.Offset; 109 | } 110 | 111 | public int GetFunctionIndex (MethodDefinition md) { 112 | var result = MethodTable.IndexOf(md); 113 | if (result < 0) { 114 | result = MethodTable.Count; 115 | MethodTable.Add(md); 116 | } 117 | 118 | return result; 119 | } 120 | 121 | private void Switch (PrecedingType newType, bool neighborSpacing = false) { 122 | Formatter.ConditionalNewLine(); 123 | 124 | var prior = Preceding; 125 | Preceding = newType; 126 | 127 | if (prior == PrecedingType.None) 128 | return; 129 | else if ((prior == newType) && !neighborSpacing) 130 | return; 131 | 132 | Formatter.NewLine(); 133 | } 134 | 135 | public void BeginEmitTypeDeclaration (TypeDefinition typedef) { 136 | TypesToStaticInitialize.Add(typedef); 137 | } 138 | 139 | public void BeginEmitTypeDefinition (IAstEmitter astEmitter, TypeDefinition typedef, TypeInfo typeInfo, TypeReference baseClass) { 140 | TypesToStaticInitialize.Add(typedef); 141 | } 142 | 143 | public void EmitAssemblyEntryPoint (AssemblyDefinition assembly, MethodDefinition entryMethod, MethodSignature signature) { 144 | } 145 | 146 | public void EmitCustomAttributes (DecompilerContext context, TypeReference declaringType, ICustomAttributeProvider member, IAstEmitter astEmitter, bool standalone = true) { 147 | } 148 | 149 | public void EmitEvent (DecompilerContext context, IAstEmitter astEmitter, EventDefinition @event, JSRawOutputIdentifier dollar) { 150 | } 151 | 152 | public void EmitHeader (bool stubbed) { 153 | Formatter.WriteRaw(";; {0}\n\n", Formatter.Assembly.Name); 154 | 155 | Formatter.WriteRaw("(module \n"); 156 | Formatter.Indent(); 157 | Formatter.NewLine(); 158 | } 159 | 160 | private void EmitFieldIntrinsics (int heapSize) { 161 | // FIXME: Gross 162 | var tis = (ITypeInfoSource)Translator.TypeInfoProvider; 163 | 164 | Formatter.WriteRaw(";; Compiler-generated field accessors"); 165 | Formatter.NewLine(); 166 | 167 | foreach (var kvp in FieldTable.OrderBy(kvp => kvp.Value.Offset)) { 168 | var fd = kvp.Value.Field; 169 | var fi = (FieldInfo)tis.Get(fd); 170 | var name = WasmUtil.FormatMemberName(fi.Member); 171 | var typeSystem = fd.FieldType.Module.TypeSystem; 172 | 173 | // HACK 174 | var baseAddressParam = new JSVariable("address", typeSystem.Int32, null); 175 | // HACK 176 | var valueParam = new JSVariable("value", fd.FieldType, null); 177 | 178 | JSExpression address; 179 | if (fd.IsStatic) { 180 | address = JSLiteral.New(kvp.Value.Offset + heapSize); 181 | } else { 182 | address = new JSBinaryOperatorExpression( 183 | JSOperator.Add, 184 | baseAddressParam, 185 | JSLiteral.New(kvp.Value.Offset), 186 | typeSystem.Int32 187 | ); 188 | } 189 | 190 | Formatter.ConditionalNewLine(); 191 | Formatter.WriteRaw( 192 | "(func $__get_{0} (result {1}){2}(return ", 193 | name, 194 | WasmUtil.PickTypeKeyword(fd.FieldType), 195 | fd.IsStatic 196 | ? " " 197 | : " (param $address i32) " 198 | ); 199 | 200 | var gm = new GetMemory( 201 | fd.FieldType, /* FIXME: Align addresses */ false, 202 | address 203 | ); 204 | 205 | // HACK 206 | EntryPointAstEmitter.Emit(gm); 207 | 208 | Formatter.WriteRaw(") )"); 209 | 210 | if (fd.IsInitOnly) 211 | continue; 212 | 213 | Formatter.NewLine(); 214 | Formatter.WriteRaw( 215 | "(func $__set_{0}{2}(param $value {1}) ", 216 | name, 217 | WasmUtil.PickTypeKeyword(fd.FieldType), 218 | fd.IsStatic 219 | ? " " 220 | : " (param $address i32) " 221 | ); 222 | Formatter.Indent(); 223 | Formatter.NewLine(); 224 | var sm = new SetMemory( 225 | fd.FieldType, /* FIXME: Align addresses */ false, 226 | address, valueParam 227 | ); 228 | 229 | // HACK 230 | EntryPointAstEmitter.Emit(sm); 231 | 232 | Formatter.Unindent(); 233 | Formatter.ConditionalNewLine(); 234 | Formatter.WriteRaw(")"); 235 | } 236 | 237 | Formatter.NewLine(); 238 | Formatter.NewLine(); 239 | } 240 | 241 | private void EmitFieldTable (int heapSize) { 242 | // FIXME: ml-proto ordered segment constraint; also cctor might do this for us 243 | /* 244 | Formatter.Indent(); 245 | Formatter.NewLine(); 246 | 247 | Formatter.WriteRaw(";; fields"); 248 | Formatter.NewLine(); 249 | 250 | foreach (var kvp in FieldTable.OrderBy(kvp => kvp.Value.Offset)) { 251 | Formatter.ConditionalNewLine(); 252 | Formatter.WriteRaw( 253 | "(segment {0} \"", 254 | kvp.Value.Offset + heapSize 255 | ); 256 | 257 | EmitStringLiteralContents(Formatter.Output, (from b in kvp.Value.Bytes select (char)b)); 258 | 259 | Formatter.WriteRaw("\")"); 260 | } 261 | 262 | Formatter.Unindent(); 263 | Formatter.ConditionalNewLine(); 264 | */ 265 | } 266 | 267 | private void EmitStringIntrinsics (int heapSize) { 268 | Formatter.WriteRaw(";; Compiler-generated string table routines"); 269 | Formatter.NewLine(); 270 | 271 | Formatter.WriteRaw("(func $__getStringFirstChar (param $offset i32) (result i32)"); 272 | Formatter.NewLine(); 273 | Formatter.Indent(); 274 | 275 | Formatter.WriteRaw( 276 | "(i32.add (i32.const {0}) (get_local $offset))", 277 | // Add 4 to skip past the length header 278 | heapSize + 4 279 | ); 280 | Formatter.NewLine(); 281 | 282 | Formatter.Unindent(); 283 | Formatter.WriteRaw(")"); 284 | Formatter.NewLine(); 285 | Formatter.NewLine(); 286 | 287 | Formatter.WriteRaw("(func $__getStringLength (param $firstCharAddress i32) (result i32)"); 288 | Formatter.NewLine(); 289 | Formatter.Indent(); 290 | 291 | Formatter.WriteRaw( 292 | "(return (i32.load/1 (i32.sub (get_local $firstCharAddress) (i32.const 4))))" 293 | ); 294 | Formatter.NewLine(); 295 | 296 | Formatter.Unindent(); 297 | Formatter.WriteRaw(")"); 298 | Formatter.NewLine(); 299 | Formatter.NewLine(); 300 | } 301 | 302 | public static void EmitStringLiteralContents (System.IO.TextWriter tw, IEnumerable text) { 303 | foreach (var ch in text) { 304 | if (ch == '\\') { 305 | tw.Write("\\\\"); 306 | } else if (ch == '"') { 307 | tw.Write("\\\""); 308 | } else if ((ch < 32) || (ch >= 127)) { 309 | tw.Write("\\{0:X2}", (byte)ch); 310 | } else { 311 | tw.Write(ch); 312 | } 313 | } 314 | } 315 | 316 | private void EmitStringTable (int heapSize) { 317 | Formatter.Indent(); 318 | Formatter.NewLine(); 319 | 320 | Formatter.WriteRaw(";; string table"); 321 | Formatter.NewLine(); 322 | 323 | foreach (var kvp in StringTable.OrderBy(kvp => kvp.Value.Offset)) { 324 | Formatter.ConditionalNewLine(); 325 | Formatter.WriteRaw( 326 | "(segment {0} \"", 327 | kvp.Value.Offset + heapSize 328 | ); 329 | 330 | var lengthBytes = BitConverter.GetBytes(kvp.Value.Bytes.Length); 331 | foreach (var b in lengthBytes) 332 | Formatter.WriteRaw("\\{0:X2}", b); 333 | 334 | EmitStringLiteralContents(Formatter.Output, (from b in kvp.Value.Bytes select (char)b)); 335 | 336 | Formatter.WriteRaw("\")"); 337 | } 338 | 339 | Formatter.Unindent(); 340 | Formatter.ConditionalNewLine(); 341 | } 342 | 343 | private void EmitFunctionTable () { 344 | if (MethodTable.Count == 0) 345 | return; 346 | 347 | Formatter.NewLine(); 348 | 349 | Formatter.WriteRaw(";; function table"); 350 | Formatter.NewLine(); 351 | 352 | Formatter.WriteRaw("(table "); 353 | Formatter.Indent(); 354 | Formatter.NewLine(); 355 | 356 | foreach (var md in MethodTable) { 357 | var name = WasmUtil.FormatMemberName(md); 358 | 359 | Formatter.ConditionalNewLine(); 360 | Formatter.WriteRaw("${0}", name); 361 | } 362 | 363 | Formatter.Unindent(); 364 | Formatter.ConditionalNewLine(); 365 | Formatter.WriteRaw(")"); 366 | Formatter.NewLine(); 367 | } 368 | 369 | private void EmitCctors () { 370 | // HACK 371 | var identifier = (MemberIdentifier)Activator.CreateInstance( 372 | typeof(MemberIdentifier), 373 | System.Reflection.BindingFlags.Instance | 374 | System.Reflection.BindingFlags.NonPublic, 375 | null, 376 | new object[] { 377 | true, MemberIdentifier.MemberType.Method, 378 | ".cctor", EntryPointAstEmitter.TypeSystem.Void, 379 | null, 0 380 | }, 381 | null 382 | ); 383 | 384 | // Find types we emitted that have static constructors 385 | var cctors = ( 386 | from t in TypesToStaticInitialize 387 | let ti = Translator.TypeInfoProvider.GetExisting(t) 388 | where ti.Members.ContainsKey(identifier) 389 | let mi = ti.Members[identifier] 390 | select (MethodInfo)mi 391 | ).ToList(); 392 | 393 | if (cctors.Count == 0) 394 | return; 395 | 396 | NeedStaticInit = true; 397 | Formatter.NewLine(); 398 | 399 | Formatter.WriteRaw(";; Compiler-generated static constructor dispatcher"); 400 | Formatter.NewLine(); 401 | // If we found any, we need to generate a special function that invokes all the cctors 402 | Formatter.WriteRaw("(func $__static_init (block "); 403 | Formatter.Indent(); 404 | Formatter.NewLine(); 405 | 406 | // FIXME: Walk cctor dependencies and invoke in correct order 407 | foreach (var cctor in cctors) { 408 | // Synthesize a regular static method call 409 | var jsm = new JSMethod( 410 | cctor.Member, cctor, Translator.FunctionCache.MethodTypes 411 | ); 412 | var call = JSInvocationExpression.InvokeStatic(jsm, new JSExpression[0], false); 413 | 414 | // HACK 415 | EntryPointAstEmitter.Emit(call); 416 | Formatter.ConditionalNewLine(); 417 | } 418 | 419 | Formatter.Unindent(); 420 | Formatter.ConditionalNewLine(); 421 | Formatter.WriteRaw(") )"); 422 | Formatter.NewLine(); 423 | Formatter.NewLine(); 424 | 425 | Formatter.WriteRaw("(export \"__static_init\" $__static_init)"); 426 | Formatter.NewLine(); 427 | } 428 | 429 | public void EmitFooter () { 430 | int heapSize = 0; 431 | if (Assembly.EntryPoint != null) 432 | WasmUtil.HeapSizes.TryGetValue(Assembly.EntryPoint.DeclaringType, out heapSize); 433 | 434 | int totalMemorySize = heapSize + AssignedHeapSize; 435 | 436 | if (totalMemorySize > 0) { 437 | Formatter.NewLine(); 438 | Formatter.WriteRaw("(import $__write \"stdio\" \"write\" (param i32 i32))"); 439 | Formatter.NewLine(); 440 | 441 | Switch(PrecedingType.Memory); 442 | 443 | if (StringTable.Count > 0) 444 | EmitStringIntrinsics(heapSize); 445 | 446 | if (FieldTable.Count > 0) 447 | EmitFieldIntrinsics(heapSize); 448 | 449 | Formatter.WriteRaw("(memory {0} {0}", totalMemorySize); 450 | 451 | if (StringTable.Count > 0) 452 | EmitStringTable(heapSize); 453 | 454 | if (FieldTable.Count > 0) 455 | EmitFieldTable(heapSize); 456 | 457 | Formatter.WriteRaw(")"); 458 | Formatter.NewLine(); 459 | } 460 | 461 | EmitFunctionTable(); 462 | 463 | EmitCctors(); 464 | 465 | Formatter.Unindent(); 466 | Formatter.NewLine(); 467 | Formatter.WriteRaw(")\n"); 468 | 469 | if (Assembly.EntryPoint != null) { 470 | Formatter.NewLine(); 471 | EmitMain(); 472 | } 473 | } 474 | 475 | public void EmitMain () { 476 | var typeId = new TypeIdentifier(Assembly.EntryPoint.DeclaringType); 477 | var memberId = new MemberIdentifier(Translator.TypeInfoProvider, Assembly.EntryPoint); 478 | var entryPointIdentifier = new QualifiedMemberIdentifier(typeId, memberId); 479 | 480 | var mainExpression = Translator.FunctionCache.GetExpression(entryPointIdentifier); 481 | 482 | var astEmitter = (AstEmitter)EntryPointAstEmitter; 483 | var mainEmitter = new AstEmitter(this, Formatter, astEmitter.JSIL, astEmitter.TypeSystem, astEmitter.TypeInfo, astEmitter.Configuration, isTopLevel: true); 484 | 485 | Switch(PrecedingType.TopLevel); 486 | 487 | if (NeedStaticInit) { 488 | Formatter.ConditionalNewLine(); 489 | Formatter.WriteRaw("(invoke \"__static_init\")"); 490 | Formatter.NewLine(); 491 | Formatter.NewLine(); 492 | } 493 | 494 | var body = mainExpression.Body; 495 | foreach (var stmt in body.Statements) { 496 | mainEmitter.Visit(stmt); 497 | Formatter.ConditionalNewLine(); 498 | } 499 | } 500 | 501 | public void EmitInterfaceList (TypeInfo typeInfo, IAstEmitter astEmitter, JSRawOutputIdentifier dollar) { 502 | } 503 | 504 | public void EmitMethodDefinition (DecompilerContext context, MethodReference methodRef, MethodDefinition method, IAstEmitter astEmitter, bool stubbed, JSRawOutputIdentifier dollar, MethodInfo methodInfo = null) { 505 | // Skip Main() and emit it at the footer 506 | if (Assembly.EntryPoint == method) { 507 | // HACK: Store this so we can use it to emit the entry point's body later 508 | EntryPointAstEmitter = astEmitter; 509 | return; 510 | } 511 | 512 | var name = WasmUtil.FormatMemberName(method); 513 | 514 | var sExport = "Wasm.Module.ExportAttribute"; 515 | 516 | methodInfo = methodInfo ?? Translator.TypeInfoProvider.GetMethod(methodRef); 517 | 518 | if (methodInfo != null && methodInfo.Metadata.HasAttribute(sExport)) { 519 | var exportName = method.Name; 520 | var ap = methodInfo.Metadata.GetAttributeParameters(sExport); 521 | if ((ap.Count == 1) && (ap[0].Value is string)) 522 | exportName = (string)ap[0].Value; 523 | 524 | Switch(PrecedingType.Export); 525 | 526 | Formatter.WriteRaw("(export \"{0}\" ${1})", exportName, name); 527 | Formatter.ConditionalNewLine(); 528 | } 529 | } 530 | 531 | public void EmitPrimitiveDefinition (DecompilerContext context, TypeDefinition typedef, bool stubbed, JSRawOutputIdentifier dollar) { 532 | } 533 | 534 | public void EmitProperty (DecompilerContext context, IAstEmitter astEmitter, PropertyDefinition property, JSRawOutputIdentifier dollar) { 535 | } 536 | 537 | public void EmitField (DecompilerContext context, IAstEmitter astEmitter, FieldDefinition field, JSRawOutputIdentifier dollar, JSExpression defaultValue) { 538 | var fieldInfo = Translator.TypeInfoProvider.GetField(field); 539 | var typeKeyword = WasmUtil.PickTypeKeyword(fieldInfo.FieldType); 540 | 541 | // Unhandled type 542 | if (typeKeyword == null) 543 | return; 544 | 545 | GetFieldOffset(field); 546 | } 547 | 548 | public void EmitConstant (DecompilerContext context, IAstEmitter astEmitter, FieldDefinition field, JSRawOutputIdentifier dollar, JSExpression value) { 549 | } 550 | 551 | public void EmitProxyComment (string fullName) { 552 | } 553 | 554 | public void EmitSemicolon () { 555 | } 556 | 557 | public void EmitSpacer () { 558 | } 559 | 560 | public void EmitTypeAlias (TypeDefinition typedef) { 561 | } 562 | 563 | public bool EmitTypeDeclarationHeader (DecompilerContext context, IAstEmitter astEmitter, TypeDefinition typedef, TypeInfo typeInfo) { 564 | return true; 565 | } 566 | 567 | public void EndEmitTypeDefinition (IAstEmitter astEmitter, DecompilerContext context, TypeDefinition typedef) { 568 | } 569 | 570 | public IAstEmitter MakeAstEmitter (JSILIdentifier jsil, TypeSystem typeSystem, TypeInfoProvider typeInfoProvider, Configuration configuration) { 571 | return new AstEmitter(this, Formatter, jsil, typeSystem, typeInfoProvider, configuration, isTopLevel: false); 572 | } 573 | 574 | public void EmitCachedValues (IAstEmitter astEmitter, TypeExpressionCacher typeCacher, SignatureCacher signatureCacher, BaseMethodCacher baseMethodCacher) { 575 | } 576 | 577 | public void EmitFunctionBody (IAstEmitter astEmitter, MethodDefinition method, JSFunctionExpression function) { 578 | // Skip Main() and emit it at the footer 579 | if (Assembly.EntryPoint == method) { 580 | // HACK: Store this so we can use it to emit the entry point's body later 581 | EntryPointAstEmitter = astEmitter; 582 | return; 583 | } 584 | 585 | var name = WasmUtil.FormatMemberName(method); 586 | 587 | Switch(PrecedingType.Function, true); 588 | 589 | Formatter.WriteRaw("(func ${0}", name); 590 | Formatter.Indent(); 591 | Formatter.NewLine(); 592 | 593 | int v = 0; 594 | foreach (var kvp in function.AllVariables) { 595 | var variable = kvp.Value; 596 | 597 | var type = WasmUtil.PickTypeKeyword(variable.IdentifierType); 598 | if (type != null) { 599 | Formatter.WriteRaw( 600 | "({0} ${1} {2}) ", 601 | variable.IsParameter 602 | ? "param" 603 | : "local", 604 | WasmUtil.EscapeIdentifier(kvp.Key), type 605 | ); 606 | 607 | if (v++ >= 3) { 608 | v = 0; 609 | Formatter.NewLine(); 610 | } 611 | } 612 | } 613 | 614 | if (function.LabelGroupCount > 0) { 615 | Formatter.NewLine(); 616 | } 617 | for (var i = 0; i < function.LabelGroupCount; i++) { 618 | Formatter.WriteRaw("(local $currentLabel_{0} i32) ", i); 619 | } 620 | 621 | var returnType = WasmUtil.PickTypeKeyword(method.ReturnType); 622 | if (returnType != "void") { 623 | Formatter.NewLine(); 624 | Formatter.WriteRaw("(result {0})", returnType); 625 | } 626 | 627 | Formatter.ConditionalNewLine(); 628 | Formatter.NewLine(); 629 | 630 | astEmitter.Emit(function.Body); 631 | 632 | Formatter.ConditionalNewLine(); 633 | Formatter.Unindent(); 634 | Formatter.WriteRaw(")"); 635 | 636 | Formatter.NewLine(); 637 | } 638 | } 639 | } 640 | -------------------------------------------------------------------------------- /WasmSExprEmitter/AstEmitter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using JSIL; 5 | using JSIL.Ast; 6 | using JSIL.Compiler.Extensibility; 7 | using JSIL.Internal; 8 | using JSIL.Transforms; 9 | using JSIL.Translator; 10 | using Mono.Cecil; 11 | 12 | namespace WasmSExprEmitter { 13 | internal class AstEmitter : JSAstVisitor, IAstEmitter { 14 | private struct LabelInfo { 15 | public readonly int GroupIndex; 16 | public readonly int LabelIndex; 17 | 18 | public LabelInfo (int groupIndex, int labelIndex) { 19 | GroupIndex = groupIndex; 20 | LabelIndex = labelIndex; 21 | } 22 | } 23 | 24 | public static readonly Dictionary OperatorTable; 25 | 26 | public readonly TypeSystem TypeSystem; 27 | public readonly Configuration Configuration; 28 | public readonly JavascriptFormatter Formatter; 29 | public readonly TypeInfoProvider TypeInfo; 30 | public readonly TypeReferenceContext ReferenceContext = new TypeReferenceContext(); 31 | public readonly bool IsTopLevel; 32 | 33 | public readonly JSILIdentifier JSIL; 34 | public readonly WasmSExprAssemblyEmitter AssemblyEmitter; 35 | 36 | private readonly Dictionary Labels = new Dictionary(StringComparer.InvariantCulture); 37 | 38 | private int NextSyntheticLoopIndex = 4096; 39 | 40 | static AstEmitter () { 41 | OperatorTable = new Dictionary { 42 | {JSOperator.Add, "add"}, 43 | {JSOperator.Subtract, "sub"}, 44 | {JSOperator.Multiply, "mul"}, 45 | {JSOperator.Divide, "div{0}"}, 46 | {JSOperator.Remainder, "rem{0}"}, 47 | {JSOperator.ShiftLeft, "shl"}, 48 | {JSOperator.ShiftRight, "shr_s"}, 49 | {JSOperator.BitwiseAnd, "and"}, 50 | {JSOperator.BitwiseOr, "or"}, 51 | {JSOperator.BitwiseXor, "xor"}, 52 | {JSOperator.Equal, "eq"}, 53 | {JSOperator.EqualLoose, "eq"}, 54 | {JSOperator.NotEqual, "ne"}, 55 | {JSOperator.NotEqualLoose, "ne"}, 56 | {JSOperator.LessThan, "lt{0}"}, 57 | {JSOperator.GreaterThan, "gt{0}"}, 58 | {JSOperator.ShiftRightUnsigned, "shr_u"}, 59 | {JSOperator.LessThanOrEqual, "le{0}"}, 60 | {JSOperator.GreaterThanOrEqual, "ge{0}"}, 61 | }; 62 | } 63 | 64 | TypeSystem IAstEmitter.TypeSystem { 65 | get { 66 | return TypeSystem; 67 | } 68 | } 69 | 70 | TypeReferenceContext IAstEmitter.ReferenceContext { 71 | get { 72 | return ReferenceContext; 73 | } 74 | } 75 | 76 | public SignatureCacher SignatureCacher { 77 | get; set; 78 | } 79 | 80 | public AstEmitter ( 81 | WasmSExprAssemblyEmitter assemblyEmitter, 82 | JavascriptFormatter formatter, JSILIdentifier jsil, 83 | TypeSystem typeSystem, TypeInfoProvider typeInfoProvider, 84 | Configuration configuration, bool isTopLevel 85 | ) : base() 86 | { 87 | AssemblyEmitter = assemblyEmitter; 88 | Formatter = formatter; 89 | TypeSystem = typeSystem; 90 | TypeInfo = typeInfoProvider; 91 | Configuration = configuration; 92 | IsTopLevel = isTopLevel; 93 | JSIL = jsil; 94 | } 95 | 96 | private void Comment (string format, params object[] values) { 97 | Formatter.WriteRaw(";; "); 98 | Formatter.WriteRaw(format, values); 99 | Formatter.NewLine(); 100 | } 101 | 102 | public void Emit (JSNode node) { 103 | Visit(node); 104 | } 105 | 106 | public void VisitNode (JSStatement statement) { 107 | Console.WriteLine("AstEmitter Unhandled statement type {0}", statement.GetType()); 108 | 109 | Formatter.WriteSExpr("untranslatable." + statement.GetType().Name, lineBreakAfter: true); 110 | } 111 | 112 | private void EmitBlockInterior (List body, string blockName = null) { 113 | while (body.Count == 1) { 114 | // HACK: Collapse '(loop (block ...) )' into '(loop ...)' 115 | var bs = body[0] as JSBlockStatement; 116 | // HACK: Collapse '(block (...))' into '(...)' 117 | var es = body[0] as JSExpressionStatement; 118 | 119 | if ( 120 | (bs != null) && 121 | // HACK: Loops are blocks 122 | (bs.GetType() == typeof(JSBlockStatement)) 123 | ) { 124 | body = bs.Statements; 125 | } else if ( 126 | (es != null) && 127 | // FIXME: Is this necessary? 128 | ((blockName == null) || (blockName == "block")) 129 | ) { 130 | Visit(es); 131 | return; 132 | } else { 133 | break; 134 | } 135 | } 136 | 137 | Formatter.WriteSExpr( 138 | blockName ?? "block", 139 | (_) => { 140 | if (body.Count == 0) 141 | _.WriteRaw("(nop)"); 142 | else 143 | EmitArgumentList(_, body, true); 144 | }, 145 | lineBreakInside: true, 146 | lineBreakAfter: true 147 | ); 148 | } 149 | 150 | private void EmitLabelledBlock (string label, Action writeBody) { 151 | if (label == null) { 152 | writeBody(Formatter); 153 | return; 154 | } 155 | 156 | Formatter.WriteSExpr( 157 | "label", (_) => { 158 | _.WriteRaw("${0} ", WasmUtil.EscapeIdentifier(label)); 159 | _.NewLine(); 160 | writeBody(_); 161 | }, 162 | lineBreakAfter: true 163 | ); 164 | } 165 | 166 | private void EmitLabelledBlock (string label, List body, string blockName = null) { 167 | EmitLabelledBlock( 168 | label, (_) => 169 | EmitBlockInterior(body, blockName) 170 | ); 171 | } 172 | 173 | public void VisitNode (JSBlockStatement block) { 174 | EmitBlockInterior(block.Statements); 175 | } 176 | 177 | private string IndexToLabel (int? index) { 178 | if (index.HasValue) 179 | return "loop_" + index.Value; 180 | else 181 | return null; 182 | } 183 | 184 | public void VisitNode (JSNullStatement ns) { 185 | Formatter.WriteRaw("(nop)"); 186 | } 187 | 188 | private void SynthesizeLoopLabel (JSLoopStatement ls) { 189 | if (ls.Index == null) 190 | ls.Index = (NextSyntheticLoopIndex++); 191 | } 192 | 193 | public void VisitNode (JSForLoop fl) { 194 | SynthesizeLoopLabel(fl); 195 | 196 | if (fl.Initializer != null) { 197 | Formatter.ConditionalNewLine(); 198 | Formatter.NewLine(); 199 | Comment("for ({0}; ...)", fl.Initializer); 200 | Visit(fl.Initializer); 201 | } 202 | 203 | Formatter.ConditionalNewLine(); 204 | Formatter.NewLine(); 205 | EmitLabelledBlock( 206 | IndexToLabel(fl.Index), 207 | (__) => 208 | __.WriteSExpr( 209 | "loop", 210 | (_) => { 211 | Comment("for (...; {0}; ...)", fl.Condition); 212 | var synthetic = new JSIfStatement( 213 | fl.Condition, 214 | new JSNullStatement(), 215 | new JSExpressionStatement(new JSBreakExpression { TargetLoop = fl.Index }) 216 | ); 217 | Visit(synthetic); 218 | 219 | _.ConditionalNewLine(); 220 | _.NewLine(); 221 | Comment("for (...) {{ "); 222 | EmitBlockInterior(fl.Statements); 223 | 224 | if (fl.Increment != null) { 225 | _.ConditionalNewLine(); 226 | _.NewLine(); 227 | Comment("for (...; ...; {0})", fl.Increment); 228 | Visit(fl.Increment); 229 | } 230 | }, 231 | true, true 232 | ) 233 | ); 234 | } 235 | 236 | public void VisitNode (JSWhileLoop wl) { 237 | SynthesizeLoopLabel(wl); 238 | 239 | Formatter.ConditionalNewLine(); 240 | Formatter.NewLine(); 241 | Comment("while ({0})", wl.Condition); 242 | EmitLabelledBlock( 243 | IndexToLabel(wl.Index), 244 | (__) => 245 | __.WriteSExpr( 246 | "loop", 247 | (_) => { 248 | var synthetic = new JSIfStatement( 249 | wl.Condition, 250 | new JSNullStatement(), 251 | new JSExpressionStatement(new JSBreakExpression { TargetLoop = wl.Index }) 252 | ); 253 | Visit(synthetic); 254 | 255 | _.ConditionalNewLine(); 256 | _.NewLine(); 257 | Comment("while (...) {{ "); 258 | EmitBlockInterior(wl.Statements); 259 | }, 260 | true, true 261 | ) 262 | ); 263 | } 264 | 265 | public void VisitNode (JSDoLoop dl) { 266 | SynthesizeLoopLabel(dl); 267 | 268 | Formatter.ConditionalNewLine(); 269 | Formatter.NewLine(); 270 | Comment("do {{"); 271 | EmitLabelledBlock( 272 | IndexToLabel(dl.Index), 273 | (__) => 274 | __.WriteSExpr( 275 | "loop", 276 | (_) => { 277 | EmitBlockInterior(dl.Statements); 278 | 279 | _.ConditionalNewLine(); 280 | _.NewLine(); 281 | Comment("do {{ ... }} while ({0})", dl.Condition); 282 | var synthetic = new JSIfStatement( 283 | dl.Condition, 284 | new JSNullStatement(), 285 | new JSExpressionStatement(new JSBreakExpression { TargetLoop = dl.Index }) 286 | ); 287 | Visit(synthetic); 288 | }, 289 | true, true 290 | ) 291 | ); 292 | } 293 | 294 | public void VisitNode (JSExpressionStatement es) { 295 | try { 296 | VisitChildren(es); 297 | } catch (Exception exc) { 298 | Console.WriteLine("// Error in statement"); 299 | Console.WriteLine("// {0}", es); 300 | throw; 301 | } 302 | } 303 | 304 | public void VisitNode (JSVariableDeclarationStatement vds) { 305 | foreach (var declaration in vds.Declarations) { 306 | if ((declaration.Right == null) || (declaration.Right.IsNull)) 307 | continue; 308 | 309 | Visit(declaration); 310 | } 311 | } 312 | 313 | private LabelInfo GetLabelInfo (string name) { 314 | return Labels[name]; 315 | } 316 | 317 | private void SetCurrentLabel (string label, int? expectedGroup = null) { 318 | var labelInfo = GetLabelInfo(label); 319 | if ( 320 | expectedGroup.HasValue && 321 | (expectedGroup.Value != labelInfo.GroupIndex) 322 | ) 323 | throw new InvalidOperationException(string.Format( 324 | "Expected label {0} to be in label group {1} but got {2}", 325 | label, expectedGroup, labelInfo.GroupIndex 326 | )); 327 | 328 | Formatter.ConditionalNewLine(); 329 | Formatter.WriteRaw("(set_local $currentLabel_{0} (i32.const {1}))", labelInfo.GroupIndex, labelInfo.LabelIndex); 330 | Formatter.NewLine(); 331 | } 332 | 333 | private void EmitCaseValue (JSExpression v) { 334 | var l = v as JSLiteral; 335 | var val = Convert.ToInt64(l.Literal); 336 | Formatter.Value(val); 337 | } 338 | 339 | private void EmitSwitchCase (JSSwitchCase sc) { 340 | Formatter.ConditionalNewLine(); 341 | 342 | if (sc.IsDefault) { 343 | Formatter.WriteRaw("(;default;) "); 344 | Visit(sc.Body); 345 | } else if (sc.Values == null) { 346 | throw new Exception("Non-default case with no values"); 347 | } else { 348 | for (var i = 0; i < sc.Values.Length - 1; i++) { 349 | var v = sc.Values[i]; 350 | Formatter.WriteSExpr( 351 | "case", 352 | (_) => 353 | EmitCaseValue(v), 354 | lineBreakAfter: true 355 | ); 356 | } 357 | 358 | var lastValue = sc.Values.Last(); 359 | Formatter.WriteSExpr( 360 | "case", 361 | (_) => { 362 | EmitCaseValue(lastValue); 363 | Formatter.NewLine(); 364 | Formatter.Indent(); 365 | Visit(sc.Body); 366 | Formatter.ConditionalNewLine(); 367 | Formatter.Unindent(); 368 | }, 369 | lineBreakAfter: true 370 | ); 371 | } 372 | } 373 | 374 | public void VisitNode (JSSwitchStatement ss) { 375 | Formatter.ConditionalNewLine(); 376 | 377 | Comment("switch ({0}) {{", ss.Condition); 378 | Formatter.WriteRaw("(i32.switch "); 379 | Formatter.Indent(); 380 | Visit(ss.Condition); 381 | Formatter.ConditionalNewLine(); 382 | 383 | var defaultCase = ss.Cases.FirstOrDefault(c => c.IsDefault); 384 | 385 | foreach (var c in ss.Cases) { 386 | if (c.IsDefault) 387 | continue; 388 | 389 | EmitSwitchCase(c); 390 | } 391 | 392 | if (defaultCase != null) 393 | EmitSwitchCase(defaultCase); 394 | 395 | Formatter.Unindent(); 396 | Formatter.ConditionalNewLine(); 397 | Formatter.WriteRaw(")"); 398 | Formatter.NewLine(); 399 | } 400 | 401 | public void VisitNode (JSLabelGroupStatement lgs) { 402 | Formatter.ConditionalNewLine(); 403 | Formatter.NewLine(); 404 | var labelName = "labelgroup_" + lgs.GroupIndex; 405 | 406 | var firstLabel = lgs.Labels.First().Key; 407 | 408 | foreach (var kvp in lgs.Labels) 409 | Labels.Add(kvp.Key, new LabelInfo(lgs.GroupIndex, Labels.Count)); 410 | 411 | Comment("LabelGroup {0} (starting at {1})", lgs.GroupIndex, firstLabel); 412 | SetCurrentLabel(firstLabel, lgs.GroupIndex); 413 | Formatter.NewLine(); 414 | 415 | Formatter.WriteSExpr( 416 | "label", (__) => { 417 | __.WriteRaw("${0} ", labelName); 418 | 419 | // HACK 420 | __.Unindent(); 421 | 422 | __.WriteSExpr( 423 | "loop", (_) => { 424 | _.WriteRaw("(label $labelgroup_{0}_dispatch ", lgs.GroupIndex); 425 | _.NewLine(); 426 | 427 | foreach (var kvp in lgs.Labels) { 428 | var labelInfo = GetLabelInfo(kvp.Key); 429 | 430 | _.ConditionalNewLine(); 431 | Comment("Begin Label {1}", lgs.GroupIndex, kvp.Key); 432 | 433 | _.WriteRaw("(if (i32.eq (get_local $currentLabel_{0}) (i32.const {1})) ", labelInfo.GroupIndex, labelInfo.LabelIndex); 434 | _.Indent(); 435 | _.NewLine(); 436 | 437 | Visit(kvp.Value); 438 | _.ConditionalNewLine(); 439 | _.NewLine(); 440 | 441 | _.Unindent(); 442 | _.WriteRaw(")"); 443 | _.NewLine(); 444 | Comment("End Label {1}", lgs.GroupIndex, kvp.Key); 445 | _.NewLine(); 446 | } 447 | 448 | _.ConditionalNewLine(); 449 | Comment("Fallthrough exit from labelgroup {0}", lgs.GroupIndex); 450 | _.WriteRaw("(break $labelgroup_{0})", lgs.GroupIndex); 451 | _.NewLine(); 452 | 453 | _.ConditionalNewLine(); 454 | _.WriteRaw(")", lgs.GroupIndex); 455 | _.NewLine(); 456 | } 457 | ); 458 | 459 | // HACK 460 | __.Indent(); 461 | } 462 | ); 463 | } 464 | 465 | public void VisitNode (JSIfStatement ifs) { 466 | Formatter.NewLine(); 467 | Formatter.WriteSExpr( 468 | "if", (_) => 469 | EmitArgumentList(_, new JSNode[] { ifs.Condition, ifs.TrueClause, ifs.FalseClause }, true), 470 | lineBreakInside: true, 471 | lineBreakAfter: true 472 | ); 473 | } 474 | 475 | public void VisitNode (JSBreakExpression be) { 476 | if (!be.TargetLoop.HasValue) { 477 | // Switch break 478 | return; 479 | } 480 | 481 | var targetLoop = be.TargetLoop.Value; 482 | Formatter.WriteSExpr( 483 | "break", (_) => _.WriteRaw("$loop_{0}", be.TargetLoop.Value) 484 | ); 485 | } 486 | 487 | public void VisitNode (JSContinueExpression ce) { 488 | if (!ce.TargetLoop.HasValue) 489 | throw new Exception("Continue expression without target loop"); 490 | 491 | var targetLoop = ce.TargetLoop.Value; 492 | Formatter.WriteSExpr( 493 | "break", (_) => _.WriteRaw("$loop_{0}_iterate", ce.TargetLoop.Value) 494 | ); 495 | } 496 | 497 | public void VisitNode (JSGotoExpression ge) { 498 | var labelInfo = GetLabelInfo(ge.TargetLabel); 499 | 500 | Formatter.ConditionalNewLine(); 501 | Comment("goto {0}", ge.TargetLabel); 502 | Formatter.WriteRaw("(block (set_local $currentLabel_{0} (i32.const {1})) (break $labelgroup_{0}_dispatch) )", labelInfo.GroupIndex, labelInfo.LabelIndex); 503 | Formatter.NewLine(); 504 | } 505 | 506 | public void VisitNode (JSExpression expression) { 507 | Console.WriteLine("AstEmitter Unhandled expression type {0}", expression.GetType()); 508 | 509 | Formatter.WriteSExpr("untranslatable." + expression.GetType().Name, lineBreakAfter: IsTopLevel); 510 | } 511 | 512 | private void VisitLiteral (JSLiteral literal, TypeReference forcedType = null) { 513 | var literalType = forcedType ?? literal.GetActualType(TypeSystem); 514 | 515 | if ((literal is JSNullLiteral) && (literalType.FullName == "System.Object")) { 516 | // HACK: ILSpy screws up the type inference... 517 | VisitStringLiteral(null); 518 | return; 519 | } 520 | 521 | var typeToken = WasmUtil.PickTypeKeyword(literalType); 522 | 523 | if (typeToken == null) { 524 | Console.WriteLine("AstEmitter Unhandled literal type {0}", literalType.FullName); 525 | 526 | Formatter.WriteSExpr("untranslatable.literal"); 527 | } 528 | 529 | if (literalType.FullName == "System.String") { 530 | if ((literal is JSDefaultValueLiteral) || (literal is JSNullLiteral)) { 531 | VisitStringLiteral(null); 532 | } else { 533 | var literalStr = (string)literal.Literal; 534 | VisitStringLiteral(literalStr); 535 | } 536 | return; 537 | } 538 | 539 | dynamic literalValue; 540 | if (literal is JSDefaultValueLiteral) { 541 | literalValue = 0; 542 | } else { 543 | literalValue = (dynamic)literal.Literal; 544 | if (literalValue is bool) 545 | literalValue = (literalValue ? 1 : 0); 546 | else if (literalValue is char) 547 | literalValue = (int)(char)literalValue; 548 | } 549 | 550 | Formatter.WriteSExpr( 551 | typeToken + ".const", 552 | // HACK 553 | (_) => Formatter.Value(literalValue) 554 | ); 555 | } 556 | 557 | public void VisitNode (JSChangeTypeExpression cte) { 558 | var literal = cte.Expression as JSLiteral; 559 | if (literal != null) 560 | VisitLiteral(literal, cte.NewType); 561 | else { 562 | // TODO: Mask if demotion? 563 | Visit(cte.Expression); 564 | } 565 | } 566 | 567 | public void VisitNode (JSLiteral literal) { 568 | VisitLiteral(literal, null); 569 | } 570 | 571 | private void VisitStringLiteral (string s) { 572 | var offset = AssemblyEmitter.GetStringOffset(s); 573 | 574 | Formatter.WriteRaw("(call $__getStringFirstChar (i32.const {0}))", offset); 575 | } 576 | 577 | public void VisitNode (GetStringLength gsl) { 578 | Formatter.WriteSExpr( 579 | "call", 580 | (_) => { 581 | _.WriteRaw("$__getStringLength "); 582 | // HACK: Assuming rhs is the result of getStringFirstChar 583 | Visit(gsl.String); 584 | } 585 | ); 586 | } 587 | 588 | private string EscapedName (JSFieldAccess fa) { 589 | return EscapedName(fa.Field); 590 | } 591 | 592 | private string EscapedName (JSField f) { 593 | return EscapedName(f.Field); 594 | } 595 | 596 | private string EscapedName (FieldInfo fi) { 597 | return WasmUtil.FormatMemberName(fi.Member); 598 | } 599 | 600 | private void Assign (JSExpression target, JSExpression value) { 601 | var leftVar = target as JSVariable; 602 | var leftField = target as JSFieldAccess; 603 | var leftProp = target as JSPropertyAccess; 604 | 605 | if (leftVar != null) { 606 | Formatter.WriteSExpr( 607 | "set_local", 608 | (_) => { 609 | _.WriteRaw("${0} ", WasmUtil.EscapeIdentifier(leftVar.Name)); 610 | Visit(value); 611 | }, 612 | lineBreakAfter: true 613 | ); 614 | 615 | } else if (leftField != null) { 616 | Formatter.WriteRaw( 617 | "(call $__set_{0} ", 618 | EscapedName(leftField) 619 | ); 620 | 621 | if (!leftField.Field.Field.IsStatic) { 622 | Visit(leftField.Target); 623 | Formatter.WriteRaw(" "); 624 | } 625 | 626 | Visit(value); 627 | Formatter.WriteRaw(")"); 628 | Formatter.NewLine(); 629 | 630 | } else if (leftProp != null) { 631 | var method = leftProp.OriginalMethod; 632 | var memberName = WasmUtil.FormatMemberName(method.Reference.Resolve()); 633 | 634 | Formatter.WriteSExpr( 635 | "call", 636 | (_) => { 637 | _.WriteRaw("${0} ", memberName); 638 | Visit(value); 639 | }, 640 | lineBreakAfter: true 641 | ); 642 | 643 | } else { 644 | Console.WriteLine("Invalid assignment target {0} {1}", target.GetType().Name, target); 645 | return; 646 | } 647 | } 648 | 649 | public void VisitNode (JSVariable variable) { 650 | Formatter.WriteSExpr( 651 | "get_local", 652 | (_) => 653 | _.WriteRaw("${0}", WasmUtil.EscapeIdentifier(variable.Name)) 654 | ); 655 | } 656 | 657 | public void VisitNode (JSUnaryOperatorExpression uoe) { 658 | var resultType = uoe.GetActualType(TypeSystem); 659 | var typeToken = WasmUtil.PickTypeKeyword(resultType); 660 | 661 | if (typeToken == null) { 662 | Console.WriteLine("Unhandled unary operator type {0}", resultType); 663 | return; 664 | } 665 | 666 | if (uoe.Operator == JSOperator.LogicalNot) { 667 | Formatter.WriteRaw("({0}.xor ", typeToken); 668 | Visit(uoe.Expression); 669 | Formatter.WriteRaw(" ({0}.const 1))", typeToken); 670 | return; 671 | } else if (uoe.Operator == JSOperator.Negation) { 672 | Formatter.WriteRaw("({0}.sub ({0}.const 0) ", typeToken); 673 | Visit(uoe.Expression); 674 | Formatter.WriteRaw(")", typeToken); 675 | return; 676 | } 677 | 678 | string keyword; 679 | if (!OperatorTable.TryGetValue(uoe.Operator, out keyword)) { 680 | Console.WriteLine("Unimplemented operator {0}", uoe.Operator); 681 | return; 682 | } 683 | 684 | var operandType = uoe.Expression.GetActualType(TypeSystem); 685 | var sign = TypeUtil.IsSigned(operandType); 686 | 687 | var signSuffix = ""; 688 | if ( 689 | (sign.HasValue && TypeUtil.IsIntegral(operandType)) || 690 | // HACK 691 | (operandType.FullName == "System.Char") 692 | ) { 693 | signSuffix = sign.GetValueOrDefault(true) 694 | ? "_s" 695 | : "_u"; 696 | } 697 | 698 | var actualKeyword = string.Format( 699 | typeToken + "." + keyword, 700 | signSuffix 701 | ); 702 | 703 | Formatter.WriteSExpr( 704 | actualKeyword, 705 | (_) => EmitArgumentList(_, new[] { uoe.Expression }, true), 706 | true, false 707 | ); 708 | } 709 | 710 | public void VisitNode (JSBinaryOperatorExpression boe) { 711 | var boeType = boe.GetActualType(TypeSystem); 712 | var typeToken = WasmUtil.PickTypeKeyword(boeType); 713 | 714 | if (boe.Operator == JSOperator.Assignment) { 715 | Assign(boe.Left, boe.Right); 716 | return; 717 | } 718 | 719 | string keyword; 720 | if (!OperatorTable.TryGetValue(boe.Operator, out keyword)) { 721 | Console.WriteLine("Unimplemented operator {0}", boe.Operator); 722 | return; 723 | } 724 | 725 | var leftType = boe.Left.GetActualType(TypeSystem); 726 | var rightType = boe.Right.GetActualType(TypeSystem); 727 | var leftSign = TypeUtil.IsSigned(leftType); 728 | 729 | // HACK: Emit the argument type since we're comparing it 730 | if (boe.Operator is JSComparisonOperator) 731 | typeToken = WasmUtil.PickTypeKeyword(leftType); 732 | 733 | var signSuffix = ""; 734 | if ( 735 | (leftSign.HasValue && TypeUtil.IsIntegral(leftType)) || 736 | // HACK 737 | (leftType.FullName == "System.Char") 738 | ) { 739 | signSuffix = leftSign.GetValueOrDefault(true) 740 | ? "_s" 741 | : "_u"; 742 | } else if ( 743 | TypeUtil.IsPointer(leftType) || 744 | TypeUtil.IsPointer(rightType) 745 | ) { 746 | signSuffix = "_u"; 747 | } 748 | 749 | var actualKeyword = string.Format( 750 | typeToken + "." + keyword, 751 | signSuffix 752 | ); 753 | 754 | // HACK: wasm i64 shift takes i64 shift amount 755 | var right = boe.Right; 756 | if ( 757 | (boe.Operator == JSOperator.ShiftLeft) || 758 | (boe.Operator == JSOperator.ShiftRight) 759 | ) { 760 | right = JSChangeTypeExpression.New(right, leftType, TypeSystem); 761 | } 762 | 763 | if (typeToken == null) { 764 | Console.WriteLine("Unhandled binary operator type {0} ({1})", boeType, boe); 765 | return; 766 | } 767 | 768 | Formatter.WriteSExpr( 769 | actualKeyword, 770 | (_) => EmitArgumentList(_, new[] { boe.Left, right }, true), 771 | true, false 772 | ); 773 | } 774 | 775 | private void EmitArgumentList (JavascriptFormatter formatter, TArgs arguments, bool interiorLineBreak = false) 776 | where TArgs : IEnumerable 777 | { 778 | var first = true; 779 | 780 | foreach (var arg in arguments) { 781 | // FIXME: Should we put something else here? 782 | if (arg == null) 783 | continue; 784 | 785 | if (!first && !formatter.PreviousWasLineBreak) { 786 | if (interiorLineBreak) 787 | formatter.NewLine(); 788 | else 789 | formatter.Space(); 790 | } 791 | 792 | Visit(arg); 793 | first = false; 794 | } 795 | } 796 | 797 | public void VisitNode (SExpression sexpr) { 798 | Formatter.WriteSExpr( 799 | sexpr.Keyword, 800 | (_) => { 801 | sexpr.BeforeWriteValues(this); 802 | EmitArgumentList(_, sexpr.Children, sexpr.LineBreakInside); 803 | sexpr.AfterWriteValues(this); 804 | }, 805 | sexpr.LineBreakInside, 806 | sexpr.LineBreakAfter 807 | ); 808 | } 809 | 810 | public void VisitNode (JSReferenceExpression re) { 811 | if (re.Referent.IsConstant) 812 | Visit(re.Referent); 813 | else { 814 | Console.WriteLine("Can't translate {0}", re); 815 | Formatter.WriteSExpr("untranslatable.reference"); 816 | } 817 | } 818 | 819 | public void VisitNode (JSReturnExpression ret) { 820 | Formatter.WriteSExpr("return", (_) => 821 | Visit(ret.Value) 822 | ); 823 | } 824 | 825 | public void VisitNode (JSDelegateInvocationExpression die) { 826 | var manyArgs = die.Arguments.Count > 2; 827 | 828 | Formatter.WriteSExpr("call_indirect", (_) => { 829 | // HACK: For some reason we need a function table number (not a name!) 830 | Formatter.WriteRaw("0 "); 831 | 832 | Visit(die.Delegate); 833 | Formatter.WriteRaw(" "); 834 | EmitArgumentList(_, die.Arguments, manyArgs); 835 | }, lineBreakInside: manyArgs); 836 | } 837 | 838 | // Why is this necessary??? 839 | private T UnwrapDeferred (JSExpression e) 840 | where T : JSExpression 841 | { 842 | var de = e as JSDeferredExpression; 843 | if (de == null) 844 | throw new Exception("Expected JSDeferredExpression, got " + e); 845 | 846 | var result = de.InnerExpression as T; 847 | if (result == null) 848 | throw new Exception("Expected DeferredExpression to contain " + typeof(T).Name + " but was " + de.InnerExpression); 849 | 850 | return result; 851 | } 852 | 853 | private void VisitFakeMethod (JSInvocationExpression ie, JSFakeMethod fakeMethod) { 854 | if (TypeUtil.IsDelegateType(fakeMethod.ReturnType) && fakeMethod.Name == "New") { 855 | var type = (JSType)ie.Arguments[0]; 856 | // FIXME: What the heck is in arg1???? 857 | var method = UnwrapDeferred(ie.Arguments[2]); 858 | 859 | var methodDef = method.Reference.Resolve(); 860 | var memberName = WasmUtil.FormatMemberName(methodDef); 861 | 862 | Formatter.WriteRaw("(i32.const {0} (; {1} ;))", AssemblyEmitter.GetFunctionIndex(methodDef), methodDef.FullName); 863 | return; 864 | } 865 | 866 | Console.WriteLine("Can't translate fake method {0}", ie); 867 | Formatter.WriteSExpr("untranslatable.call"); 868 | return; 869 | } 870 | 871 | public void VisitNode (JSInvocationExpression ie) { 872 | var jsm = ie.JSMethod; 873 | if (jsm == null) { 874 | var d = ie.Method as JSDotExpression; 875 | if (d != null) { 876 | var m = d.Member as JSFakeMethod; 877 | if (m != null) { 878 | VisitFakeMethod(ie, m); 879 | return; 880 | } 881 | } 882 | 883 | Console.WriteLine("Can't translate non-JSMethod {0}", ie); 884 | Formatter.WriteSExpr("untranslatable.call"); 885 | return; 886 | } else if (!jsm.Method.IsStatic) { 887 | Console.WriteLine("Can't translate instance call {0}", ie); 888 | Formatter.WriteSExpr("untranslatable.call"); 889 | return; 890 | } 891 | 892 | var methodDef = jsm.Reference.Resolve(); 893 | var memberName = WasmUtil.FormatMemberName(methodDef); 894 | 895 | var manyArgs = ie.Arguments.Count > 2; 896 | 897 | Formatter.WriteSExpr("call", (_) => { 898 | _.WriteRaw("${0} ", memberName); 899 | EmitArgumentList(_, ie.Arguments, manyArgs); 900 | }, lineBreakInside: manyArgs); 901 | } 902 | 903 | public void VisitNode (JSIntegerToFloatExpression itfe) { 904 | var type = itfe.GetActualType(TypeSystem); 905 | var typeToken = WasmUtil.PickTypeKeyword(type); 906 | var originalType = itfe.Expression.GetActualType(TypeSystem); 907 | var originalTypeToken = WasmUtil.PickTypeKeyword(originalType); 908 | 909 | Formatter.WriteSExpr( 910 | string.Format( 911 | "{0}.convert_s/{1}", 912 | typeToken, 913 | originalTypeToken 914 | ), (_) => { 915 | Visit(itfe.Expression); 916 | } 917 | ); 918 | } 919 | 920 | public void VisitNode (JSDoubleToFloatExpression dtfe) { 921 | Formatter.WriteSExpr( 922 | "f32.demote/f64", 923 | (_) => { 924 | Visit(dtfe.Expression); 925 | } 926 | ); 927 | } 928 | 929 | public void VisitNode (JSFloatToDoubleExpression ftde) { 930 | Formatter.WriteSExpr( 931 | "f64.promote/f32", 932 | (_) => { 933 | Visit(ftde.Expression); 934 | } 935 | ); 936 | } 937 | 938 | public void VisitNode (JSTruncateExpression trunc) { 939 | // No-op. Only necessary in JS because of int ops producing floats 940 | EmitCast( 941 | trunc.Expression, 942 | trunc.GetActualType(TypeSystem) 943 | ); 944 | } 945 | 946 | public void VisitNode (JSTernaryOperatorExpression ternary) { 947 | Formatter.WriteSExpr( 948 | "if", 949 | (_) => { 950 | Visit(ternary.Condition); 951 | Formatter.ConditionalNewLine(); 952 | Visit(ternary.True); 953 | Formatter.ConditionalNewLine(); 954 | Visit(ternary.False); 955 | } 956 | ); 957 | } 958 | 959 | public void VisitNode (AssertReturn aseq) { 960 | Formatter.WriteRaw("(assert_return (invoke "); 961 | Formatter.Value(aseq.ExportedFunctionName); 962 | Formatter.Space(); 963 | EmitArgumentList(Formatter, aseq.Arguments, false); 964 | Formatter.WriteRaw(" ) "); 965 | Visit(aseq.Expected); 966 | Formatter.WriteRaw(" )"); 967 | Formatter.NewLine(); 968 | } 969 | 970 | public void VisitNode (StdoutWrite sw) { 971 | Formatter.WriteRaw("(call_import $__write "); 972 | Visit(sw.Offset); 973 | Formatter.Space(); 974 | Visit(sw.Count); 975 | Formatter.WriteRaw(")"); 976 | Formatter.NewLine(); 977 | } 978 | 979 | public void VisitNode (JSFieldAccess fa) { 980 | if (fa.IsWrite) 981 | throw new Exception("Unhandled field write: " + fa); 982 | 983 | if (fa.Field.Field.IsStatic) 984 | Formatter.WriteRaw( 985 | "(call $__get_{0})", 986 | EscapedName(fa) 987 | ); 988 | else { 989 | Formatter.WriteRaw( 990 | "(call $__get_{0} ", 991 | EscapedName(fa) 992 | ); 993 | Visit(fa.Target); 994 | Formatter.WriteRaw(")"); 995 | } 996 | } 997 | 998 | public void VisitNode (JSPropertyAccess pa) { 999 | if (pa.IsWrite) 1000 | throw new Exception("Unhandled property write: " + pa); 1001 | 1002 | var method = pa.OriginalMethod; 1003 | var memberName = WasmUtil.FormatMemberName(method.Reference.Resolve()); 1004 | 1005 | Formatter.WriteSExpr("call", (_) => { 1006 | _.WriteRaw("${0}", memberName); 1007 | }); 1008 | } 1009 | 1010 | public void VisitNode (JSCommaExpression ce) { 1011 | Formatter.ConditionalNewLine(); 1012 | Formatter.WriteRaw("(block "); 1013 | Formatter.NewLine(); 1014 | Formatter.Indent(); 1015 | 1016 | foreach (var se in ce.SubExpressions) { 1017 | Visit(se); 1018 | Formatter.ConditionalNewLine(); 1019 | } 1020 | 1021 | Formatter.Unindent(); 1022 | Formatter.WriteRaw(")"); 1023 | } 1024 | 1025 | public void VisitNode (JSCastExpression ce) { 1026 | EmitCast( 1027 | ce.Expression, 1028 | ce.GetActualType(TypeSystem) 1029 | ); 1030 | } 1031 | 1032 | void EmitCast (JSExpression value, TypeReference toType) { 1033 | var fromType = value.GetActualType(TypeSystem); 1034 | 1035 | var fromIntegral = TypeUtil.IsIntegral(fromType); 1036 | var toIntegral = TypeUtil.IsIntegral(toType); 1037 | 1038 | var fromSize = WasmUtil.SizeOfType(fromType); 1039 | var toSize = WasmUtil.SizeOfType(toType); 1040 | 1041 | var fromSign = TypeUtil.IsSigned(fromType); 1042 | var toSign = TypeUtil.IsSigned(toType); 1043 | 1044 | var signSuffix = fromSign.GetValueOrDefault(toSign.GetValueOrDefault(true)) 1045 | ? "s" 1046 | : "u"; 1047 | 1048 | if (fromIntegral && toIntegral) { 1049 | if ((toSize == 4) && (fromSize == 8)) { 1050 | Formatter.WriteRaw("(i32.wrap/i64 "); 1051 | Visit(value); 1052 | Formatter.WriteRaw(")"); 1053 | return; 1054 | } else if ((toSize == 8) && (fromSize == 4)) { 1055 | Formatter.WriteRaw("(i64.extend_{0}/i32 ", signSuffix); 1056 | Visit(value); 1057 | Formatter.WriteRaw(")"); 1058 | return; 1059 | } else if ((toSize == fromSize) && (toSign != fromSign)) { 1060 | Visit(value); 1061 | return; 1062 | } else if ((toSize == 2) || (toSize == 1)) { 1063 | var mask = (1 << (8 * toSize)) - 1; 1064 | var synthesized = new JSBinaryOperatorExpression( 1065 | JSOperator.BitwiseAnd, 1066 | value, JSLiteral.New(mask), 1067 | toType 1068 | ); 1069 | Visit(synthesized); 1070 | return; 1071 | } else if (toType.FullName == "JSIL.Types.NativeInt") { 1072 | Visit(value); 1073 | return; 1074 | } 1075 | } else if (toIntegral) { 1076 | Formatter.WriteRaw( 1077 | "({0}.trunc_{1}/{2} ", 1078 | WasmUtil.PickTypeKeyword(toType), 1079 | signSuffix, 1080 | WasmUtil.PickTypeKeyword(fromType) 1081 | ); 1082 | Visit(value); 1083 | Formatter.WriteRaw(")"); 1084 | return; 1085 | } 1086 | 1087 | Console.Error.WriteLine("unimplemented cast {0} -> {1}", fromType, toType); 1088 | Visit(value); 1089 | } 1090 | 1091 | public void VisitNode (JSPointerAddExpression pae) { 1092 | var pointerType = pae.Pointer.GetActualType(TypeSystem); 1093 | var synthesized = new JSBinaryOperatorExpression( 1094 | JSOperator.Add, pae.Pointer, pae.Delta, pointerType 1095 | ); 1096 | 1097 | if (pae.MutateInPlace) 1098 | synthesized = new JSBinaryOperatorExpression( 1099 | JSOperator.Assignment, pae.Pointer, synthesized, pointerType 1100 | ); 1101 | 1102 | Visit(synthesized); 1103 | } 1104 | 1105 | private JSExpression ComputeAddress ( 1106 | JSExpression pointer, JSExpression offsetInElements, JSExpression offsetInBytes, 1107 | out TypeReference elementType 1108 | ) { 1109 | var pointerType = pointer.GetActualType(TypeSystem); 1110 | elementType = pointerType.GetElementType(); 1111 | var elementSize = WasmUtil.SizeOfType(elementType); 1112 | 1113 | if ( 1114 | (offsetInElements != null) && 1115 | // I'm so helpful :-))))) 1116 | (offsetInBytes == null) 1117 | ) { 1118 | offsetInBytes = new JSBinaryOperatorExpression( 1119 | JSOperator.Multiply, 1120 | offsetInElements, 1121 | JSLiteral.New(elementSize), 1122 | pointerType 1123 | ); 1124 | } 1125 | 1126 | if (offsetInBytes != null) { 1127 | var result = new JSPointerAddExpression(pointer, offsetInBytes, false); 1128 | // Console.WriteLine("Constructing pae {0} from {1} {2} {3}", result, pointer, offsetInElements, offsetInBytes); 1129 | return result; 1130 | } else { 1131 | return pointer; 1132 | } 1133 | } 1134 | 1135 | public void VisitNode (JSWriteThroughPointerExpression wtpe) { 1136 | TypeReference elementType; 1137 | var address = ComputeAddress(wtpe.Left, wtpe.OffsetInElements, wtpe.OffsetInBytes, out elementType); 1138 | 1139 | var synthesized = new SetMemory(elementType, false, address, wtpe.Right); 1140 | Visit(synthesized); 1141 | } 1142 | 1143 | public void VisitNode (JSReadThroughPointerExpression rtpe) { 1144 | TypeReference elementType; 1145 | var address = ComputeAddress(rtpe.Pointer, rtpe.OffsetInElements, rtpe.OffsetInBytes, out elementType); 1146 | 1147 | var synthesized = new GetMemory(elementType, false, address); 1148 | Visit(synthesized); 1149 | } 1150 | 1151 | public void VisitNode (JSRawOutputIdentifier roi) { 1152 | Formatter.WriteRaw(roi.Format, roi.Arguments); 1153 | } 1154 | 1155 | public void VisitNode (JSSizeOfExpression sizeofExp) { 1156 | var type = sizeofExp.Type.Type; 1157 | Formatter.WriteRaw("(i32.const (; sizeof {0} ;) {1})", type, WasmUtil.SizeOfType(type)); 1158 | } 1159 | 1160 | public void VisitNode (JSPointerCastExpression pce) { 1161 | Visit(pce.Pointer); 1162 | } 1163 | } 1164 | } -------------------------------------------------------------------------------- /WasmSExprEmitter/CustomNodes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using JSIL; 7 | using JSIL.Ast; 8 | using Mono.Cecil; 9 | 10 | namespace WasmSExprEmitter { 11 | public abstract class SExpression : JSExpression { 12 | public readonly string Keyword; 13 | public bool LineBreakAfter, LineBreakInside; 14 | 15 | public SExpression (string keyword, params JSExpression[] values) 16 | : base (values) { 17 | Keyword = keyword; 18 | } 19 | 20 | internal virtual void BeforeWriteValues (AstEmitter astEmitter) { 21 | } 22 | 23 | internal virtual void AfterWriteValues (AstEmitter astEmitter) { 24 | } 25 | 26 | public override string ToString () { 27 | return string.Format("({0} {1})", Keyword, string.Join(", ", Values)); 28 | } 29 | } 30 | 31 | public class AbstractSExpression : SExpression { 32 | public readonly TypeReference Type; 33 | private readonly bool _IsConstant; 34 | public readonly bool IsConstantIfArgumentsAre; 35 | private readonly bool _HasGlobalStateDependency; 36 | 37 | public AbstractSExpression ( 38 | string keyword, TypeReference type, JSExpression[] values, 39 | bool isConstant = false, bool isConstantIfArgumentsAre = false, 40 | bool hasGlobalStateDependency = false, 41 | bool lineBreakAfter = false, bool lineBreakInside = false 42 | ) : base (keyword, values) { 43 | Type = type; 44 | _IsConstant = isConstant; 45 | IsConstantIfArgumentsAre = isConstantIfArgumentsAre; 46 | _HasGlobalStateDependency = hasGlobalStateDependency; 47 | LineBreakAfter = lineBreakAfter; 48 | LineBreakInside = lineBreakInside; 49 | } 50 | 51 | public override bool HasGlobalStateDependency { 52 | get { 53 | return _HasGlobalStateDependency; 54 | } 55 | } 56 | 57 | public override bool IsConstant { 58 | get { 59 | if (_IsConstant) 60 | return true; 61 | 62 | if (IsConstantIfArgumentsAre && Values.All(v => v.IsConstant)) 63 | return true; 64 | 65 | return false; 66 | } 67 | } 68 | 69 | public override TypeReference GetActualType (TypeSystem typeSystem) { 70 | return Type; 71 | } 72 | 73 | public override bool IsLValue { 74 | get { 75 | return false; 76 | } 77 | } 78 | } 79 | 80 | public class GetMemory : SExpression { 81 | public readonly TypeReference Type; 82 | public readonly bool IsAligned; 83 | 84 | public GetMemory ( 85 | TypeReference type, bool isAligned, 86 | JSExpression addressInBytes 87 | ) : base ( 88 | string.Format( 89 | "{0}.load{1}{2}", 90 | WasmUtil.PickTypeKeyword(type), 91 | WasmUtil.PickMemoryTypeSuffix(type, false), 92 | isAligned 93 | ? "" 94 | : "/1" 95 | ), 96 | addressInBytes 97 | ) { 98 | Type = type; 99 | IsAligned = isAligned; 100 | } 101 | 102 | public override TypeReference GetActualType (TypeSystem typeSystem) { 103 | return Type; 104 | } 105 | 106 | public override bool HasGlobalStateDependency { 107 | get { 108 | return true; 109 | } 110 | } 111 | 112 | public override bool IsConstant { 113 | get { 114 | return false; 115 | } 116 | } 117 | } 118 | 119 | public class SetMemory : SExpression { 120 | public readonly TypeReference Type; 121 | public readonly bool IsAligned; 122 | 123 | public SetMemory ( 124 | TypeReference type, bool isAligned, 125 | JSExpression addressInBytes, JSExpression value 126 | ) : base ( 127 | string.Format( 128 | "{0}.store{1}{2}", 129 | WasmUtil.PickTypeKeyword(type), 130 | WasmUtil.PickMemoryTypeSuffix(type, true), 131 | isAligned 132 | ? "" 133 | : "/1" 134 | ), 135 | addressInBytes, value 136 | ) { 137 | Type = type; 138 | IsAligned = isAligned; 139 | 140 | LineBreakInside = true; 141 | LineBreakAfter = true; 142 | } 143 | 144 | public override TypeReference GetActualType (TypeSystem typeSystem) { 145 | return Type; 146 | } 147 | 148 | public override bool HasGlobalStateDependency { 149 | get { 150 | return true; 151 | } 152 | } 153 | 154 | public override bool IsConstant { 155 | get { 156 | return false; 157 | } 158 | } 159 | } 160 | 161 | public class InvokeExport : SExpression { 162 | public readonly string ExportedFunctionName; 163 | 164 | public InvokeExport (string exportedFunctionName, JSExpression[] arguments) 165 | : base ("invoke", arguments) { 166 | 167 | ExportedFunctionName = exportedFunctionName; 168 | } 169 | 170 | internal override void BeforeWriteValues (AstEmitter astEmitter) { 171 | astEmitter.Formatter.Value(ExportedFunctionName); 172 | astEmitter.Formatter.Space(); 173 | } 174 | 175 | public override TypeReference GetActualType (TypeSystem typeSystem) { 176 | return typeSystem.Object; 177 | } 178 | } 179 | 180 | public class AssertReturn : SExpression { 181 | public readonly string ExportedFunctionName; 182 | 183 | public AssertReturn (JSExpression expected, string exportedFunctionName, JSExpression[] arguments) 184 | : base ( 185 | "assert_return", 186 | (new[] { expected }).Concat(arguments).ToArray() 187 | ) { 188 | 189 | ExportedFunctionName = exportedFunctionName; 190 | } 191 | 192 | public JSExpression Expected { 193 | get { 194 | return Values[0]; 195 | } 196 | } 197 | 198 | public IEnumerable Arguments { 199 | get { 200 | return Values.Skip(1); 201 | } 202 | } 203 | 204 | public override TypeReference GetActualType (TypeSystem typeSystem) { 205 | return typeSystem.Void; 206 | } 207 | } 208 | 209 | public class StdoutWrite : SExpression { 210 | public StdoutWrite (JSExpression offset, JSExpression count) 211 | : base ( 212 | "unimplemented.stdoutwrite", offset, count 213 | ) { 214 | } 215 | 216 | public JSExpression Offset { 217 | get { 218 | return Values[0]; 219 | } 220 | } 221 | 222 | public JSExpression Count { 223 | get { 224 | return Values[1]; 225 | } 226 | } 227 | 228 | public override TypeReference GetActualType (TypeSystem typeSystem) { 229 | return typeSystem.Void; 230 | } 231 | } 232 | 233 | public class GetStringLength : JSExpression { 234 | public GetStringLength (JSExpression str) 235 | : base(str) { 236 | } 237 | 238 | public JSExpression String { 239 | get { 240 | return Values[0]; 241 | } 242 | } 243 | 244 | public override TypeReference GetActualType (TypeSystem typeSystem) { 245 | return typeSystem.Int32; 246 | } 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /WasmSExprEmitter/EmitterFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using JSIL; 8 | using JSIL.Compiler.Extensibility; 9 | using JSIL.Internal; 10 | using Mono.Cecil; 11 | using Configuration = JSIL.Translator.Configuration; 12 | 13 | namespace WasmSExprEmitter { 14 | public class WasmSExprEmitterFactory : IEmitterFactory { 15 | static WasmSExprEmitterFactory () { 16 | // HACK: Make sure our custom node types work 17 | JSIL.Ast.JSNode.NodeAssemblies.Add(Assembly.GetExecutingAssembly()); 18 | } 19 | 20 | public string FileExtension { 21 | get { 22 | return ".sexpr"; 23 | } 24 | } 25 | 26 | public IAssemblyEmitter MakeAssemblyEmitter ( 27 | AssemblyTranslator translator, 28 | AssemblyDefinition assembly, 29 | JavascriptFormatter formatter, 30 | IDictionary referenceOverrides 31 | ) { 32 | if (translator.IsStubbed(assembly) || translator.IsIgnored(assembly)) 33 | return new NullAssemblyEmitter(); 34 | else 35 | return new WasmSExprAssemblyEmitter(translator, assembly, formatter); 36 | } 37 | 38 | public IEnumerable GetAnalyzers () { 39 | yield return new Analyzer(); 40 | } 41 | 42 | public Configuration FilterConfiguration (Configuration configuration) { 43 | var result = configuration.Clone(); 44 | 45 | // The default proxies contain JS-specific replacements/hacks that aren't necessarily correct for wasm 46 | result.UseDefaultProxies = false; 47 | result.IncludeDependencies = false; 48 | 49 | result.CodeGenerator.IntroduceCharCasts = false; 50 | result.CodeGenerator.IntroduceEnumCasts = false; 51 | result.CodeGenerator.EmulateInt64 = false; 52 | result.CodeGenerator.DecomposeAllMutationOperators = true; 53 | 54 | return result; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /WasmSExprEmitter/FunctionTransformer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.IO; 7 | using JSIL; 8 | using JSIL.Ast; 9 | using JSIL.Compiler.Extensibility; 10 | using JSIL.Internal; 11 | using Mono.Cecil; 12 | 13 | namespace WasmSExprEmitter { 14 | public class FunctionTransformer : IFunctionTransformer { 15 | internal FunctionTransformer () { 16 | } 17 | 18 | public void InitializeTransformPipeline (AssemblyTranslator translator, FunctionTransformPipeline transformPipeline) { 19 | } 20 | 21 | private static bool ExtractLiteral (JSExpression expr, out T result) { 22 | result = default(T); 23 | 24 | var literal = expr as JSLiteral; 25 | if (literal == null) { 26 | // Console.WriteLine("ExtractLiteral<{0}> got non-literal {1}", typeof(T).FullName, expr); 27 | return false; 28 | } 29 | 30 | try { 31 | result = (T)Convert.ChangeType(literal.Literal, typeof(T)); 32 | return true; 33 | } catch (Exception exception) { 34 | Console.WriteLine("ExtractLiteral<{0}> failed {1}", typeof(T).FullName, exception.Message); 35 | return false; 36 | } 37 | } 38 | 39 | private static JSExpression Add (TypeSystem typeSystem, JSExpression lhs, JSExpression rhs) { 40 | long iLhs, iRhs; 41 | if (ExtractLiteral(lhs, out iLhs) && ExtractLiteral(rhs, out iRhs)) 42 | return JSLiteral.New((int)(iLhs + iRhs)); 43 | 44 | var type = lhs.GetActualType(typeSystem); 45 | return new JSBinaryOperatorExpression(JSOperator.Add, lhs, rhs, type); 46 | } 47 | 48 | private static JSExpression Mul (TypeSystem typeSystem, JSExpression lhs, JSExpression rhs) { 49 | long iLhs, iRhs; 50 | if (ExtractLiteral(lhs, out iLhs) && ExtractLiteral(rhs, out iRhs)) 51 | return JSLiteral.New((int)(iLhs * iRhs)); 52 | 53 | return new JSBinaryOperatorExpression(JSOperator.Multiply, lhs, rhs, typeSystem.Int32); 54 | } 55 | 56 | private JSExpression[] UnpackArgsArray (JSExpression expr) { 57 | JSExpression[] result; 58 | 59 | var invoke = expr as JSInvocationExpression; 60 | if (invoke != null) { 61 | var m = invoke.JSMethod.Reference; 62 | if ((m.Name == "Empty") && (m.DeclaringType.FullName == "System.Array")) 63 | return new JSExpression[0]; 64 | 65 | throw new Exception("Unhandled invocation as args array: " + invoke); 66 | } 67 | 68 | var argumentsLiteral = expr as JSNewArrayExpression; 69 | if (argumentsLiteral == null) 70 | throw new Exception("Expected arguments array but got " + expr.GetType().Name); 71 | 72 | var initializer = argumentsLiteral.SizeOrArrayInitializer; 73 | if (initializer is JSLiteral) { 74 | // HACK: Is this right? 75 | long size; 76 | if (ExtractLiteral(initializer, out size)) 77 | result = new JSExpression[(int)size]; 78 | else 79 | result = new JSExpression[0]; 80 | } else if (!(initializer is JSArrayExpression)) { 81 | throw new Exception("Expected either size literal or array expression"); 82 | } else { 83 | var argumentsArray = (JSArrayExpression)initializer; 84 | 85 | // HACK: Values used as initializers may be references for no particularly good reason. 86 | // Just unfold them to values. 87 | result = argumentsArray.Values.Select( 88 | (arg) => { 89 | var re = arg as JSReferenceExpression; 90 | if (re != null) 91 | return re.Referent; 92 | else 93 | return arg; 94 | } 95 | ).ToArray(); 96 | } 97 | 98 | return result; 99 | } 100 | 101 | public JSExpression MaybeReplaceMethodCall (MethodReference caller, MethodReference method, MethodInfo methodInfo, JSExpression thisExpression, JSExpression[] arguments, TypeReference resultType, bool explicitThis) { 102 | var typeSystem = method.Module.TypeSystem; 103 | var fullName = method.FullName; 104 | 105 | switch (fullName) { 106 | case "System.Double System.Math::Sqrt(System.Double)": 107 | return new AbstractSExpression( 108 | "f64.sqrt", 109 | typeSystem.Double, 110 | arguments, 111 | isConstantIfArgumentsAre: true 112 | ); 113 | 114 | case "System.Double System.Math::Floor(System.Double)": 115 | return new AbstractSExpression( 116 | "f64.floor", 117 | typeSystem.Double, 118 | arguments, 119 | isConstantIfArgumentsAre: true 120 | ); 121 | 122 | case "System.Double System.Math::Ceiling(System.Double)": 123 | return new AbstractSExpression( 124 | "f64.ceil", 125 | typeSystem.Double, 126 | arguments, 127 | isConstantIfArgumentsAre: true 128 | ); 129 | 130 | case "System.Void Wasm.Test::Printf(System.String,System.Object[])": 131 | // HACK: Ignored for now 132 | return new JSNullExpression(); 133 | 134 | case "System.Void Wasm.Test::Invoke(System.String,System.Object[])": { 135 | var literalName = (JSStringLiteral)arguments[0]; 136 | var argumentValues = UnpackArgsArray(arguments[1]); 137 | 138 | return new InvokeExport( 139 | literalName.Value, argumentValues 140 | ); 141 | } 142 | 143 | case "System.Void Wasm.Test::AssertReturn(System.Object,System.String,System.Object[])": { 144 | var expected = arguments[0]; 145 | string methodName; 146 | if (!ExtractLiteral(arguments[1], out methodName)) 147 | throw new Exception("Expected export name as arg1 of assertreturn"); 148 | var invokeArguments = UnpackArgsArray(arguments[2]); 149 | 150 | return new AssertReturn(expected, methodName, invokeArguments); 151 | } 152 | 153 | case "System.Void Wasm.Heap::SetHeapSize(System.Int32)": { 154 | var td = caller.DeclaringType.Resolve(); 155 | var hs = WasmUtil.HeapSizes; 156 | if (hs.ContainsKey(td)) 157 | throw new Exception("Heap size for type " + td.FullName + " already set"); 158 | 159 | long heapSize; 160 | if (!ExtractLiteral(arguments[0], out heapSize)) 161 | throw new ArgumentException("SetHeapSize's argument must be an int literal"); 162 | 163 | hs.Add(td, (int)heapSize); 164 | 165 | return new JSNullExpression(); 166 | } 167 | 168 | case "System.Int32 Wasm.HeapI32::get_Item(System.Int32)": 169 | case "System.Int32 Wasm.HeapI32::get_Item(System.Int32,System.Int32)": { 170 | JSExpression actualAddress; 171 | if (arguments.Length == 2) 172 | actualAddress = Add(typeSystem, arguments[0], arguments[1]); 173 | else 174 | actualAddress = arguments[0]; 175 | 176 | // HACK: Indices are in elements, not bytes 177 | var actualAddressBytes = Mul(typeSystem, actualAddress, JSLiteral.New(4)); 178 | 179 | return new GetMemory(typeSystem.Int32, false, actualAddressBytes); 180 | } 181 | 182 | case "System.Byte Wasm.HeapU8::get_Item(System.Int32)": 183 | case "System.Byte Wasm.HeapU8::get_Item(System.Int32,System.Int32)": { 184 | JSExpression actualAddress; 185 | if (arguments.Length == 2) 186 | actualAddress = Add(typeSystem, arguments[0], arguments[1]); 187 | else 188 | actualAddress = arguments[0]; 189 | 190 | return new GetMemory(typeSystem.Byte, false, actualAddress); 191 | } 192 | 193 | case "System.Void Wasm.HeapI32::set_Item(System.Int32,System.Int32)": 194 | case "System.Void Wasm.HeapI32::set_Item(System.Int32,System.Int32,System.Int32)": { 195 | JSExpression actualAddress; 196 | if (arguments.Length == 3) 197 | actualAddress = Add(typeSystem, arguments[0], arguments[1]); 198 | else 199 | actualAddress = arguments[0]; 200 | 201 | // HACK: Indices are in elements, not bytes 202 | var actualAddressBytes = Mul(typeSystem, actualAddress, JSLiteral.New(4)); 203 | 204 | return new SetMemory(typeSystem.Int32, false, actualAddressBytes, arguments[arguments.Length - 1]); 205 | } 206 | 207 | case "System.Void Wasm.HeapU8::set_Item(System.Int32,System.Byte)": 208 | case "System.Void Wasm.HeapU8::set_Item(System.Int32,System.Int32,System.Byte)": { 209 | JSExpression actualAddress; 210 | if (arguments.Length == 3) 211 | actualAddress = Add(typeSystem, arguments[0], arguments[1]); 212 | else 213 | actualAddress = arguments[0]; 214 | 215 | return new SetMemory(typeSystem.Byte, false, actualAddress, arguments[arguments.Length - 1]); 216 | } 217 | 218 | case "System.Char System.String::get_Chars(System.Int32)": { 219 | var actualAddress = Add(typeSystem, thisExpression, arguments[0]); 220 | return new GetMemory( 221 | typeSystem.Byte, false, actualAddress 222 | ); 223 | } 224 | 225 | case "System.Int32 System.String::get_Length()": { 226 | return new GetStringLength(thisExpression); 227 | } 228 | 229 | case "System.Int32* Wasm.HeapI32::get_Base()": 230 | case "System.Byte* Wasm.HeapU8::get_Base()": { 231 | return JSLiteral.DefaultValue(method.ReturnType); 232 | } 233 | 234 | case "System.Void Wasm.Heap::SetStdout(System.String)": { 235 | // FIXME: Store the filename somewhere 236 | return new JSNullExpression(); 237 | } 238 | 239 | case "System.Void Wasm.Heap::Write(System.Int32,System.Int32)": { 240 | return new StdoutWrite(arguments[0], arguments[1]); 241 | } 242 | } 243 | 244 | if (false) 245 | Console.WriteLine("// Treating method '{0}' as runtime call", fullName); 246 | 247 | return null; 248 | } 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /WasmSExprEmitter/NullAssemblyEmitter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using ICSharpCode.Decompiler; 7 | using JSIL; 8 | using JSIL.Ast; 9 | using JSIL.Compiler.Extensibility; 10 | using JSIL.Internal; 11 | using JSIL.Transforms; 12 | using JSIL.Translator; 13 | using Mono.Cecil; 14 | 15 | namespace WasmSExprEmitter { 16 | public class NullAssemblyEmitter : IAssemblyEmitter { 17 | public void BeginEmitTypeDeclaration (TypeDefinition typedef) { 18 | } 19 | 20 | public void BeginEmitTypeDefinition (IAstEmitter astEmitter, TypeDefinition typedef, TypeInfo typeInfo, TypeReference baseClass) { 21 | } 22 | 23 | public void EmitAssemblyEntryPoint (AssemblyDefinition assembly, MethodDefinition entryMethod, MethodSignature signature) { 24 | } 25 | 26 | public void EmitCachedValues (IAstEmitter astEmitter, TypeExpressionCacher typeCacher, SignatureCacher signatureCacher, BaseMethodCacher baseMethodCacher) { 27 | } 28 | 29 | public void EmitCustomAttributes (DecompilerContext context, TypeReference declaringType, ICustomAttributeProvider member, IAstEmitter astEmitter, bool standalone = true) { 30 | } 31 | 32 | public void EmitEvent (DecompilerContext context, IAstEmitter astEmitter, EventDefinition @event, JSRawOutputIdentifier dollar) { 33 | } 34 | 35 | public void EmitField (DecompilerContext context, IAstEmitter astEmitter, FieldDefinition field, JSRawOutputIdentifier dollar, JSExpression defaultValue) { 36 | } 37 | 38 | public void EmitConstant (DecompilerContext context, IAstEmitter astEmitter, FieldDefinition field, JSRawOutputIdentifier dollar, JSExpression value) { 39 | } 40 | 41 | public void EmitFooter () { 42 | } 43 | 44 | public void EmitFunctionBody (IAstEmitter astEmitter, MethodDefinition method, JSFunctionExpression function) { 45 | } 46 | 47 | public void EmitHeader (bool stubbed) { 48 | } 49 | 50 | public void EmitInterfaceList (TypeInfo typeInfo, IAstEmitter astEmitter, JSRawOutputIdentifier dollar) { 51 | } 52 | 53 | public void EmitMethodDefinition (DecompilerContext context, MethodReference methodRef, MethodDefinition method, IAstEmitter astEmitter, bool stubbed, JSRawOutputIdentifier dollar, MethodInfo methodInfo = null) { 54 | } 55 | 56 | public void EmitPrimitiveDefinition (DecompilerContext context, TypeDefinition typedef, bool stubbed, JSRawOutputIdentifier dollar) { 57 | } 58 | 59 | public void EmitProperty (DecompilerContext context, IAstEmitter astEmitter, PropertyDefinition property, JSRawOutputIdentifier dollar) { 60 | } 61 | 62 | public void EmitProxyComment (string fullName) { 63 | } 64 | 65 | public void EmitSemicolon () { 66 | } 67 | 68 | public void EmitSpacer () { 69 | } 70 | 71 | public void EmitTypeAlias (TypeDefinition typedef) { 72 | } 73 | 74 | public bool EmitTypeDeclarationHeader (DecompilerContext context, IAstEmitter astEmitter, TypeDefinition typedef, TypeInfo typeInfo) { 75 | return false; 76 | } 77 | 78 | public void EndEmitTypeDefinition (IAstEmitter astEmitter, DecompilerContext context, TypeDefinition typedef) { 79 | } 80 | 81 | public IAstEmitter MakeAstEmitter (JSILIdentifier jsil, TypeSystem typeSystem, TypeInfoProvider typeInfoProvider, Configuration configuration) { 82 | return new NullAstEmitter(typeSystem); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /WasmSExprEmitter/NullAstEmitter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using JSIL.Ast; 7 | using JSIL.Compiler.Extensibility; 8 | using JSIL.Internal; 9 | using JSIL.Transforms; 10 | using Mono.Cecil; 11 | 12 | namespace WasmSExprEmitter { 13 | public class NullAstEmitter : JSAstVisitor, IAstEmitter { 14 | public readonly TypeReferenceContext ReferenceContext = new TypeReferenceContext(); 15 | public readonly TypeSystem TypeSystem; 16 | 17 | public NullAstEmitter (TypeSystem typeSystem) { 18 | TypeSystem = typeSystem; 19 | } 20 | 21 | public SignatureCacher SignatureCacher { 22 | get; set; 23 | } 24 | 25 | TypeSystem IAstEmitter.TypeSystem { 26 | get { 27 | return TypeSystem; 28 | } 29 | } 30 | 31 | TypeReferenceContext IAstEmitter.ReferenceContext { 32 | get { 33 | return ReferenceContext; 34 | } 35 | } 36 | 37 | public void Emit (JSNode node) { 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /WasmSExprEmitter/Util.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using JSIL; 7 | using JSIL.Internal; 8 | using Mono.Cecil; 9 | 10 | namespace WasmSExprEmitter { 11 | public static class WasmUtil { 12 | public static readonly Dictionary HeapSizes = new Dictionary(new ReferenceComparer()); 13 | 14 | public static string FormatMemberName (IMemberDefinition member) { 15 | var typePrefix = member.DeclaringType.Name + "_"; 16 | 17 | if (member.Name.Contains(typePrefix)) 18 | return EscapeIdentifier(member.Name); 19 | else 20 | return EscapeIdentifier(typePrefix + member.Name); 21 | } 22 | 23 | public static string EscapeIdentifier (string identifier) { 24 | return Util.EscapeIdentifier(identifier).Replace("$", "_"); 25 | } 26 | 27 | public static string PickTypeKeyword (TypeReference type) { 28 | if (type.IsPointer) 29 | return "i32"; 30 | else if (TypeUtil.IsDelegateType(type)) 31 | return "i32"; 32 | 33 | // FIXME 34 | switch (type.FullName) { 35 | case "System.Void": 36 | return "void"; 37 | 38 | case "System.Boolean": 39 | case "System.Int32": 40 | case "System.UInt32": 41 | case "JSIL.Types.NativeInt": 42 | return "i32"; 43 | 44 | case "System.Int64": 45 | case "System.UInt64": 46 | return "i64"; 47 | 48 | case "System.Single": 49 | return "f32"; 50 | 51 | case "System.Double": 52 | return "f64"; 53 | 54 | case "System.Byte": 55 | case "System.UInt16": 56 | case "System.SByte": 57 | case "System.Int16": 58 | return "i32"; 59 | 60 | case "System.Char": 61 | return "i32"; 62 | 63 | case "System.String": 64 | // HACK: String table offset 65 | return "i32"; 66 | } 67 | 68 | return null; 69 | } 70 | 71 | public static string PickMemoryTypeSuffix (TypeReference type, bool isStore) { 72 | switch (type.FullName) { 73 | case "System.Byte": 74 | if (isStore) 75 | return "8"; 76 | else 77 | return "8_u"; 78 | 79 | case "System.SByte": 80 | return "8"; 81 | 82 | case "System.UInt16": 83 | if (isStore) 84 | return "16"; 85 | else 86 | return "16_u"; 87 | 88 | case "System.Int16": 89 | return "16"; 90 | 91 | // FIXME: i8 or i16 for char? 92 | } 93 | 94 | return ""; 95 | } 96 | 97 | public static int OffsetOfField (FieldDefinition fd) { 98 | int result = 0; 99 | var td = fd.DeclaringType.Resolve(); 100 | 101 | foreach (var _fd in td.Fields) { 102 | if (fd == _fd) 103 | return result; 104 | 105 | result += SizeOfType(_fd.FieldType); 106 | } 107 | 108 | throw new Exception("Error computing field offset"); 109 | } 110 | 111 | public static int SizeOfStruct (TypeReference type) { 112 | int result = 0; 113 | var td = type.Resolve(); 114 | 115 | foreach (var fd in td.Fields) 116 | result += SizeOfType(fd.FieldType); 117 | 118 | return result; 119 | } 120 | 121 | public static int SizeOfType (TypeReference type) { 122 | if (type.IsPointer) 123 | return 4; 124 | else if (TypeUtil.IsDelegateType(type)) 125 | return 4; 126 | 127 | if (type.IsValueType && !type.IsPrimitive) 128 | return SizeOfStruct(type); 129 | 130 | return TypeUtil.SizeOfType(type); 131 | } 132 | 133 | public static void ConditionalNewLine (this JavascriptFormatter formatter) { 134 | if (!formatter.PreviousWasLineBreak) 135 | formatter.NewLine(); 136 | } 137 | 138 | public static void WriteSExpr ( 139 | this JavascriptFormatter formatter, string keyword, 140 | Action interior = null, 141 | bool lineBreakInside = false, bool lineBreakAfter = false 142 | ) { 143 | formatter.WriteRaw("({0} ", keyword); 144 | formatter.Indent(); 145 | if (lineBreakInside) 146 | formatter.NewLine(); 147 | 148 | if (interior != null) 149 | interior(formatter); 150 | 151 | if (lineBreakInside && !formatter.PreviousWasLineBreak) 152 | formatter.NewLine(); 153 | formatter.Unindent(); 154 | 155 | formatter.WriteRaw(")"); 156 | if (lineBreakAfter) 157 | formatter.NewLine(); 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /WasmSExprEmitter/WasmSExprEmitter.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {36B188A1-1C32-4895-BF73-CAE32CBC91AC} 8 | Library 9 | Properties 10 | WasmSExprEmitter 11 | JSIL.Emitters.WasmSExpr 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | ..\third_party\JSIL\bin\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | true 24 | 25 | 26 | pdbonly 27 | true 28 | ..\third_party\JSIL\bin\ 29 | TRACE 30 | prompt 31 | 4 32 | true 33 | 34 | 35 | 36 | ..\third_party\JSIL\bin\ICSharpCode.Decompiler.dll 37 | 38 | 39 | ..\third_party\JSIL\bin\JSIL.dll 40 | False 41 | 42 | 43 | ..\third_party\JSIL\bin\Mono.Cecil.dll 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | {5395b317-3b75-4f1b-9d89-1ba35d2fd33b} 66 | WasmMeta 67 | 68 | 69 | 70 | 77 | -------------------------------------------------------------------------------- /ilwasm.jsilconfig: -------------------------------------------------------------------------------- 1 | { 2 | "OutputDirectory": "%currentdirectory%/output", 3 | "ProxyWarnings": false, 4 | "UseDefaultProxies": false, 5 | "UseLocalProxies": false, 6 | "UseThreads": false, 7 | "CodeGenerator": { 8 | "StripUnusedLoopNames": "false", 9 | "EnableUnsafeCode": "true" 10 | }, 11 | "Assemblies": { 12 | "Stubbed": [ 13 | "^mscorlib,", 14 | "^System,", 15 | "^WasmMeta," 16 | ], 17 | "Ignored": [ 18 | "Microsoft\\.VisualC,", 19 | "FSharp.Core,", 20 | "Accessibility,", 21 | "SMDiagnostics,", 22 | "System\\.EnterpriseServices,", 23 | "System\\.Security,", 24 | "System\\.Runtime\\.Serialization\\.Formatters\\.Soap,", 25 | "System\\.Runtime\\.DurableInstancing,", 26 | "System\\.Data\\.SqlXml,", 27 | "JSIL\\.Meta,", 28 | "^System\\.(.+),", 29 | "^Microsoft\\.(.+)," 30 | ], 31 | "Proxies": [ 32 | ] 33 | } 34 | } -------------------------------------------------------------------------------- /linux-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SCRIPTPATH=`readlink -f "$BASH_SOURCE"` 4 | ILWASMDIR=`dirname $SCRIPTPATH` 5 | JSILDIR=$ILWASMDIR/third_party/JSIL 6 | 7 | pushd $ILWASMDIR 8 | 9 | rm -rf output 10 | 11 | echo // Building ilwasm 12 | xbuild WasmSExprEmitter.sln /v:m 13 | 14 | popd -------------------------------------------------------------------------------- /runtest-d8.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SCRIPTPATH=`readlink -f "$BASH_SOURCE"` 4 | ILWASMDIR=`dirname $SCRIPTPATH` 5 | SEXPRDIR=$ILWASMDIR/../sexpr-wasm-prototype/ 6 | 7 | MODULEPATH=`readlink -f "$1"` 8 | BINPATH="$MODULEPATH.v8native" 9 | 10 | pushd $SEXPRDIR > /dev/null 11 | 12 | out/sexpr-wasm "$MODULEPATH" -o "$BINPATH" --multi-module 13 | BINPATH_ABS=`readlink -f "$BINPATH"` 14 | third_party/v8-native-prototype/v8/v8/out/Release/d8 test/spec.js "$BINPATH" 15 | 16 | popd > /dev/null -------------------------------------------------------------------------------- /runtests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import platform 4 | import os 5 | import os.path 6 | import unittest 7 | import subprocess 8 | import glob 9 | import sys 10 | import shutil 11 | import re 12 | 13 | def get_libs(fileName): 14 | with open(fileName, "r") as f: 15 | text = f.read() 16 | 17 | libComments = re.finditer("//#use (.*)", text) 18 | 19 | return list( 20 | os.path.join(os.path.dirname(fileName), match.group(1)) for match in libComments 21 | ) 22 | 23 | def get_stdout_path(fileName): 24 | with open(fileName, "r") as f: 25 | text = f.read() 26 | 27 | m = re.search("SetStdout\\(\"([^\"]*)\"\\);", text) 28 | 29 | if m: 30 | return m.group(1) 31 | else: 32 | return None 33 | 34 | def compile_cs(fileName): 35 | if platform.system() == "Windows": 36 | csc = "csc" 37 | else: 38 | csc = "mcs" 39 | 40 | testName = os.path.basename(fileName) 41 | compiledPath = os.path.join("output", testName.replace(".cs", ".exe")) 42 | 43 | libs = get_libs(fileName) 44 | 45 | inTime = os.path.getmtime(fileName) 46 | 47 | try: 48 | outTime = os.path.getmtime(compiledPath) 49 | except OSError: 50 | outTime = 0 51 | pass 52 | 53 | isNewer = (outTime > inTime) 54 | 55 | for libName in libs: 56 | if os.path.getmtime(libName) > outTime: 57 | isNewer = False 58 | break 59 | 60 | if isNewer: 61 | return (0, compiledPath, get_stdout_path(fileName)) 62 | 63 | fileNames = [fileName] + libs 64 | 65 | commandStr = ("%s /nologo /langversion:6 /debug+ /unsafe+ /debug:full %s /reference:%s /out:%s") % ( 66 | csc, " ".join(fileNames), 67 | os.path.join("WasmMeta", "bin", "WasmMeta.dll"), 68 | compiledPath 69 | ) 70 | 71 | exitCode = subprocess.call(commandStr, shell=True) 72 | 73 | if exitCode != 0: 74 | print("failed while running '%s'" % commandStr) 75 | print("") 76 | 77 | return (exitCode, compiledPath, get_stdout_path(fileName)) 78 | 79 | def translate(compiledPath): 80 | wasmPath = compiledPath.replace(".exe", ".wasm") 81 | 82 | inTime = os.path.getmtime(compiledPath) 83 | try: 84 | outTime = os.path.getmtime(wasmPath) 85 | if (outTime > inTime): 86 | return (0, wasmPath) 87 | except OSError: 88 | pass 89 | 90 | commandStr = ("mono %s ilwasm.jsilconfig --quiet --nodefaults --e=WasmSExpr --outputFile=%s %s") % ( 91 | os.path.join("third_party", "JSIL", "bin", "JSILc.exe"), 92 | wasmPath, compiledPath 93 | ) 94 | exitCode = subprocess.call(commandStr, shell=True) 95 | 96 | if exitCode != 0: 97 | print("failed while running '%s'" % commandStr) 98 | print("") 99 | 100 | return (exitCode, wasmPath) 101 | 102 | def run_csharp(compiledPath): 103 | commandStr = ("mono %s --quiet") % (compiledPath) 104 | exitCode = subprocess.call(commandStr, shell=True) 105 | 106 | if exitCode != 0: 107 | print("failed while running '%s'" % commandStr) 108 | print("") 109 | 110 | return exitCode 111 | 112 | def run_wasm(wasmPath, stdoutPath): 113 | interpreterPath = os.path.realpath(os.path.join("third_party", "wasm-spec", "ml-proto", "_build", "host", "main.d.byte")) 114 | 115 | commandStr = ("%s %s") % (interpreterPath, wasmPath) 116 | 117 | csDataPath = None 118 | wasmDataPath = None 119 | csBytes = None 120 | wasmBytes = None 121 | 122 | if stdoutPath: 123 | csDataPath = os.path.join("output", "cs-data", stdoutPath) 124 | wasmDataPath = os.path.join("output", "wasm-data", stdoutPath) 125 | commandStr += " > " + wasmDataPath 126 | 127 | exitCode = subprocess.call(commandStr, shell=True) 128 | 129 | if exitCode != 0: 130 | print("failed while running '%s'" % commandStr) 131 | print("") 132 | 133 | if stdoutPath: 134 | with open(csDataPath, "rb") as f: 135 | csBytes = f.read() 136 | 137 | with open(wasmDataPath, "rb") as f: 138 | wasmBytes = f.read() 139 | 140 | return (exitCode, csBytes, wasmBytes) 141 | 142 | 143 | class RunTests(unittest.TestCase): 144 | def _runTestFile(self, fileName): 145 | (exitCode, compiledPath, stdoutPath) = compile_cs(fileName) 146 | self.assertEqual(0, exitCode, "C# compiler failed with exit code %i" % exitCode) 147 | (exitCode, wasmPath) = translate(compiledPath) 148 | 149 | if exitCode != 0: 150 | # HACK: If JSILc fails ensure that the C# compile and JSILc compile are repeated next run 151 | os.unlink(compiledPath) 152 | self.assertEqual(0, exitCode, "JSILc failed with exit code %i" % exitCode) 153 | 154 | exitCode = run_csharp(compiledPath) 155 | self.assertEqual(0, exitCode, "C# test case failed with exit code %i" % exitCode) 156 | (exitCode, csBytes, wasmBytes) = run_wasm(wasmPath, stdoutPath) 157 | 158 | self.assertEqual(csBytes, wasmBytes, "C# and wasm output did not match (%s)" % stdoutPath) 159 | 160 | if exitCode != 0: 161 | # HACK: If JSILc fails ensure that the C# compile and JSILc compile are repeated next run 162 | os.unlink(compiledPath) 163 | self.assertEqual(0, exitCode, "wasm interpreter failed with exit code %i" % exitCode) 164 | 165 | def generate_test_case(fileName): 166 | return lambda self : self._runTestFile(fileName) 167 | 168 | 169 | def generate_test_cases(cls, files): 170 | for fileName in files: 171 | testCase = generate_test_case(fileName) 172 | setattr(cls, fileName, testCase) 173 | 174 | if __name__ == "__main__": 175 | try: 176 | os.makedirs("output/wasm-data") 177 | except OSError: 178 | pass 179 | 180 | testFiles = glob.glob(os.path.join("third_party", "tests", "*.cs")) 181 | unittest.TestLoader.testMethodPrefix = os.path.join("third_party", "tests") 182 | generate_test_cases(RunTests, testFiles) 183 | shutil.copy2(os.path.join("WasmMeta", "bin", "WasmMeta.dll"), "output") 184 | unittest.main() 185 | -------------------------------------------------------------------------------- /third_party/tests/BlendFunc.cs: -------------------------------------------------------------------------------- 1 | //#use lib/Targa.cs 2 | 3 | using System; 4 | using Wasm.Module; 5 | 6 | using static Wasm.Heap; 7 | using static Wasm.Test; 8 | 9 | public static unsafe class Program { 10 | public delegate void BlendFunc (byte* src, byte* dest, int count); 11 | 12 | public const int width = 16; 13 | public const int height = 16; 14 | public const int numPixels = (width * height); 15 | public const int numBytes = numPixels * 3; 16 | 17 | public static void blend50 (byte* src, byte* dest, int count) { 18 | while (count-- > 0) { 19 | var r = (byte)((src[0] + dest[0]) / 2); 20 | var g = (byte)((src[1] + dest[1]) / 2); 21 | var b = (byte)((src[2] + dest[2]) / 2); 22 | 23 | dest[0] = r; 24 | dest[1] = g; 25 | dest[2] = b; 26 | 27 | src += 3; 28 | dest += 3; 29 | } 30 | } 31 | 32 | public static void checkerboard (byte* dest, int count, byte initial) { 33 | var v = initial; 34 | 35 | while (count-- > 0) { 36 | *dest++ = (byte)(count % 255); 37 | *dest++ = v; 38 | *dest++ = v; 39 | 40 | v = (byte)(v ^ 0xFF); 41 | } 42 | } 43 | 44 | public static void rasterize (BlendFunc bf, byte* src, byte* dest) { 45 | checkerboard(src, numPixels, 0x0); 46 | checkerboard(dest, numPixels, 0xFF); 47 | 48 | var rowSize = width * 3; 49 | 50 | for (var y = 0; y < height; y++) { 51 | bf(src, dest, width); 52 | src += rowSize; 53 | dest += rowSize; 54 | } 55 | } 56 | 57 | [Export] 58 | public static int test () { 59 | var offset = Targa.EmitHeader(0, width, height); 60 | 61 | byte* dest = &U8.Base[offset]; 62 | byte* src = &dest[numBytes]; 63 | 64 | rasterize(blend50, src, dest); 65 | 66 | SetStdout("blendfunc.tga"); 67 | Write(offset, numBytes); 68 | 69 | return offset + numBytes; 70 | } 71 | 72 | public static void Main () { 73 | SetHeapSize(8192); 74 | 75 | const int expectedLength = 786; 76 | AssertReturn(expectedLength, "test"); 77 | } 78 | } -------------------------------------------------------------------------------- /third_party/tests/CountUp.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Wasm.Module; 3 | 4 | using static Wasm.Heap; 5 | using static Wasm.Test; 6 | 7 | public static class Program { 8 | [Export] 9 | public static void countUp (int outOffset, int count) { 10 | for (int i = 0; i < count; i++) 11 | I32[outOffset, i] = i; 12 | } 13 | 14 | [Export] 15 | public static int readI32 (int @base, int offset) { 16 | return I32[@base, offset]; 17 | } 18 | 19 | public static void Main () { 20 | SetHeapSize(4096); 21 | 22 | Invoke("countUp", 0, 32); 23 | Invoke("countUp", 16, 4); 24 | 25 | AssertReturn(0, "readI32", 0, 0 ); 26 | AssertReturn(2, "readI32", 0, 2 ); 27 | AssertReturn(31, "readI32", 0, 31); 28 | AssertReturn(0, "readI32", 16, 0 ); 29 | AssertReturn(3, "readI32", 16, 3 ); 30 | } 31 | } -------------------------------------------------------------------------------- /third_party/tests/FannkuchRedux.cs: -------------------------------------------------------------------------------- 1 | /* 2 | The Computer Language Benchmarks Game 3 | http://shootout.alioth.debian.org/ 4 | contributed by Isaac Gouy, transliterated from Mike Pall's Lua program 5 | */ 6 | 7 | // Derived from third_party/JSIL/Tests/TestCases/FannkuchRedux.cs 8 | // Rewritten to run as wasm 9 | 10 | using System; 11 | using Wasm.Module; 12 | 13 | using static Wasm.Heap; 14 | using static Wasm.Test; 15 | 16 | public static class Program { 17 | const int _Result = 0; 18 | const int _P = 32; 19 | const int _Q = 32 * 2; 20 | const int _S = 32 * 3; 21 | 22 | static int maxflips, sum; 23 | 24 | private static void initHeap (int n) { 25 | for (int i = 0; i < n; i++) { 26 | I32[_P, i] = i; 27 | I32[_Q, i] = i; 28 | I32[_S, i] = i; 29 | } 30 | } 31 | 32 | private static void process0thElement (int n, int sign, int q0) { 33 | if (q0 != 0) { 34 | for (int i = 1; i < n; i++) 35 | I32[_Q, i] = I32[_P, i]; // Work on a copy. 36 | 37 | var flips = 1; 38 | do { 39 | var qq = I32[_Q, q0]; 40 | if (qq == 0) { // ... until 0th element is 0. 41 | sum += sign * flips; 42 | if (flips > maxflips) 43 | maxflips = flips; // New maximum? 44 | break; 45 | } 46 | 47 | I32[_Q, q0] = q0; 48 | if (q0 >= 3) { 49 | int i = 1, j = q0 - 1, t; 50 | 51 | do { 52 | t = I32[_Q, i]; 53 | I32[_Q, i] = I32[_Q, j]; 54 | I32[_Q, j] = t; 55 | i++; 56 | j--; 57 | } while (i < j); 58 | } 59 | 60 | q0 = qq; flips++; 61 | } while (true); 62 | } 63 | } 64 | 65 | private static bool permuteNegativeSign (int n) { 66 | int m = n - 1; 67 | var t = I32[_P, 1]; 68 | I32[_P, 1] = I32[_P, 2]; 69 | I32[_P, 2] = t; 70 | 71 | for (int i = 2; i < n; i++) { 72 | var sx = I32[_S, i]; 73 | if (sx != 0) { 74 | I32[_S, i] = sx - 1; 75 | break; 76 | } 77 | 78 | if (i == m) 79 | return true; 80 | 81 | I32[_S, i] = i; 82 | 83 | // Rotate 0<-...<-i+1. 84 | t = I32[_P, 0]; 85 | for (int j = 0; j <= i; j++) { 86 | I32[_P, j] = I32[_P, j + 1]; 87 | } 88 | I32[_P, i + 1] = t; 89 | } 90 | 91 | return false; 92 | } 93 | 94 | [Export] 95 | public static int get_Sum () { 96 | return sum; 97 | } 98 | 99 | [Export] 100 | public static int get_MaxFlips () { 101 | return maxflips; 102 | } 103 | 104 | [Export] 105 | public static void fannkuch (int n) { 106 | int sign = 1; 107 | maxflips = 0; 108 | sum = 0; 109 | 110 | initHeap(n); 111 | 112 | do { 113 | // Copy and flip. 114 | process0thElement(n, sign, I32[_P, 0]); 115 | 116 | // Permute. 117 | if (sign == 1) { 118 | var t = I32[_P, 1]; 119 | I32[_P, 1] = I32[_P, 0]; 120 | I32[_P, 0] = t; 121 | sign = -1; // Rotate 0<-1. 122 | } else { 123 | sign = 1; // Rotate 0<-1 and 0<-1<-2. 124 | if (permuteNegativeSign(n)) 125 | return; 126 | } 127 | } while (true); 128 | } 129 | 130 | public static void Main () { 131 | SetHeapSize(4096); 132 | 133 | int n = 7; 134 | Invoke("fannkuch", n); 135 | 136 | AssertReturn(228, "get_Sum"); 137 | AssertReturn(16, "get_MaxFlips"); 138 | } 139 | } -------------------------------------------------------------------------------- /third_party/tests/Goto.cs: -------------------------------------------------------------------------------- 1 | // Derived from third_party/JSIL/Tests/TestCases/Goto.cs 2 | // Rewritten to run as wasm 3 | 4 | using System; 5 | using Wasm.Module; 6 | 7 | using static Wasm.Heap; 8 | using static Wasm.Test; 9 | 10 | public static class Program { 11 | public const int HeapSize = 1024; 12 | public const int MaxResultCount = 128; 13 | 14 | [Export] 15 | public static int ResultCount { get; set; } 16 | 17 | public const int _Result = 0; 18 | 19 | public const byte A = 1; 20 | public const byte C = 3; 21 | 22 | private static void Clear (int offset, int count) { 23 | for (var i = 0; i < count; i++) 24 | U8[offset, i] = 0; 25 | } 26 | 27 | private static void AddResult (byte result) { 28 | var count = ResultCount; 29 | U8[_Result, count] = result; 30 | ResultCount = count + 1; 31 | } 32 | 33 | private static void GotosInner () { 34 | int i = 0; 35 | a: 36 | i += 1; 37 | AddResult(A); 38 | 39 | for (; i < 16; i++) { 40 | if (i == 8) 41 | goto a; 42 | else 43 | goto c; 44 | 45 | c: 46 | AddResult(C); 47 | } 48 | } 49 | 50 | [Export("gotos")] 51 | public static void Gotos () { 52 | ResultCount = 0; 53 | Clear(_Result, MaxResultCount); 54 | GotosInner(); 55 | } 56 | 57 | [Export("getResult")] 58 | public static int GetResult (int index) { 59 | return (int)U8[_Result, index]; 60 | } 61 | 62 | public static void Main () { 63 | SetHeapSize(HeapSize); 64 | 65 | Invoke("gotos"); 66 | 67 | AssertReturn(16, "get_ResultCount"); 68 | AssertReturn(A, "getResult", 0); 69 | AssertReturn(C, "getResult", 1); 70 | AssertReturn(C, "getResult", 2); 71 | AssertReturn(C, "getResult", 3); 72 | AssertReturn(C, "getResult", 4); 73 | AssertReturn(C, "getResult", 5); 74 | AssertReturn(C, "getResult", 6); 75 | AssertReturn(C, "getResult", 7); 76 | AssertReturn(A, "getResult", 8); 77 | AssertReturn(C, "getResult", 9); 78 | AssertReturn(C, "getResult", 10); 79 | AssertReturn(C, "getResult", 11); 80 | AssertReturn(C, "getResult", 12); 81 | AssertReturn(C, "getResult", 13); 82 | AssertReturn(C, "getResult", 14); 83 | AssertReturn(C, "getResult", 15); 84 | AssertReturn(0, "getResult", 16); 85 | } 86 | } -------------------------------------------------------------------------------- /third_party/tests/Gradient.cs: -------------------------------------------------------------------------------- 1 | //#use lib/Targa.cs 2 | 3 | using System; 4 | using Wasm.Module; 5 | 6 | using static Wasm.Heap; 7 | using static Wasm.Test; 8 | 9 | public static class Program { 10 | [Export] 11 | public static int generateGradient (int heapOffset) { 12 | const int width = 8; 13 | const int height = 8; 14 | const int xScale = 256 / width; 15 | const int yScale = 256 / height; 16 | const int bytesPerPixel = Targa.BPP / 8; 17 | 18 | heapOffset = Targa.EmitHeader(heapOffset, width, height); 19 | 20 | for (int y = 0; y < height; y++) { 21 | int rowOffset = heapOffset + ((width * bytesPerPixel) * y); 22 | 23 | for (int x = 0; x < width; x++) { 24 | int pixelOffset = rowOffset + (x * bytesPerPixel); 25 | 26 | U8[pixelOffset, 0] = (byte)(y * yScale); 27 | U8[pixelOffset, 1] = 0; 28 | U8[pixelOffset, 2] = (byte)(x * xScale); 29 | } 30 | } 31 | 32 | const int length = (width * height * bytesPerPixel); 33 | SetStdout("gradient.tga"); 34 | Write(heapOffset, length); 35 | 36 | return heapOffset + length; 37 | } 38 | 39 | public static void Main () { 40 | SetHeapSize(2048); 41 | 42 | const int expectedLength = 210; 43 | AssertReturn(expectedLength, "generateGradient", 0); 44 | } 45 | } -------------------------------------------------------------------------------- /third_party/tests/NBody.cs: -------------------------------------------------------------------------------- 1 | /* The Computer Language Benchmarks Game 2 | http://shootout.alioth.debian.org/ 3 | 4 | contributed by Isaac Gouy, optimization and use of more C# idioms by Robert F. Tobler 5 | */ 6 | 7 | using System; 8 | using Wasm.Module; 9 | 10 | using static Wasm.Heap; 11 | using static Wasm.Test; 12 | 13 | public static class Program { 14 | // HACK: Scale and truncate before comparing 15 | const double Scale = 1000000000000000; 16 | 17 | [Export] 18 | public static long InitialEnergy { get; set; } 19 | [Export] 20 | public static long FinalEnergy { get; set; } 21 | 22 | public static void Main () { 23 | SetHeapSize(8192); 24 | 25 | Invoke("Test"); 26 | 27 | const long initial = -169075163828525; 28 | const long final = -169016441264431; 29 | 30 | AssertReturn(initial, "get_InitialEnergy"); 31 | AssertReturn(final, "get_FinalEnergy"); 32 | } 33 | 34 | [Export] 35 | public static void Test () { 36 | const int n = 10000; 37 | NBodySystem.Initialize(); 38 | 39 | InitialEnergy = (long)Math.Floor(NBodySystem.Energy() * Scale); 40 | 41 | for (int i = 0; i < n; i++) 42 | NBodySystem.Advance(0.01); 43 | 44 | FinalEnergy = (long)Math.Floor(NBodySystem.Energy() * Scale); 45 | } 46 | } 47 | 48 | public struct Body { 49 | public double x, y, z, vx, vy, vz, mass; 50 | } 51 | 52 | public unsafe struct Pair { 53 | public Body* bi, bj; 54 | } 55 | 56 | public static unsafe class NBodySystem { 57 | const int BodyCount = 5; 58 | const int PairCount = BodyCount * (BodyCount - 1) / 2; 59 | 60 | const double Pi = 3.141592653589793; 61 | const double Solarmass = 4 * Pi * Pi; 62 | const double DaysPeryear = 365.24; 63 | 64 | public static Body* bodies; 65 | public static Pair* pairs; 66 | 67 | private static void InitBody ( 68 | int index, 69 | double x = 0, 70 | double y = 0, 71 | double z = 0, 72 | double vx = 0, 73 | double vy = 0, 74 | double vz = 0, 75 | double mass = 0 76 | ) { 77 | var body = &bodies[index]; 78 | body->x = x; 79 | body->y = y; 80 | body->z = z; 81 | body->vx = vx; 82 | body->vy = vy; 83 | body->vz = vz; 84 | body->mass = mass; 85 | } 86 | 87 | public static void Initialize () { 88 | bodies = (Body*)(U8.Base); 89 | pairs = (Pair*)(&bodies[BodyCount]); 90 | 91 | InitBody( 92 | 0, // Sun 93 | mass: Solarmass 94 | ); 95 | 96 | InitBody( 97 | 1, // Jupiter 98 | x: 4.84143144246472090e+00, 99 | y: -1.16032004402742839e+00, 100 | z: -1.03622044471123109e-01, 101 | vx: 1.66007664274403694e-03 * DaysPeryear, 102 | vy: 7.69901118419740425e-03 * DaysPeryear, 103 | vz: -6.90460016972063023e-05 * DaysPeryear, 104 | mass: 9.54791938424326609e-04 * Solarmass 105 | ); 106 | 107 | InitBody( 108 | 2, // Saturn 109 | x: 8.34336671824457987e+00, 110 | y: 4.12479856412430479e+00, 111 | z: -4.03523417114321381e-01, 112 | vx: -2.76742510726862411e-03 * DaysPeryear, 113 | vy: 4.99852801234917238e-03 * DaysPeryear, 114 | vz: 2.30417297573763929e-05 * DaysPeryear, 115 | mass: 2.85885980666130812e-04 * Solarmass 116 | ); 117 | 118 | InitBody( 119 | 3, // Uranus 120 | x: 1.28943695621391310e+01, 121 | y: -1.51111514016986312e+01, 122 | z: -2.23307578892655734e-01, 123 | vx: 2.96460137564761618e-03 * DaysPeryear, 124 | vy: 2.37847173959480950e-03 * DaysPeryear, 125 | vz: -2.96589568540237556e-05 * DaysPeryear, 126 | mass: 4.36624404335156298e-05 * Solarmass 127 | ); 128 | 129 | InitBody( 130 | 4, // Neptune 131 | x: 1.53796971148509165e+01, 132 | y: -2.59193146099879641e+01, 133 | z: 1.79258772950371181e-01, 134 | vx: 2.68067772490389322e-03 * DaysPeryear, 135 | vy: 1.62824170038242295e-03 * DaysPeryear, 136 | vz: -9.51592254519715870e-05 * DaysPeryear, 137 | mass: 5.15138902046611451e-05 * Solarmass 138 | ); 139 | 140 | int pi = 0; 141 | for (int i = 0; i < BodyCount - 1; i++) { 142 | for (int j = i + 1; j < BodyCount; j++) { 143 | var p = &pairs[pi++]; 144 | p->bi = &bodies[i]; 145 | p->bj = &bodies[j]; 146 | } 147 | } 148 | 149 | double px = 0.0, py = 0.0, pz = 0.0; 150 | for (int i = 0; i < BodyCount; i++) { 151 | var b = &(bodies[i]); 152 | px += b->vx * b->mass; 153 | py += b->vy * b->mass; 154 | pz += b->vz * b->mass; 155 | } 156 | 157 | var sol = &bodies[0]; 158 | sol->vx = -px / Solarmass; 159 | sol->vy = -py / Solarmass; 160 | sol->vz = -pz / Solarmass; 161 | } 162 | 163 | public static void Advance (double dt) { 164 | for (int i = 0; i < PairCount; i++) { 165 | var p = &pairs[i]; 166 | var bi = p->bi; 167 | var bj = p->bj; 168 | 169 | double dx = bi->x - bj->x, 170 | dy = bi->y - bj->y, 171 | dz = bi->z - bj->z; 172 | double d2 = dx * dx + dy * dy + dz * dz; 173 | double mag = dt / (d2 * Math.Sqrt(d2)); 174 | bi->vx -= dx * bj->mass * mag; 175 | bj->vx += dx * bi->mass * mag; 176 | bi->vy -= dy * bj->mass * mag; 177 | bj->vy += dy * bi->mass * mag; 178 | bi->vz -= dz * bj->mass * mag; 179 | bj->vz += dz * bi->mass * mag; 180 | } 181 | 182 | for (int i = 0; i < BodyCount; i++) { 183 | var b = &bodies[i]; 184 | b->x += dt * b->vx; 185 | b->y += dt * b->vy; 186 | b->z += dt * b->vz; 187 | } 188 | } 189 | 190 | public static double Energy () { 191 | double e = 0.0; 192 | for (int i = 0; i < BodyCount; i++) { 193 | var bi = &bodies[i]; 194 | 195 | e += 0.5 * bi->mass * 196 | (bi->vx * bi->vx + 197 | bi->vy * bi->vy + 198 | bi->vz * bi->vz); 199 | 200 | for (int j = i + 1; j < BodyCount; j++) { 201 | var bj = &bodies[j]; 202 | 203 | double dx = bi->x - bj->x, 204 | dy = bi->y - bj->y, 205 | dz = bi->z - bj->z; 206 | e -= (bi->mass * bj->mass) / 207 | Math.Sqrt(dx * dx + dy * dy + dz * dz); 208 | } 209 | } 210 | return e; 211 | } 212 | } -------------------------------------------------------------------------------- /third_party/tests/Puzzle.cs: -------------------------------------------------------------------------------- 1 | //#use lib/Stdout.cs 2 | 3 | // see llvm_license.txt 4 | // https://github.com/llvm-mirror/test-suite/blob/master/SingleSource/Benchmarks/BenchmarkGame/puzzle.c 5 | 6 | using System; 7 | using Wasm.Module; 8 | 9 | using static Wasm.Heap; 10 | using static Wasm.Test; 11 | using static Stdout; 12 | 13 | public static partial class Stdout { 14 | public const int HeapBase = 20480; 15 | public const int HeapCapacity = 2048; 16 | } 17 | 18 | public static unsafe class Program { 19 | const int ARRAY_SIZE = 5000; 20 | const int NLOOPS1 = 10; 21 | 22 | const int RAND_MAX = 32767; 23 | const float RAND_MAX_PLUSONEF = RAND_MAX + 1.0f; 24 | 25 | const int INT_SIZE = 4; 26 | 27 | static ulong next = 1; 28 | 29 | // RNG implemented localy to avoid library incongruences 30 | static int rand () { 31 | next = next * 1103515245 + 12345; 32 | return (int)((uint)(next / 65536) % RAND_MAX+1); 33 | } 34 | 35 | static void srand (uint seed) { 36 | next = seed; 37 | } 38 | 39 | static int randInt (int min, int max) { 40 | int n = (max - min) + 1; 41 | float randval = rand() / RAND_MAX_PLUSONEF; 42 | int k = (int)(n * randval); 43 | return (k == n) ? k + min - 1 : k + min; 44 | } 45 | 46 | static void shuffle (int* items, int len) { 47 | for (int i = len-1; i > 0; --i) { 48 | float randval = rand() / RAND_MAX_PLUSONEF; 49 | int k = (int)((i + 1) * randval); 50 | int j = (k == (i + 1)) ? k - 1 : k; 51 | 52 | int aux = items[i]; 53 | items[i] = items[j]; 54 | items[j] = aux; 55 | } 56 | } 57 | 58 | static int* createRandomArray (int size) { 59 | int len = size + 1; 60 | int* result = imalloc(len); 61 | 62 | for (int i = 0; i < len; i++) 63 | result[i] = i; 64 | 65 | result[0] = randInt(1, size); 66 | 67 | shuffle(result, len); 68 | return result; 69 | } 70 | 71 | static int findDuplicate (int* data, int len) { 72 | int result = 0; 73 | 74 | for (int i = 0; i < len; i++) 75 | result = result ^ (i + 1) ^ data[i]; 76 | 77 | result ^= len; 78 | return result; 79 | } 80 | 81 | [Export] 82 | static void test () { 83 | int duplicate = 0; 84 | int* rndArr; 85 | 86 | srand(1); 87 | 88 | for (int i = 0; i < NLOOPS1; i++) { 89 | rndArr = createRandomArray(ARRAY_SIZE); 90 | duplicate = findDuplicate(rndArr, ARRAY_SIZE+1); 91 | ifree(rndArr); 92 | 93 | prints("Found duplicate: "); 94 | printi(duplicate); 95 | prints("\n"); 96 | } 97 | 98 | SetStdout("puzzle.log"); 99 | Write(Stdout.HeapBase, Stdout.Length); 100 | } 101 | 102 | static int* imalloc (int size) { 103 | // HACK: always put data at 0 104 | return I32.Base; 105 | } 106 | 107 | static void ifree (int* ptr) { 108 | // HACK: no-op 109 | } 110 | 111 | static void Main () { 112 | SetHeapSize(Stdout.HeapBase + Stdout.HeapCapacity); 113 | 114 | Invoke("test"); 115 | 116 | const int expectedLength = 219; 117 | 118 | AssertReturn(expectedLength, "get_stdout_length"); 119 | } 120 | 121 | } -------------------------------------------------------------------------------- /third_party/tests/Raytracer.cs: -------------------------------------------------------------------------------- 1 | //#use lib/Targa.cs 2 | 3 | // ncbray/wassembler demos/raytrace.wasm 4 | 5 | using System; 6 | using System.Runtime.InteropServices; 7 | using Wasm.Module; 8 | 9 | using static Wasm.Heap; 10 | using static Wasm.Test; 11 | 12 | [StructLayout(LayoutKind.Sequential, Pack=1)] 13 | public struct Vec3f { 14 | public float x, y, z; 15 | } 16 | 17 | public static unsafe class Raytrace { 18 | public const int TargaHeaderSize = 18; 19 | public const int BytesPerPixel = 3; 20 | 21 | private static int width, height; 22 | private static byte* frame_buffer; 23 | 24 | // TODO: Implement ref/out and turn these into struct fields 25 | private static Vec3f* 26 | pos, dir, 27 | light, half, 28 | color, intersection_normal; 29 | 30 | // Convert [0.0, 1.0] to [0, 255]. 31 | public static int f2b (float v) { 32 | var vi = (int)(v * 255.0f); 33 | if (vi < 0) 34 | return 0; 35 | else if (vi > 255) 36 | return 255; 37 | else 38 | return vi; 39 | } 40 | 41 | public static float fsin (float v) { 42 | if (v < 0.5f) 43 | return -16.0f * v * v + 8.0f * v; 44 | else 45 | return 16.0f * v * v - 24.0f * v + 8.0f; 46 | } 47 | 48 | public static float fsqrt (float v) { 49 | return (float)Math.Sqrt(v); 50 | } 51 | 52 | // Convert a linear color value to a gamma-space byte. 53 | // Square root approximates gamma-correct rendering. 54 | public static int l2g (float v) { 55 | return f2b(fsqrt(v)); 56 | } 57 | 58 | public static void storeColor (int x, int y, float r, float g, float b) { 59 | // Invert y 60 | y = height - y - 1; 61 | 62 | var index = (x + (y * width)) * BytesPerPixel; 63 | var ptr = &frame_buffer[index]; 64 | 65 | // Reversed r/g/b order 66 | ptr[2] = (byte)l2g(r); 67 | ptr[1] = (byte)l2g(g); 68 | ptr[0] = (byte)l2g(b); 69 | } 70 | 71 | public static void vecStore (float x, float y, float z, Vec3f* ptr) { 72 | ptr->x = x; 73 | ptr->y = y; 74 | ptr->z = z; 75 | } 76 | 77 | public static void vecAdd (Vec3f* a, Vec3f* b, Vec3f* ptr) { 78 | vecStore( 79 | a->x + b->x, 80 | a->y + b->y, 81 | a->z + b->z, 82 | ptr 83 | ); 84 | } 85 | 86 | public static void vecScale (Vec3f* a, float scale, Vec3f* ptr) { 87 | vecStore( 88 | a->x * scale, 89 | a->y * scale, 90 | a->z * scale, 91 | ptr 92 | ); 93 | } 94 | 95 | public static void vecNormalize (Vec3f* ptr) { 96 | var x = ptr->x; 97 | var y = ptr->y; 98 | var z = ptr->z; 99 | 100 | float invLen = 1.0f / fsqrt((x * x) + (y * y) + (z * z)); 101 | vecStore(x * invLen, y * invLen, z * invLen, ptr); 102 | } 103 | 104 | public static float vecLen (Vec3f* ptr) { 105 | var x = ptr->x; 106 | var y = ptr->y; 107 | var z = ptr->z; 108 | 109 | return fsqrt((x * x) + (y * y) + (z * z)); 110 | } 111 | 112 | public static float vecDot (Vec3f* a, Vec3f* b) { 113 | return (a->x * b->x) + (a->y * b->y) + (a->z * b->z); 114 | } 115 | 116 | public static float vecNLDot (Vec3f* a, Vec3f* b) { 117 | var value = vecDot(a, b); 118 | if (value < 0) 119 | return 0; 120 | else 121 | return value; 122 | } 123 | 124 | public static void sampleEnv (Vec3f* dir, Vec3f* ptr) { 125 | var y = dir->y; 126 | var amt = y * 0.5f + 0.5f; 127 | var keep = 1.0f - amt; 128 | vecStore( 129 | keep * 0.1f + amt * 0.1f, 130 | keep * 1.0f + amt * 0.1f, 131 | keep * 0.1f + amt * 1.0f, 132 | ptr 133 | ); 134 | } 135 | 136 | public static bool intersect () { 137 | var px = pos->x; 138 | var py = pos->y; 139 | var pz = pos->z; 140 | 141 | var vx = dir->x; 142 | var vy = dir->y; 143 | var vz = dir->z; 144 | 145 | // The sphere. 146 | var radius = 4.0f; 147 | var cx = 0.0f; 148 | var cy = 0; // fsin(phase); 149 | var cz = -6.0f; 150 | 151 | // Calculate the position relative to the center of the sphere. 152 | var ox = px - cx; 153 | var oy = py - cy; 154 | var oz = pz - cz; 155 | 156 | var dot = vx * ox + vy * oy + vz * oz; 157 | 158 | var partial = dot * dot + radius * radius - (ox * ox + oy * oy + oz * oz); 159 | if (partial >= 0.0f) { 160 | var d = -dot - fsqrt(partial); 161 | 162 | if (d >= 0.0f) { 163 | vecStore(px + vx * d - cx, py + vy * d - cy, pz + vz * d - cz, intersection_normal); 164 | vecNormalize(intersection_normal); 165 | return true; 166 | } 167 | } 168 | 169 | return false; 170 | } 171 | 172 | [Export] 173 | public static void renderFrame () { 174 | var w = width; 175 | var h = height; 176 | 177 | vecStore(20.0f, 20.0f, 15.0f, light); 178 | vecNormalize(light); 179 | 180 | var j = 0; 181 | while (j < h) { 182 | var y = 0.5f - (float)(j) / (float)(h); 183 | 184 | var i = 0; 185 | while (i < w) { 186 | var x = (float)(i) / (float)(w) - 0.5f; 187 | vecStore(x, y, 0.0f, pos); 188 | vecStore(x, y, -0.5f, dir); 189 | vecNormalize(dir); 190 | 191 | // Compute the half vector; 192 | vecScale(dir, -1.0f, half); 193 | vecAdd(half, light, half); 194 | vecNormalize(half); 195 | 196 | // Light accumulation 197 | var r = 0.0f; 198 | var g = 0.0f; 199 | var b = 0.0f; 200 | 201 | // Surface diffuse. 202 | var dr = 0.7f; 203 | var dg = 0.7f; 204 | var db = 0.7f; 205 | 206 | if (intersect()) { 207 | sampleEnv(intersection_normal, color); 208 | 209 | const float ambientScale = 0.2f; 210 | r = r + dr * color->x * ambientScale; 211 | g = g + dg * color->y * ambientScale; 212 | b = b + db * color->z * ambientScale; 213 | 214 | var diffuse = vecNLDot(intersection_normal, light); 215 | var specular = vecNLDot(intersection_normal, half); 216 | 217 | // Take it to the 64th power, manually. 218 | specular = specular * specular; 219 | specular = specular * specular; 220 | specular = specular * specular; 221 | specular = specular * specular; 222 | specular = specular * specular; 223 | specular = specular * specular; 224 | 225 | specular = specular * 0.6f; 226 | 227 | r = r + dr * diffuse + specular; 228 | g = g + dg * diffuse + specular; 229 | b = b + db * diffuse + specular; 230 | } else { 231 | sampleEnv(dir, color); 232 | r = color->x; 233 | g = color->y; 234 | b = color->z; 235 | } 236 | 237 | storeColor(i, j, r, g, b); 238 | 239 | i = i + 1; 240 | } 241 | 242 | j = j + 1; 243 | } 244 | 245 | int expectedSize = width * height * BytesPerPixel; 246 | 247 | SetStdout("raytraced.tga"); 248 | Write(0, TargaHeaderSize + expectedSize); 249 | } 250 | 251 | [Export] 252 | public static void init (int w, int h, int pFrameBuffer) { 253 | width = w; 254 | height = h; 255 | frame_buffer = &U8.Base[pFrameBuffer]; 256 | 257 | var pScratch = pFrameBuffer + (width * height * BytesPerPixel) + 1024; 258 | var scratch = (Vec3f*)&U8.Base[pScratch]; 259 | 260 | // FIXME 261 | pos = &scratch[0]; 262 | dir = &scratch[1]; 263 | light = &scratch[2]; 264 | half = &scratch[3]; 265 | color = &scratch[4]; 266 | intersection_normal = &scratch[5]; 267 | } 268 | } 269 | 270 | public static class Program { 271 | public static void Main () { 272 | SetHeapSize(64 * 1024); 273 | 274 | const int width = 16; 275 | const int height = 16; 276 | const int heapOffset = 0; 277 | 278 | Invoke("init", width, height, heapOffset + Raytrace.TargaHeaderSize); 279 | AssertReturn(18, "emitTargaHeader", heapOffset, width, height); 280 | Invoke("renderFrame"); 281 | } 282 | } -------------------------------------------------------------------------------- /third_party/tests/Sieve.cs: -------------------------------------------------------------------------------- 1 | // Derived from third_party/JSIL/Tests/PerformanceTestCases/Sieve.cs 2 | // Rewritten to run as wasm 3 | 4 | using System; 5 | using Wasm.Module; 6 | 7 | using static Wasm.Heap; 8 | using static Wasm.Test; 9 | 10 | public static class Program { 11 | public const int HeapSize = (1024 * 4); 12 | public const int MaxResultCount = 512; 13 | 14 | [Export] 15 | public static int ResultCount { get; set; } 16 | 17 | public const int _Result = 0; 18 | public const int _Scratch = ((MaxResultCount * 4) + 4); 19 | 20 | private static void Clear (int offset, int count) { 21 | for (var i = 0; i < count; i++) 22 | U8[offset, i] = 0; 23 | } 24 | 25 | private static void AddResult (int result) { 26 | var count = ResultCount; 27 | I32[_Result, count] = result; 28 | ResultCount = count + 1; 29 | } 30 | 31 | [Export("sieve")] 32 | public static void Sieve (int target) { 33 | ResultCount = 0; 34 | Clear(_Result, MaxResultCount * 4); 35 | Clear(_Scratch, target); 36 | 37 | var squareRoot = Math.Sqrt(target); 38 | 39 | AddResult(2); 40 | 41 | for (int candidate = 3; candidate < target; candidate += 2) { 42 | if (U8[_Scratch, candidate] != 0) 43 | continue; 44 | 45 | if (candidate < squareRoot) { 46 | for (int multiple = candidate * candidate; multiple < target; multiple += 2 * candidate) 47 | U8[_Scratch, multiple] = 1; 48 | } 49 | 50 | AddResult(candidate); 51 | } 52 | } 53 | 54 | [Export("getResult")] 55 | public static int GetResult (int index) { 56 | return I32[_Result, index]; 57 | } 58 | 59 | public static void Main () { 60 | SetHeapSize(HeapSize); 61 | 62 | Invoke("sieve", 24); 63 | 64 | AssertReturn(9, "get_ResultCount"); 65 | AssertReturn(2, "getResult", 0); 66 | AssertReturn(3, "getResult", 1); 67 | AssertReturn(5, "getResult", 2); 68 | AssertReturn(7, "getResult", 3); 69 | AssertReturn(11, "getResult", 4); 70 | AssertReturn(13, "getResult", 5); 71 | AssertReturn(17, "getResult", 6); 72 | AssertReturn(19, "getResult", 7); 73 | AssertReturn(23, "getResult", 8); 74 | } 75 | } -------------------------------------------------------------------------------- /third_party/tests/Strcat.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Wasm.Module; 3 | 4 | using static Wasm.Heap; 5 | using static Wasm.Test; 6 | 7 | public static class Program { 8 | private static int strcat (int dest, string src) { 9 | for (int i = 0, l = src.Length; i < l; i++) 10 | U8[dest, i] = (byte)src[i]; 11 | 12 | U8[dest, src.Length] = 0; 13 | return dest + src.Length; 14 | } 15 | 16 | [Export] 17 | public static void buildString () { 18 | int offset = strcat(0, "hello"); 19 | offset = strcat(offset, ", "); 20 | offset = strcat(offset, "world"); 21 | offset = strcat(offset, "!"); 22 | 23 | SetStdout("strcat.log"); 24 | Write(0, offset); 25 | } 26 | 27 | public static void Main () { 28 | SetHeapSize(1024); 29 | 30 | Invoke("buildString"); 31 | } 32 | } -------------------------------------------------------------------------------- /third_party/tests/Strings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Wasm.Module; 3 | 4 | using static Wasm.Heap; 5 | using static Wasm.Test; 6 | 7 | public static class Program { 8 | public static string getAString (int index) { 9 | switch (index) { 10 | case 1: 11 | return "hello"; 12 | case 2: 13 | return "world!"; 14 | case 3: 15 | return "\0\r\n\x07"; 16 | default: 17 | return null; 18 | } 19 | } 20 | 21 | public static int strcmp (string lhs, string rhs) { 22 | if (lhs == null) { 23 | if (rhs == null) 24 | return 0; 25 | else 26 | return -1; 27 | } else if (rhs == null) { 28 | return 1; 29 | } 30 | 31 | var length = lhs.Length; 32 | if (rhs.Length < length) 33 | length = rhs.Length; 34 | 35 | for (var i = 0; i < length; i++) { 36 | var chL = lhs[i]; 37 | var chR = rhs[i]; 38 | 39 | if (chL < chR) 40 | return -1; 41 | else if (chL > chR) 42 | return 1; 43 | } 44 | 45 | // FIXME: Length difference 46 | return 0; 47 | } 48 | 49 | [Export] 50 | public static int compareStrings (int lhsIndex, int rhsIndex) { 51 | return strcmp(getAString(lhsIndex), getAString(rhsIndex)); 52 | } 53 | 54 | [Export] 55 | public static int getStringLength (int stringIndex) { 56 | var str = getAString(stringIndex); 57 | return str.Length; 58 | } 59 | 60 | [Export] 61 | public static char readStringChar (int stringIndex, int offset) { 62 | var str = getAString(stringIndex); 63 | return str[offset]; 64 | } 65 | 66 | public static void Main () { 67 | AssertReturn(5, "getStringLength", 1); 68 | AssertReturn(6, "getStringLength", 2); 69 | 70 | AssertReturn('h', "readStringChar", 1, 0); 71 | AssertReturn('l', "readStringChar", 1, 2); 72 | 73 | AssertReturn('w', "readStringChar", 2, 0); 74 | AssertReturn('l', "readStringChar", 2, 3); 75 | AssertReturn('!', "readStringChar", 2, 5); 76 | 77 | AssertReturn(0, "compareStrings", 0, 0); 78 | AssertReturn(-1, "compareStrings", 0, 1); 79 | AssertReturn(1, "compareStrings", 2, 0); 80 | AssertReturn(0, "compareStrings", 1, 1); 81 | AssertReturn(-1, "compareStrings", 1, 2); 82 | AssertReturn(1, "compareStrings", 2, 1); 83 | 84 | AssertReturn('\0', "readStringChar", 3, 0); 85 | AssertReturn('\r', "readStringChar", 3, 1); 86 | AssertReturn('\n', "readStringChar", 3, 2); 87 | AssertReturn('\x07', "readStringChar", 3, 3); 88 | } 89 | } -------------------------------------------------------------------------------- /third_party/tests/lib/Stdout.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Wasm.Module; 3 | 4 | using static Wasm.Heap; 5 | using static Wasm.Test; 6 | 7 | public static partial class Stdout { 8 | public static int Length; 9 | 10 | public static void putchar (int ch) { 11 | U8[HeapBase, Length++] = (byte)ch; 12 | } 13 | 14 | public static void prints (string str) { 15 | for (int i = 0, l = str.Length; i < l; i++) 16 | putchar(str[i]); 17 | } 18 | 19 | public static void printi (int value) { 20 | const int zero = '0'; 21 | 22 | int initial_offset = Length; 23 | bool negative = value < 0; 24 | 25 | if (negative) 26 | value = -value; 27 | 28 | // Output number in reverse 29 | for (; value > 0; value = value / 10) 30 | putchar(zero + (value % 10)); 31 | 32 | if (negative) 33 | putchar('-'); 34 | 35 | // In-place reverse result into correct order 36 | int j = (Length - initial_offset) - 1; 37 | int reverse_base = HeapBase + initial_offset; 38 | // Console.WriteLine("j={0} reverse_base={1}", j, reverse_base); 39 | for (int i = 0; i < j; i += 1, j -= 1) { 40 | byte temp = U8[reverse_base, i]; 41 | U8[reverse_base, i] = U8[reverse_base, j]; 42 | U8[reverse_base, j] = temp; 43 | } 44 | } 45 | 46 | [Export] 47 | static int get_stdout_length () { 48 | return Length; 49 | } 50 | } -------------------------------------------------------------------------------- /third_party/tests/lib/Targa.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Wasm.Module; 3 | 4 | using static Wasm.Heap; 5 | 6 | public static partial class Targa { 7 | public const int BPP = 24; 8 | 9 | [Export("emitTargaHeader")] 10 | public static unsafe int EmitHeader (int offset, int width, int height) { 11 | var p = &U8.Base[offset]; 12 | 13 | // ID length 14 | *p++ = 0; 15 | 16 | // Colormap type - none 17 | *p++ = 0; 18 | 19 | // Image type - uncompressed truecolor 20 | *p++ = 2; 21 | 22 | // Colormap specification 23 | for (var j = 0; j < 5; j++) 24 | *p++ = 0; 25 | 26 | // Image specification 27 | 28 | // X origin 29 | *p++ = 0; 30 | *p++ = 0; 31 | 32 | // Y origin 33 | *p++ = 0; 34 | *p++ = 0; 35 | 36 | // Width 37 | *p++ = (byte)width; 38 | width = width >> 8; 39 | *p++ = (byte)width; 40 | 41 | // Height 42 | *p++ = (byte)height; 43 | height = height >> 8; 44 | *p++ = (byte)height; 45 | 46 | // Bits per pixel 47 | *p++ = (byte)BPP; 48 | 49 | // Image descriptor [0..3] = alpha depth, [4..5] direction 50 | *p++ = 0; 51 | 52 | return (int)(p - U8.Base); 53 | } 54 | } -------------------------------------------------------------------------------- /third_party/tests/llvm_license.txt: -------------------------------------------------------------------------------- 1 | ============================================================================== 2 | LLVM Release License 3 | ============================================================================== 4 | University of Illinois/NCSA 5 | Open Source License 6 | 7 | Copyright (c) 2003, 2004, 2005 University of Illinois at Urbana-Champaign. 8 | All rights reserved. 9 | 10 | Developed by: 11 | 12 | LLVM Team 13 | 14 | University of Illinois at Urbana-Champaign 15 | 16 | http://llvm.org/ 17 | 18 | Permission is hereby granted, free of charge, to any person obtaining a copy of 19 | this software and associated documentation files (the "Software"), to deal with 20 | the Software without restriction, including without limitation the rights to 21 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 22 | of the Software, and to permit persons to whom the Software is furnished to do 23 | so, subject to the following conditions: 24 | 25 | * Redistributions of source code must retain the above copyright notice, 26 | this list of conditions and the following disclaimers. 27 | 28 | * Redistributions in binary form must reproduce the above copyright notice, 29 | this list of conditions and the following disclaimers in the 30 | documentation and/or other materials provided with the distribution. 31 | 32 | * Neither the names of the LLVM Team, University of Illinois at 33 | Urbana-Champaign, nor the names of its contributors may be used to 34 | endorse or promote products derived from this Software without specific 35 | prior written permission. 36 | 37 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 38 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 39 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 40 | CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 41 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 42 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE 43 | SOFTWARE. 44 | 45 | ============================================================================== 46 | Copyrights and Licenses for Third Party Software Distributed with LLVM: 47 | ============================================================================== 48 | The LLVM software contains code written by third parties. Such software will 49 | have its own individual LICENSE.TXT file in the directory in which it appears. 50 | This file will describe the copyrights, license, and restrictions which apply 51 | to that code. 52 | 53 | The disclaimer of warranty in the University of Illinois Open Source License 54 | applies to all code in the LLVM Distribution, and nothing in any of the 55 | other licenses gives permission to use the names of the LLVM Team or the 56 | University of Illinois to endorse or promote products derived from this 57 | Software. 58 | 59 | The following pieces of software have additional or alternate copyrights, 60 | licenses, and/or restrictions: 61 | 62 | Program Directory 63 | ------- --------- 64 | Autoconf: llvm-test/autoconf 65 | Burg: llvm-test/MultiSource/Applications/Burg 66 | Aha: llvm-test/MultiSource/Applications/aha 67 | SGEFA: llvm-test/MultiSource/Applications/sgefa 68 | SIOD: llvm-test/MultiSource/Applications/siod 69 | Spiff: llvm-test/MultiSource/Applications/spiff 70 | D: llvm-test/MultiSource/Applications/d 71 | Lambda: llvm-test/MultiSource/Applications/lambda-0.1.3 72 | hbd: llvm-test/MultiSource/Applications/hbd 73 | treecc: llvm-test/MultiSource/Applications/treecc 74 | kimwitu++: llvm-test/MultiSource/Applications/kimwitu++ 75 | obsequi: llvm-test/MultiSource/Applications/obsequi 76 | Hexxagon: llvm-test/MultiSource/Applications/hexxagon 77 | SPASS: llvm-test/MultiSource/Applications/SPASS 78 | oggenc: llvm-test/MultiSource/Applications/oggenc 79 | JM: llvm-test/MultiSource/Applications/JM 80 | ALAC: llvm-test/MultiSource/Applications/ALAC 81 | ASC Sequoia: llvm-test/MultiSource/Benchmarks/ASC_Sequoia/AMGmk 82 | llvm-test/MultiSource/Benchmarks/ASC_Sequoia/IRSmk 83 | llvm-test/MultiSource/Benchmarks/ASC_Sequoia/sphot 84 | smg2000: llvm-test/MultiSource/Benchmarks/ASCI_Purple/SMG2000 85 | Fhourstones: llvm-test/MultiSource/Benchmarks/Fhourstones 86 | Fhourstones-3.1: llvm-test/MultiSource/Benchmarks/Fhourstones-3.1 87 | McCat: llvm-test/MultiSource/Benchmarks/McCat 88 | Olden: llvm-test/MultiSource/Benchmarks/Olden 89 | OptimizerEval: llvm-test/MultiSource/Benchmarks/OptimizerEval 90 | Ptrdist: llvm-test/MultiSource/Benchmarks/Ptrdist 91 | LLUBenchmark: llvm-test/MultiSource/Benchmarks/llubenchmark 92 | SIM: llvm-test/MultiSource/Benchmarks/sim 93 | cfrac: llvm-test/MultiSource/Benchmarks/MallocBench/cfrac 94 | espresso: llvm-test/MultiSource/Benchmarks/MallocBench/espresso 95 | gs: llvm-test/MultiSource/Benchmarks/MallocBench/gs 96 | p2c: llvm-test/MultiSource/Benchmarks/MallocBench/p2c 97 | gawk: llvm-test/MultiSource/Benchmarks/MallocBench/gawk 98 | make: llvm-test/MultiSource/Benchmarks/MallocBench/make 99 | perl: llvm-test/MultiSource/Benchmarks/MallocBench/perl 100 | adpcm: llvm-test/MultiSource/Benchmarks/mediabench/adpcm 101 | g721: llvm-test/MultiSource/Benchmarks/mediabench/g721 102 | gsm: llvm-test/MultiSource/Benchmarks/mediabench/gsm 103 | jpeg: llvm-test/MultiSource/Benchmarks/mediabench/jpeg 104 | mpeg2: llvm-test/MultiSource/Benchmarks/mediabench/mpeg2 105 | TSVC: llvm-test/MultiSource/Benchmarks/TSVC 106 | 7zip (Benchmark): llvm-test/MultiSource/Benchmarks/7zip 107 | Dhrystone: llvm-test/SingleSource/Benchmarks/Dhrystone 108 | SingleSource Tests: llvm-test/SingleSource/Benchmarks/Misc 109 | llvm-test/SingleSource/CustomChecked 110 | llvm-test/SingleSource/Gizmos 111 | llvm-test/SingleSource/Benchmarks/SmallPT 112 | -------------------------------------------------------------------------------- /windows-build.bat: -------------------------------------------------------------------------------- 1 | @del /F/Q output\* 2 | @msbuild WasmSExprEmitter.sln /p:Platform="Any CPU" /p:Configuration="Debug" /v:m --------------------------------------------------------------------------------