[qjs] Initial port to Fuchsia

Contains some untested affordances for polling handles.

Largely based on work by ianloic@

Change-Id: I7589c8c9206b1925f0e4014c5b4a87e90dd04079
diff --git a/BUILD.gn b/BUILD.gn
index 2344b7f..339d596 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -4,103 +4,147 @@
 
 import("//build/compiled_action.gni")
 import("//build/host.gni")
+import("//build/package.gni")
+import("//build/test.gni")
+import("//build/test/test_package.gni")
 
-if (current_toolchain == host_toolchain) {
-  config("qjs-config") {
-    version_lines = read_file("VERSION", "list lines")
-    defines = [
-      "_GNU_SOURCE",
-      "CONFIG_VERSION=\"" + version_lines[0] + "\"",
-    ]
-    cflags = [
-      "-Wno-sign-compare",
-      "-Wno-unused-variable",
-      "-funsigned-char",
-    ]
-  }
+config("qjs-config") {
+  version_lines = read_file("VERSION", "list lines")
+  defines = [
+    "_GNU_SOURCE",
+    "CONFIG_VERSION=\"" + version_lines[0] + "\"",
+  ]
+  cflags = [
+    "-Wno-sign-compare",
+    "-Wno-unused-variable",
+    "-funsigned-char",
+  ]
+}
 
-  source_set("qjs-lib") {
-    # based on QJS_LIB_OBJS
-    sources = [
-      "cutils.c",
-      "libbf.c",
-      "libbf.h",
-      "libregexp.c",
-      "libunicode.c",
-      "quickjs-libc.c",
-      "quickjs.c",
-    ]
+source_set("qjs-lib") {
+  # based on QJS_LIB_OBJS
+  sources = [
+    "cutils.c",
+    "libbf.c",
+    "libbf.h",
+    "libregexp.c",
+    "libunicode.c",
+    "quickjs-libc.c",
+    "quickjs.c",
+  ]
 
-    public_configs = [ ":qjs-config" ]
-  }
-
-  executable("qjsc") {
-    sources = [
-      "qjsc.c",
-    ]
+   if (is_fuchsia) {
     deps = [
-      ":qjs-lib",
+      "//zircon/public/lib/fdio",
     ]
   }
+   public_configs = [ ":qjs-config" ]
 
-  template("compiled_js") {
-    generate_target = "${target_name}_gen"
-    generated_file = "${target_gen_dir}/${target_name}.c"
+  
+}
 
-    compiled_action(generate_target) {
-      tool = ":qjsc"
-      inputs = [
-        invoker.source,
-      ]
-      outputs = [
-        generated_file,
-      ]
-      args = [
-        "-c",
-        "-o",
-        rebase_path(generated_file),
-      ]
-      if (defined(invoker.module) && invoker.module) {
-        args += [ "-m" ]
-      }
+executable("qjsc") {
+  sources = [
+    "qjsc.c",
+  ]
+  deps = [
+    ":qjs-lib",
+  ]
+}
 
-      args += [ rebase_path(invoker.source) ]
+template("compiled_js") {
+  generate_target = "${target_name}_gen"
+  generated_file = "${target_gen_dir}/${target_name}.c"
+
+  compiled_action(generate_target) {
+    tool = ":qjsc"
+    inputs = [
+      invoker.source,
+    ]
+    outputs = [
+      generated_file,
+    ]
+    args = [
+      "-c",
+      "-o",
+      rebase_path(generated_file),
+    ]
+    if (defined(invoker.module) && invoker.module) {
+      args += [ "-m" ]
     }
 
-    source_set(target_name) {
-      sources = [
-        generated_file,
-      ]
-      deps = [
-        ":${generate_target}",
-      ]
-    }
+    args += [ rebase_path(invoker.source) ]
   }
 
-  compiled_js("repl") {
-    source = "repl.js"
-    module = true
-  }
-
-  executable("qjs") {
-    # based on QJS_OBJS
+  source_set(target_name) {
     sources = [
-      "qjs.c",
+      generated_file,
     ]
-
     deps = [
-      ":qjs-lib",
-      ":repl",
-    ]
-  }
-
-  group("quickjs") {
-    deps = [
-      ":qjs",
+      ":${generate_target}",
     ]
   }
 }
 
+compiled_js("repl") {
+  source = "repl.js"
+  module = true
+}
+
+executable("qjs") {
+  # based on QJS_OBJS
+  sources = [
+    "qjs.c",
+  ]
+
+  deps = [
+    ":qjs-lib",
+    ":repl",
+  ]
+
+  if (is_fuchsia) {
+    libs = [ "zircon" ]
+  }
+}
+
+package("quickjs") {
+  deps = [
+    ":qjs",
+  ]
+  binaries = [
+    {
+      name = "qjs"
+      shell = true
+    },
+  ]
+}
+
+test("qjs_test") {
+  sources = [
+    "basic_test.cc",
+  ]
+
+  deps = [
+    ":qjs-lib",
+    "//third_party/googletest:gtest",
+    "//src/lib/fxl/test:gtest_main",
+  ]
+
+  if (is_fuchsia) {
+    libs = [ "zircon" ]
+  }
+}
+
+unittest_package("qjs_tests") {
+   deps = [ ":qjs_test" ]
+   tests = [
+     {
+       name = "qjs_test"
+       environments = basic_envs
+    },
+   ]
+ }
+
 install_host_tools("quickjs_host") {
   deps = [
     ":qjs",
diff --git a/basic_test.cc b/basic_test.cc
new file mode 100644
index 0000000..7d59732
--- /dev/null
+++ b/basic_test.cc
@@ -0,0 +1,47 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "gtest/gtest.h"
+#include "quickjs-libc.h"
+#include "quickjs.h"
+
+// Sanity check test to make sure Hello World works.
+TEST(Qjs, Sanity) {
+  JSRuntime *rt = JS_NewRuntime();
+  ASSERT_NE(rt, nullptr) << "Cannot allocate JS runtime";
+
+  JSContext *ctx = JS_NewContext(rt);
+  ASSERT_NE(ctx, nullptr) << "Cannot allocate JS context";
+
+  /* system modules */
+  js_init_module_std(ctx, "std");
+  js_init_module_os(ctx, "os");
+
+  const char *str =
+      "import * as std from 'std';\n"
+      "import * as os from 'os';\n"
+      "globalThis.std = std;\n"
+      "globalThis.os = os;\n";
+  JSValue init_compile =
+      JS_Eval(ctx, str, strlen(str), "<input>", JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
+
+  ASSERT_FALSE(JS_IsException(init_compile));
+  js_module_set_import_meta(ctx, init_compile, 1, 1);
+  JSValue init_run = JS_EvalFunction(ctx, init_compile);
+
+  ASSERT_FALSE(JS_IsException(init_run));
+
+  std::string hello_world = "Hello World\n";
+  std::string test_val = "globalThis.std.printf('" + hello_world + "');";
+  JSValue result = JS_Eval(ctx, test_val.c_str(), test_val.length(), "test", 0);
+  int32_t len;
+  int to_int_32;
+  if ((to_int_32 = JS_ToInt32(ctx, &len, result)) != 0) {
+    js_std_dump_error(ctx);
+    ASSERT_EQ(0, JS_ToInt32(ctx, &len, result));
+  }
+  ASSERT_EQ(hello_world.length(), len);
+}
diff --git a/qjs.c b/qjs.c
index fbff078..c14ed95 100644
--- a/qjs.c
+++ b/qjs.c
@@ -128,6 +128,8 @@
     return 0;
 #elif defined(__linux__)
     return malloc_usable_size(ptr);
+#elif defined(__Fuchsia__)
+    return 0;
 #else
     /* change this to `return 0;` if compilation fails */
     return malloc_usable_size(ptr);
@@ -244,6 +246,8 @@
     NULL,
 #elif defined(__linux__)
     (size_t (*)(const void *))malloc_usable_size,
+#elif defined(__Fuchsia__)
+    NULL,
 #else
     /* change this to `NULL,` if compilation fails */
     malloc_usable_size,
diff --git a/quickjs-libc.c b/quickjs-libc.c
index eba52cf..261b556 100644
--- a/quickjs-libc.c
+++ b/quickjs-libc.c
@@ -49,6 +49,14 @@
 typedef sig_t sighandler_t;
 #endif
 #endif
+#if defined(__Fuchsia__)
+#include <poll.h>
+#include <zircon/status.h>
+#include <zircon/syscalls.h>
+#include <zircon/syscalls/port.h>
+#include <lib/fdio/fdio.h>
+#include <lib/fdio/unsafe.h>
+#endif
 
 #include "cutils.h"
 #include "list.h"
@@ -84,10 +92,22 @@
     JSValue func;
 } JSOSTimer;
 
+#ifdef __Fuchsia__
+typedef struct {
+    struct list_head link;
+    zx_handle_t handle;
+    zx_signals_t signals;
+    JSValue func;
+} JSOSHandleHandler;
+#endif
+
 /* initialize the lists so js_std_free_handlers() can always be called */
 static struct list_head os_rw_handlers = LIST_HEAD_INIT(os_rw_handlers);
 static struct list_head os_signal_handlers = LIST_HEAD_INIT(os_signal_handlers);
 static struct list_head os_timers = LIST_HEAD_INIT(os_timers);
+#ifdef __Fuchsia__
+static struct list_head os_handle_handlers = LIST_HEAD_INIT(os_handle_handlers);
+#endif
 static uint64_t os_pending_signals;
 static int eval_script_recurse;
 static int (*os_poll_func)(JSContext *ctx);
@@ -1628,6 +1648,18 @@
     return JS_UNDEFINED;
 }
 
+#if defined(__Fuchsia__)
+
+static void free_handle_handler(JSRuntime *rt, JSOSHandleHandler *hh) {
+  list_del(&hh->link);
+  JS_FreeValueRT(rt, hh->func);
+  js_free_rt(rt, hh);
+}
+
+#endif
+
+#if !defined(__Fuchsia__)
+
 static JSOSSignalHandler *find_sh(int sig_num)
 {
     JSOSSignalHandler *sh;
@@ -1698,6 +1730,8 @@
     return JS_UNDEFINED;
 }
 
+#endif  // !defined(__fuchsia__)
+
 #if defined(__linux__) || defined(__APPLE__)
 static int64_t get_time_ms(void)
 {
@@ -1881,6 +1915,129 @@
     }
     return 0;
 }
+#elif defined(__Fuchsia__)
+void wait_for_zx_handle(JSContext *ctx, JSFuchsiaHandle *h, uint32_t signals, JSValueConst *value) {
+  JSOSHandleHandler *hh = js_mallocz(ctx, sizeof(JSOSHandleHandler));
+  hh->handle = h->handle;
+  hh->signals = signals;
+  hh->func = JS_DupValue(ctx, *value);
+  list_add_tail(&hh->link, &os_handle_handlers);
+}
+
+static int js_os_poll(JSContext *ctx) {
+  struct list_head *el;
+  zx_status_t status;
+  zx_time_t deadline = ZX_TIME_INFINITE;
+
+  if (list_empty(&os_rw_handlers) && list_empty(&os_handle_handlers) && list_empty(&os_timers)) {
+    return -1;
+  }
+
+  if (!list_empty(&os_timers)) {
+    zx_time_t cur_time;
+    status = zx_clock_get(ZX_CLOCK_MONOTONIC, &cur_time);
+    assert(status == ZX_OK);
+
+    list_for_each(el, &os_timers) {
+      JSOSTimer *th = list_entry(el, JSOSTimer, link);
+      // TODO(ianloic): make safe from overflows
+      zx_time_t timer_deadline = th->timeout * 1000000;
+      if (cur_time >= timer_deadline) {
+        /* the timer expired */
+        JSValue func = th->func;
+        th->func = JS_UNDEFINED;
+        unlink_timer(JS_GetRuntime(ctx), th);
+        if (!th->has_object)
+          free_timer(JS_GetRuntime(ctx), th);
+        call_handler(ctx, func);
+        JS_FreeValue(ctx, func);
+        return 0;
+      } else if (timer_deadline < deadline) {
+        deadline = timer_deadline;
+      }
+    }
+  }
+  // TODO: timer slack
+
+  if (list_empty(&os_rw_handlers) && list_empty(&os_handle_handlers)) {
+    // Not waiting on any IO
+    assert(deadline != ZX_TIME_INFINITE);
+    status = zx_nanosleep(deadline);
+    assert(status == ZX_OK);
+    return 0;
+  }
+
+  zx_handle_t port = ZX_HANDLE_INVALID;
+  status = zx_port_create(0, &port);
+  assert(status == ZX_OK);
+
+  if (!list_empty(&os_handle_handlers)) {
+    list_for_each(el, &os_handle_handlers) {
+      JSOSHandleHandler *hh = list_entry(el, JSOSHandleHandler, link);
+      status = zx_object_wait_async(hh->handle, port, (uint64_t)hh, hh->signals, 0);
+      assert(status == ZX_OK);
+    }
+  }
+
+  if (!list_empty(&os_rw_handlers)) {
+    list_for_each(el, &os_rw_handlers) {
+      JSOSRWHandler *rh = list_entry(el, JSOSRWHandler, link);
+
+      fdio_t *io = fdio_unsafe_fd_to_io(rh->fd);
+      assert(io != NULL);
+
+      zx_handle_t handle;
+      zx_signals_t signals;
+      uint32_t events = 0;
+      if (!JS_IsNull(rh->rw_func[0])) {
+        events |= POLLIN;
+      }
+      if (!JS_IsNull(rh->rw_func[1])) {
+        events |= POLLOUT;
+      }
+      fdio_unsafe_wait_begin(io, events, &handle, &signals);
+      fdio_unsafe_release(io);
+
+      status = zx_object_wait_async(handle, port, ((uint64_t)rh) + 1, signals, 0);
+      assert(status == ZX_OK);
+    }
+  }
+
+  zx_port_packet_t packet;
+  status = zx_port_wait(port, deadline, &packet);
+  zx_handle_close(port);
+
+  if (status == ZX_ERR_TIMED_OUT) {
+    return 0;
+  }
+
+  assert(status == ZX_OK);
+  assert(packet.type == ZX_PKT_TYPE_SIGNAL_ONE);
+  assert(packet.status == ZX_OK);
+
+  if (packet.key % 2) {
+    // Packet for rw handler
+    JSOSRWHandler *rh = (JSOSRWHandler *)(packet.key - 1);
+    fdio_t *io = fdio_unsafe_fd_to_io(rh->fd);
+    assert(io != NULL);
+
+    uint32_t events;
+    fdio_unsafe_wait_end(io, packet.signal.observed, &events);
+    fdio_unsafe_release(io);
+
+    if (events & POLLIN && !JS_IsNull(rh->rw_func[0])) {
+      call_handler(ctx, rh->rw_func[0]);
+    } else if (events & POLLOUT && !JS_IsNull(rh->rw_func[1])) {
+      call_handler(ctx, rh->rw_func[1]);
+    }
+  } else {
+    // Packet for handle handler
+    JSOSHandleHandler *hh = (JSOSHandleHandler *)packet.key;
+    call_handler(ctx, hh->func);
+  }
+  return 0;
+}
+
 #else
 static int js_os_poll(JSContext *ctx)
 {
@@ -2512,6 +2669,8 @@
 #define OS_PLATFORM "darwin"
 #elif defined(EMSCRIPTEN)
 #define OS_PLATFORM "js"
+#elif defined(__Fuchsia__)
+#define OS_PLATFORM "fuchsia"
 #else
 #define OS_PLATFORM "linux"
 #endif
@@ -2542,6 +2701,7 @@
     JS_CFUNC_DEF("rename", 2, js_os_rename ),
     JS_CFUNC_MAGIC_DEF("setReadHandler", 2, js_os_setReadHandler, 0 ),
     JS_CFUNC_MAGIC_DEF("setWriteHandler", 2, js_os_setReadHandler, 1 ),
+#if !defined(__Fuchsia__)
     JS_CFUNC_DEF("signal", 2, js_os_signal ),
     OS_FLAG(SIGINT),
     OS_FLAG(SIGABRT),
@@ -2549,6 +2709,7 @@
     OS_FLAG(SIGILL),
     OS_FLAG(SIGSEGV),
     OS_FLAG(SIGTERM),
+#endif // !defined(__Fuchsia__)
 #if !defined(_WIN32)
     OS_FLAG(SIGQUIT),
     OS_FLAG(SIGPIPE),
@@ -2671,6 +2832,11 @@
     init_list_head(&os_rw_handlers);
     init_list_head(&os_signal_handlers);
     init_list_head(&os_timers);
+
+#if defined(__Fuchsia__)
+    init_list_head(&os_handle_handlers);
+#endif
+
     os_pending_signals = 0;
 }
 
@@ -2683,10 +2849,12 @@
         free_rw_handler(rt, rh);
     }
 
+#if !defined(__Fuchsia__)
     list_for_each_safe(el, el1, &os_signal_handlers) {
         JSOSSignalHandler *sh = list_entry(el, JSOSSignalHandler, link);
         free_sh(rt, sh);
     }
+#endif  // !defined(__Fuchsia__)
     
     list_for_each_safe(el, el1, &os_timers) {
         JSOSTimer *th = list_entry(el, JSOSTimer, link);
@@ -2694,6 +2862,13 @@
         if (!th->has_object)
             free_timer(rt, th);
     }
+
+#if defined(__Fuchsia__)
+    list_for_each_safe(el, el1, &os_handle_handlers) {
+      JSOSHandleHandler *hh = list_entry(el, JSOSHandleHandler, link);
+      free_handle_handler(rt, hh);
+    }
+#endif
 }
 
 void js_std_dump_error(JSContext *ctx)
diff --git a/quickjs-libc.h b/quickjs-libc.h
index ac7947e..8ec8383 100644
--- a/quickjs-libc.h
+++ b/quickjs-libc.h
@@ -29,6 +29,14 @@
 
 #include "quickjs.h"
 
+#if defined(__Fuchsia__)
+#include <zircon/types.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 JSModuleDef *js_init_module_std(JSContext *ctx, const char *module_name);
 JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name);
 void js_std_add_helpers(JSContext *ctx, int argc, char **argv);
@@ -43,4 +51,19 @@
 void js_std_eval_binary(JSContext *ctx, const uint8_t *buf, size_t buf_len,
                         int flags);
 
+#if defined(__Fuchsia__)
+
+typedef struct {
+  zx_handle_t handle;
+  zx_obj_type_t type;
+} JSFuchsiaHandle;
+
+void wait_for_zx_handle(JSContext *ctx, JSFuchsiaHandle *handle, uint32_t signals,
+                        JSValueConst *value);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+#endif // defined(__Fuchsia__)
+
 #endif /* QUICKJS_LIBC_H */
diff --git a/quickjs.c b/quickjs.c
index 5d833b0..f6225d9 100644
--- a/quickjs.c
+++ b/quickjs.c
@@ -1347,6 +1347,8 @@
     return 0;
 #elif defined(__linux__)
     return malloc_usable_size(ptr);
+#elif defined(__Fuchsia__)
+    return 0;
 #else
     /* change this to `return 0;` if compilation fails */
     return malloc_usable_size(ptr);
@@ -1421,6 +1423,8 @@
     NULL,
 #elif defined(__linux__)
     (size_t (*)(const void *))malloc_usable_size,
+#elif defined(__Fuchsia__)
+    NULL,
 #else
     /* change this to `NULL,` if compilation fails */
     malloc_usable_size,
diff --git a/repl.js b/repl.js
index b51d2d6..9c70ee1 100644
--- a/repl.js
+++ b/repl.js
@@ -157,8 +157,10 @@
             }
         }
 
-        /* install a Ctrl-C signal handler */
-        os.signal(os.SIGINT, sigint_handler);
+        if (os.signal) {
+            /* install a Ctrl-C signal handler */
+            os.signal(os.SIGINT, sigint_handler);
+	}
 
         /* install a handler to read stdin */
         term_read_buf = new Uint8Array(64);
@@ -838,8 +840,10 @@
                 readline_cb(null);
                 return;
             case -3:
-                /* uninstall a Ctrl-C signal handler */
-                os.signal(os.SIGINT, null);
+		if (os.signal) {
+                    /* uninstall a Ctrl-C signal handler */
+                    os.signal(os.SIGINT, null);
+		}
                 /* uninstall the stdin read handler */
                 os.setReadHandler(term_fd, null);
                 return;