1
0
mirror of https://github.com/janet-lang/janet synced 2024-12-23 06:50:26 +00:00

Web assembly build with emscripten.

This commit is contained in:
Calvin Rose 2018-10-16 23:08:26 -04:00
parent dde6218bff
commit c1923c5ada
12 changed files with 219 additions and 64 deletions

5
.gitignore vendored
View File

@ -10,6 +10,11 @@ janet
/Emscripten /Emscripten
/src/include/generated/*.h /src/include/generated/*.h
# Emscripten
*.bc
janet.js
janet.wasm
# Generated files # Generated files
*.gen.h *.gen.h
*.gen.c *.gen.c

View File

@ -55,6 +55,7 @@ JANET_LOCAL_HEADERS=$(sort $(wildcard src/*/*.h))
# Source files # Source files
JANET_CORE_SOURCES=$(sort $(wildcard src/core/*.c)) JANET_CORE_SOURCES=$(sort $(wildcard src/core/*.c))
JANET_MAINCLIENT_SOURCES=$(sort $(wildcard src/mainclient/*.c)) JANET_MAINCLIENT_SOURCES=$(sort $(wildcard src/mainclient/*.c))
JANET_WEBCLIENT_SOURCES=$(sort $(wildcard src/webclient/*.c))
all: $(JANET_TARGET) $(JANET_LIBRARY) all: $(JANET_TARGET) $(JANET_LIBRARY)
@ -72,6 +73,9 @@ xxd: src/tools/xxd.c
src/include/generated/init.h: src/mainclient/init.janet xxd src/include/generated/init.h: src/mainclient/init.janet xxd
./xxd $< $@ janet_gen_init ./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 src/include/generated/core.h: src/core/core.janet xxd
./xxd $< $@ janet_gen_core ./xxd $< $@ janet_gen_core
@ -93,10 +97,30 @@ JANET_ALL_OBJECTS=$(patsubst %.c,%.o,$(JANET_ALL_SOURCES))
$(CC) $(CFLAGS) -o $@ -c $< $(CC) $(CFLAGS) -o $@ -c $<
$(JANET_TARGET): $(JANET_ALL_OBJECTS) $(JANET_TARGET): $(JANET_ALL_OBJECTS)
$(CC) $(CFLAGS) -o $(JANET_TARGET) $^ $(CLIBS) $(CC) $(CFLAGS) -o $@ $^ $(CLIBS)
$(JANET_LIBRARY): $(JANET_CORE_OBJECTS) $(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 ##### ##### Testing #####
@ -139,8 +163,8 @@ clean-natives:
clean: clean:
-rm $(JANET_TARGET) -rm $(JANET_TARGET)
-rm src/**/*.o -rm $(JANET_LIBRARY)
-rm vgcore.* -rm src/**/*.o src/**/*.bc vgcore.* *.js *.wasm *.html
-rm $(JANET_GENERATED_HEADERS) -rm $(JANET_GENERATED_HEADERS)
install: $(JANET_TARGET) install: $(JANET_TARGET)

View File

@ -792,8 +792,9 @@
(defn pp (defn pp
"Pretty print a value. Displays values inside collections, and is safe "Pretty print a value. Displays values inside collections, and is safe
to call on any table. Does not print table prototype information." to call on any table. Does not print table prototype information."
[x] @[x file]
(default file stdout)
(def buf @"") (def buf @"")
(def indent @"\n") (def indent @"\n")
(def seen @{}) (def seen @{})
@ -853,12 +854,11 @@
(def complex? (> (length y) 4)) (def complex? (> (length y) 4))
((if complex? pp-dict-nested pp-dict-simple) y)) ((if complex? pp-dict-nested pp-dict-simple) y))
(def printers { (def printers
:array (fn [y] (do-ds y "@[" "]" true pp-seq)) {:array (fn [y] (do-ds y "@[" "]" true pp-seq))
:tuple (fn [y] (do-ds y "(" ")" false pp-seq)) :tuple (fn [y] (do-ds y "(" ")" false pp-seq))
:table (fn [y] (do-ds y "@{" "}" true pp-dict)) :table (fn [y] (do-ds y "@{" "}" true pp-dict))
:struct (fn [y] (do-ds y "{" "}" false pp-dict)) :struct (fn [y] (do-ds y "{" "}" false pp-dict))})
})
(:= recur (fn [y] (:= recur (fn [y]
(def p (get printers (type y))) (def p (get printers (type y)))
@ -869,7 +869,7 @@
(recur x) (recur x)
(buffer.push-string buf "\n") (buffer.push-string buf "\n")
(file.write stdout buf) (file.write file buf)
nil) nil)
### ###
@ -1041,8 +1041,8 @@
(def (line col) (parser.where p)) (def (line col) (parser.where p))
(onerr where "parse" (string (parser.error p) " on line " line ", column " col))) (onerr where "parse" (string (parser.error p) " on line " line ", column " col)))
(case (fiber.status chars) (case (fiber.status chars)
:new (parser.byte p (resume chars)) :new (parser.byte p (resume chars nil))
:pending (parser.byte p (resume chars)) :pending (parser.byte p (resume chars nil))
(:= going false)))) (:= going false))))
(when (not= :root (parser.status p)) (when (not= :root (parser.status p))
(onerr where "parse" "unexpected end of source")))) (onerr where "parse" "unexpected end of source"))))
@ -1067,7 +1067,7 @@
err) err)
errf)))) errf))))
:a)) :a))
(def res (resume f)) (def res (resume f nil))
(when good (when good
(def sig (fiber.status f)) (def sig (fiber.status f))
(if going (if going
@ -1078,18 +1078,18 @@
# Run loop # Run loop
(def oldenv *env*) (def oldenv *env*)
(:= *env* env) (:= *env* env)
(while going (eval1 (resume vals))) (while going (eval1 (resume vals nil)))
(:= *env* oldenv) (:= *env* oldenv)
env) env)
(defn default-error-handler (defn default-error-handler
@[source t x f] @[source t x f]
(file.write stdout (string t " error in " source ": ")) (file.write stderr (string t " error in " source ": "))
(if (bytes? x) (if (bytes? x)
(do (file.write stdout x) (do (file.write stderr x)
(file.write stdout "\n")) (file.write stderr "\n"))
(pp x)) (pp x stderr))
(when f (when f
(def st (fiber.stack f)) (def st (fiber.stack f))
(loop (loop
@ -1101,25 +1101,25 @@
:source source :source source
:line source-line :line source-line
:column source-col} :in st] :column source-col} :in st]
(file.write stdout " in") (file.write stderr " in")
(when c (file.write stdout " cfunction")) (when c (file.write stderr " cfunction"))
(if name (if name
(file.write stdout " " name) (file.write stderr " " name)
(when func (file.write stdout " " (string func)))) (when func (file.write stderr " " (string func))))
(if source (if source
(do (do
(file.write stdout " [" source "]") (file.write stderr " [" source "]")
(if source-line (if source-line
(file.write (file.write
stdout stderr
" on line " " on line "
(string source-line) (string source-line)
", column " ", column "
(string source-col))))) (string source-col)))))
(if (and (not source-line) pc) (if (and (not source-line) pc)
(file.write stdout " (pc=" (string pc) ")")) (file.write stderr " (pc=" (string pc) ")"))
(when tail (file.write stdout " (tailcall)")) (when tail (file.write stderr " (tailcall)"))
(file.write stdout "\n")))) (file.write stderr "\n"))))
(defn eval (defn eval
"Evaluates a string in the current environment. If more control over the "Evaluates a string in the current environment. If more control over the
@ -1237,10 +1237,8 @@
(defn import* [env path & args] (defn import* [env path & args]
(def targs (apply table args)) (def targs (apply table args))
(def { (def {:as as
:as as :prefix prefix} targs)
:prefix prefix
} targs)
(def newenv (require path targs)) (def newenv (require path targs))
(var k (next newenv nil)) (var k (next newenv nil))
(def {:meta meta} newenv) (def {:meta meta} newenv)

View File

@ -28,24 +28,19 @@
/* Generated header */ /* Generated header */
#include <generated/core.h> #include <generated/core.h>
/* Only include dynamic modules if enabled */
#ifdef JANET_DYNAMIC_MODULES
/* Use LoadLibrary on windows or dlopen on posix to load dynamic libaries /* Use LoadLibrary on windows or dlopen on posix to load dynamic libaries
* with native code. */ * 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 <windows.h> #include <windows.h>
typedef HINSTANCE Clib; typedef HINSTANCE Clib;
#define load_clib(name) LoadLibrary((name)) #define load_clib(name) LoadLibrary((name))
#define symbol_clib(lib, sym) GetProcAddress((lib), (sym)) #define symbol_clib(lib, sym) GetProcAddress((lib), (sym))
#define error_clib() "could not load dynamic library" #define error_clib() "could not load dynamic library"
#elif defined(JANET_WEB)
#include <emscripten.h>
/* 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 #else
#include <dlfcn.h> #include <dlfcn.h>
typedef void *Clib; typedef void *Clib;
@ -82,9 +77,6 @@ static int janet_core_native(JanetArgs args) {
JANET_RETURN_CFUNCTION(args, init); JANET_RETURN_CFUNCTION(args, init);
} }
#endif
/* end JANET_DYNAMIC_MODULES */
static int janet_core_print(JanetArgs args) { static int janet_core_print(JanetArgs args) {
int32_t i; int32_t i;
for (i = 0; i < args.n; ++i) { for (i = 0; i < args.n; ++i) {
@ -295,9 +287,7 @@ static int janet_core_hash(JanetArgs args) {
} }
static const JanetReg cfuns[] = { static const JanetReg cfuns[] = {
#ifdef JANET_DYNAMIC_MODULES
{"native", janet_core_native}, {"native", janet_core_native},
#endif
{"print", janet_core_print}, {"print", janet_core_print},
{"describe", janet_core_describe}, {"describe", janet_core_describe},
{"string", janet_core_string}, {"string", janet_core_string},
@ -605,7 +595,7 @@ JanetTable *janet_core_env(void) {
janet_def(env, "_env", ret); janet_def(env, "_env", ret);
/* Run bootstrap source */ /* 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; return env;
} }

View File

@ -158,6 +158,9 @@ static int janet_io_popen(JanetArgs args) {
flags = (fmode[0] == 'r') ? IO_PIPED | IO_READ : IO_PIPED | IO_WRITE; flags = (fmode[0] == 'r') ? IO_PIPED | IO_READ : IO_PIPED | IO_WRITE;
#ifdef JANET_WINDOWS #ifdef JANET_WINDOWS
#define popen _popen #define popen _popen
#endif
#ifdef __EMSCRIPTEN__
#define popen(A, B) (errno = 0, NULL)
#endif #endif
f = popen((const char *)fname, (const char *)fmode); f = popen((const char *)fname, (const char *)fmode);
if (!f) { if (!f) {

View File

@ -45,6 +45,8 @@ static int os_which(JanetArgs args) {
JANET_RETURN_CSYMBOL(args, ":windows"); JANET_RETURN_CSYMBOL(args, ":windows");
#elif __APPLE__ #elif __APPLE__
JANET_RETURN_CSYMBOL(args, ":macos"); JANET_RETURN_CSYMBOL(args, ":macos");
#elif defined(__EMSCRIPTEN__)
JANET_RETURN_CSYMBOL(args, ":web");
#else #else
JANET_RETURN_CSYMBOL(args, ":posix"); JANET_RETURN_CSYMBOL(args, ":posix");
#endif #endif

View File

@ -66,12 +66,13 @@ void janet_stacktrace(JanetFiber *fiber, const char *errtype, Janet err) {
} }
/* Run a string */ /* 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; JanetParser parser;
int errflags = 0; int errflags = 0;
int32_t index = 0; int32_t index = 0;
int dudeol = 0; int dudeol = 0;
int done = 0; int done = 0;
Janet ret = janet_wrap_nil();
const uint8_t *where = sourcePath ? janet_cstring(sourcePath) : NULL; const uint8_t *where = sourcePath ? janet_cstring(sourcePath) : NULL;
if (where) janet_gcroot(janet_wrap_string(where)); if (where) janet_gcroot(janet_wrap_string(where));
janet_parser_init(&parser); 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) { if (cres.status == JANET_COMPILE_OK) {
JanetFunction *f = janet_thunk(cres.funcdef); JanetFunction *f = janet_thunk(cres.funcdef);
JanetFiber *fiber = janet_fiber(f, 64); JanetFiber *fiber = janet_fiber(f, 64);
Janet ret = janet_wrap_nil();
JanetSignal status = janet_run(fiber, &ret); JanetSignal status = janet_run(fiber, &ret);
if (status != JANET_SIGNAL_OK) { if (status != JANET_SIGNAL_OK) {
janet_stacktrace(fiber, "runtime", ret); 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); janet_parser_deinit(&parser);
if (where) janet_gcunroot(janet_wrap_string(where)); if (where) janet_gcunroot(janet_wrap_string(where));
if (out) *out = ret;
return errflags; 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; int32_t len = 0;
while (str[len]) ++len; 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);
} }

View File

@ -57,7 +57,7 @@ void janet_symcache_deinit() {
} }
/* Mark an entry in the table as deleted. */ /* 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. /* Find an item in the cache and return its location.
* If the item is not found, return the location * If the item is not found, return the location

View File

@ -93,6 +93,11 @@ extern "C" {
#define JANET_LITTLE_ENDIAN 1 #define JANET_LITTLE_ENDIAN 1
#endif #endif
/* Check emscripten */
#ifdef __EMSCRIPTEN__
#define JANET_NO_DYNAMIC_MODULES
#endif
/* Define how global janet state is declared */ /* Define how global janet state is declared */
#ifdef JANET_SINGLE_THREADED #ifdef JANET_SINGLE_THREADED
#define JANET_THREAD_LOCAL #define JANET_THREAD_LOCAL
@ -339,7 +344,7 @@ union Janet {
/* This representation uses 48 bit pointers. The trade off vs. the LuaJIT style /* 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 * 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 * 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. */ * 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 arity; /* Not including varargs */
int32_t constants_length; int32_t constants_length;
int32_t bytecode_length; int32_t bytecode_length;
int32_t environments_length; int32_t environments_length;
int32_t defs_length; int32_t defs_length;
}; };
/* A fuction environment */ /* A fuction environment */
@ -893,8 +898,8 @@ JANET_API JanetCompileResult janet_compile(Janet source, JanetTable *env, const
/* Get the default environment for janet */ /* Get the default environment for janet */
JANET_API JanetTable *janet_core_env(void); 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_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_API int janet_dostring(JanetTable *env, const char *str, const char *sourcePath, Janet *out);
/* Number scanning */ /* Number scanning */
JANET_API Janet janet_scan_number(const uint8_t *src, int32_t len); 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_marshal(JanetBuffer *buf, Janet x, int flags);
JANET_API int janet_unmarshal( JANET_API int janet_unmarshal(
const uint8_t *bytes, const uint8_t *bytes,
size_t len, size_t len,
int flags, int flags,
Janet *out, Janet *out,
const uint8_t **next); const uint8_t **next);
/* GC */ /* GC */

View File

@ -46,7 +46,7 @@ int main(int argc, char **argv) {
janet_line_init(); janet_line_init();
/* Run startup script */ /* 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 */ /* Deinitialize vm */
janet_deinit(); janet_deinit();

116
src/webclient/main.c Normal file
View File

@ -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 <janet/janet.h>
#include <generated/webinit.h>
#include <emscripten.h>
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();
}
}

View File

@ -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