Repository: migueldeicaza/mono-wasm Branch: master Commit: c46c1e0185d1 Files: 20 Total size: 81.3 KB Directory structure: gitextract_u8dox53_/ ├── .gitignore ├── LICENSE.txt ├── Makefile ├── README.md ├── boot.c ├── index.js ├── jsmin.c ├── mono-wasm.cpp ├── mscorlib.xml ├── sample/ │ ├── hello/ │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── hello.cs │ │ └── index.html │ └── hello2/ │ ├── .gitignore │ ├── Makefile │ ├── hello.cs │ └── index.html └── tests/ └── Mono.WebAssembly/ ├── Makefile ├── index.html └── test.cs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .DS_Store .gdb_history build dist mscorlib.dll *.o .*.swo .*.swp mono-wasm *.dSYM ================================================ FILE: LICENSE.txt ================================================ MIT License Copyright (c) 2017 - present Microsoft Corporation All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ MONO_RUNTIME_PATH = ../mono-runtime MONO_COMPILER_PATH = ../mono-compiler LIBC_PATH = ../libc LLVM_PATH = ../llvm-build CLANG = $(LLVM_PATH)/bin/clang LIBC_CFLAGS = -fno-stack-protector -nostdinc -I$(LIBC_PATH)/include -I$(LIBC_PATH)/arch/wasm32 -target wasm32 -Wno-shift-op-parentheses -Wno-incompatible-library-redeclaration -Wno-bitwise-op-parentheses LIBC_INTERNAL_CFLAGS = $(LIBC_CFLAGS) -I$(LIBC_PATH)/src/internal MONO_CFLAGS = $(LIBC_CFLAGS) -I$(MONO_RUNTIME_PATH) -I$(MONO_RUNTIME_PATH)/mono -I$(MONO_RUNTIME_PATH)/eglib/src -DHAVE_CONFIG_H -D_THREAD_SAFE -DUSE_MMAP -DUSE_MUNMAP -std=gnu99 -fwrapv -DMONO_DLL_EXPORT -Wno-unused-value -Wno-tautological-compare -Wno-bitwise-op-parentheses all: dist-install build/libmini.bc: $(patsubst %, build/mini/%.bc, abcremoval alias-analysis aot-compiler aot-runtime branch-opts cfgdump cfold debug-mini debugger-agent decompose dominators driver dwarfwriter graph helpers image-writer jit-icalls linear-scan liveness lldb local-propagation memory-access method-to-ir mini-codegen mini-cross-helpers mini-wasm32 mini-exceptions mini-gc mini-generic-sharing mini-native-types mini-posix mini-runtime mini-trampolines mini seq-points simd-intrinsics ssa tasklets trace type-checking unwind xdebug) build/libmetadata.bc: $(patsubst %, build/metadata/%.bc, appdomain assembly attach class-accessors class cominterop console-wasm coree custom-attrs debug-helpers debug-mono-ppdb debug-mono-symfile decimal-ms domain dynamic-image dynamic-stream environment exception file-mmap-posix file-mmap-windows filewatcher gc-stats gc handle icall image jit-info loader locales lock-tracer marshal mempool metadata-cross-helpers metadata-verify metadata method-builder monitor mono-basic-block mono-conc-hash mono-config mono-config-dirs mono-debug mono-endian mono-hash mono-mlist mono-perfcounters mono-route mono-security number-ms object opcodes profiler property-bag rand reflection remoting runtime security-core-clr security-manager seq-points-data sgen-bridge sgen-mono sgen-new-bridge sgen-old-bridge sgen-stw sgen-tarjan-bridge sgen-toggleref sre-encode sre-save sre string-icalls sysmath threadpool-io threadpool-worker-default threadpool threads verify w32error-unix w32event-unix w32file-unix-glob w32file-unix w32file w32handle-namespace w32handle w32mutex-unix w32process-unix-bsd w32process-unix-default w32process-unix-haiku w32process-unix-osx w32process-unix w32process w32semaphore-unix w32socket-unix w32socket) build/libutils.bc: $(patsubst %, build/utils/%.bc, atomic mono-os-mutex bsearch mono-path checked-build mono-poll dlmalloc mono-proclib-windows hazard-pointer mono-proclib json mono-property-hash lock-free-alloc mono-publib lock-free-array-queue mono-rand-windows lock-free-queue mono-rand mono-sha1 mach-support mono-stdlib memfuncs mono-threads-android mono-codeman mono-threads-coop mono-conc-hashtable mono-threads-freebsd mono-context mono-threads-haiku mono-counters mono-threads-linux mono-dl-darwin mono-threads-mach-helper mono-dl-posix mono-threads-mach mono-dl-windows mono-threads-netbsd mono-dl mono-threads-openbsd mono-error mono-threads-posix-signals mono-filemap mono-threads-posix mono-threads-state-machine mono-hwcap mono-hwcap-wasm32 mono-threads-windows mono-internal-hash mono-threads mono-io-portability mono-time mono-linked-list-set mono-tls mono-log-android mono-uri mono-log-common mono-value-hash mono-log-darwin monobitset mono-log-posix networking-fallback mono-log-windows networking-missing mono-logger networking-posix mono-math networking-windows mono-md5 networking mono-mmap-windows os-event-unix mono-mmap parse mono-networkinterfaces strenc) build/libsgen.bc: $(patsubst %, build/sgen/sgen-%.bc, alloc array-list cardtable debug descriptor fin-weak-hash gc gchandles gray hash-table internal layout-stats los marksweep memory-governor nursery-allocator pinning-stats pinning pointer-queue protocol qsort simple-nursery split-nursery thread-pool workers) build/libeglib.bc: $(patsubst %, build/eglib/%.bc, garray goutput gbytearray gpath gdate-unix gpattern gdir-unix gptrarray gerror gqsort gfile-posix gqueue gfile-unix gshell gfile gslist ghashtable gspawn giconv gstr glist gstring gmarkup gtimer-unix gmem gunicode gmisc-unix gutf8 gmodule-unix) build/libmono.bc: build/libmini.bc build/libmetadata.bc build/libutils.bc build/libsgen.bc build/libeglib.bc build/libc.bc: $(patsubst %.c, build/libc/%.bc, $(shell (cd $(LIBC_PATH)/src && ls {ctype,env,errno,exit,internal,ldso,dlmalloc,fcntl,locale,math,prng,signal,stdio,string,stdlib,time,unistd}/*.c | grep -Ev "(pread|pwrite|sigaltstack|strtok_r)")) conf/sysconf.c thread/__lock.c misc/getrlimit.c mman/madvise.c stat/stat.c stat/fstat.c) build/libmini.bc build/libmetadata.bc build/libeglib.bc build/libsgen.bc build/libutils.bc build/libmono.bc build/libc.bc: $(LLVM_PATH)/bin/llvm-link $^ -o $@ build/libc/%.bc: $(LIBC_PATH)/src/%.c @/bin/mkdir -p $(dir $@) $(CLANG) $(LIBC_INTERNAL_CFLAGS) $< -c -emit-llvm -o $@ build/mini/%.bc : $(MONO_RUNTIME_PATH)/mono/mini/%.c @/bin/mkdir -p $(dir $@) $(CLANG) -I$(MONO_RUNTIME_PATH)/mono/mini $(MONO_CFLAGS) $< -c -emit-llvm -o $@ build/metadata/%.bc : $(MONO_RUNTIME_PATH)/mono/metadata/%.c @/bin/mkdir -p $(dir $@) $(CLANG) -I$(MONO_RUNTIME_PATH)/mono/metadata $(MONO_CFLAGS) -DHAVE_SGEN_GC $< -c -emit-llvm -o $@ build/utils/%.bc : $(MONO_RUNTIME_PATH)/mono/utils/%.c @/bin/mkdir -p $(dir $@) $(CLANG) -I$(MONO_RUNTIME_PATH)/mono/utils $(MONO_CFLAGS) -DHAVE_SGEN_GC $< -c -emit-llvm -o $@ build/sgen/%.bc : $(MONO_RUNTIME_PATH)/mono/sgen/%.c @/bin/mkdir -p $(dir $@) $(CLANG) -I$(MONO_RUNTIME_PATH)/mono/sgen $(MONO_CFLAGS) -DHAVE_SGEN_GC $< -c -emit-llvm -o $@ build/eglib/%.bc : $(MONO_RUNTIME_PATH)/eglib/src/%.c @/bin/mkdir -p $(dir $@) $(CLANG) $(MONO_CFLAGS) $< -c -emit-llvm -o $@ mscorlib.dll: $(MONO_COMPILER_PATH)/mcs/class/lib/wasm/mscorlib.dll cp $< $@ build/boot.bc: boot.c @/bin/mkdir -p $(dir $@) $(CLANG) $(MONO_CFLAGS) boot.c -c -emit-llvm -o build/boot.bc build/runtime.bc: build/boot.bc build/libc.bc build/libmono.bc @/bin/mkdir -p $(dir $@) $(LLVM_PATH)/bin/llvm-link build/libc.bc build/libmono.bc build/boot.bc -o build/runtime.bc MONO_WASM_CXXFLAGS = -Wno-sign-compare -std=c++1y -UNDEBUG -fexceptions MONO_WASM_LLVM_COMPONENTS = BitReader BitWriter Core IRReader Linker Object Support TransformUtils IPO webassembly Option jsmin.o: jsmin.c /usr/bin/clang -c jsmin.c -o jsmin.o mono-wasm: jsmin.o mono-wasm.cpp /usr/bin/clang++ $(shell $(LLVM_PATH)/bin/llvm-config --cxxflags --ldflags) -Wno-gnu $(MONO_WASM_CXXFLAGS) -I$(shell $(LLVM_PATH)/bin/llvm-config --src-root)/tools/lld/include -g mono-wasm.cpp -o mono-wasm -lncurses -lz jsmin.o $(shell $(LLVM_PATH)/bin/llvm-config --libs $(MONO_WASM_LLVM_COMPONENTS)) -llldCommon -llldCore -llldDriver -llldReaderWriter -llldWasm dist-install: mono-wasm build/runtime.bc mscorlib.dll rm -rf dist mkdir -p dist/bin cp mono-wasm dist/bin cp $(MONO_COMPILER_PATH)/mono/mini/mono dist/bin/monoc mkdir -p dist/lib cp mscorlib.dll dist/lib cp mscorlib.xml dist/lib cp build/runtime.bc dist/lib cp index.js dist/lib need-version: ifndef VERSION $(error VERSION is undefined) endif make-dist-dir: DIST_DIR = mono-wasm-macos-$(VERSION) release: need-version make-dist-dir dist-install mkdir -p releases (cd releases \ && mkdir $(DIST_DIR) \ && ditto ../dist $(DIST_DIR)/dist \ && ditto ../sample $(DIST_DIR)/sample \ && for i in `ls $(DIST_DIR)/sample`; do (cd $(DIST_DIR)/sample/$$i && make clean); done \ && zip -r $(DIST_DIR).zip $(DIST_DIR)) clean: /bin/rm -rf build dist mscorlib.dll jsmin.o mono-wasm ================================================ FILE: README.md ================================================ # mono-wasm This project is a proof-of-concept aiming at building C# applications into WebAssembly, by using Mono and compiling/linking everything statically into one .wasm file that can be easily delivered to browsers. The process does not use Emscripten (or Binaryen) but instead uses the experimental WebAssembly backend of LLVM with `clang` and `lld` to generate the final .wasm code. The goal is to use as few dependencies as possible. At the moment the only dependencies are LLVM, `clang` and `lld` trunk. `mono-wasm` supports 2 build modes: one that links all the LLVM bitcode into one module then performs a WebAssembly codegen on it, and one that compiles project dependencies into WebAssembly incrementally (the runtime and the mscorlib assembly) then uses `lld` to link into a final .wasm file. The later is experimental but will become the default as it allows build times lesser than a second. The .wasm file is loaded from JavaScript (see `index.js`), which also exposes proper callbacks for system calls that the C library will be calling into. These syscalls are responsible for heap management, I/O, etc. This project is a work in progress. Feel free to ping me if you have questions or feedback: laurent.sansonetti@microsoft.com ## Related repositories * [mono-wasm-mono](https://github.com/lrz/mono-wasm-mono): a fork of Mono with changes for a wasm32 target, used to build the runtime and compiler. * [mono-wasm-libc](https://github.com/lrz/mono-wasm-libc): a fork of the WebAssembly/musl C library with tweaks for our version of Mono and our JS glue. ## Current status This is a work in progress, but you can see `sample/hello/hello.cs` running here: www.hipbyte.com/~lrz/mono-wasm-hello ### Binary releases Binary releases are avalable here (for testing only): https://github.com/lrz/mono-wasm/releases ## How does it work? An ASCII graph is worth a thousand words: ``` +----------------+-------------+ +---------------------+ | Mono runtime | C library | | C# assemblies | <-------+ +----------------+-------------+ +----------+----------+ | clang | | mono | -target=wasm32 | | -aot=llvmonly | v v | +-------------------------------------------------------+ | load | LLVM bitcode | | metadata +----------------------------+--------------------------+ | (runtime) | mono-wasm | | (bitcode -> wasm) | v | +-------------------------------------------------------+ | | index.wasm |---------+ +----------------------------------------+--------------+ ^ | libc load, compile | | syscalls + run main() | v +----------------+--------------------------------------+ +-----------+ | index.js | <-----> | Browser | +-------------------------------------------------------+ +-----------+ ``` ## Build instructions We will assume that you want to build everything in the ~/src/mono-wasm directory. ``` $ mkdir ~/src/mono-wasm $ cd ~/src/mono-wasm $ git clone git@github.com:lrz/mono-wasm.git build ``` ### LLVM+clang+lld with WebAssembly target We need a copy of the LLVM tooling (clang and lld included) with the experimental WebAssembly target enabled. Make sure to build a Release build (as indicated below) otherwise the WASM codegen will be significantly slower. ``` $ cd ~/src/mono-wasm $ svn co http://llvm.org/svn/llvm-project/llvm/trunk llvm $ cd llvm/tools $ svn co http://llvm.org/svn/llvm-project/cfe/trunk clang $ svn co http://llvm.org/svn/llvm-project/lld/trunk lld $ cd ../.. $ mkdir llvm-build $ cd llvm-build $ cmake -G "Unix Makefiles" -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=WebAssembly -DCMAKE_BUILD_TYPE=Release ../llvm $ make ``` After you did this you should have the LLVM static libraries for the WebAssembly target: ``` $ ls ~/src/mono-wasm/llvm-build/lib | grep WebAssembly libLLVMWebAssemblyAsmPrinter.a libLLVMWebAssemblyCodeGen.a libLLVMWebAssemblyDesc.a libLLVMWebAssemblyDisassembler.a libLLVMWebAssemblyInfo.a ``` You should also have the `~/src/mono-wasm/llvm-build/bin/clang` program built with the wasm32 target: ``` $ ~/src/mono-wasm/llvm-build/bin/clang --version clang version 5.0.0 (trunk 306818) Target: x86_64-apple-darwin15.6.0 Thread model: posix InstalledDir: /Users/lrz/src/mono-wasm/llvm-build/bin Registered Targets: [...] wasm32 - WebAssembly 32-bit wasm64 - WebAssembly 64-bit ``` You should also have the wasm lld (linker) library: ``` $ ls ~/src/mono-wasm/llvm-build/lib/liblldWasm.a /Users/lrz/src/mono-wasm/llvm-build/lib/liblldWasm.a ``` ### Mono compiler We need a build a copy of the Mono compiler that we will use to generate LLVM bitcode from assemblies. We are building this for 32-bit Intel (i386) because the Mono compiler assumes way too many things from the host environment when generating the bitcode, so we want to match the target architecture (which is also 32-bit). First, you need to build a copy of the Mono fork of LLVM. We are building it for both 32-bit and 64-bit Intel so that we can easily switch the Mono compiler back to 64-bit later, and we manually have to copy the headers to the build directory as the Mono build system doesn't support external LLVM builds. ``` $ cd ~/src/mono-wasm $ git clone git@github.com:mono/llvm.git llvm-mono $ mkdir llvm-mono-build $ cd llvm-mono-build $ cmake -G "Unix Makefiles" -DCMAKE_OSX_ARCHITECTURES="i386;x86_64" ../llvm-mono $ ditto ../llvm-mono/include include $ make ``` Now, we can now build the Mono compiler itself. ``` $ cd ~/src/mono-wasm $ git clone git@github.com:lrz/mono-wasm-mono.git mono-compiler $ cd mono-compiler $ ./autogen.sh --host=i386-darwin --with-cross-offsets=offsets-wasm32.h CFLAGS="-DCOMPILE_WASM32 -DMONO_CROSS_COMPILE" CXXFLAGS="-DCOMPILE_WASM32 -DMONO_CROSS_COMPILE" --disable-boehm --with-sigaltstack=no --enable-llvm --enable-llvm-runtime --with-llvm=../llvm-mono-build --disable-btls --with-runtime_preset=testing_aot_full $ cd eglib $ make $ cd ../mono $ make ``` At the end of this process you should have a `mono` executable installed as `~/src/mono-wasm/mono-compiler/mono/mini/mono` built for the i386 architecture. ``` $ file ~/src/mono-wasm/mono-compiler/mono/mini/mono mono/mini/mono: Mach-O executable i386 ``` Now let's build the `mscorlib.dll` assembly for the WebAssembly profile. We can't use the mono runtime we just built as it's full AOT, so we use assume you have a normal `mono` runtime in your PATH that we can use. Clearly a hack, but in the meantime it works. ``` $ cd ~/src/mono-wasm/mono-compiler/mcs/class/corlib $ make V=1 PROFILE=wasm RUNTIME=mono STRING_REPLACER=true SN=true ``` After this you should have the assembly file created in the proper location: ``` $ file ~/src/mono-wasm/mono-compiler/mcs/class/lib/wasm/mscorlib.dll /Users/lrz/src/mono-wasm/mono-compiler/mcs/class/lib/wasm/mscorlib.dll: PE32 executable (DLL) (console) Intel 80386 Mono/.Net assembly, for MS Windows ``` ### Mono runtime Now we can prepare the Mono runtime. We have to clone a new copy of the source code. We are not building the runtime code using the Mono autotools system, so we have to copy header files that are normally generated. ``` $ cd ~/src/mono-wasm $ git clone git@github.com:lrz/mono-wasm-mono.git mono-runtime $ cd mono-runtime $ cp config-wasm32.h config.h $ cp eglib/src/eglib-config-wasm32.h eglib/src/eglib-config.h ``` ### C library Similarly as above, we clone a copy of the C library that we will be using. ``` $ cd ~/src/mono-wasm $ git clone git@github.com:lrz/mono-wasm-libc.git libc ``` ### OK ready! We are ready to build our Hello World. First, we need to build everything into the `dist` directory: ``` $ cd ~/src/mono-wasm/build $ vi Makefile # make sure the *_PATH variables point to proper locations, should be the case if you followed these instructions $ make ``` This will build the mono runtime and the libc as LLVM bitcode using our version of clang, then link everything into a `runtime.bc` file. This will also build the `mono-wasm` tool which links against the LLVM and lld libraries. Finally, we will copy the Mono compiler and its `mscorlib.dll` file. ``` $ find dist -type f dist/bin/monoc dist/bin/mono-wasm dist/lib/runtime.bc dist/lib/index.js dist/lib/mscorlib.dll dist/lib/mscorlib.xml ``` Once done, you can build the Hello World sample: ``` $ cd sample/hello $ make ``` ## TODO TODO (now): * fix garbage collection (need to figure out how to scan the stack) * ship a first 'alpha' release TODO (later): * put mscorlib on a diet (currently 'hello world' is 10MB) by removing more functionality within the `wasm.make` profile and doing more aggressive IL linking * work on patches for mono based on the changes made in the fork * merge the WebAssembly LLVM code into the mono/llvm fork so that the Mono compiler can target wasm32 directly, and that we can merge the code into `mono-wasm` (we won't have to ship the Mono compiler separately as `monoc`) * improve the C# -> JS interop by doing a full C# API replication in JS like embeddinator 4000 * investigate: threads, sockets, debugger, stack unwinding, simd and atomic operations, etc. ## License This work is distributed under the terms of the MIT license. See the LICENSE.txt file for more information. ================================================ FILE: boot.c ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See the LICENSE.txt file in the project root // for the license information. #include #include #include void mono_wasm_aot_init(void); __attribute__ ((__visibility__ ("default"))) int mono_wasm_main(char *main_assembly_name, int debug) { g_log("mono-wasm", G_LOG_LEVEL_INFO, "booting main()"); setlocale(LC_ALL, ""); g_setenv("LANG", "en_US", 1); g_setenv("MONO_PATH", ".", 1); g_setenv("MONO_LOG_LEVEL", debug ? "debug" : "error", 1); g_log_set_always_fatal(G_LOG_LEVEL_ERROR); g_log_set_fatal_mask(G_LOG_DOMAIN, G_LOG_LEVEL_ERROR); g_set_prgname("hello"); mono_wasm_aot_init(); g_log("mono-wasm", G_LOG_LEVEL_INFO, "initializing mono runtime"); mono_jit_set_aot_mode(MONO_AOT_MODE_LLVMONLY); MonoDomain *domain = mono_jit_init_version("hello", "v4.0.30319"); g_log("mono-wasm", G_LOG_LEVEL_INFO, "opening main assembly `%s'", main_assembly_name); MonoAssembly *assembly = mono_assembly_open(main_assembly_name, NULL); g_assert(assembly != NULL); g_log("mono-wasm", G_LOG_LEVEL_INFO, "running Main()"); int mono_argc = 1; char *mono_argv[] = { main_assembly_name, NULL }; return mono_jit_exec(domain, assembly, mono_argc, mono_argv); } ================================================ FILE: index.js ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See the LICENSE.txt file in the project root // for the license information. // JavaScript WASM support for libc+mono. Inspired from WebAssembly/musl's // wasm.js file. Error.stackTraceLimit = Infinity; // print the entire callstack on errors var dump_cross_offsets = false; var debug_logs = false; var functions = { env: {} }; var instance; var heap; var heap_size; var browser_environment = (typeof window != "undefined"); function heap_get_short(ptr) { var d = 0; d += (heap[ptr + 0] << 0); d += (heap[ptr + 1] << 8); return d; } function heap_get_int(ptr) { var d = 0; d += (heap[ptr + 0] << 0); d += (heap[ptr + 1] << 8); d += (heap[ptr + 2] << 16); d += (heap[ptr + 3] << 24); return d; } function heap_get_long(ptr) { var d = 0; d += (heap[ptr + 0] << 0); d += (heap[ptr + 1] << 8); d += (heap[ptr + 2] << 16); d += (heap[ptr + 3] << 24); d += (heap[ptr + 4] << 32); d += (heap[ptr + 5] << 40); d += (heap[ptr + 6] << 48); d += (heap[ptr + 7] << 56); return d; } function heap_set_int(ptr, d) { heap[ptr + 0] = ((d & 0x000000ff) >> 0); heap[ptr + 1] = ((d & 0x0000ff00) >> 8); heap[ptr + 2] = ((d & 0x00ff0000) >> 16); heap[ptr + 3] = ((d & 0xff000000) >> 24); return d; } function heap_set_long(ptr, d) { heap[ptr + 0] = ((d & 0x00000000000000ff) >> 0); heap[ptr + 1] = ((d & 0x000000000000ff00) >> 8); heap[ptr + 2] = ((d & 0x0000000000ff0000) >> 16); heap[ptr + 3] = ((d & 0x00000000ff000000) >> 24); heap[ptr + 4] = ((d & 0x000000ff00000000) >> 32); heap[ptr + 5] = ((d & 0x0000ff0000000000) >> 40); heap[ptr + 6] = ((d & 0x00ff000000000000) >> 48); heap[ptr + 7] = ((d & 0xff00000000000000) >> 56); return d; } function heap_get_string(ptr, len=-1) { var str = ''; var i = 0; while (true) { var c = heap[ptr + i]; if (c == 0) { break; } if (i == len) { break; } str += String.fromCharCode(c); i++; } return str; } function heap_get_mono_string(ptr) { var str_length = heap_get_int(ptr + 8) var str_chars = ptr + 12 var str = '' for (var i = 0; i < str_length; i++) { var c = heap_get_short(str_chars + (i * 2)) str += String.fromCharCode(c); } return str; } function heap_set_string(ptr, str) { for (var i = 0; i < str.length; i++) { heap[ptr + i] = str.charCodeAt(i); } heap[ptr + str.length] = 0 } function heap_malloc_string(str) { var ptr = instance.exports.malloc(str.length + 1) heap_set_string(ptr, str) return ptr } function heap_human(size) { var suffixes = ['B', 'K', 'M', 'G'] var suffix; for (var i in suffixes) { suffix = suffixes[i] if (size < 1000) { break } size /= 1000 } return size.toFixed(2) + suffix } function log(str) { browser_environment ? console.log(str) : print(str) } function debug(str) { if (debug_logs) { log(">> " + str); } } function error(str) { log("!! " + str + ": " + new Error().stack); } function TerminateWasmException(value) { this.message = 'Terminating WebAssembly'; this.value = value; this.toString = function() { return this.message + ': ' + this.value; }; } function NotYetImplementedException(what) { this.message = 'Not yet implemented'; this.what = what; this.toString = function() { return this.message + ': ' + this.what; }; } // TODO: these missing (imported) functions shouldn't be called from the runtime. var missing_functions=["__addtf3","__clone","__divdc3","__divtf3","__eqtf2","__extenddftf2","__extendsftf2","__fixtfdi","__fixtfsi","__fixunstfsi","__floatsitf","__floatunsitf","__getf2","__lsysinfo","__lttf2","__mmap","__multf3","__munmap","__netf2","__randname","__set_thread_area","__subtf3","__synccall","__syscall","__syscall0","__syscall1","__syscall2","__syscall3","__syscall4","__syscall5","__syscall6","__syscall_cp","__trunctfdf2","__trunctfsf2","__unordtf2","__wait","_pthread_cleanup_pop","_pthread_cleanup_push","accept","bind","btowc","cabs","chmod","closedir","closelog","connect","execv","execve","execvp","feclearexcept","fegetround","feraiseexcept","fesetround","fetestexcept","fork","freeaddrinfo","getaddrinfo","getgrgid_r","getgrnam_r","getnameinfo","getpeername","getpriority","getprotobyname","getpwnam_r","getpwuid_r","getrusage","getsockname","getsockopt","htons","ioctl","listen","longjmp","lstat","mbrtowc","mbsinit","mbsnrtowcs","mbstowcs","mbtowc","mincore","mkdir","mkdtemp","mkstemp","mmap","mono_arch_cleanup","mono_arch_context_get_int_reg","mono_arch_create_generic_trampoline","mono_arch_create_rgctx_lazy_fetch_trampoline","mono_arch_create_specific_trampoline","mono_arch_find_imt_method","mono_arch_find_static_call_vtable","mono_arch_flush_register_windows","mono_arch_free_jit_tls_data","mono_arch_get_argument_info","mono_arch_get_call_filter","mono_arch_get_delegate_invoke_impl","mono_arch_get_delegate_virtual_invoke_impl","mono_arch_get_gsharedvt_arg_trampoline","mono_arch_get_gsharedvt_call_info","mono_arch_get_gsharedvt_trampoline","mono_arch_get_restore_context","mono_arch_get_rethrow_exception","mono_arch_get_static_rgctx_trampoline","mono_arch_get_this_arg_from_call","mono_arch_get_throw_corlib_exception","mono_arch_get_throw_exception","mono_arch_get_unbox_trampoline","mono_arch_handle_exception","mono_arch_ip_from_context","mono_arch_patch_callsite","mono_arch_patch_plt_entry","mono_arch_regname","mono_arch_unwind_frame","mono_interp_frame_iter_init","mono_interp_frame_iter_next","mono_interp_run_finally","mono_interp_set_resume_state","mono_monoctx_to_sigctx","mono_mprotect","mono_sigctx_to_monoctx","mono_vfree","mono_w32file_get_volume_information","mono_wasm_js_eval_imp","mono_wasm_throw_exception","msync","munmap","opendir","openlog","posix_spawn","posix_spawn_file_actions_adddup2","posix_spawn_file_actions_destroy","posix_spawn_file_actions_init","pthread_attr_destroy","pthread_attr_getstacksize","pthread_attr_init","pthread_attr_setdetachstate","pthread_attr_setstacksize","pthread_barrier_init","pthread_barrier_wait","pthread_cond_broadcast","pthread_cond_destroy","pthread_cond_init","pthread_cond_signal","pthread_cond_timedwait","pthread_cond_wait","pthread_condattr_destroy","pthread_condattr_init","pthread_condattr_setclock","pthread_create","pthread_exit","pthread_getschedparam","pthread_getspecific","pthread_join","pthread_key_create","pthread_key_delete","pthread_kill","pthread_mutex_destroy","pthread_mutex_init","pthread_mutex_lock","pthread_mutex_trylock","pthread_mutex_unlock","pthread_mutexattr_destroy","pthread_mutexattr_init","pthread_mutexattr_settype","pthread_once","pthread_self","pthread_setcancelstate","pthread_setschedparam","pthread_setspecific","pthread_sigmask","readdir","recvfrom","recvmsg","sched_get_priority_max","sched_yield","select","sem_destroy","sem_init","sem_post","sem_timedwait","sem_trywait","sem_wait","send","sendmsg","sendto","setjmp","setpriority","setsockopt","shutdown","socket","statvfs","syslog","uname","utimensat","waitpid","wcsrtombs","wctomb","mono_arch_build_imt_trampoline"]; // TODO: these missing (imported) globals should also be removed from the runtime. var missing_globals=["_ZTIPi"]; // variables generated by `mono-wasm': // files: an array of IL assemblies files if (typeof files == "undefined") { var files = []; } for (var i in missing_functions) { f = missing_functions[i]; functions['env'][f] = (function(f) { return function() { error("Not Yet Implemented: " + f) throw new NotYetImplementedException(f); } })(f); } var do_nothing_functions = ['pthread_mutexattr_init', 'pthread_mutexattr_settype', 'pthread_mutex_init', 'pthread_mutexattr_destroy', 'pthread_mutex_lock', 'pthread_mutex_unlock', 'pthread_condattr_init', 'pthread_condattr_setclock', 'pthread_cond_init', 'pthread_condattr_destroy', 'pthread_sigmask', '_pthread_cleanup_push', '_pthread_cleanup_pop', 'pthread_self', 'pthread_create', 'pthread_mutex_trylock', 'pthread_attr_init', 'pthread_attr_setstacksize', 'pthread_attr_getstacksize', 'pthread_attr_destroy', 'sem_init', 'sem_wait', 'sem_post', 'mono_console_init', '__munmap', 'pthread_cond_broadcast', 'pthread_mutex_destroy', 'pthread_cond_destroy', 'mono_mprotect', 'sched_yield'] for (var i in do_nothing_functions) { f = do_nothing_functions[i]; functions['env'][f] = function() { } } // A (way too) simple implementation for thread-local variables. Should be // removed once we enable the relevant code in the libc. var tls_variables = {} functions['env']['pthread_key_create'] = function(key_ptr, destructor) { key = Object.keys(tls_variables).length; tls_variables[key] = 0; heap_set_int(key_ptr, key); return 0; } functions['env']['pthread_getspecific'] = function(key) { var value = tls_variables[key]; debug('pthread_getspecific(' + key + ') -> ' + value); return value; } functions['env']['pthread_setspecific'] = function(key, value) { debug('pthread_setspecific(' + key + ', ' + value + ')'); tls_variables[key] = value; return 0; } for (var i in missing_globals) { g = missing_globals[i]; functions['env'][g] = 0; } // Temporary entry-point for exceptions raised by Mono. We assume that all // exceptions are fatal at this point. functions['env']['mono_wasm_throw_exception'] = function(exc) { var class_str = heap_get_mono_string(heap_get_int(exc + 8)) var message_str = heap_get_mono_string(heap_get_int(exc + 12)) msg = ('Mono Exception: ' + (class_str.length > 0 ? class_str + ': ' : '') + message_str) error(msg) throw new TerminateWasmException(msg) } // Implementation of the C# WebAssembly API. functions['env']['mono_wasm_js_eval_imp'] = function(expr, exception_raised) { var str = heap_get_string(expr); var res = undefined; try { res = eval(str); } catch (e) { heap_set_int(exception_raised, 1); res = e; } return heap_malloc_string(String(res)); } var mono_wasm_refs = {}; var mono_wasm_ref_counter = 0; Object.defineProperty(Object.prototype, "__mono_wasm_ref__", { writable: true }); function mono_wasm_wrap_obj(obj) { var ref = undefined; if (obj != null) { ref = obj.__mono_wasm_ref__; if (ref == undefined) { obj.__mono_wasm_ref__ = ref = mono_wasm_ref_counter++; } mono_wasm_refs[ref] = obj; } return ref; } function mono_wasm_unwrap_obj(ref) { return mono_wasm_refs[ref]; } // Implementation of the JS/Mono API. function _MonoDomain() { return instance.exports.mono_domain_get(); } function _MonoGPtrToArray(ptr) { var pdata = heap_get_int(ptr + 0); var plen = heap_get_int(ptr + 4); var ary = []; for (var i = 0; i < plen; i++) { ary.push(heap_get_int(pdata + (i * 4))); } return ary; } function _MonoAssemblies() { var ptr = instance.exports.mono_domain_get_assemblies(_MonoDomain(), false); return _MonoGPtrToArray(ptr); } function _MonoImage(assembly) { return instance.exports.mono_assembly_get_image(assembly); } function MonoClass(namespace, name) { var namespace_str = heap_malloc_string(namespace); var name_str = heap_malloc_string(name); var assemblies = _MonoAssemblies(); var klass = undefined; for (var i in assemblies) { var assembly = assemblies[i]; var image = _MonoImage(assembly); var klass = instance.exports.mono_class_from_name(image, namespace_str, name_str); if (klass) { break; } } instance.exports.free(namespace_str); instance.exports.free(name_str); return klass; } function MonoMethod(klass, name, is_static) { var flags = (is_static ? 0x10 : 0); var name_str = heap_malloc_string(name); var method = instance.exports.mono_class_get_method_from_name_flags(klass, name_str, -1, flags); instance.exports.free(name_str); return method; } function MonoInvoke(obj, method, params) { var sig = instance.exports.mono_method_signature(method); var argc = instance.exports.mono_signature_get_param_count(sig); if (params.length != argc) { throw "invalid number of parameters"; } var argv = 0; if (argc > 0) { argv = instance.exports.malloc(4 * argc); for (var i in params) { var param = params[i]; var arg = undefined; if (Number.isInteger(param)) { arg = instance.exports.malloc(4); heap_set_int(arg, param); } else if (typeof param === 'string') { var param_str = heap_malloc_string(param); arg = instance.exports.mono_string_new(_MonoDomain(), param_str) instance.exports.free(param_str); } else { throw "unsupported param type"; } heap_set_int(argv + (i * 4), arg); } } var res = instance.exports.mono_runtime_invoke(method, obj, argv); if (argc > 0) { //for (var i = 0; i < argc; i++) { // instance.exports.free(heap_get_int(argv + (i * 4))) //} instance.exports.free(argv); } //if (res) { // res = instance.exports.mono_object_unbox(res); //} return res; } // System calls. var fds = {} fds[0] = undefined fds[1] = undefined fds[2] = undefined var out_buffer = ''; function out_buffer_add(ptr, len) { out_buffer += heap_get_string(ptr, len) } function out_buffer_flush() { if (out_buffer.charAt(out_buffer.length - 1) == '\n') { log(out_buffer.substr(0, out_buffer.length - 1)) out_buffer = '' } } var files_content = {} var syscalls = {} syscalls[3] = function SYS_read(fd, buf, len) { var obj = fds[fd] if (obj) { debug('read(' + fd + ') -> ' + len) offset = obj['offset'] buffer = obj['content'] heap.set(buffer.subarray(offset, offset + len), buf) return len } error('read() called with invalid fd ' + fd) return -1 } syscalls[4] = function SYS_write(fd, buf, len) { if (fd == 1 || fd == 2) { out_buffer_add(buf, len) out_buffer_flush() return len } error('write() called with invalid fd ' + fd) } syscalls[6] = function SYS_close(fd) { var obj = fds[fd] if (obj) { fds[fd] = undefined return 0 } error('close() called with invalid fd ' + fd) return -1 } syscalls[20] = function SYS_getpid() { return 42 } var brk_current = 0 syscalls[45] = function SYS_brk(inc) { if (inc == 0) { brk_current = heap_size; debug("brk: current heap " + heap_human(brk_current)) return brk_current; } if (brk_current + inc > heap_size) { var delta = inc - (heap_size - brk_current) brk_current += inc var new_pages_needed = Math.ceil(delta / 65536.0) var memory = instance.exports.memory var n = memory.grow(new_pages_needed); var new_heap_size = memory.buffer.byteLength debug("brk: pages " + n + " -> " + (n + new_pages_needed) + " (+" + new_pages_needed + "), heap " + heap_human(heap_size) + " -> " + heap_human(new_heap_size) + " (+" + heap_human(new_heap_size - heap_size) + ")") heap = new Uint8Array(memory.buffer) heap_size = new_heap_size } return inc } syscalls[54] = function SYS_ioctl(fd, req, arg) { // TODO return 0 } syscalls[55] = function SYS_fcntl(fd, cmd, arg) { if (cmd == 3) { // F_GETFL if (fd == 1 || fd == 2) { return 1 // O_WRONLY } if (fd == 0 || fds[fd]) { return 0 // O_RDONLY } } error('fcntl() called with invalid fd ' + fd + ' and/or cmd ' + cmd) return -1 } syscalls[76] = function SYS_getrlimit(resource, rlim) { // TODO return 0 } syscalls[85] = function SYS_readlink(path, buf, buflen) { // TODO debug('readlink("' + heap_get_string(path) + '")') return -1 } syscalls[146] = function SYS_writev(fd, iovs, iov_count) { if (fd == 1 || fd == 2) { var all_lens = 0 for (var i = 0; i < iov_count; i++) { var base = heap_get_int(iovs + (i * 8)) var len = heap_get_int(iovs + 4 + (i * 8)) debug("write fd: " + fd + ", base: " + base + ", len: " + len) out_buffer_add(base, len) all_lens += len } out_buffer_flush() return all_lens } error("can only write on stdout and stderr") return -1 } var sizeof_k_sigaction = 20 var signals = {} // maps signal numbers to k_sigaction UInt8Array syscalls[174] = function SYS_sigaction(sig, act, oact, mask_len) { if (mask_len != 8) { error('mask_len should be 8 (is ' + mask_len + ')') mask_len = 8 } sig_act = (signals[sig] || new Uint8Array(sizeof_k_sigaction)) if (oact != 0) { heap.set(sig_act, oact) } if (act != 0) { sig_act.set(heap.slice(act, sizeof_k_sigaction)) } return 0 } syscalls[106] = function SYS_stat(path, s) { var path_str = heap_get_string(path) debug('stat("' + path_str + '")') if (path_str == "/") { heap_set_int(s + 16, 0040000) // st_mode -> S_IFDIR return 0 } for (var i in files) { var file = "/" + files[i]; if (path_str == file) { heap_set_int(s + 16, 0100000) // st_mode -> S_IFREG return 0 } } return -1 } syscalls[108] = function SYS_fstat(fd, s) { var obj = fds[fd] if (obj) { var st_size = obj['content'].length debug('fstat(' + fd + ') -> { st_size: ' + st_size + ' }') heap_set_int(s + 40, st_size) // st_size return 0 } error('fstat() called with invalid fd ' + fd) return -1 } syscalls[140] = function SYS_lseek(fd, unused, offset, result, whence) { var obj = fds[fd] if (obj) { if (whence == 0) { // SEEK_SET obj['offset'] = offset } else if (whence == 1) { // SEEK_CUR offset = obj['offset'] } else { error('lseek() called with invalid whence ' + whence) return -1 } debug('lseek(' + fd + ', ...) -> ' + offset) heap_set_long(result, offset) return 0 } error('lseek() called with invalid fd ' + fd) return -1 } syscalls[175] = function SYS_sigprocmask(action, mask, set, sig_n) { // TODO return 0 } syscalls[183] = function SYS_getcwd(buf, buflen) { if (buflen > 1) { heap_set_string(buf, "/") return 0 } error('getcwd() called with buflen ' + buflen) return -1 } var process_tid = 42 // Should fix this once we get multithreading syscalls[224] = function SYS_gettid() { return process_tid } syscalls[219] = function SYS_madvise(addr, len, advice) { if (advice == 4) { // TODO return 0 } return -1 } syscalls[238] = function SYS_tkill(tid, signal) { if (tid == process_tid) { if (signal == 6) { // SIGABRT error("received SIGABRT") throw new TerminateWasmException('SIGABRT'); } error('tkill() with unsupported signal: ' + signal) } else { error('tkill() with wrong tid: ' + tid) } return -1 } syscalls[252] = function SYS_exit(code) { log("exit(" + code + "): " + new Error().stack) throw new TerminateWasmException('exit(' + code + ')'); } syscalls[265] = function SYS_clock_gettime(clock_id, timespec) { // TODO should switch to something else with a higher resolution + support // the different CLOCK_ ids. if (timespec) { var ms = new Date().getTime() var sec = Math.floor(ms / 1000) var nsec = (ms % 1000) * 1000000 debug("clock_gettime: msec: " + ms + " -> sec: " + sec + ", nsec: " + nsec) heap_set_int(timespec, sec) // tv_sec heap_set_int(timespec + 4, nsec) // tv_nsec } return 0; } syscalls[266] = function SYS_clock_getred(clock_id, timespec) { if (timespec) { // Our gettime JS implementation has a 1ms resolution. heap_set_int(timespec, 0) // tv_sec heap_set_int(timespec + 4, 1000000) // tv_nsec } return 0 } syscalls[295] = function SYS_openat(at, filename, flags, mode) { if (at == -100) { // AT_FDCWD if (flags == 0100000) { var filename_str = heap_get_string(filename) var fd = -1 if (filename_str.charAt(0) == '/') { filename_str = filename_str.substr(1) } if (files.indexOf(filename_str) != -1) { var obj = {}; obj['offset'] = 0; obj['path'] = filename_str; var buf = files_content[filename_str] if (!buf) { buf = new Uint8Array(readbuffer(filename_str)) files_content[filename_str] = buf } obj['content'] = buf fd = Object.keys(fds).length; fds[fd] = obj; } debug('open("' + filename_str + '") -> ' + fd); return fd } } error('openat() called with at ' + at + ' and flags ' + flags) return -1 } syscalls[340] = function SYS_prlimit64(pid, resource, new_rlim, old_rlim) { // TODO return 0 } syscalls[375] = function SYS_membarrier() { return 0 } function route_syscall() { n = arguments[0] argv = [].slice.call(arguments, 1) f = syscalls[n] name = f ? f.name : n debug('syscall(' + name + (argv.length > 0 ? ', ' + argv.join(', ') : '') + ')') if (!f) { error('unimplemented syscall ' + n + ' called') return -1 } return f.apply(this, argv) } for (var i in [0, 1, 2, 3, 4, 5, 6]) { functions['env']['__syscall' + i] = route_syscall } functions['env']['__syscall_cp'] = route_syscall function run_wasm_code() { heap = new Uint8Array(instance.exports.memory.buffer); heap_size = instance.exports.memory.buffer.byteLength; if (dump_cross_offsets) { // We don't care about freeing the memory as we exit soon after. instance.exports.setenv(heap_malloc_string('DUMP_CROSS_OFFSETS'), heap_malloc_string('1'), 1) } debug("running main()") var ret = instance.exports.mono_wasm_main(heap_malloc_string(files[0]), debug_logs); debug('main() returned: ' + ret); } if (browser_environment) { fetch('index.wasm').then(function(response) { return response.arrayBuffer() }).then(function(buf) { return WebAssembly.compile(buf) }).then(function(mod) { return WebAssembly.instantiate(mod, functions) }).then(function(i) { instance = i var files_promises = []; files.forEach(function(url, i) { files_promises.push( fetch(url).then(function(res){ return res.arrayBuffer(); }).then(function(buf){ files_content[url] = new Uint8Array(buf) }) ); }); Promise.all(files_promises).then(function() { run_wasm_code(); document.dispatchEvent(new Event('WebAssemblyContentLoaded')); }); }) } else { var module = new WebAssembly.Module(read('index.wasm', 'binary')) instance = new WebAssembly.Instance(module, functions) run_wasm_code() } ================================================ FILE: jsmin.c ================================================ /* jsmin.c 2013-03-29 Copyright (c) 2002 Douglas Crockford (www.crockford.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. The Software shall be used for Good, not Evil. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include FILE *jsmin_in = NULL; FILE *jsmin_out = NULL; static int theA; static int theB; static int theLookahead = EOF; static int theX = EOF; static int theY = EOF; static void error(const char* s) { fputs("JSMIN Error: ", stderr); fputs(s, stderr); fputc('\n', stderr); exit(1); } /* isAlphanum -- return true if the character is a letter, digit, underscore, dollar sign, or non-ASCII character. */ static int isAlphanum(int c) { return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || c == '_' || c == '$' || c == '\\' || c > 126); } /* get -- return the next character from stdin. Watch out for lookahead. If the character is a control character, translate it to a space or linefeed. */ static int get() { int c = theLookahead; theLookahead = EOF; if (c == EOF) { c = getc(jsmin_in); } if (c >= ' ' || c == '\n' || c == EOF) { return c; } if (c == '\r') { return '\n'; } return ' '; } /* peek -- get the next character without getting it. */ static int peek() { theLookahead = get(); return theLookahead; } /* next -- get the next character, excluding comments. peek() is used to see if a '/' is followed by a '/' or '*'. */ static int next() { int c = get(); if (c == '/') { switch (peek()) { case '/': for (;;) { c = get(); if (c <= '\n') { break; } } break; case '*': get(); while (c != ' ') { switch (get()) { case '*': if (peek() == '/') { get(); c = ' '; } break; case EOF: error("Unterminated comment."); } } break; } } theY = theX; theX = c; return c; } /* action -- do something! What you do is determined by the argument: 1 Output A. Copy B to A. Get the next B. 2 Copy B to A. Get the next B. (Delete A). 3 Get the next B. (Delete B). action treats a string as a single character. Wow! action recognizes a regular expression if it is preceded by ( or , or =. */ static void action(int d) { switch (d) { case 1: putc(theA, jsmin_out); if ( (theY == '\n' || theY == ' ') && (theA == '+' || theA == '-' || theA == '*' || theA == '/') && (theB == '+' || theB == '-' || theB == '*' || theB == '/') ) { putc(theY, jsmin_out); } case 2: theA = theB; if (theA == '\'' || theA == '"' || theA == '`') { for (;;) { putc(theA, jsmin_out); theA = get(); if (theA == theB) { break; } if (theA == '\\') { putc(theA, jsmin_out); theA = get(); } if (theA == EOF) { error("Unterminated string literal."); } } } case 3: theB = next(); if (theB == '/' && ( theA == '(' || theA == ',' || theA == '=' || theA == ':' || theA == '[' || theA == '!' || theA == '&' || theA == '|' || theA == '?' || theA == '+' || theA == '-' || theA == '~' || theA == '*' || theA == '/' || theA == '{' || theA == '\n' )) { putc(theA, jsmin_out); if (theA == '/' || theA == '*') { putc(' ', jsmin_out); } putc(theB, jsmin_out); for (;;) { theA = get(); if (theA == '[') { for (;;) { putc(theA, jsmin_out); theA = get(); if (theA == ']') { break; } if (theA == '\\') { putc(theA, jsmin_out); theA = get(); } if (theA == EOF) { error("Unterminated set in Regular Expression literal."); } } } else if (theA == '/') { switch (peek()) { case '/': case '*': error("Unterminated set in Regular Expression literal."); } break; } else if (theA =='\\') { putc(theA, jsmin_out); theA = get(); } if (theA == EOF) { error("Unterminated Regular Expression literal."); } putc(theA, jsmin_out); } theB = next(); } } } /* jsmin -- Copy the input to the output, deleting the characters which are insignificant to JavaScript. Comments will be removed. Tabs will be replaced with spaces. Carriage returns will be replaced with linefeeds. Most spaces and linefeeds will be removed. */ void jsmin(void) { if (peek() == 0xEF) { get(); get(); get(); } theA = '\n'; action(3); while (theA != EOF) { switch (theA) { case ' ': action(isAlphanum(theB) ? 1 : 2); break; case '\n': switch (theB) { case '{': case '[': case '(': case '+': case '-': case '!': case '~': action(1); break; case ' ': action(3); break; default: action(isAlphanum(theB) ? 1 : 2); } break; default: switch (theB) { case ' ': action(isAlphanum(theA) ? 1 : 3); break; case '\n': switch (theA) { case '}': case ']': case ')': case '+': case '-': case '"': case '\'': case '`': action(1); break; default: action(isAlphanum(theA) ? 1 : 3); } break; default: action(1); break; } } } } ================================================ FILE: mono-wasm.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See the LICENSE.txt file in the project root // for the license information. #include #include #include #include #include #include #include #include "llvm/Analysis/TargetLibraryInfo.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Bitcode/BitcodeReader.h" #include "llvm/Bitcode/BitcodeWriter.h" #include "llvm/IR/AutoUpgrade.h" #include "llvm/IR/DiagnosticInfo.h" #include "llvm/IR/DiagnosticPrinter.h" #include "llvm/IR/LegacyPassManager.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/Module.h" #include "llvm/IR/ModuleSummaryIndex.h" #include "llvm/IR/Verifier.h" #include "llvm/IRReader/IRReader.h" #include "llvm/Linker/Linker.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/ManagedStatic.h" #include "llvm/Support/Path.h" #include "llvm/Support/PrettyStackTrace.h" #include "llvm/Support/Signals.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/SystemUtils.h" #include "llvm/Support/TargetRegistry.h" #include "llvm/Support/TargetSelect.h" #include "llvm/Support/ToolOutputFile.h" #include "llvm/Target/TargetMachine.h" #include "llvm/Transforms/IPO/FunctionImport.h" #include "llvm/Transforms/IPO/Internalize.h" #include "llvm/Transforms/Utils/FunctionImportUtils.h" #include "lld/Common/Driver.h" #define ERROR(...) \ do { \ fprintf(stderr, __VA_ARGS__); \ exit(1); \ } \ while (0) #define _PATH_CHECK(path, iftype, what, must_exist) \ ({ \ struct stat s; \ bool exists = false; \ if (stat(path, &s) != 0) { \ if (must_exist) { \ ERROR("path `%s' does not exist\n", path); \ } \ } \ else { \ if ((s.st_mode & S_IFMT) != iftype) { \ ERROR("path `%s' is not a %s\n", path, what); \ } \ exists = true; \ } \ exists; \ }) #define _FILE_CHECK(path, must_exist) \ _PATH_CHECK(path, S_IFREG, "file", must_exist) #define FILE_MUST_EXIST(path) _FILE_CHECK(path, true) #define FILE_MAY_EXIST(path) _FILE_CHECK(path, false) #define _DIR_CHECK(path, must_exist) \ _PATH_CHECK(path, S_IFDIR, "directory", must_exist) #define DIR_MUST_EXIST(path) _DIR_CHECK(path, true) #define DIR_MAY_EXIST(path) _DIR_CHECK(path, false) static int timespec_cmp (struct timespec a, struct timespec b) { return a.tv_sec < b.tv_sec ? -1 : (a.tv_sec > b.tv_sec ? 1 : a.tv_nsec - b.tv_nsec); } #define FILE_IS_OLDER(source_path, dest_path) \ ({ \ struct stat source_s; \ assert(stat(source_path, &source_s) == 0); \ struct stat dest_s; \ stat(dest_path, &dest_s) == 0 \ ? timespec_cmp(source_s.st_mtimespec, dest_s.st_mtimespec) > 0 \ : true; \ }) static char libdir_path[PATH_MAX] = { 0 }; static char bindir_path[PATH_MAX] = { 0 }; static void setup_paths(const char *arg0) { char path[PATH_MAX]; snprintf(path, sizeof path, "%s/../../lib", arg0); if (realpath(path, libdir_path) == NULL) { ERROR("can't resolve `lib' directory\n"); } DIR_MUST_EXIST(libdir_path); snprintf(path, sizeof path, "%s/..", arg0); if (realpath(path, bindir_path) == NULL) { ERROR("can't resolve `bin' directory\n"); } DIR_MUST_EXIST(bindir_path); } static void diagnostic_handler(const llvm::DiagnosticInfo &DI, void *ctx) { switch (DI.getSeverity()) { case llvm::DS_Error: llvm::errs() << "ERROR: "; break; case llvm::DS_Warning: llvm::errs() << "WARNING: "; break; default: break; } llvm::DiagnosticPrinterRawOStream DP(llvm::errs()); DI.print(DP); llvm::errs() << '\n'; } static void assembly_link(std::vector &assembly_paths, const char *output_path) { auto dest_base = std::string(output_path) + "/"; if (DIR_MAY_EXIST(output_path)) { bool need_link = false; for (auto assembly_path : assembly_paths) { auto linked_path = dest_base + assembly_path; if (FILE_IS_OLDER(assembly_path.c_str(), linked_path.c_str())) { need_link = true; break; } } if (!need_link) { goto skip_link; } } char cmd[PATH_MAX]; snprintf(cmd, sizeof cmd, "monolinker -d %s -c link -l none -o %s", libdir_path, output_path); for (auto assembly_path : assembly_paths) { strlcat(cmd, " -a ", sizeof cmd); strlcat(cmd, assembly_path.c_str(), sizeof cmd); } if (system(cmd) != 0) { ERROR("monolinker pass failed (command was: %s)\n", cmd); } skip_link: char *first_assembly = strdup(basename((char *)assembly_paths[0].c_str())); assert(first_assembly != NULL); assembly_paths.clear(); DIR *dir = opendir(output_path); assert(dir != NULL); struct dirent *entry; int i = 0, first_assembly_i = -1; while ((entry = readdir(dir)) != NULL) { const char *s = entry->d_name; size_t sl = strlen(s); if (sl > 4) { const char *sp = s + sl - 4; if (strcmp(sp, ".exe") == 0 || strcmp(sp, ".dll") == 0) { auto linked_path = dest_base + s; assembly_paths.push_back(linked_path); if (strcmp(s, first_assembly) == 0) { first_assembly_i = i; } i++; } } } closedir(dir); // The first assembly path must remain the same given to the command line. assert (first_assembly_i >= 0); if (first_assembly_i > 0) { std::iter_swap(assembly_paths.begin() + first_assembly_i, assembly_paths.begin()); } free(first_assembly); } static std::string assembly_compile(std::string assembly_path, const char *build_dir, std::string bitcode_path) { static char monoc_path[PATH_MAX] = { '\0' }; if (monoc_path[0] == '\0') { snprintf(monoc_path, sizeof monoc_path, "%s/monoc", bindir_path); FILE_MUST_EXIST(monoc_path); } if (FILE_IS_OLDER(assembly_path.c_str(), bitcode_path.c_str())) { char cmd[PATH_MAX]; snprintf(cmd, sizeof cmd, "MONO_PATH=\"%s\" MONO_ENABLE_COOP=1 " \ "%s --aot=asmonly,llvmonly,static,llvm-outfile=%s %s " \ ">& /dev/null", build_dir, monoc_path, bitcode_path.c_str(), assembly_path.c_str()); if (system(cmd) != 0) { ERROR("bitcode compilation for `%s' failed " \ "(command was: %s)\n", assembly_path.c_str(), cmd); } } return bitcode_path; } static std::unique_ptr bitcode_link(std::vector &paths, llvm::LLVMContext &context) { auto module = llvm::make_unique("index.bc", context); llvm::Linker linker(*module); for (auto path : paths) { llvm::SMDiagnostic err; auto file_module = llvm::parseIRFile(path, err, context); if (!file_module) { ERROR("bitcode parsing error: %s:%d: %s\n", err.getFilename().str().c_str(), err.getLineNo(), err.getMessage().str().c_str()); } if (linker.linkInModule(std::move(file_module), llvm::Linker::Flags::OverrideFromSrc)) { ERROR("linking %s failed\n", path.c_str()); } } return module; } static llvm::Module * aot_init_gen(std::vector &assembly_paths, llvm::Module *module, llvm::LLVMContext &context) { if (module == NULL) { module = new llvm::Module("aot_init.bc", context); } auto ptr_ty = llvm::PointerType::getUnqual(llvm::Type::getInt8Ty(context)); auto register_f = module->getFunction("mono_aot_register_module"); if (register_f == NULL) { std::vector types; types.push_back(ptr_ty); auto c = module->getOrInsertFunction("mono_aot_register_module", llvm::FunctionType::get(llvm::Type::getVoidTy(context), types, false)); register_f = llvm::cast(c); } auto c = module->getOrInsertFunction("mono_wasm_aot_init", llvm::FunctionType::get(llvm::Type::getVoidTy(context), false)); auto f = llvm::cast(c); auto bb = llvm::BasicBlock::Create(context, "entry", f); for (auto path : assembly_paths) { // //foo.{exe,dll} -> mono_aot_module_foo_info assert(path.size() > 4); assert(path[path.size() - 4] == '.'); size_t beg = path.rfind('/'); assert(beg != std::string::npos); beg++; auto name = std::string("mono_aot_module_") + path.substr(beg, path.size() - beg - 4) + "_info"; auto aot_info = module->getGlobalVariable(name.c_str()); if (aot_info == NULL) { auto c = module->getOrInsertGlobal(name.c_str(), ptr_ty); aot_info = llvm::cast(c); } llvm::CallInst::Create(register_f, new llvm::LoadInst(aot_info, "", bb), "", bb); } llvm::ReturnInst::Create(context, bb); return module; } static void wasm_codegen(llvm::Module *module, llvm::CodeGenOpt::Level opt_level, llvm::LLVMContext &context, std::string wasm_path) { static bool init_done = false; if (!init_done) { LLVMInitializeWebAssemblyTarget(); LLVMInitializeWebAssemblyTargetMC(); LLVMInitializeWebAssemblyTargetInfo(); LLVMInitializeWebAssemblyAsmPrinter(); init_done = true; } // Important to generate a proper wasm object file. module->setTargetTriple("wasm32-unknown-unknown-wasm"); std::string err; auto triple = llvm::Triple(module->getTargetTriple()); auto target = llvm::TargetRegistry::lookupTarget("wasm32", triple, err); if (target == NULL) { ERROR("can't lookup wasm32 target: %s\n", err.c_str()); } std::string cpu_str = ""; std::string features_str = ""; llvm::TargetOptions options; options.MCOptions.AsmVerbose = false; auto target_machine = target->createTargetMachine(triple.getTriple(), cpu_str, features_str, options, llvm::None, llvm::CodeModel::Large, opt_level); if (target_machine == NULL) { ERROR("couldn't allocate target machine\n"); } llvm::legacy::PassManager pm; pm.add(new llvm::TargetLibraryInfoWrapperPass( llvm::TargetLibraryInfoImpl(triple))); module->setDataLayout(target_machine->createDataLayout()); std::error_code EC; llvm::raw_fd_ostream dest(wasm_path, EC, llvm::sys::fs::F_None); if (EC) { ERROR("error when opening file %s: %s\n", wasm_path.c_str(), EC.message().c_str()); } if (target_machine->addPassesToEmitFile(pm, dest, llvm::TargetMachine::CGFT_ObjectFile)) { ERROR("target does not support assembly generation\n"); } pm.run(*module); dest.flush(); } static void wasm_codegen2(std::string &bitcode_path, llvm::CodeGenOpt::Level opt, llvm::LLVMContext &context, std::string wasm_path) { if (FILE_IS_OLDER(bitcode_path.c_str(), wasm_path.c_str())) { llvm::SMDiagnostic err; auto module = llvm::parseIRFile(bitcode_path, err, context); if (!module) { ERROR("parsing bitcode file `%s' failed: %s:%d: %s\n", bitcode_path.c_str(), err.getFilename().str().c_str(), err.getLineNo(), err.getMessage().str().c_str()); } wasm_codegen(module.get(), opt, context, wasm_path); } } static void wasm_link(std::vector &paths, std::string output, bool strip_debug_info) { std::vector args; args.push_back("wasm-lld"); for (auto path : paths) { args.push_back(strdup(path.c_str())); } args.push_back("-o"); args.push_back(output.c_str()); args.push_back("--allow-undefined"); args.push_back("--no-entry"); if (strip_debug_info) { args.push_back("--strip-debug"); } if (!lld::wasm::link(args, false)) { ERROR("failed to link wasm files\n"); } for (int i = 0; i < paths.size(); i++) { free((char *)args[i + 1]); } } static void assembly_strip(std::vector &paths, const char *output_path) { for (auto path : paths) { const char *base = strrchr(path.c_str(), '/'); assert(base != NULL); char cmd[PATH_MAX]; snprintf(cmd, sizeof cmd, "mono-cil-strip %s %s%s >& /dev/null", path.c_str(), output_path, base); if (system(cmd) != 0) { ERROR("IL strip for `%s' failed (command was: %s)\n", path.c_str(), cmd); } } } extern "C" { extern FILE *jsmin_in; extern FILE *jsmin_out; void jsmin(void); } static void js_gen(std::vector &assembly_paths, const char *output_path) { auto index_js = std::string(libdir_path) + "/index.js"; FILE_MUST_EXIST(index_js.c_str()); auto output_index_js = std::string(output_path) + "/index.js"; FILE *output = fopen(output_index_js.c_str(), "w+"); if (output == NULL) { ERROR("can't open `%s': %s\n", output_index_js.c_str(), strerror(errno)); } fprintf(output, "var files=["); for (auto path : assembly_paths) { const char *base = strrchr(path.c_str(), '/'); assert(base != NULL); fprintf(output, "\"%s\",", base + 1); } fprintf(output, "];"); jsmin_in = fopen(index_js.c_str(), "r"); jsmin_out = output; jsmin(); fclose(jsmin_in); fclose(output); jsmin_in = NULL; jsmin_out = NULL; } static std::string swap_extension(std::string path, const char *new_extension) { auto pos = path.rfind('.'); if (pos != std::string::npos) { assert(pos > 0); path = path.substr(0, pos); } return path + new_extension; } int main(int argc, char **argv) { if (argc < 2) { ERROR("Usage: %s [options] \n\n" \ "Options:\n" \ " -b Specify build directory\n" \ " (default is `./build')\n" \ " -o Specify output directory\n" \ " -On Specify optimization level\n" \ " (0, 1, 2, 3, default is 2)\n" \ " --strip-debug Strip debugging information\n" \ " -v Verbose output\n" \ " -i Incremental build (experimental)\n", argv[0]); } const char *build_path = "./build"; const char *output_path = NULL; llvm::CodeGenOpt::Level opt = llvm::CodeGenOpt::Default; bool strip_debug_info = false; bool verbose = false; bool incremental = false; std::vector assembly_paths, bitcode_paths, wasm_paths; for (int i = 1; i < argc; i++) { const char *arg = argv[i]; if (arg[0] == '-') { if (arg[1] == 'b' && arg[2] == '\0') { i++; if (i >= argc) { ERROR("expected value for `-b' option\n"); } build_path = argv[i]; } else if (arg[1] == 'o' && arg[2] == '\0') { i++; if (i >= argc) { ERROR("expected value for `-o' option\n"); } output_path = argv[i]; } else if (arg[1] == 'v' && arg[2] == '\0') { verbose = true; } else if (arg[1] == 'i' && arg[2] == '\0') { incremental = true; } else if (arg[1] == 'O' && arg[3] == '\0') { switch (arg[2]) { case '0': opt = llvm::CodeGenOpt::None; break; case '1': opt = llvm::CodeGenOpt::Less; break; case '2': opt = llvm::CodeGenOpt::Default; break; case '3': opt = llvm::CodeGenOpt::Aggressive; break; default: ERROR("malformed `-On' option\n"); } } else if (strcmp(arg, "--strip-debug") == 0) { strip_debug_info = true; } else { ERROR("invalid `%s' option\n", arg); } } else { assembly_paths.push_back(arg); } } if (output_path == NULL) { ERROR("`-o' option required\n"); } if (assembly_paths.size() == 0) { ERROR("at least one input file is required\n"); } setup_paths(argv[0]); if (!DIR_MAY_EXIST(output_path)) { if (mkdir(output_path, 0755) != 0) { ERROR("can't create output directory `%s': %s\n", output_path, strerror(errno)); } } auto output_wasm = std::string(output_path) + "/index.wasm"; llvm::LLVMContext context; context.setDiagnosticHandlerCallBack(diagnostic_handler, NULL, true); uint64_t start = 0, total = 0, delta = 0; mach_timebase_info_data_t timebase_info; mach_timebase_info(&timebase_info); #define T_MEASURE(what, code) \ start = mach_absolute_time(); \ if (verbose) { \ std::string _what = what; \ printf("%s ... ", _what.c_str()); \ } \ code; \ delta = mach_absolute_time() - start; \ total += delta; \ if (verbose) { \ printf("%.3fs\n", (((double)(delta) * timebase_info.numer) \ / (timebase_info.denom * 1000000000))); \ } T_MEASURE("IL link", assembly_link(assembly_paths, build_path)); bitcode_paths.push_back(std::string(libdir_path) + "/runtime.bc"); for (auto assembly_path : assembly_paths) { auto bitcode_path = swap_extension(assembly_path, ".bc"); T_MEASURE(std::string("IL/IR compile ") + assembly_path, assembly_compile(assembly_path, build_path, bitcode_path)); bitcode_paths.push_back(bitcode_path); } if (incremental) { for (auto bitcode_path : bitcode_paths) { auto wasm_path = swap_extension(bitcode_path, ".wasm"); T_MEASURE(std::string("IR/WASM codegen ") + bitcode_path.c_str(), wasm_codegen2(bitcode_path, opt, context, wasm_path)); wasm_paths.push_back(wasm_path); } auto path = std::string(build_path) + "/aot_init.wasm"; auto aot_init_mod = aot_init_gen(assembly_paths, NULL, context); wasm_codegen(aot_init_mod, opt, context, path); wasm_paths.push_back(path); delete aot_init_mod; } else { T_MEASURE("IR link", auto module = bitcode_link(bitcode_paths, context)); aot_init_gen(assembly_paths, module.get(), context); auto path = std::string(build_path) + "/index.wasm"; T_MEASURE("IR/WASM codegen", wasm_codegen(module.get(), opt, context, path)); wasm_paths.push_back(path); } T_MEASURE("WASM link", wasm_link(wasm_paths, output_wasm, strip_debug_info)); T_MEASURE("IL strip", assembly_strip(assembly_paths, output_path)); T_MEASURE("JS gen", js_gen(assembly_paths, output_path)); #undef T_MEASURE return 0; } ================================================ FILE: mscorlib.xml ================================================ ================================================ FILE: sample/hello/.gitignore ================================================ build output hello.exe ================================================ FILE: sample/hello/Makefile ================================================ all: hello.exe output/index.wasm output/index.html hello.exe: hello.cs mcs -nostdlib -noconfig -r:../../dist/lib/mscorlib.dll hello.cs -out:hello.exe output/index.wasm: hello.exe ../../dist/bin/mono-wasm -i hello.exe -o output output/index.html: index.html cp index.html output clean: rm -rf build output hello.exe ================================================ FILE: sample/hello/hello.cs ================================================ class Hello { static int Main(string[] args) { System.Console.WriteLine("hello world!"); return 0; } } ================================================ FILE: sample/hello/index.html ================================================ WebAssembly Example ================================================ FILE: sample/hello2/.gitignore ================================================ build output hello.exe ================================================ FILE: sample/hello2/Makefile ================================================ all: hello.exe output/index.wasm output/index.html hello.exe: hello.cs mcs -nostdlib -noconfig -r:../../dist/lib/mscorlib.dll hello.cs -out:hello.exe output/index.wasm: hello.exe ../../dist/bin/mono-wasm -i hello.exe -o output output/index.html: index.html cp index.html output clean: rm -rf build output hello.exe ================================================ FILE: sample/hello2/hello.cs ================================================ using Mono.WebAssembly; using System; class Hello { static int Factorial(int n) { if (n == 0) { return 1; } return n * Factorial(n - 1); } // This function is called from the browser by JavaScript. // Here we calculate the factorial of the given number then use the // Mono.WebAssembly API to retrieve the element from the DOM and set its // innerText property to the factorial result. static void FactorialInElement(int n, string element_id) { Console.WriteLine( "Calculating factorial of {0} into DOM element {1}", n, element_id); int f = Factorial(n); var elem = HtmlPage.Document.GetElementById(element_id); elem.InnerText = f.ToString(); } static void PrintHtmlElements(HtmlElement elem, int level) { string str = ""; for (int i = 0; i < level; i++) { str += " "; } str += $"<{elem.TagName}"; foreach (var name in elem.AttributeNames) { var value = elem.GetAttribute(name); str += $" {name}='{value}'"; } str += ">"; Console.WriteLine(str); var list = elem.Children; for (int i = 0; i < list.Count; i++) { var child = list[i]; PrintHtmlElements(child, level + 1); } } static int Main(string[] args) { int f = Factorial(6); HtmlPage.Window.Alert($"Hello world! factorial(6) -> {f}"); var bi = HtmlPage.BrowserInformation; Console.WriteLine($"BrowserInformation: Name {bi.Name} BrowserVersion {bi.BrowserVersion} UserAgent {bi.UserAgent} Platform {bi.Platform} CookiesEnabled {bi.CookiesEnabled} ProductName {bi.ProductName}"); var d = HtmlPage.Document; Console.WriteLine($"Document Location: {d.Location}"); PrintHtmlElements(d.DocumentElement, 0); var p = d.CreateElement("p"); p.InnerText = "This text was added at runtime."; d.Body.AppendChild(p); if (args.Length > 0) FactorialInElement(0, ""); // this is a hack so that the linker does not remove the FactorialInElement() method return f; } } ================================================ FILE: sample/hello2/index.html ================================================ WebAssembly Example

Number:

Result: ...

================================================ FILE: tests/Mono.WebAssembly/Makefile ================================================ all: run test.exe: test.cs mcs -nostdlib -noconfig -r:../../dist/lib/mscorlib.dll test.cs -out:test.exe output/index.wasm: test.exe ../../dist/bin/mono-wasm -g test.exe -o output run: output/index.wasm cp index.html output (cd output && python -m SimpleHTTPServer 9000) ================================================ FILE: tests/Mono.WebAssembly/index.html ================================================ WebAssembly Example span-id text
my-class-container
text

================================================ FILE: tests/Mono.WebAssembly/test.cs ================================================ using Mono.WebAssembly; using System; class Test { int count = 0; int failures = 0; void _assert(bool condition, string msg) { if (condition) { count++; } else { Console.WriteLine(msg); failures++; } } void assert(bool condition) { _assert(condition, $"assertion {count} failed"); } void assert_Equals(object obj1, object obj2) { _assert(((obj1 == null && obj2 == null) || obj1.Equals(obj2)), $"assertion {count} failed: `{obj1}' should be equal to `{obj2}'"); } void test_Runtime() { assert_Equals(Runtime.JavaScriptEval("1+2") , "3"); assert_Equals(Runtime.JavaScriptEval("var x = 42; x"), "42"); assert_Equals(Runtime.JavaScriptEval( "(function(x, y) { return x + y; })(40, 2);"), "42"); } void test_BrowserInformation() { var bi = HtmlPage.BrowserInformation; assert_Equals(bi.Name, "Netscape"); assert(bi.BrowserVersion.Contains("5.0")); assert(bi.UserAgent.Contains("Firefox") || bi.UserAgent.Contains("Chrome") || bi.UserAgent.Contains("Safari")); assert_Equals(bi.Platform, "MacIntel"); assert_Equals(bi.CookiesEnabled, true); assert_Equals(bi.ProductName, "Mozilla"); } void test_HtmlDocument() { var doc = HtmlPage.Document; var root = doc.DocumentElement; assert_Equals(root.TagName, "HTML"); assert_Equals(doc.GetElementsByTagName("html")[0], root); assert_Equals(root.Parent, null); var body = doc.Body; assert_Equals(body.TagName, "BODY"); assert_Equals(doc.GetElementsByTagName("body")[0], body); assert_Equals(body.Parent, root); // We can't use `Contains()' or even `foreach' yet due to a compiler // limitation. bool found_body_in_root_children = false; var root_children = root.Children; for (int i = 0, len = root_children.Count; i < len; i++) { var child = root_children[i]; if (child.Equals(body)) { found_body_in_root_children = true; break; } } assert(found_body_in_root_children); var span_id = doc.GetElementById("span-id"); assert(span_id != null); assert_Equals(span_id.TagName, "SPAN"); assert_Equals(span_id.Id, "span-id"); assert_Equals(span_id.Parent, body); assert_Equals(span_id.InnerText, "span-id text"); assert_Equals(doc.GetElementById("does-not-exist"), null); var elem = doc.CreateElement("span"); elem.Id = "span-id2"; assert_Equals(elem.TagName, "SPAN"); assert_Equals(elem.Parent, null); body.AppendChild(elem); assert_Equals(elem.Parent, body); assert_Equals(doc.GetElementById("span-id2"), elem); body.RemoveChild(elem); assert_Equals(elem.Parent, null); assert_Equals(doc.GetElementById("span-id2"), null); } void test_HtmlNode() { var doc = HtmlPage.Document; assert_Equals(doc.GetElementsByTagName("does-not-exist").Count, 0); assert_Equals(doc.Body.GetElementsByTagName("does-not-exist").Count, 0); assert_Equals(doc.GetElementsByTagName("p").Count, 3); assert_Equals(doc.Body.GetElementsByTagName("p").Count, 3); assert_Equals(doc.GetElementsByClassName("my-class").Count, 4); assert_Equals(doc.Body.GetElementsByClassName("my-class").Count, 4); var ary = doc.GetElementsByClassName("container"); var ary2 = doc.Body.GetElementsByClassName("container"); assert_Equals(ary.Count, 1); assert_Equals(ary2.Count, 1); var container = ary[0]; assert_Equals(container, ary2[0]); assert_Equals(container.GetElementsByClassName("my-class").Count, 1); assert_Equals(container.InnerText, "my-class-container\ntext"); var ary3 = container.GetElementsByTagName("span"); assert_Equals(ary3.Count, 1); ary3[0].InnerText = "42"; assert_Equals(container.InnerText, "my-class-container\n42"); assert_Equals(doc.QuerySelector(".does-not-exist"), null); assert_Equals(doc.Body.QuerySelector(".does-not-exist"), null); var elem = doc.QuerySelector(".my-class"); assert(elem != null); assert_Equals(elem, doc.QuerySelector(".container")); assert_Equals(elem, doc.Body.QuerySelector(".my-class")); assert_Equals(elem, doc.Body.QuerySelector(".container")); var ary4 = doc.QuerySelectorAll(".my-class"); var ary5 = doc.Body.QuerySelectorAll(".my-class"); assert_Equals(ary4.Count, 4); assert_Equals(ary5.Count, 4); assert_Equals(ary4[0], elem); assert_Equals(ary5[0], elem); assert_Equals(doc.QuerySelector(".container span"), ary3[0]); } void test_HtmlElement() { var doc = HtmlPage.Document; var elem = doc.CreateElement("div"); assert_Equals(elem.TagName, "DIV"); assert_Equals(elem.ClassName, ""); assert_Equals(elem.Id, ""); assert_Equals(elem.Parent, null); assert_Equals(elem.Children.Count, 0); assert_Equals(elem.AttributeNames.Length, 0); assert_Equals(elem.InnerText, ""); elem.InnerText = "foo"; assert_Equals(elem.InnerText, "foo"); assert_Equals(elem.GetAttribute("does-not-exist"), null); elem.ClassName = "my-class"; assert_Equals(elem.ClassName, "my-class"); assert_Equals(elem.AttributeNames.Length, 1); assert_Equals(elem.AttributeNames[0], "class"); assert_Equals(elem.GetAttribute("class"), "my-class"); elem.SetAttribute("id", "my-id"); assert_Equals(elem.Id, "my-id"); assert_Equals(elem.AttributeNames.Length, 2); elem.RemoveAttribute("id"); assert_Equals(elem.Id, ""); assert_Equals(elem.AttributeNames.Length, 1); var ary = doc.GetElementsByTagName("script"); assert_Equals(ary.Count, 1); var elem2 = ary[0]; assert_Equals(elem2.AttributeNames.Length, 1); assert_Equals(elem.AttributeNames[0], "src"); assert_Equals(elem.GetAttribute("src"), "index.js"); } void run_tests() { test_Runtime(); test_BrowserInformation(); test_HtmlDocument(); test_HtmlNode(); test_HtmlElement(); if (failures == 0) { Console.WriteLine("All tests ({0}) successful", count); } else { Console.WriteLine("Tests ran with {0} failures", failures); } } static void Main() { var r = new Test(); r.run_tests(); } }