From c1923c5ada780ac7d09dad79ff133da811743341 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Tue, 16 Oct 2018 23:08:26 -0400 Subject: [PATCH] Web assembly build with emscripten. --- .gitignore | 5 ++ Makefile | 32 ++++++++-- src/core/core.janet | 56 +++++++++-------- src/core/corelib.c | 24 +++----- src/core/io.c | 3 + src/core/os.c | 2 + src/core/run.c | 9 +-- src/core/symcache.c | 2 +- src/include/janet/janet.h | 21 ++++--- src/mainclient/main.c | 2 +- src/webclient/main.c | 116 ++++++++++++++++++++++++++++++++++++ src/webclient/webinit.janet | 11 ++++ 12 files changed, 219 insertions(+), 64 deletions(-) create mode 100644 src/webclient/main.c create mode 100644 src/webclient/webinit.janet diff --git a/.gitignore b/.gitignore index 010ed64f..9643925c 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,11 @@ janet /Emscripten /src/include/generated/*.h +# Emscripten +*.bc +janet.js +janet.wasm + # Generated files *.gen.h *.gen.c diff --git a/Makefile b/Makefile index 82eb2458..90ceb3db 100644 --- a/Makefile +++ b/Makefile @@ -55,6 +55,7 @@ JANET_LOCAL_HEADERS=$(sort $(wildcard src/*/*.h)) # Source files JANET_CORE_SOURCES=$(sort $(wildcard src/core/*.c)) JANET_MAINCLIENT_SOURCES=$(sort $(wildcard src/mainclient/*.c)) +JANET_WEBCLIENT_SOURCES=$(sort $(wildcard src/webclient/*.c)) all: $(JANET_TARGET) $(JANET_LIBRARY) @@ -72,6 +73,9 @@ xxd: src/tools/xxd.c src/include/generated/init.h: src/mainclient/init.janet xxd ./xxd $< $@ janet_gen_init +src/include/generated/webinit.h: src/webclient/webinit.janet xxd + ./xxd $< $@ janet_gen_webinit + src/include/generated/core.h: src/core/core.janet xxd ./xxd $< $@ janet_gen_core @@ -93,10 +97,30 @@ JANET_ALL_OBJECTS=$(patsubst %.c,%.o,$(JANET_ALL_SOURCES)) $(CC) $(CFLAGS) -o $@ -c $< $(JANET_TARGET): $(JANET_ALL_OBJECTS) - $(CC) $(CFLAGS) -o $(JANET_TARGET) $^ $(CLIBS) + $(CC) $(CFLAGS) -o $@ $^ $(CLIBS) $(JANET_LIBRARY): $(JANET_CORE_OBJECTS) - $(CC) $(CFLAGS) -shared -o $(JANET_LIBRARY) $^ $(CLIBS) + $(CC) $(CFLAGS) -shared -o $@ $^ $(CLIBS) + +###################### +##### Emscripten ##### +###################### + +EMCC=emcc +EMCCFLAGS=-std=c99 -Wall -Wextra -Isrc/include -fpic -O2 -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]' +JANET_EMTARGET=janet.js +JANET_WEB_SOURCES=$(JANET_CORE_SOURCES) $(JANET_WEBCLIENT_SOURCES) +JANET_EMOBJECTS=$(patsubst %.c,%.bc,$(JANET_WEB_SOURCES)) + +# Only a few files depend on generated headers +src/core/corelib.bc: src/include/generated/core.h +src/webclient/main.bc: src/include/generated/webinit.h + +%.bc: %.c $(JANET_HEADERS) $(JANET_LOCAL_HEADERS) + $(EMCC) $(EMCCFLAGS) -o $@ -c $< + +$(JANET_EMTARGET): $(JANET_EMOBJECTS) + $(EMCC) $(EMCCFLAGS) -shared -o $@ $^ ################### ##### Testing ##### @@ -139,8 +163,8 @@ clean-natives: clean: -rm $(JANET_TARGET) - -rm src/**/*.o - -rm vgcore.* + -rm $(JANET_LIBRARY) + -rm src/**/*.o src/**/*.bc vgcore.* *.js *.wasm *.html -rm $(JANET_GENERATED_HEADERS) install: $(JANET_TARGET) diff --git a/src/core/core.janet b/src/core/core.janet index f7136492..3e6f1b3c 100644 --- a/src/core/core.janet +++ b/src/core/core.janet @@ -792,8 +792,9 @@ (defn pp "Pretty print a value. Displays values inside collections, and is safe to call on any table. Does not print table prototype information." - [x] + @[x file] + (default file stdout) (def buf @"") (def indent @"\n") (def seen @{}) @@ -853,12 +854,11 @@ (def complex? (> (length y) 4)) ((if complex? pp-dict-nested pp-dict-simple) y)) - (def printers { - :array (fn [y] (do-ds y "@[" "]" true pp-seq)) - :tuple (fn [y] (do-ds y "(" ")" false pp-seq)) - :table (fn [y] (do-ds y "@{" "}" true pp-dict)) - :struct (fn [y] (do-ds y "{" "}" false pp-dict)) - }) + (def printers + {:array (fn [y] (do-ds y "@[" "]" true pp-seq)) + :tuple (fn [y] (do-ds y "(" ")" false pp-seq)) + :table (fn [y] (do-ds y "@{" "}" true pp-dict)) + :struct (fn [y] (do-ds y "{" "}" false pp-dict))}) (:= recur (fn [y] (def p (get printers (type y))) @@ -869,7 +869,7 @@ (recur x) (buffer.push-string buf "\n") - (file.write stdout buf) + (file.write file buf) nil) ### @@ -1041,8 +1041,8 @@ (def (line col) (parser.where p)) (onerr where "parse" (string (parser.error p) " on line " line ", column " col))) (case (fiber.status chars) - :new (parser.byte p (resume chars)) - :pending (parser.byte p (resume chars)) + :new (parser.byte p (resume chars nil)) + :pending (parser.byte p (resume chars nil)) (:= going false)))) (when (not= :root (parser.status p)) (onerr where "parse" "unexpected end of source")))) @@ -1067,7 +1067,7 @@ err) errf)))) :a)) - (def res (resume f)) + (def res (resume f nil)) (when good (def sig (fiber.status f)) (if going @@ -1078,18 +1078,18 @@ # Run loop (def oldenv *env*) (:= *env* env) - (while going (eval1 (resume vals))) + (while going (eval1 (resume vals nil))) (:= *env* oldenv) env) (defn default-error-handler @[source t x f] - (file.write stdout (string t " error in " source ": ")) + (file.write stderr (string t " error in " source ": ")) (if (bytes? x) - (do (file.write stdout x) - (file.write stdout "\n")) - (pp x)) + (do (file.write stderr x) + (file.write stderr "\n")) + (pp x stderr)) (when f (def st (fiber.stack f)) (loop @@ -1101,25 +1101,25 @@ :source source :line source-line :column source-col} :in st] - (file.write stdout " in") - (when c (file.write stdout " cfunction")) + (file.write stderr " in") + (when c (file.write stderr " cfunction")) (if name - (file.write stdout " " name) - (when func (file.write stdout " " (string func)))) + (file.write stderr " " name) + (when func (file.write stderr " " (string func)))) (if source (do - (file.write stdout " [" source "]") + (file.write stderr " [" source "]") (if source-line (file.write - stdout + stderr " on line " (string source-line) ", column " (string source-col))))) (if (and (not source-line) pc) - (file.write stdout " (pc=" (string pc) ")")) - (when tail (file.write stdout " (tailcall)")) - (file.write stdout "\n")))) + (file.write stderr " (pc=" (string pc) ")")) + (when tail (file.write stderr " (tailcall)")) + (file.write stderr "\n")))) (defn eval "Evaluates a string in the current environment. If more control over the @@ -1237,10 +1237,8 @@ (defn import* [env path & args] (def targs (apply table args)) - (def { - :as as - :prefix prefix - } targs) + (def {:as as + :prefix prefix} targs) (def newenv (require path targs)) (var k (next newenv nil)) (def {:meta meta} newenv) diff --git a/src/core/corelib.c b/src/core/corelib.c index 08766a2b..be45e6e2 100644 --- a/src/core/corelib.c +++ b/src/core/corelib.c @@ -28,24 +28,19 @@ /* Generated header */ #include -/* Only include dynamic modules if enabled */ -#ifdef JANET_DYNAMIC_MODULES - /* Use LoadLibrary on windows or dlopen on posix to load dynamic libaries * with native code. */ -#ifdef JANET_WINDOWS +#if defined(JANET_NO_DYNAMIC_MODULES) +typedef int Clib; +#define load_clib(name) ((void) name, 0) +#define symbol_clib(lib, sym) ((void) lib, (void) sym, 0) +#define error_clib() "dynamic libraries not supported" +#elif defined(JANET_WINDOWS) #include typedef HINSTANCE Clib; #define load_clib(name) LoadLibrary((name)) #define symbol_clib(lib, sym) GetProcAddress((lib), (sym)) #define error_clib() "could not load dynamic library" -#elif defined(JANET_WEB) -#include -/* TODO - figure out how loading modules will work in JS */ -typedef int Clib; -#define load_clib(name) 0 -#define symbol_clib(lib, sym) 0 -#define error_clib() "dynamic libraries not supported" #else #include typedef void *Clib; @@ -82,9 +77,6 @@ static int janet_core_native(JanetArgs args) { JANET_RETURN_CFUNCTION(args, init); } -#endif -/* end JANET_DYNAMIC_MODULES */ - static int janet_core_print(JanetArgs args) { int32_t i; for (i = 0; i < args.n; ++i) { @@ -295,9 +287,7 @@ static int janet_core_hash(JanetArgs args) { } static const JanetReg cfuns[] = { -#ifdef JANET_DYNAMIC_MODULES {"native", janet_core_native}, -#endif {"print", janet_core_print}, {"describe", janet_core_describe}, {"string", janet_core_string}, @@ -605,7 +595,7 @@ JanetTable *janet_core_env(void) { janet_def(env, "_env", ret); /* Run bootstrap source */ - janet_dobytes(env, janet_gen_core, sizeof(janet_gen_core), "core.janet"); + janet_dobytes(env, janet_gen_core, sizeof(janet_gen_core), "core.janet", NULL); return env; } diff --git a/src/core/io.c b/src/core/io.c index cfe19a8a..8afd0019 100644 --- a/src/core/io.c +++ b/src/core/io.c @@ -158,6 +158,9 @@ static int janet_io_popen(JanetArgs args) { flags = (fmode[0] == 'r') ? IO_PIPED | IO_READ : IO_PIPED | IO_WRITE; #ifdef JANET_WINDOWS #define popen _popen +#endif +#ifdef __EMSCRIPTEN__ +#define popen(A, B) (errno = 0, NULL) #endif f = popen((const char *)fname, (const char *)fmode); if (!f) { diff --git a/src/core/os.c b/src/core/os.c index bb6c29a0..cca1e3e4 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -45,6 +45,8 @@ static int os_which(JanetArgs args) { JANET_RETURN_CSYMBOL(args, ":windows"); #elif __APPLE__ JANET_RETURN_CSYMBOL(args, ":macos"); + #elif defined(__EMSCRIPTEN__) + JANET_RETURN_CSYMBOL(args, ":web"); #else JANET_RETURN_CSYMBOL(args, ":posix"); #endif diff --git a/src/core/run.c b/src/core/run.c index 466c37c8..79fa2b16 100644 --- a/src/core/run.c +++ b/src/core/run.c @@ -66,12 +66,13 @@ void janet_stacktrace(JanetFiber *fiber, const char *errtype, Janet err) { } /* Run a string */ -int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char *sourcePath) { +int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char *sourcePath, Janet *out) { JanetParser parser; int errflags = 0; int32_t index = 0; int dudeol = 0; int done = 0; + Janet ret = janet_wrap_nil(); const uint8_t *where = sourcePath ? janet_cstring(sourcePath) : NULL; if (where) janet_gcroot(janet_wrap_string(where)); janet_parser_init(&parser); @@ -85,7 +86,6 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char if (cres.status == JANET_COMPILE_OK) { JanetFunction *f = janet_thunk(cres.funcdef); JanetFiber *fiber = janet_fiber(f, 64); - Janet ret = janet_wrap_nil(); JanetSignal status = janet_run(fiber, &ret); if (status != JANET_SIGNAL_OK) { janet_stacktrace(fiber, "runtime", ret); @@ -126,12 +126,13 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char } janet_parser_deinit(&parser); if (where) janet_gcunroot(janet_wrap_string(where)); + if (out) *out = ret; return errflags; } -int janet_dostring(JanetTable *env, const char *str, const char *sourcePath) { +int janet_dostring(JanetTable *env, const char *str, const char *sourcePath, Janet *out) { int32_t len = 0; while (str[len]) ++len; - return janet_dobytes(env, (const uint8_t *)str, len, sourcePath); + return janet_dobytes(env, (const uint8_t *)str, len, sourcePath, out); } diff --git a/src/core/symcache.c b/src/core/symcache.c index f21311c2..f827d234 100644 --- a/src/core/symcache.c +++ b/src/core/symcache.c @@ -57,7 +57,7 @@ void janet_symcache_deinit() { } /* Mark an entry in the table as deleted. */ -#define JANET_SYMCACHE_DELETED ((const uint8_t *)0 + 1) +static const uint8_t JANET_SYMCACHE_DELETED[1] = {0}; /* Find an item in the cache and return its location. * If the item is not found, return the location diff --git a/src/include/janet/janet.h b/src/include/janet/janet.h index 56f22b31..4bfd4d41 100644 --- a/src/include/janet/janet.h +++ b/src/include/janet/janet.h @@ -93,6 +93,11 @@ extern "C" { #define JANET_LITTLE_ENDIAN 1 #endif +/* Check emscripten */ +#ifdef __EMSCRIPTEN__ +#define JANET_NO_DYNAMIC_MODULES +#endif + /* Define how global janet state is declared */ #ifdef JANET_SINGLE_THREADED #define JANET_THREAD_LOCAL @@ -339,7 +344,7 @@ union Janet { /* This representation uses 48 bit pointers. The trade off vs. the LuaJIT style * 47 bit payload representaion is that the type bits are no long contiguous. Type - * checking can still be fast, but typewise polymorphism takes a bit longer. However, + * checking can still be fast, but typewise polymorphism takes a bit longer. However, * hopefully we can avoid some annoying problems that occur when trying to use 47 bit pointers * in a 48 bit address space (Linux on ARM). If JANET_NANBOX_47 is set, use 47 bit tagged pointers. */ @@ -653,8 +658,8 @@ struct JanetFuncDef { int32_t arity; /* Not including varargs */ int32_t constants_length; int32_t bytecode_length; - int32_t environments_length; - int32_t defs_length; + int32_t environments_length; + int32_t defs_length; }; /* A fuction environment */ @@ -893,8 +898,8 @@ JANET_API JanetCompileResult janet_compile(Janet source, JanetTable *env, const /* Get the default environment for janet */ JANET_API JanetTable *janet_core_env(void); -JANET_API int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char *sourcePath); -JANET_API int janet_dostring(JanetTable *env, const char *str, const char *sourcePath); +JANET_API int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char *sourcePath, Janet *out); +JANET_API int janet_dostring(JanetTable *env, const char *str, const char *sourcePath, Janet *out); /* Number scanning */ JANET_API Janet janet_scan_number(const uint8_t *src, int32_t len); @@ -1023,9 +1028,9 @@ JANET_API JanetCFunction janet_native(const char *name, const uint8_t **error); JANET_API int janet_marshal(JanetBuffer *buf, Janet x, int flags); JANET_API int janet_unmarshal( const uint8_t *bytes, - size_t len, - int flags, - Janet *out, + size_t len, + int flags, + Janet *out, const uint8_t **next); /* GC */ diff --git a/src/mainclient/main.c b/src/mainclient/main.c index eddb7f11..80233a12 100644 --- a/src/mainclient/main.c +++ b/src/mainclient/main.c @@ -46,7 +46,7 @@ int main(int argc, char **argv) { janet_line_init(); /* Run startup script */ - status = janet_dobytes(env, janet_gen_init, sizeof(janet_gen_init), "init.janet"); + status = janet_dobytes(env, janet_gen_init, sizeof(janet_gen_init), "init.janet", NULL); /* Deinitialize vm */ janet_deinit(); diff --git a/src/webclient/main.c b/src/webclient/main.c new file mode 100644 index 00000000..a30ccb2b --- /dev/null +++ b/src/webclient/main.c @@ -0,0 +1,116 @@ +/* +* Copyright (c) 2018 Calvin Rose +* +* 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. +*/ + +#include +#include +#include + +static JanetFiber *repl_fiber = NULL; +static JanetBuffer *line_buffer = NULL; +static const uint8_t *line_prompt = NULL; + +/* Yield to JS event loop from janet. Takes a repl prompt + * and a buffer to fill with input data. */ +static int repl_yield(JanetArgs args) { + JANET_FIXARITY(args, 2); + JANET_ARG_STRING(line_prompt, args, 0); + JANET_ARG_BUFFER(line_buffer, args, 1); + /* Suspend janet repl by throwing a user defined signal */ + return JANET_SIGNAL_USER9; +} + +/* Re-enter the loop */ +static int enter_loop(void) { + Janet ret; + JanetSignal status = janet_continue(repl_fiber, janet_wrap_nil(), &ret); + if (status == JANET_SIGNAL_ERROR) { + janet_stacktrace(repl_fiber, "runtime", ret); + janet_deinit(); + repl_fiber = NULL; + return 1; + } + return 0; +} + +/* Intialize the repl */ +EMSCRIPTEN_KEEPALIVE +void repl_init(void) { + int status; + JanetTable *env; + + /* Set up VM */ + janet_init(); + env = janet_core_env(); + + /* Janet line getter */ + janet_def(env, "repl-yield", janet_wrap_cfunction(repl_yield)); + janet_register("repl-yield", janet_wrap_cfunction(repl_yield)); + + /* Run startup script */ + Janet ret; + status = janet_dobytes(env, janet_gen_webinit, sizeof(janet_gen_webinit), "webinit.janet", &ret); + if (status == JANET_SIGNAL_ERROR) { + printf("start up error.\n"); + janet_deinit(); + repl_fiber = NULL; + return; + } + janet_gcroot(ret); + repl_fiber = janet_unwrap_fiber(ret); + + /* Start repl */ + if (enter_loop()) return; +} + +/* Deinitialize the repl */ +EMSCRIPTEN_KEEPALIVE +void repl_deinit(void) { + if (!repl_fiber) { + return; + } + repl_fiber = NULL; + line_buffer = NULL; + janet_deinit(); +} + +/* Get the prompt to show in the repl */ +EMSCRIPTEN_KEEPALIVE +const char *repl_prompt(void) { + return line_prompt ? ((const char *)line_prompt) : ""; +} + +/* Restart the repl calling from JS. Pass in the input for the next line. */ +EMSCRIPTEN_KEEPALIVE +void repl_input(char *input) { + + /* Create the repl if we haven't yet */ + if (!repl_fiber) { + printf("initialize the repl first"); + } + + /* Now fill the pending line_buffer and resume the repl loop */ + if (line_buffer) { + janet_buffer_push_cstring(line_buffer, input); + line_buffer = NULL; + enter_loop(); + } +} diff --git a/src/webclient/webinit.janet b/src/webclient/webinit.janet new file mode 100644 index 00000000..dca86a6c --- /dev/null +++ b/src/webclient/webinit.janet @@ -0,0 +1,11 @@ +# Copyright 2017-2018 (C) Calvin Rose +(print (string "Janet " janet.version " Copyright (C) 2017-2018 Calvin Rose")) + +(fiber.new + (fn @[] + (repl (fn [buf p] + (def [line] (parser.where p)) + (def prompt (string "janet:" line ":" (parser.state p) "> ")) + (repl-yield prompt buf) + buf))) + :9e) # stop fiber on error signals and user9 signals