cosmopolitan/third_party/quickjs/gen.c
Justine Tunney ae5d06dc53 Unbloat build config
- 10.5% reduction of o//depend dependency graph
- 8.8% reduction in latency of make command
- Fix issue with temporary file cleanup

There's a new -w option in compile.com that turns off the recent
Landlock output path workaround for "good commands" which do not
unlink() the output file like GNU tooling does.

Our new GNU Make unveil sandboxing appears to have zero overhead
in the grand scheme of things. Full builds are pretty fast since
the only thing that's actually slowed us down is probably libcxx

    make -j16 MODE=rel
    RL: took 85,732,063µs wall time
    RL: ballooned to 323,612kb in size
    RL: needed 828,560,521µs cpu (11% kernel)
    RL: caused 39,080,670 page faults (99% memcpy)
    RL: 350,073 context switches (72% consensual)
    RL: performed 0 reads and 11,494,960 write i/o operations

pledge() and unveil() no longer consider ENOSYS to be an error.
These functions have also been added to Python's cosmo module.

This change also removes some WIN32 APIs and System Five magnums
which we're not using and it's doubtful anyone else would be too
2022-08-10 04:43:09 -07:00

510 lines
19 KiB
C

/*
* QuickJS Javascript Engine
*
* Copyright (c) 2017-2021 Fabrice Bellard
* Copyright (c) 2017-2021 Charlie Gordon
*
* 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 "libc/assert.h"
#include "libc/runtime/runtime.h"
#include "third_party/quickjs/internal.h"
asm(".ident\t\"\\n\\n\
QuickJS (MIT License)\\n\
Copyright (c) 2017-2021 Fabrice Bellard\\n\
Copyright (c) 2017-2021 Charlie Gordon\"");
asm(".include \"libc/disclaimer.inc\"");
/* clang-format off */
typedef enum JSGeneratorStateEnum {
JS_GENERATOR_STATE_SUSPENDED_START,
JS_GENERATOR_STATE_SUSPENDED_YIELD,
JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR,
JS_GENERATOR_STATE_EXECUTING,
JS_GENERATOR_STATE_COMPLETED,
} JSGeneratorStateEnum;
typedef struct JSGeneratorData {
JSGeneratorStateEnum state;
JSAsyncFunctionState func_state;
} JSGeneratorData;
static void free_generator_stack_rt(JSRuntime *rt, JSGeneratorData *s)
{
if (s->state == JS_GENERATOR_STATE_COMPLETED)
return;
async_func_free(rt, &s->func_state);
s->state = JS_GENERATOR_STATE_COMPLETED;
}
void js_generator_finalizer(JSRuntime *rt, JSValue obj)
{
JSGeneratorData *s = JS_GetOpaque(obj, JS_CLASS_GENERATOR);
if (s) {
free_generator_stack_rt(rt, s);
js_free_rt(rt, s);
}
}
static void free_generator_stack(JSContext *ctx, JSGeneratorData *s)
{
free_generator_stack_rt(ctx->rt, s);
}
void js_generator_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func)
{
JSObject *p = JS_VALUE_GET_OBJ(val);
JSGeneratorData *s = p->u.generator_data;
if (!s || s->state == JS_GENERATOR_STATE_COMPLETED)
return;
async_func_mark(rt, &s->func_state, mark_func);
}
static JSValue js_generator_next(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv,
BOOL *pdone, int magic)
{
JSGeneratorData *s = JS_GetOpaque(this_val, JS_CLASS_GENERATOR);
JSStackFrame *sf;
JSValue ret, func_ret;
*pdone = TRUE;
if (!s)
return JS_ThrowTypeError(ctx, "not a generator");
sf = &s->func_state.frame;
switch(s->state) {
default:
case JS_GENERATOR_STATE_SUSPENDED_START:
if (magic == GEN_MAGIC_NEXT) {
goto exec_no_arg;
} else {
free_generator_stack(ctx, s);
goto done;
}
break;
case JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR:
case JS_GENERATOR_STATE_SUSPENDED_YIELD:
/* cur_sp[-1] was set to JS_UNDEFINED in the previous call */
ret = JS_DupValue(ctx, argv[0]);
if (magic == GEN_MAGIC_THROW &&
s->state == JS_GENERATOR_STATE_SUSPENDED_YIELD) {
JS_Throw(ctx, ret);
s->func_state.throw_flag = TRUE;
} else {
sf->cur_sp[-1] = ret;
sf->cur_sp[0] = JS_NewInt32(ctx, magic);
sf->cur_sp++;
exec_no_arg:
s->func_state.throw_flag = FALSE;
}
s->state = JS_GENERATOR_STATE_EXECUTING;
func_ret = async_func_resume(ctx, &s->func_state);
s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD;
if (JS_IsException(func_ret)) {
/* finalize the execution in case of exception */
free_generator_stack(ctx, s);
return func_ret;
}
if (JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT) {
/* get the returned yield value at the top of the stack */
ret = sf->cur_sp[-1];
sf->cur_sp[-1] = JS_UNDEFINED;
if (JS_VALUE_GET_INT(func_ret) == FUNC_RET_YIELD_STAR) {
s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR;
/* return (value, done) object */
*pdone = 2;
} else {
*pdone = FALSE;
}
} else {
/* end of iterator */
ret = sf->cur_sp[-1];
sf->cur_sp[-1] = JS_UNDEFINED;
JS_FreeValue(ctx, func_ret);
free_generator_stack(ctx, s);
}
break;
case JS_GENERATOR_STATE_COMPLETED:
done:
/* execution is finished */
switch(magic) {
default:
case GEN_MAGIC_NEXT:
ret = JS_UNDEFINED;
break;
case GEN_MAGIC_RETURN:
ret = JS_DupValue(ctx, argv[0]);
break;
case GEN_MAGIC_THROW:
ret = JS_Throw(ctx, JS_DupValue(ctx, argv[0]));
break;
}
break;
case JS_GENERATOR_STATE_EXECUTING:
ret = JS_ThrowTypeError(ctx, "cannot invoke a running generator");
break;
}
return ret;
}
JSValue js_generator_function_call(JSContext *ctx, JSValueConst func_obj,
JSValueConst this_obj, int argc,
JSValueConst *argv, int flags)
{
JSValue obj, func_ret;
JSGeneratorData *s;
s = js_mallocz(ctx, sizeof(*s));
if (!s)
return JS_EXCEPTION;
s->state = JS_GENERATOR_STATE_SUSPENDED_START;
if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) {
s->state = JS_GENERATOR_STATE_COMPLETED;
goto fail;
}
/* execute the function up to 'OP_initial_yield' */
func_ret = async_func_resume(ctx, &s->func_state);
if (JS_IsException(func_ret))
goto fail;
JS_FreeValue(ctx, func_ret);
obj = js_create_from_ctor(ctx, func_obj, JS_CLASS_GENERATOR);
if (JS_IsException(obj))
goto fail;
JS_SetOpaque(obj, s);
return obj;
fail:
free_generator_stack_rt(ctx->rt, s);
js_free(ctx, s);
return JS_EXCEPTION;
}
static JSValue js_async_generator_resolve_function(JSContext *ctx,
JSValueConst this_obj,
int argc, JSValueConst *argv,
int magic, JSValue *func_data);
static int js_async_generator_resolve_function_create(JSContext *ctx,
JSValueConst generator,
JSValue *resolving_funcs,
BOOL is_resume_next)
{
int i;
JSValue func;
for(i = 0; i < 2; i++) {
func = JS_NewCFunctionData(ctx, js_async_generator_resolve_function, 1,
i + is_resume_next * 2, 1, &generator);
if (JS_IsException(func)) {
if (i == 1)
JS_FreeValue(ctx, resolving_funcs[0]);
return -1;
}
resolving_funcs[i] = func;
}
return 0;
}
static int js_async_generator_await(JSContext *ctx,
JSAsyncGeneratorData *s,
JSValueConst value)
{
JSValue promise, resolving_funcs[2], resolving_funcs1[2];
int i, res;
promise = js_promise_resolve(ctx, ctx->promise_ctor,
1, &value, 0);
if (JS_IsException(promise))
goto fail;
if (js_async_generator_resolve_function_create(ctx, JS_MKPTR(JS_TAG_OBJECT, s->generator),
resolving_funcs, FALSE)) {
JS_FreeValue(ctx, promise);
goto fail;
}
/* Note: no need to create 'thrownawayCapability' as in
the spec */
for(i = 0; i < 2; i++)
resolving_funcs1[i] = JS_UNDEFINED;
res = perform_promise_then(ctx, promise,
(JSValueConst *)resolving_funcs,
(JSValueConst *)resolving_funcs1);
JS_FreeValue(ctx, promise);
for(i = 0; i < 2; i++)
JS_FreeValue(ctx, resolving_funcs[i]);
if (res)
goto fail;
return 0;
fail:
return -1;
}
static void js_async_generator_resolve_or_reject(JSContext *ctx,
JSAsyncGeneratorData *s,
JSValueConst result,
int is_reject)
{
JSAsyncGeneratorRequest *next;
JSValue ret;
next = list_entry(s->queue.next, JSAsyncGeneratorRequest, link);
list_del(&next->link);
ret = JS_Call(ctx, next->resolving_funcs[is_reject], JS_UNDEFINED, 1,
&result);
JS_FreeValue(ctx, ret);
JS_FreeValue(ctx, next->result);
JS_FreeValue(ctx, next->promise);
JS_FreeValue(ctx, next->resolving_funcs[0]);
JS_FreeValue(ctx, next->resolving_funcs[1]);
js_free(ctx, next);
}
static void js_async_generator_resolve(JSContext *ctx,
JSAsyncGeneratorData *s,
JSValueConst value,
BOOL done)
{
JSValue result;
result = js_create_iterator_result(ctx, JS_DupValue(ctx, value), done);
/* XXX: better exception handling ? */
js_async_generator_resolve_or_reject(ctx, s, result, 0);
JS_FreeValue(ctx, result);
}
static void js_async_generator_reject(JSContext *ctx,
JSAsyncGeneratorData *s,
JSValueConst exception)
{
js_async_generator_resolve_or_reject(ctx, s, exception, 1);
}
static void js_async_generator_complete(JSContext *ctx,
JSAsyncGeneratorData *s)
{
if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED) {
s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED;
async_func_free(ctx->rt, &s->func_state);
}
}
static int js_async_generator_completed_return(JSContext *ctx,
JSAsyncGeneratorData *s,
JSValueConst value)
{
JSValue promise, resolving_funcs[2], resolving_funcs1[2];
int res;
promise = js_promise_resolve(ctx, ctx->promise_ctor,
1, (JSValueConst *)&value, 0);
if (JS_IsException(promise))
return -1;
if (js_async_generator_resolve_function_create(ctx,
JS_MKPTR(JS_TAG_OBJECT, s->generator),
resolving_funcs1,
TRUE)) {
JS_FreeValue(ctx, promise);
return -1;
}
resolving_funcs[0] = JS_UNDEFINED;
resolving_funcs[1] = JS_UNDEFINED;
res = perform_promise_then(ctx, promise,
(JSValueConst *)resolving_funcs1,
(JSValueConst *)resolving_funcs);
JS_FreeValue(ctx, resolving_funcs1[0]);
JS_FreeValue(ctx, resolving_funcs1[1]);
JS_FreeValue(ctx, promise);
return res;
}
void js_async_generator_resume_next(JSContext *ctx, JSAsyncGeneratorData *s)
{
JSAsyncGeneratorRequest *next;
JSValue func_ret, value;
for(;;) {
if (list_empty(&s->queue))
break;
next = list_entry(s->queue.next, JSAsyncGeneratorRequest, link);
switch(s->state) {
case JS_ASYNC_GENERATOR_STATE_EXECUTING:
/* only happens when restarting execution after await() */
goto resume_exec;
case JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN:
goto done;
case JS_ASYNC_GENERATOR_STATE_SUSPENDED_START:
if (next->completion_type == GEN_MAGIC_NEXT) {
goto exec_no_arg;
} else {
js_async_generator_complete(ctx, s);
}
break;
case JS_ASYNC_GENERATOR_STATE_COMPLETED:
if (next->completion_type == GEN_MAGIC_NEXT) {
js_async_generator_resolve(ctx, s, JS_UNDEFINED, TRUE);
} else if (next->completion_type == GEN_MAGIC_RETURN) {
s->state = JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN;
js_async_generator_completed_return(ctx, s, next->result);
goto done;
} else {
js_async_generator_reject(ctx, s, next->result);
}
goto done;
case JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD:
case JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR:
value = JS_DupValue(ctx, next->result);
if (next->completion_type == GEN_MAGIC_THROW &&
s->state == JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD) {
JS_Throw(ctx, value);
s->func_state.throw_flag = TRUE;
} else {
/* 'yield' returns a value. 'yield *' also returns a value
in case the 'throw' method is called */
s->func_state.frame.cur_sp[-1] = value;
s->func_state.frame.cur_sp[0] =
JS_NewInt32(ctx, next->completion_type);
s->func_state.frame.cur_sp++;
exec_no_arg:
s->func_state.throw_flag = FALSE;
}
s->state = JS_ASYNC_GENERATOR_STATE_EXECUTING;
resume_exec:
func_ret = async_func_resume(ctx, &s->func_state);
if (JS_IsException(func_ret)) {
value = JS_GetException(ctx);
js_async_generator_complete(ctx, s);
js_async_generator_reject(ctx, s, value);
JS_FreeValue(ctx, value);
} else if (JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT) {
int func_ret_code;
value = s->func_state.frame.cur_sp[-1];
s->func_state.frame.cur_sp[-1] = JS_UNDEFINED;
func_ret_code = JS_VALUE_GET_INT(func_ret);
switch(func_ret_code) {
case FUNC_RET_YIELD:
case FUNC_RET_YIELD_STAR:
if (func_ret_code == FUNC_RET_YIELD_STAR)
s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR;
else
s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD;
js_async_generator_resolve(ctx, s, value, FALSE);
JS_FreeValue(ctx, value);
break;
case FUNC_RET_AWAIT:
js_async_generator_await(ctx, s, value);
JS_FreeValue(ctx, value);
goto done;
default:
abort();
}
} else {
assert(JS_IsUndefined(func_ret));
/* end of function */
value = s->func_state.frame.cur_sp[-1];
s->func_state.frame.cur_sp[-1] = JS_UNDEFINED;
js_async_generator_complete(ctx, s);
js_async_generator_resolve(ctx, s, value, TRUE);
JS_FreeValue(ctx, value);
}
break;
default:
abort();
}
}
done: ;
}
static JSValue js_async_generator_resolve_function(JSContext *ctx,
JSValueConst this_obj,
int argc, JSValueConst *argv,
int magic, JSValue *func_data)
{
BOOL is_reject = magic & 1;
JSAsyncGeneratorData *s = JS_GetOpaque(func_data[0], JS_CLASS_ASYNC_GENERATOR);
JSValueConst arg = argv[0];
/* XXX: what if s == NULL */
if (magic >= 2) {
/* resume next case in AWAITING_RETURN state */
assert(s->state == JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN ||
s->state == JS_ASYNC_GENERATOR_STATE_COMPLETED);
s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED;
if (is_reject) {
js_async_generator_reject(ctx, s, arg);
} else {
js_async_generator_resolve(ctx, s, arg, TRUE);
}
} else {
/* restart function execution after await() */
assert(s->state == JS_ASYNC_GENERATOR_STATE_EXECUTING);
s->func_state.throw_flag = is_reject;
if (is_reject) {
JS_Throw(ctx, JS_DupValue(ctx, arg));
} else {
/* return value of await */
s->func_state.frame.cur_sp[-1] = JS_DupValue(ctx, arg);
}
js_async_generator_resume_next(ctx, s);
}
return JS_UNDEFINED;
}
void js_async_generator_free(JSRuntime *rt, JSAsyncGeneratorData *s)
{
struct list_head *el, *el1;
JSAsyncGeneratorRequest *req;
list_for_each_safe(el, el1, &s->queue) {
req = list_entry(el, JSAsyncGeneratorRequest, link);
JS_FreeValueRT(rt, req->result);
JS_FreeValueRT(rt, req->promise);
JS_FreeValueRT(rt, req->resolving_funcs[0]);
JS_FreeValueRT(rt, req->resolving_funcs[1]);
js_free_rt(rt, req);
}
if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED &&
s->state != JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN) {
async_func_free(rt, &s->func_state);
}
js_free_rt(rt, s);
}
static const JSCFunctionListEntry js_generator_function_proto_funcs[] = {
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "GeneratorFunction", JS_PROP_CONFIGURABLE),
};
static const JSCFunctionListEntry js_generator_proto_funcs[] = {
JS_ITERATOR_NEXT_DEF("next", 1, js_generator_next, GEN_MAGIC_NEXT ),
JS_ITERATOR_NEXT_DEF("return", 1, js_generator_next, GEN_MAGIC_RETURN ),
JS_ITERATOR_NEXT_DEF("throw", 1, js_generator_next, GEN_MAGIC_THROW ),
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Generator", JS_PROP_CONFIGURABLE),
};
void JS_AddIntrinsicGenerator(JSContext *ctx)
{
JSValue obj1;
ctx->class_proto[JS_CLASS_GENERATOR] = JS_NewObjectProto(ctx, ctx->iterator_proto);
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_GENERATOR],
js_generator_proto_funcs,
countof(js_generator_proto_funcs));
ctx->class_proto[JS_CLASS_GENERATOR_FUNCTION] = JS_NewObjectProto(ctx, ctx->function_proto);
obj1 = JS_NewCFunctionMagic(ctx, js_function_constructor,
"GeneratorFunction", 1,
JS_CFUNC_constructor_or_func_magic, JS_FUNC_GENERATOR);
JS_SetPropertyFunctionList(ctx,
ctx->class_proto[JS_CLASS_GENERATOR_FUNCTION],
js_generator_function_proto_funcs,
countof(js_generator_function_proto_funcs));
JS_SetConstructor2(ctx, ctx->class_proto[JS_CLASS_GENERATOR_FUNCTION],
ctx->class_proto[JS_CLASS_GENERATOR],
JS_PROP_CONFIGURABLE, JS_PROP_CONFIGURABLE);
JS_SetConstructor2(ctx, obj1, ctx->class_proto[JS_CLASS_GENERATOR_FUNCTION],
0, JS_PROP_CONFIGURABLE);
JS_FreeValue(ctx, obj1);
}