[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;