diff --git a/examples/results.txt b/examples/results.txt index b3e1f5f..44776b0 100644 --- a/examples/results.txt +++ b/examples/results.txt @@ -420,12 +420,12 @@ es5/strict.unmapped-arguments.js: OK es5/this.thrown-function.js: OK compat-table/es6/Array.Symbol.species.js: OK compat-table/es6/Array.from.array-like.js: OK -compat-table/es6/Array.from.generator.js: failed +compat-table/es6/Array.from.generator.js: OK compat-table/es6/Array.from.iterable-instance.js: OK compat-table/es6/Array.from.iterable.js: OK compat-table/es6/Array.from.iterator-closing.js: OK compat-table/es6/Array.from.map.array-like.js: OK -compat-table/es6/Array.from.map.generator.js: failed +compat-table/es6/Array.from.map.generator.js: OK compat-table/es6/Array.from.map.iterable-instance.js: OK compat-table/es6/Array.from.map.iterable.js: OK compat-table/es6/Array.iterator-prototype-chain.js: OK @@ -738,7 +738,7 @@ compat-table/es6/destructuring-assign.chained-object.js: OK compat-table/es6/destructuring-assign.computed-properties.js: OK compat-table/es6/destructuring-assign.defaults.js: OK compat-table/es6/destructuring-assign.empty-patterns.js: OK -compat-table/es6/destructuring-assign.generator.js: TypeError: not iterable +compat-table/es6/destructuring-assign.generator.js: OK compat-table/es6/destructuring-assign.iterable-expression.js: OK compat-table/es6/destructuring-assign.iterable-instance.js: OK compat-table/es6/destructuring-assign.iterable.js: OK @@ -763,7 +763,7 @@ compat-table/es6/destructuring-decl.defaults-tdz.js: OK compat-table/es6/destructuring-decl.defaults.js: OK compat-table/es6/destructuring-decl.for-in.js: OK compat-table/es6/destructuring-decl.for-of.js: OK -compat-table/es6/destructuring-decl.generator.js: TypeError: not iterable +compat-table/es6/destructuring-decl.generator.js: OK compat-table/es6/destructuring-decl.iterable-instance.js: OK compat-table/es6/destructuring-decl.iterable.js: OK compat-table/es6/destructuring-decl.iterator-closing.js: OK @@ -787,7 +787,7 @@ compat-table/es6/destructuring-params.defaults-separate-scope.js: OK compat-table/es6/destructuring-params.defaults.js: OK compat-table/es6/destructuring-params.duplicate-identifier.js: OK compat-table/es6/destructuring-params.empty-patterns.js: OK -compat-table/es6/destructuring-params.generator.js: TypeError: not iterable +compat-table/es6/destructuring-params.generator.js: OK compat-table/es6/destructuring-params.iterable-instance.js: OK compat-table/es6/destructuring-params.iterable.js: OK compat-table/es6/destructuring-params.iterator-closing.js: OK @@ -805,7 +805,7 @@ compat-table/es6/destructuring-params.trailing-comma-iterable.js: OK compat-table/es6/destructuring-params.trailing-comma-object.js: OK compat-table/es6/for-of.array.js: OK compat-table/es6/for-of.astral-string.js: OK -compat-table/es6/for-of.generator.js: TypeError: not iterable +compat-table/es6/for-of.generator.js: OK compat-table/es6/for-of.iterable-instance.js: OK compat-table/es6/for-of.iterable.js: OK compat-table/es6/for-of.iterator-closing-break.js: OK @@ -813,33 +813,33 @@ compat-table/es6/for-of.iterator-closing-throw.js: OK compat-table/es6/for-of.sparse-array.js: OK compat-table/es6/for-of.string.js: OK compat-table/es6/function.block-level-decl.js: OK -compat-table/es6/generators.basic.js: TypeError: Cannot read properties of undefined (reading 'next') -compat-table/es6/generators.constructor.js: TypeError: Cannot read properties of undefined (reading 'next') -compat-table/es6/generators.expression.js: TypeError: Cannot read properties of undefined (reading 'next') +compat-table/es6/generators.basic.js: OK +compat-table/es6/generators.constructor.js: OK +compat-table/es6/generators.expression.js: OK compat-table/es6/generators.no-new-this.js: OK -compat-table/es6/generators.prototype-chain.js: TypeError: Cannot read properties of null (reading 'hasOwnProperty') -compat-table/es6/generators.prototype.js: failed -compat-table/es6/generators.return.js: TypeError: Cannot read properties of undefined (reading 'next') -compat-table/es6/generators.sending.js: TypeError: Cannot read properties of undefined (reading 'next') -compat-table/es6/generators.shorthand.class.js: TypeError: Cannot read properties of undefined (reading 'next') -compat-table/es6/generators.shorthand.computed.class.js: TypeError: Cannot read properties of undefined (reading 'next') -compat-table/es6/generators.shorthand.computed.js: SyntaxError: Unexpected token '}' -compat-table/es6/generators.shorthand.js: SyntaxError: Unexpected token '}' -compat-table/es6/generators.shorthand.no-constructor.js: failed -compat-table/es6/generators.shorthand.string-keyed.js: SyntaxError: Unexpected token '}' -compat-table/es6/generators.this.js: TypeError: Cannot read properties of undefined (reading 'next') -compat-table/es6/generators.throw.js: TypeError: Cannot read properties of undefined (reading 'next') -compat-table/es6/generators.yield-precedence.js: TypeError: Cannot read properties of undefined (reading 'next') -compat-table/es6/generators.yield-star.array.js: TypeError: Cannot read properties of undefined (reading 'next') -compat-table/es6/generators.yield-star.astral-string.js: TypeError: Cannot read properties of undefined (reading 'next') -compat-table/es6/generators.yield-star.generator.js: TypeError: Cannot read properties of undefined (reading 'next') -compat-table/es6/generators.yield-star.iterable-instance.js: TypeError: Cannot read properties of undefined (reading 'next') -compat-table/es6/generators.yield-star.iterable.js: TypeError: Cannot read properties of undefined (reading 'next') -compat-table/es6/generators.yield-star.iterator-closing-throw.js: TypeError: Cannot read properties of undefined (reading 'next') -compat-table/es6/generators.yield-star.iterator-closing.js: TypeError: Cannot read properties of undefined (reading 'next') -compat-table/es6/generators.yield-star.non-iterable-error.js: TypeError: Cannot read properties of undefined (reading 'next') -compat-table/es6/generators.yield-star.sparse-array.js: TypeError: Cannot read properties of undefined (reading 'next') -compat-table/es6/generators.yield-star.string.js: TypeError: Cannot read properties of undefined (reading 'next') +compat-table/es6/generators.prototype-chain.js: OK +compat-table/es6/generators.prototype.js: OK +compat-table/es6/generators.return.js: OK +compat-table/es6/generators.sending.js: OK +compat-table/es6/generators.shorthand.class.js: OK +compat-table/es6/generators.shorthand.computed.class.js: OK +compat-table/es6/generators.shorthand.computed.js: OK +compat-table/es6/generators.shorthand.js: OK +compat-table/es6/generators.shorthand.no-constructor.js: OK +compat-table/es6/generators.shorthand.string-keyed.js: OK +compat-table/es6/generators.this.js: OK +compat-table/es6/generators.throw.js: OK +compat-table/es6/generators.yield-precedence.js: OK +compat-table/es6/generators.yield-star.array.js: OK +compat-table/es6/generators.yield-star.astral-string.js: OK +compat-table/es6/generators.yield-star.generator.js: OK +compat-table/es6/generators.yield-star.iterable-instance.js: OK +compat-table/es6/generators.yield-star.iterable.js: OK +compat-table/es6/generators.yield-star.iterator-closing-throw.js: OK +compat-table/es6/generators.yield-star.iterator-closing.js: OK +compat-table/es6/generators.yield-star.non-iterable-error.js: OK +compat-table/es6/generators.yield-star.sparse-array.js: OK +compat-table/es6/generators.yield-star.string.js: OK compat-table/es6/let.basic.js: OK compat-table/es6/let.block-scoped.js: OK compat-table/es6/let.for-in-shadow.js: OK @@ -941,11 +941,11 @@ compat-table/es6/misc.Proxy.set.Array.unshift.js: OK compat-table/es6/misc.Proxy.set.Object.assign.js: OK compat-table/es6/misc.RegExp-alter-flags.js: OK compat-table/es6/misc.RegExp-prototype-toString-generic.js: OK -compat-table/es6/misc.accessors-no-constructor.js: failed -compat-table/es6/misc.bound-function-prototype.arrow.js: failed -compat-table/es6/misc.bound-function-prototype.class.js: failed -compat-table/es6/misc.bound-function-prototype.function.js: failed -compat-table/es6/misc.bound-function-prototype.generator.js: failed +compat-table/es6/misc.accessors-no-constructor.js: OK +compat-table/es6/misc.bound-function-prototype.arrow.js: OK +compat-table/es6/misc.bound-function-prototype.class.js: OK +compat-table/es6/misc.bound-function-prototype.function.js: OK +compat-table/es6/misc.bound-function-prototype.generator.js: OK compat-table/es6/misc.bound-function-prototype.subclass.js: OK compat-table/es6/misc.do-while-no-semicolon.js: OK compat-table/es6/misc.duplicate-properties-strict.js: OK @@ -979,8 +979,8 @@ compat-table/es6/spread.array-call.js: OK compat-table/es6/spread.array-literal.js: OK compat-table/es6/spread.astral-call.js: OK compat-table/es6/spread.astral-literal.js: OK -compat-table/es6/spread.generator-call.js: TypeError: not iterable -compat-table/es6/spread.generator-literal.js: TypeError: not iterable +compat-table/es6/spread.generator-call.js: OK +compat-table/es6/spread.generator-literal.js: OK compat-table/es6/spread.iterable-call.js: OK compat-table/es6/spread.iterable-instance-call.js: OK compat-table/es6/spread.iterable-instance-literal.js: OK @@ -1125,8 +1125,8 @@ compat-table/es2016/exponentiation.basic.js: OK compat-table/es2016/exponentiation.unary-negation-error.js: OK compat-table/es2016/misc.Proxy-Array-includes.js: failed compat-table/es2016/misc.Proxy-enumerate-removed.js: OK -compat-table/es2016/misc.generator-no-new.js: failed -compat-table/es2016/misc.generator-throw-inner.js: TypeError: Cannot read properties of undefined (reading 'next') +compat-table/es2016/misc.generator-no-new.js: OK +compat-table/es2016/misc.generator-throw-inner.js: OK compat-table/es2016/misc.nested-rest-destructuring-decl.js: OK compat-table/es2016/misc.nested-rest-destructuring-params.js: OK compat-table/es2016/misc.strict-fn-non-simple-params-error.js: failed @@ -1198,7 +1198,7 @@ compat-table/es2018/Promise.prototype.finally.change-rejection.js: OK compat-table/es2018/Promise.prototype.finally.js: OK compat-table/es2018/Promise.prototype.finally.no-change-resolution.js: OK compat-table/es2018/async-iterators.for-await-of.js: OK -compat-table/es2018/async-iterators.generators.js: TypeError: undefined is not a function +compat-table/es2018/async-iterators.generators.js: OK compat-table/es2018/misc.Proxy-ownKeys-duplicate-keys.js: OK compat-table/es2018/misc.template-literal-revision.js: OK compat-table/es2018/object-rest.js: OK @@ -1239,7 +1239,7 @@ compat-table/es2019/misc.JSON-superset.line-separator.js: OK compat-table/es2019/misc.JSON-superset.paragraph-separator.js: OK compat-table/es2019/misc.optional-catch-binding.await.js: OK compat-table/es2019/misc.optional-catch-binding.js: OK -compat-table/es2019/misc.optional-catch-binding.yield.js: TypeError: Cannot read properties of undefined (reading 'next') +compat-table/es2019/misc.optional-catch-binding.yield.js: OK compat-table/es2020/BigInt64Array.js: failed compat-table/es2020/BigInt.asIntN.js: OK compat-table/es2020/BigInt.asUintN.js: OK diff --git a/examples/spec/generators.js b/examples/spec/generators.js new file mode 100644 index 0000000..225ca05 --- /dev/null +++ b/examples/spec/generators.js @@ -0,0 +1,387 @@ +import { test, testDeep, summary } from './helpers.js'; + +console.log('Generator Tests\n'); + +function* simple() { + yield 1; + yield 2; + yield 3; +} + +const gen = simple(); +test('generator object typeof', typeof gen, 'object'); +test('generator is iterable', gen[Symbol.iterator](), gen); + +try { + new simple(); + test('new generator function throws', false, true); +} catch { + test('new generator function throws', true, true); +} + +test('first yield', gen.next().value, 1); +test('second yield', gen.next().value, 2); +test('third yield', gen.next().value, 3); +test('done', gen.next().done, true); + +function* withReturn() { + yield 1; + return 'final'; +} + +const gen2 = withReturn(); +test('yield before return', gen2.next().value, 1); +const last = gen2.next(); +test('return value', last.value, 'final'); +test('return done', last.done, true); + +function* counter(start) { + let i = start; + while (true) yield i++; +} + +const c = counter(10); +test('counter 10', c.next().value, 10); +test('counter 11', c.next().value, 11); +test('counter 12', c.next().value, 12); + +function* range(start, end) { + for (let i = start; i <= end; i++) yield i; +} + +const arr = [...range(1, 5)]; +testDeep('spread generator', arr, [1, 2, 3, 4, 5]); + +let forOfSum = 0; +for (const n of range(1, 4)) { + forOfSum += n; +} +test('for-of generator', forOfSum, 10); + +function* twoWay() { + const x = yield 1; + const y = yield x + 1; + yield y + 1; +} + +const tw = twoWay(); +test('two way first', tw.next().value, 1); +test('two way second', tw.next(10).value, 11); +test('two way third', tw.next(20).value, 21); + +function runDynamicBool(source) { + try { + return Function(source)(); + } catch { + return false; + } +} + +function* yieldStarArray() { + yield* [4, 5, 6]; +} + +testDeep('yield* array', [...yieldStarArray()], [4, 5, 6]); + +function* yieldStarString() { + yield* 'a\uD83D\uDCABb'; +} + +testDeep('yield* string keeps code points', [...yieldStarString()], ['a', '\uD83D\uDCAB', 'b']); + +function* yieldStarSparseArray() { + yield* [7, , 9]; +} + +const sparse = yieldStarSparseArray(); +test('yield* sparse array first', sparse.next().value, 7); +test('yield* sparse array hole value', sparse.next().value, undefined); +test('yield* sparse array third', sparse.next().value, 9); +test('yield* sparse array done', sparse.next().done, true); + +function makeIterable(values, methods = {}) { + values = values.slice(); + values.push(undefined); + const iterator = { + next(value) { + if (methods.next) return methods.next.call(this, value); + return { value: values.shift(), done: values.length === 0 }; + }, + return: methods.return, + throw: methods.throw + }; + return { + [Symbol.iterator]() { + return iterator; + } + }; +} + +function* yieldStarIterable() { + yield* makeIterable([10, 11, 12]); +} + +testDeep('yield* generic iterable', [...yieldStarIterable()], [10, 11, 12]); + +function* yieldStarIterableInstance() { + yield* Object.create(makeIterable([13, 14])); +} + +testDeep('yield* iterable through prototype', [...yieldStarIterableInstance()], [13, 14]); + +function* innerGenerator() { + yield 15; + yield 16; + return 17; +} + +function* yieldStarGenerator() { + return yield* innerGenerator(); +} + +const ysg = yieldStarGenerator(); +test('yield* generator first', ysg.next().value, 15); +test('yield* generator second', ysg.next().value, 16); +const ysgDone = ysg.next(); +test('yield* generator return value', ysgDone.value, 17); +test('yield* generator return done', ysgDone.done, true); + +function* yieldStarReceivesSentValues() { + return yield* (function* () { + const received = yield 'ready'; + return received + 1; + })(); +} + +const yss = yieldStarReceivesSentValues(); +test('yield* sent first', yss.next().value, 'ready'); +const yssDone = yss.next(40); +test('yield* sent return value', yssDone.value, 41); +test('yield* sent return done', yssDone.done, true); + +let yieldStarThrowCaught = false; +function* yieldStarThrowForwarding() { + yield* (function* () { + try { + yield 'before throw'; + } catch (e) { + yieldStarThrowCaught = e === 'boom'; + return 'caught'; + } + })(); + yield 'after throw'; +} + +const yst = yieldStarThrowForwarding(); +test('yield* throw forwarding first', yst.next().value, 'before throw'); +test('yield* throw forwarding continues outer', yst.throw('boom').value, 'after throw'); +test('yield* throw forwarding caught by delegate', yieldStarThrowCaught, true); + +let yieldStarClosed = ''; +const closingIter = makeIterable([1, 2, 3], { + return() { + yieldStarClosed += 'a'; + return { done: true }; + } +}); + +function* yieldStarReturnClosing() { + try { + yield* closingIter; + } finally { + yieldStarClosed += 'b'; + } +} + +const ysrc = yieldStarReturnClosing(); +test('yield* return closing first', ysrc.next().value, 1); +ysrc.return('closed'); +test('yield* return calls delegate and finally', yieldStarClosed, 'ab'); + +let yieldStarThrowClosed = false; +const noThrowIter = makeIterable([1, 2], { + throw: undefined, + return() { + yieldStarThrowClosed = true; + return { done: true }; + } +}); + +function* yieldStarThrowClosing() { + try { + yield* noThrowIter; + } catch {} +} + +const ystc = yieldStarThrowClosing(); +test('yield* throw closing first', ystc.next().value, 1); +ystc.throw('close please'); +test('yield* throw without delegate throw closes iterator', yieldStarThrowClosed, true); + +try { + (function* () { + yield* 123; + })().next(); + test('yield* non-iterable throws', false, true); +} catch { + test('yield* non-iterable throws', true, true); +} + +function* catchesThrow() { + try { + yield 'waiting'; + } catch (e) { + return e; + } +} + +const ct = catchesThrow(); +test('generator throw setup', ct.next().value, 'waiting'); +const ctDone = ct.throw('thrown value'); +test('generator throw caught value', ctDone.value, 'thrown value'); +test('generator throw caught done', ctDone.done, true); + +let finallyRan = false; +function* returnRunsFinally() { + try { + yield 'open'; + } finally { + finallyRan = true; + } +} + +const rrf = returnRunsFinally(); +test('generator return setup', rrf.next().value, 'open'); +const rrfDone = rrf.return('done now'); +test('generator return result value', rrfDone.value, 'done now'); +test('generator return result done', rrfDone.done, true); +test('generator return runs finally', finallyRan, true); + +function* protoShapeFn() {} +const protoShapeGen = protoShapeFn(); +const ownGeneratorProto = Object.getPrototypeOf(protoShapeGen); +const sharedGeneratorProto = Object.getPrototypeOf(ownGeneratorProto); +const iteratorProto = Object.getPrototypeOf(sharedGeneratorProto); +test('generator instance uses function prototype', ownGeneratorProto, protoShapeFn.prototype); +test('generator shared prototype has next', sharedGeneratorProto.hasOwnProperty('next'), true); +test('generator Symbol.iterator inherited from iterator prototype', iteratorProto.hasOwnProperty(Symbol.iterator), true); + +test( + 'shorthand object generator method', + runDynamicBool(` + const o = { + *generator() { + yield 5; + yield 6; + } + }; + const it = o.generator(); + return it.next().value === 5 && + it.next().value === 6 && + it.next().done === true; +`), + true +); + +test( + 'shorthand computed generator method', + runDynamicBool(` + const name = 'generator'; + const o = { + *[name]() { + yield 7; + yield 8; + } + }; + const it = o.generator(); + return it.next().value === 7 && + it.next().value === 8 && + it.next().done === true; +`), + true +); + +test( + 'shorthand string-keyed generator method', + runDynamicBool(` + const o = { + *"foo bar"() { + yield 9; + yield 10; + } + }; + const it = o['foo bar'](); + return it.next().value === 9 && + it.next().value === 10 && + it.next().done === true; +`), + true +); + +test( + 'class generator method', + runDynamicBool(` + class C { + *generator() { + yield 11; + yield 12; + } + } + const it = new C().generator(); + return it.next().value === 11 && + it.next().value === 12 && + it.next().done === true; +`), + true +); + +test( + 'class computed generator method', + runDynamicBool(` + const name = 'generator'; + class C { + *[name]() { + yield 13; + yield 14; + } + } + const it = new C().generator(); + return it.next().value === 13 && + it.next().value === 14 && + it.next().done === true; +`), + true +); + +test( + 'generator method cannot be constructor', + runDynamicBool(` + const o = { + *generator() { + yield 1; + } + }; + try { + new o.generator(); + return false; + } catch { + return true; + } +`), + true +); + +test( + 'class generator constructor syntax rejected', + runDynamicBool(` + try { + Function('class Bad { *constructor() { yield 1; } }'); + return false; + } catch { + return true; + } +`), + true +); + +summary(); diff --git a/examples/spec/generators.js.disabled b/examples/spec/generators.js.disabled deleted file mode 100644 index 3babc8f..0000000 --- a/examples/spec/generators.js.disabled +++ /dev/null @@ -1,60 +0,0 @@ -import { test, testDeep, summary } from './helpers.js'; - -console.log('Generator Tests\n'); - -function* simple() { - yield 1; - yield 2; - yield 3; -} - -const gen = simple(); -test('first yield', gen.next().value, 1); -test('second yield', gen.next().value, 2); -test('third yield', gen.next().value, 3); -test('done', gen.next().done, true); - -function* withReturn() { - yield 1; - return 'final'; -} - -const gen2 = withReturn(); -test('yield before return', gen2.next().value, 1); -const last = gen2.next(); -test('return value', last.value, 'final'); -test('return done', last.done, true); - -function* counter(start) { - let i = start; - while (true) { - yield i++; - } -} - -const c = counter(10); -test('counter 10', c.next().value, 10); -test('counter 11', c.next().value, 11); -test('counter 12', c.next().value, 12); - -function* range(start, end) { - for (let i = start; i <= end; i++) { - yield i; - } -} - -const arr = [...range(1, 5)]; -testDeep('spread generator', arr, [1, 2, 3, 4, 5]); - -function* twoWay() { - const x = yield 1; - const y = yield x + 1; - yield y + 1; -} - -const tw = twoWay(); -test('two way first', tw.next().value, 1); -test('two way second', tw.next(10).value, 11); -test('two way third', tw.next(20).value, 21); - -summary(); diff --git a/include/ant.h b/include/ant.h index 9d96f43..9a8c97d 100644 --- a/include/ant.h +++ b/include/ant.h @@ -66,6 +66,7 @@ ant_value_t js_mkundef(void); ant_value_t js_mknull(void); ant_value_t js_mknum(double); ant_value_t js_mkpromise(ant_t *js); +ant_value_t js_mkgenerator(ant_t *js); ant_value_t js_getthis(ant_t *); void js_setthis(ant_t *, ant_value_t); diff --git a/include/common.h b/include/common.h index a5a3271..1b7310d 100644 --- a/include/common.h +++ b/include/common.h @@ -15,6 +15,7 @@ X(SLOT_PROTO) \ X(SLOT_FUNC_PROTO) \ X(SLOT_ASYNC_PROTO) \ + X(SLOT_GENERATOR_PROTO) \ X(SLOT_AUX) \ X(SLOT_TARGET_FUNC) \ X(SLOT_MODULE_CTX) \ @@ -73,7 +74,8 @@ X(SLOT_PIPE_ABORT_LISTENER) \ X(SLOT_MATCHALL_RX) \ X(SLOT_MATCHALL_STR) \ - X(SLOT_MATCHALL_DONE) + X(SLOT_MATCHALL_DONE) \ + X(SLOT_GENERATOR_STATE) #define ANT_DECLARE_INTERNAL_SLOT(name) name, typedef enum { diff --git a/include/gc.h b/include/gc.h index 9559b39..19c4cab 100644 --- a/include/gc.h +++ b/include/gc.h @@ -12,10 +12,10 @@ #define GC_HEAP_GROWTH(n) ((n) + (n) / 2) -#define GC_OBJ_TYPE_MASK (JS_TYPE_FLAG(T_OBJ) \ - | JS_TYPE_FLAG(T_ARR) \ - | JS_TYPE_FLAG(T_PROMISE) \ - | JS_TYPE_FLAG(T_GENERATOR)) +#define GC_OBJ_TYPE_MASK (JS_TPFLG(T_OBJ) \ + | JS_TPFLG(T_ARR) \ + | JS_TPFLG(T_PROMISE) \ + | JS_TPFLG(T_GENERATOR)) typedef struct gc_func_mark_profile { bool enabled; diff --git a/include/internal.h b/include/internal.h index 6465a6e..cf5890c 100644 --- a/include/internal.h +++ b/include/internal.h @@ -59,8 +59,8 @@ #define NANBOX_PREFIX 0xFFF0000000000000ULL #define NANBOX_DATA_MASK 0x00007FFFFFFFFFFFULL -#define JS_ERR_NO_STACK (1 << 8) -#define JS_TYPE_FLAG(t) (1u << (t)) +#define JS_ERR_NO_STACK (1 << 8) +#define JS_TPFLG(t) (1u << (t)) #define MAX_STRINGIFY_DEPTH 64 #define MAX_PROTO_CHAIN_DEPTH 256 @@ -74,10 +74,10 @@ #define ROPE_FLATTEN_THRESHOLD (32 * 1024) #define T_EMPTY (NANBOX_PREFIX | ((ant_value_t)T_SENTINEL << NANBOX_TYPE_SHIFT) | 0xDEADULL) -#define T_SPECIAL_OBJECT_MASK (JS_TYPE_FLAG(T_OBJ) | JS_TYPE_FLAG(T_ARR)) -#define T_NEEDS_PROTO_FALLBACK (JS_TYPE_FLAG(T_FUNC) | JS_TYPE_FLAG(T_ARR) | JS_TYPE_FLAG(T_PROMISE)) -#define T_OBJECT_MASK (JS_TYPE_FLAG(T_OBJ) | JS_TYPE_FLAG(T_ARR) | JS_TYPE_FLAG(T_FUNC) | JS_TYPE_FLAG(T_PROMISE)) -#define T_NON_NUMERIC_MASK (JS_TYPE_FLAG(T_STR) | JS_TYPE_FLAG(T_ARR) | JS_TYPE_FLAG(T_FUNC) | JS_TYPE_FLAG(T_CFUNC) | JS_TYPE_FLAG(T_OBJ)) +#define T_SPECIAL_OBJECT_MASK (JS_TPFLG(T_OBJ) | JS_TPFLG(T_ARR)) +#define T_NEEDS_PROTO_FALLBACK (JS_TPFLG(T_FUNC) | JS_TPFLG(T_ARR) | JS_TPFLG(T_PROMISE) | JS_TPFLG(T_GENERATOR)) +#define T_OBJECT_MASK (JS_TPFLG(T_OBJ) | JS_TPFLG(T_ARR) | JS_TPFLG(T_FUNC) | JS_TPFLG(T_PROMISE) | JS_TPFLG(T_GENERATOR)) +#define T_NON_NUMERIC_MASK (JS_TPFLG(T_STR) | JS_TPFLG(T_ARR) | JS_TPFLG(T_FUNC) | JS_TPFLG(T_CFUNC) | JS_TPFLG(T_OBJ) | JS_TPFLG(T_GENERATOR)) #define is_non_numeric(v) ((1u << vtype(v)) & T_NON_NUMERIC_MASK) #define is_object_type(v) ((1u << vtype(v)) & T_OBJECT_MASK) @@ -194,6 +194,7 @@ struct ant_isolate_t { ant_value_t iterator_proto; ant_value_t array_iterator_proto; ant_value_t string_iterator_proto; + ant_value_t generator_proto; } sym; ant_offset_t max_size; diff --git a/include/modules/generator.h b/include/modules/generator.h new file mode 100644 index 0000000..8a85887 --- /dev/null +++ b/include/modules/generator.h @@ -0,0 +1,6 @@ +#ifndef GENERATOR_H +#define GENERATOR_H + +void init_generator_module(void); + +#endif diff --git a/include/silver/compiler.h b/include/silver/compiler.h index 906f049..19ca216 100644 --- a/include/silver/compiler.h +++ b/include/silver/compiler.h @@ -27,7 +27,7 @@ sv_func_t *sv_compile( sv_func_t *sv_compile_function( ant_t *js, const char *source, - size_t len, bool is_async + size_t len, bool is_async, bool is_generator ); sv_func_t *sv_compile_function_with_params( diff --git a/include/silver/engine.h b/include/silver/engine.h index 2928c6b..0355d43 100644 --- a/include/silver/engine.h +++ b/include/silver/engine.h @@ -202,6 +202,12 @@ typedef struct { ant_value_t value; } sv_completion_t; +typedef enum { + SV_RESUME_NEXT = 0, + SV_RESUME_THROW = 1, + SV_RESUME_RETURN = 2, +} sv_resume_kind_t; + typedef struct sv_upvalue sv_upvalue_t; @@ -334,6 +340,7 @@ struct sv_vm { bool suspended; bool suspended_resume_pending; bool suspended_resume_is_error; + sv_resume_kind_t suspended_resume_kind; int suspended_entry_fp; int suspended_saved_fp; @@ -753,6 +760,12 @@ ant_value_t sv_call_async_closure_dispatch( ant_value_t this_val, ant_value_t *args, int argc ); +ant_value_t sv_call_generator_closure_dispatch( + sv_vm_t *vm, ant_t *js, sv_closure_t *closure, + ant_value_t callee_func, ant_value_t super_val, + ant_value_t this_val, ant_value_t *args, int argc +); + static inline ant_value_t sv_call_async_closure( sv_vm_t *vm, ant_t *js, sv_closure_t *closure, ant_value_t callee_func, sv_call_ctx_t *ctx @@ -765,6 +778,18 @@ static inline ant_value_t sv_call_async_closure( return result; } +static inline ant_value_t sv_call_generator_closure( + sv_vm_t *vm, ant_t *js, sv_closure_t *closure, + ant_value_t callee_func, sv_call_ctx_t *ctx +) { + ant_value_t result = sv_call_generator_closure_dispatch( + vm, js, closure, callee_func, + ctx->super_val, ctx->this_val, ctx->args, ctx->argc + ); + sv_call_cleanup(js, ctx); + return result; +} + static inline ant_value_t sv_call_closure( sv_vm_t *vm, ant_t *js, sv_closure_t *closure, ant_value_t callee_func, sv_call_ctx_t *ctx, ant_value_t *out_this @@ -1009,6 +1034,8 @@ static inline ant_value_t sv_call_resolve_closure( sv_vm_t *vm, ant_t *js, sv_closure_t *closure, ant_value_t callee_func, sv_call_ctx_t *ctx, ant_value_t *out_this ) { + if (closure->func->is_generator) + return sv_call_generator_closure(vm, js, closure, callee_func, ctx); if (closure->func->is_async) return sv_call_async_closure(vm, js, closure, callee_func, ctx); #ifdef ANT_JIT diff --git a/include/silver/opcode.h b/include/silver/opcode.h index d6d9d57..17fc763 100644 --- a/include/silver/opcode.h +++ b/include/silver/opcode.h @@ -195,8 +195,10 @@ OP_DEF( DESTRUCTURE_CLOSE, 1, 3, 0, none) /* close destructuring itera OP_DEF( AWAIT, 1, 1, 1, none) /* promise -> resolved value */ OP_DEF( YIELD, 1, 1, 2, none) /* val -> received */ -OP_DEF( YIELD_STAR, 1, 1, 2, none) /* delegate to sub-iterator */ -OP_DEF( INITIAL_YIELD, 1, 0, 0, none) /* generator entry point */ +OP_DEF( YIELD_STAR_INIT, 3, 1, 0, loc) /* iterable -> delegate locals */ +OP_DEF( YIELD_STAR_NEXT, 3, 1, 2, loc) /* sent -> final value | suspend */ +OP_DEF( YIELD_STAR_THROW, 3, 1, 2, loc) /* thrown -> final value | suspend */ +OP_DEF( YIELD_STAR_RETURN, 3, 1, 2, loc) /* return value -> final value | suspend */ OP_DEF( SPREAD, 1, 1, 0, none) /* arr iterable -> arr */ OP_DEF( DEFINE_METHOD, 6, 2, 1, atom_u8) /* obj func -> obj (flags: get/set/static) */ diff --git a/meson/ant.version b/meson/ant.version index 899f24f..f514a2f 100644 --- a/meson/ant.version +++ b/meson/ant.version @@ -1 +1 @@ -0.9.0 \ No newline at end of file +0.9.1 \ No newline at end of file diff --git a/src/ant.c b/src/ant.c index f98f442..85c43bb 100644 --- a/src/ant.c +++ b/src/ant.c @@ -456,7 +456,7 @@ const char *typestr(uint8_t t) { [T_NUM] = "number", [T_BIGINT] = "bigint", [T_STR] = "string", [T_SYMBOL] = "symbol", [T_OBJ] = "object", [T_ARR] = "object", [T_FUNC] = "function", [T_CFUNC] = "function", [T_CLOSURE] = "closure", - [T_PROMISE] = "object", [T_GENERATOR] = "generator", + [T_PROMISE] = "object", [T_GENERATOR] = "object", [T_ERR] = "err", [T_TYPEDARRAY] = "typedarray", [T_FFI] = "ffi", [T_NTARG] = "ntarg" }; @@ -641,16 +641,17 @@ static ant_value_t to_string_val(ant_t *js, ant_value_t val) { bool js_truthy(ant_t *js, ant_value_t v) { static const void *dispatch[] = { - [T_OBJ] = &&l_true, - [T_FUNC] = &&l_true, - [T_CFUNC] = &&l_true, - [T_ARR] = &&l_true, - [T_PROMISE] = &&l_true, - [T_SYMBOL] = &&l_true, - [T_BOOL] = &&l_bool, - [T_STR] = &&l_str, - [T_BIGINT] = &&l_bigint, - [T_NUM] = &&l_num, + [T_OBJ] = &&l_true, + [T_FUNC] = &&l_true, + [T_CFUNC] = &&l_true, + [T_ARR] = &&l_true, + [T_PROMISE] = &&l_true, + [T_GENERATOR] = &&l_true, + [T_SYMBOL] = &&l_true, + [T_BOOL] = &&l_bool, + [T_STR] = &&l_str, + [T_BIGINT] = &&l_bigint, + [T_NUM] = &&l_num, }; uint8_t t = vtype(v); @@ -2731,7 +2732,7 @@ static inline bool proto_walk_next(ant_t *js, ant_value_t *cur, uint8_t *t, uint return true; } - if (ct == T_OBJ || ct == T_ARR || ct == T_FUNC || ct == T_PROMISE) { + if (JS_TPFLG(ct) & T_OBJECT_MASK) { ant_value_t as_obj = js_as_obj(*cur); ant_value_t proto = get_slot(as_obj, SLOT_PROTO); @@ -2742,7 +2743,7 @@ static inline bool proto_walk_next(ant_t *js, ant_value_t *cur, uint8_t *t, uint return true; } - if (JS_TYPE_FLAG(ct) & T_NEEDS_PROTO_FALLBACK) { + if (JS_TPFLG(ct) & T_NEEDS_PROTO_FALLBACK) { ant_value_t fallback = get_prototype_for_type(js, ct); uint8_t ft = vtype(fallback); if (ft == T_NULL || ft == T_UNDEF) return false; @@ -3790,7 +3791,7 @@ ant_value_t js_get_proto(ant_t *js, ant_value_t obj) { ant_object_t *ptr = js_obj_ptr(as_obj); ant_value_t proto = ptr ? ptr->proto : js_mkundef(); - if (is_object_type(proto)) return proto; + if (is_object_type(proto) || vtype(proto) == T_NULL) return proto; if (t != T_OBJ) return get_prototype_for_type(js, t); return js_mknull(); @@ -3850,6 +3851,7 @@ switch (type) { case T_BOOL: return get_ctor_proto(js, "Boolean", 7); case T_FUNC: return get_ctor_proto(js, "Function", 8); case T_PROMISE: return get_ctor_proto(js, "Promise", 7); + case T_GENERATOR: return js->sym.generator_proto; case T_BIGINT: return get_ctor_proto(js, "BigInt", 6); case T_SYMBOL: return get_ctor_proto(js, "Symbol", 6); default: return js_mknull(); @@ -3865,7 +3867,7 @@ ant_offset_t lkp_proto(ant_t *js, ant_value_t obj, const char *key, size_t len) proto_walk_overflow_guard_init(&guard); while (true) { - if (t == T_OBJ || t == T_ARR || t == T_FUNC || t == T_PROMISE) { + if (t == T_OBJ || t == T_ARR || t == T_FUNC || t == T_PROMISE || t == T_GENERATOR) { ant_value_t as_obj = js_as_obj(cur); ant_offset_t off = lkp_interned(js, as_obj, key_intern, len); if (off != 0) return off; @@ -4624,7 +4626,7 @@ static ant_value_t builtin_Object(ant_t *js, ant_value_t *args, int nargs) { static ant_value_t builtin_function_empty(ant_t *, ant_value_t *, int); -static ant_value_t build_dynamic_function(ant_t *js, ant_value_t *args, int nargs, bool is_async) { +static ant_value_t build_dynamic_function(ant_t *js, ant_value_t *args, int nargs, bool is_async, bool is_generator) { if (nargs == 0) { ant_value_t func_obj = mkobj(js, 0); if (is_err(func_obj)) return func_obj; @@ -4634,6 +4636,9 @@ static ant_value_t build_dynamic_function(ant_t *js, ant_value_t *args, int narg set_slot(func_obj, SLOT_ASYNC, js_true); ant_value_t async_proto = get_slot(js_glob(js), SLOT_ASYNC_PROTO); if (vtype(async_proto) == T_FUNC) js_set_proto_init(func_obj, async_proto); + } else if (is_generator) { + ant_value_t generator_proto = get_slot(js_glob(js), SLOT_GENERATOR_PROTO); + if (vtype(generator_proto) == T_FUNC) js_set_proto_init(func_obj, generator_proto); } else { ant_value_t func_proto = get_slot(js_glob(js), SLOT_FUNC_PROTO); ant_value_t instance_proto = js_instance_proto_from_new_target(js, func_proto); @@ -4685,7 +4690,7 @@ static ant_value_t build_dynamic_function(ant_t *js, ant_value_t *args, int narg ant_value_t func_obj = mkobj(js, 0); if (is_err(func_obj)) { free(code_buf); return func_obj; } - sv_func_t *compiled = sv_compile_function(js, code_buf, pos, is_async); + sv_func_t *compiled = sv_compile_function(js, code_buf, pos, is_async, is_generator); if (!compiled) { free(code_buf); return js_mkerr_typed(js, JS_ERR_SYNTAX, "invalid function body"); @@ -4700,14 +4705,18 @@ static ant_value_t build_dynamic_function(ant_t *js, ant_value_t *args, int narg size_t params_len = (size_t)(pos - 2) - (size_t)body_len - 2; const char *async_prefix = is_async ? "async " : ""; + const char *generator_marker = is_generator ? "*" : ""; size_t async_len = is_async ? 6 : 0; - size_t display_len = async_len + 19 + params_len + 5 + (size_t)body_len + 2; + size_t generator_len = is_generator ? 1 : 0; + size_t display_len = async_len + 19 + generator_len + params_len + 5 + (size_t)body_len + 2; char *display = (char *)malloc(display_len + 1); if (!display) { free(code_buf); return js_mkerr(js, "oom"); } size_t n = 0; memcpy(display + n, async_prefix, async_len); n += async_len; - memcpy(display + n, "function anonymous(", 19); n += 19; + memcpy(display + n, "function", 8); n += 8; + memcpy(display + n, generator_marker, generator_len); n += generator_len; + memcpy(display + n, " anonymous(", 11); n += 11; memcpy(display + n, code_buf + 1, params_len); n += params_len; memcpy(display + n, "\n) {\n", 5); n += 5; memcpy(display + n, code_buf + 1 + params_len + 2, (size_t)body_len); n += (size_t)body_len; @@ -4721,6 +4730,9 @@ static ant_value_t build_dynamic_function(ant_t *js, ant_value_t *args, int narg set_slot(func_obj, SLOT_ASYNC, js_true); ant_value_t async_proto = get_slot(js_glob(js), SLOT_ASYNC_PROTO); if (vtype(async_proto) == T_FUNC) js_set_proto_init(func_obj, async_proto); + } else if (is_generator) { + ant_value_t generator_proto = get_slot(js_glob(js), SLOT_GENERATOR_PROTO); + if (vtype(generator_proto) == T_FUNC) js_set_proto_init(func_obj, generator_proto); } else { ant_value_t func_proto = get_slot(js_glob(js), SLOT_FUNC_PROTO); ant_value_t instance_proto = js_instance_proto_from_new_target(js, func_proto); @@ -4730,16 +4742,30 @@ static ant_value_t build_dynamic_function(ant_t *js, ant_value_t *args, int narg ant_value_t func = mkval(T_FUNC, (uintptr_t)closure); ant_value_t proto_setup = setup_func_prototype(js, func); if (is_err(proto_setup)) return proto_setup; + if (is_generator) { + ant_value_t prototype = js_get(js, func, "prototype"); + if (is_object_type(prototype) && is_object_type(js->sym.generator_proto)) + js_set_proto_wb(js, prototype, js->sym.generator_proto); + } return func; } static ant_value_t builtin_Function(ant_t *js, ant_value_t *args, int nargs) { - return build_dynamic_function(js, args, nargs, false); + return build_dynamic_function(js, args, nargs, false, false); } static ant_value_t builtin_AsyncFunction(ant_t *js, ant_value_t *args, int nargs) { - return build_dynamic_function(js, args, nargs, true); + return build_dynamic_function(js, args, nargs, true, false); +} + +static ant_value_t builtin_GeneratorFunction(ant_t *js, ant_value_t *args, int nargs) { + if (nargs == 0) { + ant_value_t empty = js_mkstr(js, "", 0); + if (is_err(empty)) return empty; + return build_dynamic_function(js, &empty, 1, false, true); + } + return build_dynamic_function(js, args, nargs, false, true); } static ant_value_t builtin_function_empty(ant_t *js, ant_value_t *args, int nargs) { @@ -4976,7 +5002,7 @@ static ant_value_t builtin_function_bind(ant_t *js, ant_value_t *args, int nargs } ant_value_t target_proto = get_proto(js, func); - if (is_object_type(target_proto)) { + if (is_object_type(target_proto) || vtype(target_proto) == T_NULL) { js_set_proto_init(bound_func, target_proto); } else if (vtype(async_slot) == T_BOOL && vdata(async_slot) == 1) { ant_value_t async_proto = get_slot(js_glob(js), SLOT_ASYNC_PROTO); @@ -10072,6 +10098,22 @@ ant_value_t js_mkpromise(ant_t *js) { return mkval(T_PROMISE, vdata(obj)); } +ant_value_t js_mkgenerator(ant_t *js) { + ant_value_t obj = mkobj(js, 0); + if (is_err(obj)) return obj; + + ant_object_t *ptr = js_obj_ptr(obj); + if (!ptr) return js_mkerr(js, "invalid generator object"); + ptr->type_tag = T_GENERATOR; + + ant_value_t gen = mkval(T_GENERATOR, vdata(obj)); + if (is_object_type(js->sym.generator_proto)) { + js_set_proto_init(gen, js->sym.generator_proto); + } + + return gen; +} + ant_value_t js_promise_then(ant_t *js, ant_value_t promise, ant_value_t on_fulfilled, ant_value_t on_rejected) { ant_value_t args_then[2] = { on_fulfilled, on_rejected }; ant_value_t saved_this = js->this_val; @@ -12683,6 +12725,23 @@ ant_t *js_create(void *buf, size_t len) { js_setprop(js, async_func_proto_obj, js_mkstr(js, "constructor", 11), async_func_ctor); js_set_descriptor(js, async_func_proto_obj, "constructor", 11, JS_DESC_W | JS_DESC_C); + + ant_value_t generator_func_proto_obj = js_mkobj(js); + set_proto(js, generator_func_proto_obj, function_proto); + ant_value_t generator_func_proto = js_obj_to_func(generator_func_proto_obj); + set_slot(glob, SLOT_GENERATOR_PROTO, generator_func_proto); + + ant_value_t generator_func_ctor_obj = mkobj(js, 0); + set_proto(js, generator_func_ctor_obj, function_proto); + set_slot(generator_func_ctor_obj, SLOT_CFUNC, js_mkfun(builtin_GeneratorFunction)); + js_setprop_nonconfigurable(js, generator_func_ctor_obj, "prototype", 9, generator_func_proto); + js_setprop(js, generator_func_ctor_obj, js->length_str, tov(1.0)); + js_set_descriptor(js, generator_func_ctor_obj, "length", 6, JS_DESC_C); + js_setprop(js, generator_func_ctor_obj, ANT_STRING("name"), ANT_STRING("GeneratorFunction")); + ant_value_t generator_func_ctor = js_obj_to_func(generator_func_ctor_obj); + + js_setprop(js, generator_func_proto_obj, js_mkstr(js, "constructor", 11), generator_func_ctor); + js_set_descriptor(js, generator_func_proto_obj, "constructor", 11, JS_DESC_W | JS_DESC_C); ant_value_t str_ctor_obj = mkobj(js, 0); set_proto(js, str_ctor_obj, function_proto); @@ -12986,6 +13045,7 @@ void js_set_sym(ant_t *js, ant_value_t obj, ant_value_t sym, ant_value_t val) { ant_offset_t sym_off = (ant_offset_t)vdata(sym); if (vtype(obj) == T_FUNC) obj = js_func_obj(obj); + else if (is_object_type(obj)) obj = js_as_obj(obj); if (vtype(obj) != T_OBJ && vtype(obj) != T_ARR) return; ant_offset_t existing = lkp_sym(js, obj, sym_off); diff --git a/src/errors.c b/src/errors.c index ff9b877..ed31a22 100644 --- a/src/errors.c +++ b/src/errors.c @@ -788,12 +788,12 @@ ant_value_t js_create_error(ant_t *js, js_err_type_t err_type, ant_value_t props js_set_slot(err_obj, SLOT_ERR_TYPE, js_mknum((double)err_type)); int props_type = vtype(props); - if ((JS_TYPE_FLAG(props_type) & T_SPECIAL_OBJECT_MASK) != 0) { + if ((JS_TPFLG(props_type) & T_SPECIAL_OBJECT_MASK) != 0) { js_merge_obj(js, err_obj, props); } ant_value_t proto = js_get_ctor_proto(js, err_name, err_name_len); int proto_type = vtype(proto); - if ((JS_TYPE_FLAG(proto_type) & T_SPECIAL_OBJECT_MASK) != 0) { + if ((JS_TPFLG(proto_type) & T_SPECIAL_OBJECT_MASK) != 0) { js_set_proto_init(err_obj, proto); } diff --git a/src/esm/commonjs.c b/src/esm/commonjs.c index df3a247..0c6e498 100644 --- a/src/esm/commonjs.c +++ b/src/esm/commonjs.c @@ -146,8 +146,7 @@ static ant_value_t esm_eval_commonjs_function( sv_func_t *compiled = sv_compile_function_with_params( js, cjs_params, (int)(sizeof(cjs_params) / sizeof(cjs_params[0])), - code, code_len, - false + code, code_len, false ); if (!compiled) { diff --git a/src/gc/objects.c b/src/gc/objects.c index f2d20d7..8a365b4 100644 --- a/src/gc/objects.c +++ b/src/gc/objects.c @@ -55,6 +55,8 @@ static inline bool gc_get_stack_bounds( } static gc_func_mark_profile_t g_gc_func_mark_profile = {0}; +static void gc_mark_coroutine(ant_t *js, coroutine_t *c); + static uint32_t g_gc_func_mark_profile_depth = 0; static uint64_t g_gc_func_mark_profile_start_ns = 0; @@ -229,6 +231,10 @@ static void gc_scan_obj(ant_t *js, ant_object_t *obj) { gc_mark_value(js, obj->proto); if (obj->type_tag != T_ARR) gc_mark_value(js, obj->u.data.value); + if (obj->type_tag == T_GENERATOR) { + ant_value_t coro_val = js_get_slot(js_obj_from_ptr(obj), SLOT_CORO); + if (vtype(coro_val) == T_NUM) gc_mark_coroutine(js, (coroutine_t *)(uintptr_t)js_getnum(coro_val)); + } if (obj->type_tag == T_MAP) { map_entry_t **head = (map_entry_t **)(uintptr_t)js_getnum(obj->u.data.value); @@ -418,6 +424,7 @@ static void gc_mark_coroutine(ant_t *js, coroutine_t *c) { gc_mark_value(js, c->yield_value); gc_mark_value(js, c->super_val); gc_mark_value(js, c->new_target); + if (c->args) for (int i = 0; i < c->nargs; i++) gc_mark_value(js, c->args[i]); } static inline void gc_mark_promise_handler(ant_t *js, const promise_handler_t *h) { diff --git a/src/main.c b/src/main.c index 2ac99b9..5a7a9ed 100644 --- a/src/main.c +++ b/src/main.c @@ -66,6 +66,7 @@ #include "modules/observable.h" #include "modules/collections.h" #include "modules/iterator.h" +#include "modules/generator.h" #include "modules/module.h" #include "modules/util.h" #include "modules/async_hooks.h" @@ -579,6 +580,7 @@ int main(int argc, char *argv[]) { init_symbol_module(); init_iterator_module(); + init_generator_module(); init_timer_module(); init_domexception_module(); init_globals_module(); diff --git a/src/modules/generator.c b/src/modules/generator.c new file mode 100644 index 0000000..e511478 --- /dev/null +++ b/src/modules/generator.c @@ -0,0 +1,281 @@ +#include + +#include "ant.h" +#include "errors.h" +#include "internal.h" +#include "runtime.h" +#include "silver/engine.h" +#include "silver/vm.h" +#include "sugar.h" + +#include "modules/generator.h" +#include "modules/symbol.h" + +typedef enum { + GEN_SUSPENDED_START = 0, + GEN_SUSPENDED_YIELD = 1, + GEN_EXECUTING = 2, + GEN_COMPLETED = 3, +} generator_state_t; + +static ant_value_t generator_result(ant_t *js, bool done, ant_value_t value) { + ant_value_t result = js_mkobj(js); + js_set(js, result, "done", js_bool(done)); + js_set(js, result, "value", value); + return result; +} + +static generator_state_t generator_state(ant_value_t gen) { + ant_value_t state = js_get_slot(gen, SLOT_GENERATOR_STATE); + if (vtype(state) != T_NUM) return GEN_COMPLETED; + return (generator_state_t)(int)js_getnum(state); +} + +static void generator_set_state(ant_value_t gen, generator_state_t state) { + js_set_slot(gen, SLOT_GENERATOR_STATE, js_mknum((double)state)); +} + +static bool generator_is_async(ant_value_t gen) { + return js_get_slot(gen, SLOT_ASYNC) == js_true; +} + +static ant_value_t generator_async_wrap_result(ant_t *js, ant_value_t result) { + ant_value_t promise = js_mkpromise(js); + if (is_err(promise)) return promise; + + if (is_err(result)) { + ant_value_t reject_value = js->thrown_exists ? js->thrown_value : result; + js->thrown_exists = false; + js->thrown_value = js_mkundef(); + js_reject_promise(js, promise, reject_value); + } else js_resolve_promise(js, promise, result); + + return promise; +} + +static coroutine_t *generator_coro(ant_value_t gen) { + ant_value_t coro_val = js_get_slot(gen, SLOT_CORO); + if (vtype(coro_val) != T_NUM) return NULL; + return (coroutine_t *)(uintptr_t)js_getnum(coro_val); +} + +static void generator_clear_coro(ant_value_t gen, coroutine_t *coro) { + js_set_slot(gen, SLOT_CORO, js_mkundef()); + if (coro) free_coroutine(coro); +} + +static void generator_finalize(ant_t *js, ant_object_t *obj) { + ant_value_t gen = js_obj_from_ptr(obj); + ant_value_t coro_val = js_get_slot(gen, SLOT_CORO); + if (vtype(coro_val) != T_NUM) return; + coroutine_t *coro = (coroutine_t *)(uintptr_t)js_getnum(coro_val); + js_set_slot(gen, SLOT_CORO, js_mkundef()); + free_coroutine(coro); +} + +static ant_value_t generator_resume_kind( + ant_t *js, ant_value_t gen, ant_value_t resume_value, sv_resume_kind_t resume_kind +) { + coroutine_t *coro = generator_coro(gen); + if (!coro || !coro->sv_vm) { + generator_set_state(gen, GEN_COMPLETED); + if (resume_kind == SV_RESUME_THROW) return js_throw(js, resume_value); + return generator_result(js, true, resume_kind == SV_RESUME_RETURN ? resume_value : js_mkundef()); + } + + generator_state_t state = generator_state(gen); + if (state == GEN_EXECUTING) return js_mkerr_typed( + js, JS_ERR_TYPE, "Generator is already executing" + ); + + if (state == GEN_COMPLETED) { + if (resume_kind == SV_RESUME_THROW) return js_throw(js, resume_value); + return generator_result(js, true, resume_kind == SV_RESUME_RETURN ? resume_value : js_mkundef()); + } + + if (state == GEN_SUSPENDED_START && resume_kind == SV_RESUME_THROW) { + generator_set_state(gen, GEN_COMPLETED); + generator_clear_coro(gen, coro); + return js_throw(js, resume_value); + } + + if (state == GEN_SUSPENDED_START && resume_kind == SV_RESUME_RETURN) { + generator_set_state(gen, GEN_COMPLETED); + generator_clear_coro(gen, coro); + return generator_result(js, true, resume_value); + } + + generator_set_state(gen, GEN_EXECUTING); + coroutine_t *saved_active = js->active_async_coro; + + coro->active_parent = saved_active; + js->active_async_coro = coro; + + ant_value_t result; + if (state == GEN_SUSPENDED_START) { + sv_closure_t *closure = (vtype(coro->async_func) == T_FUNC) ? js_func_closure(coro->async_func) : NULL; + if (!closure || !closure->func) result = js_mkerr(js, "invalid generator function"); + else result = sv_execute_closure_entry( + coro->sv_vm, closure, coro->async_func, + coro->super_val, coro->this_val, coro->args, coro->nargs, NULL + ); + } else { + coro->sv_vm->suspended_resume_value = resume_value; + coro->sv_vm->suspended_resume_is_error = (resume_kind == SV_RESUME_THROW); + coro->sv_vm->suspended_resume_kind = resume_kind; + coro->sv_vm->suspended_resume_pending = true; + result = sv_resume_suspended(coro->sv_vm); + } + + js->active_async_coro = saved_active; + coro->active_parent = NULL; + + if (is_err(result)) { + generator_set_state(gen, GEN_COMPLETED); + generator_clear_coro(gen, coro); + return result; + } + + if (coro->sv_vm && coro->sv_vm->suspended) { + generator_set_state(gen, GEN_SUSPENDED_YIELD); + return generator_result(js, false, result); + } + + generator_set_state(gen, GEN_COMPLETED); + generator_clear_coro(gen, coro); + + return generator_result(js, true, result); +} + +static ant_value_t generator_resume(ant_t *js, ant_value_t gen, ant_value_t resume_value) { + return generator_resume_kind(js, gen, resume_value, SV_RESUME_NEXT); +} + +static ant_value_t generator_next(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t gen = js->this_val; + if (vtype(gen) != T_GENERATOR) + return js_mkerr_typed(js, JS_ERR_TYPE, "Generator.prototype.next called on incompatible receiver"); + + ant_value_t resume_value = nargs > 0 ? args[0] : js_mkundef(); + ant_value_t result = generator_resume(js, gen, resume_value); + + return generator_is_async(gen) ? generator_async_wrap_result(js, result) : result; +} + +static ant_value_t generator_return(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t gen = js->this_val; + if (vtype(gen) != T_GENERATOR) + return js_mkerr_typed(js, JS_ERR_TYPE, "Generator.prototype.return called on incompatible receiver"); + + generator_state_t state = generator_state(gen); + if (state == GEN_EXECUTING) + return js_mkerr_typed(js, JS_ERR_TYPE, "Generator is already executing"); + + ant_value_t value = nargs > 0 ? args[0] : js_mkundef(); + ant_value_t result = generator_resume_kind(js, gen, value, SV_RESUME_RETURN); + + return generator_is_async(gen) ? generator_async_wrap_result(js, result) : result; +} + +static ant_value_t generator_throw(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t gen = js->this_val; + if (vtype(gen) != T_GENERATOR) + return js_mkerr_typed(js, JS_ERR_TYPE, "Generator.prototype.throw called on incompatible receiver"); + + generator_state_t state = generator_state(gen); + if (state == GEN_EXECUTING) + return js_mkerr_typed(js, JS_ERR_TYPE, "Generator is already executing"); + + ant_value_t value = nargs > 0 ? args[0] : js_mkundef(); + ant_value_t result = generator_resume_kind(js, gen, value, SV_RESUME_THROW); + + return generator_is_async(gen) ? generator_async_wrap_result(js, result) : result; +} + +void init_generator_module(void) { + ant_t *js = rt->js; + ant_value_t proto = js_mkobj(js); + + js->sym.generator_proto = proto; + js_set_proto_init(proto, js->sym.iterator_proto); + + js_set(js, proto, "next", js_mkfun(generator_next)); + js_set(js, proto, "return", js_mkfun(generator_return)); + js_set(js, proto, "throw", js_mkfun(generator_throw)); + js_set_sym(js, proto, get_toStringTag_sym(), js_mkstr(js, "Generator", 9)); +} + +ant_value_t sv_call_generator_closure_dispatch( + sv_vm_t *caller_vm, ant_t *js, sv_closure_t *closure, + ant_value_t callee_func, ant_value_t super_val, + ant_value_t this_val, ant_value_t *args, int argc +) { + if (!closure || !closure->func) return js_mkerr(js, "invalid generator function"); + + sv_vm_t *gen_vm = sv_vm_create(js, SV_VM_ASYNC); + if (!gen_vm) return js_mkerr(js, "out of memory for generator VM"); + + coroutine_t *coro = (coroutine_t *)CORO_MALLOC(sizeof(coroutine_t)); + if (!coro) { + sv_vm_destroy(gen_vm); + return js_mkerr(js, "out of memory for generator"); + } + + ant_value_t *copied_args = NULL; + if (argc > 0 && args) { + copied_args = (ant_value_t *)CORO_MALLOC(sizeof(ant_value_t) * (size_t)argc); + if (!copied_args) { + sv_vm_destroy(gen_vm); + CORO_FREE(coro); + return js_mkerr(js, "out of memory for generator args"); + } + memcpy(copied_args, args, sizeof(ant_value_t) * (size_t)argc); + } + + ant_value_t gen = js_mkgenerator(js); + if (is_err(gen)) { + if (copied_args) CORO_FREE(copied_args); + sv_vm_destroy(gen_vm); + CORO_FREE(coro); + return gen; + } + + ant_value_t instance_proto = js_get(js, callee_func, "prototype"); + if (is_object_type(instance_proto)) js_set_proto_wb(js, gen, instance_proto); + if (closure->func->is_async) { + js_set_slot(gen, SLOT_ASYNC, js_true); + js_set_sym(js, gen, get_asyncIterator_sym(), js_mkfun(sym_this_cb)); + } + + *coro = (coroutine_t){ + .js = js, + .type = CORO_GENERATOR, + .this_val = this_val, + .super_val = super_val, + .new_target = js->new_target, + .awaited_promise = js_mkundef(), + .result = js_mkundef(), + .async_func = callee_func, + .args = copied_args, + .nargs = argc, + .active_parent = NULL, + .is_settled = false, + .is_error = false, + .is_done = false, + .resume_point = 0, + .yield_value = js_mkundef(), + .async_promise = js_mkundef(), + .next = NULL, + .mco = NULL, + .mco_started = false, + .is_ready = false, + .did_suspend = false, + .sv_vm = gen_vm, + }; + + js_set_slot(gen, SLOT_GENERATOR_STATE, js_mknum((double)GEN_SUSPENDED_START)); + js_set_slot(gen, SLOT_CORO, ANT_PTR(coro)); + js_set_finalizer(gen, generator_finalize); + + return gen; +} diff --git a/src/modules/symbol.c b/src/modules/symbol.c index 2ef8050..0870cda 100644 --- a/src/modules/symbol.c +++ b/src/modules/symbol.c @@ -286,10 +286,12 @@ void init_symbol_module(void) { js->sym.iterator_proto = js_mkundef(); js->sym.array_iterator_proto = js_mkundef(); js->sym.string_iterator_proto = js_mkundef(); + js->sym.generator_proto = js_mkundef(); gc_register_root(&js->sym.iterator_proto); gc_register_root(&js->sym.array_iterator_proto); gc_register_root(&js->sym.string_iterator_proto); + gc_register_root(&js->sym.generator_proto); #define INIT_SYM(name, desc) g_##name = js_mksym_well_known(js, desc); WELLKNOWN_SYMBOLS(INIT_SYM) @@ -352,9 +354,9 @@ void gc_mark_symbols(ant_t *js, gc_mark_fn mark) { mark(js, js->sym.iterator_proto); mark(js, js->sym.array_iterator_proto); mark(js, js->sym.string_iterator_proto); + mark(js, js->sym.generator_proto); #define GC_SYM(name, _desc) mark(js, g_##name); WELLKNOWN_SYMBOLS(GC_SYM) #undef GC_SYM } - diff --git a/src/silver/ast.c b/src/silver/ast.c index 4f54aaf..671ef59 100644 --- a/src/silver/ast.c +++ b/src/silver/ast.c @@ -844,6 +844,34 @@ static sv_ast_t *parse_object(P) { expect(p, TOK_RBRACKET); prop->flags |= FN_COMPUTED; } + else if (TOK == TOK_MUL) { + prop->flags |= FN_GENERATOR; + CONSUME(); + NEXT(); + + if (TOK == TOK_LBRACKET) { + CONSUME(); + prop->left = parse_assign(p); + expect(p, TOK_RBRACKET); + prop->flags |= FN_COMPUTED; + } else if (TOK == TOK_NUMBER) { + CONSUME(); + prop->left = mk_num(tod(TVAL)); + } else if (TOK == TOK_STRING) { + prop->left = mk(N_STRING); + sv_ast_set_string(prop->left, sv_lexer_str_literal(&p->lx)); + CONSUME(); + } else { + prop->left = mk_ident_from_tok(p); + CONSUME(); + } + + prop->right = parse_func(p); + prop->right->flags |= FN_GENERATOR | FN_METHOD; + sv_ast_list_push(&n->args, prop); + if (NEXT() == TOK_COMMA) CONSUME(); + continue; + } else if ( (TLEN == 3 && memcmp(tok_str(p), "get", 3) == 0) || (TLEN == 3 && memcmp(tok_str(p), "set", 3) == 0)) { @@ -873,6 +901,7 @@ static sv_ast_t *parse_object(P) { } prop->right = parse_func(p); + prop->right->flags |= FN_METHOD; sv_ast_list_push(&n->args, prop); if (NEXT() == TOK_COMMA) CONSUME(); continue; @@ -911,7 +940,7 @@ static sv_ast_t *parse_object(P) { } prop->right = parse_func(p); - prop->right->flags |= FN_ASYNC; + prop->right->flags |= FN_ASYNC | FN_METHOD; if (prop->flags & FN_GENERATOR) prop->right->flags |= FN_GENERATOR; sv_ast_list_push(&n->args, prop); @@ -946,6 +975,7 @@ static sv_ast_t *parse_object(P) { prop->right = parse_assign(p); } else if (TOK == TOK_LPAREN) { prop->right = parse_func(p); + prop->right->flags |= FN_METHOD; } else { prop->right = mk_ident(prop->left->str, prop->left->len); if (NEXT() == TOK_ASSIGN) { @@ -1366,6 +1396,14 @@ static sv_ast_t *parse_class(P) { method->flags = flags; + if (!(flags & FN_STATIC) && (flags & FN_GENERATOR) && + method->left && method->left->type == N_IDENT && + method->left->len == 11 && + memcmp(method->left->str, "constructor", 11) == 0) { + SV_MKERR_TYPED(JS, JS_ERR_SYNTAX, "Class constructor may not be a generator"); + return cls; + } + if (NEXT() == TOK_LPAREN) { method->right = parse_func(p); method->right->flags |= (flags & (FN_ASYNC | FN_GENERATOR)) | FN_METHOD; diff --git a/src/silver/compiler.c b/src/silver/compiler.c index b9eef1e..bcb5399 100644 --- a/src/silver/compiler.c +++ b/src/silver/compiler.c @@ -628,6 +628,13 @@ static int add_local( return idx; } +static int reserve_hidden_locals(sv_compiler_t *c, int count) { + int base = c->local_count; + for (int i = 0; i < count; i++) + add_local(c, "", 0, false, c->scope_depth); + return base; +} + static inline bool sv_type_is_known(uint8_t t) { return t != SV_TI_UNKNOWN; } @@ -1385,6 +1392,21 @@ static uint8_t infer_expr_type(sv_compiler_t *c, sv_ast_t *node) { } } +static void compile_yield_star_expr(sv_compiler_t *c, sv_ast_t *node) { + if (node->right) compile_expr(c, node->right); + else emit_op(c, OP_UNDEF); + + int base_local = reserve_hidden_locals(c, 4); + uint16_t base_slot = (uint16_t)(base_local - c->param_locals); + + emit_op(c, OP_YIELD_STAR_INIT); + emit_u16(c, base_slot); + + emit_op(c, OP_UNDEF); + emit_op(c, OP_YIELD_STAR_NEXT); + emit_u16(c, base_slot); +} + static void compile_expr(sv_compiler_t *c, sv_ast_t *node) { if (!node) { emit_op(c, OP_UNDEF); return; } emit_srcpos(c, node); @@ -1540,9 +1562,12 @@ static void compile_expr(sv_compiler_t *c, sv_ast_t *node) { break; case N_YIELD: - if (node->right) compile_expr(c, node->right); - else emit_op(c, OP_UNDEF); - emit_op(c, node->flags ? OP_YIELD_STAR : OP_YIELD); + if (node->flags) compile_yield_star_expr(c, node); + else { + if (node->right) compile_expr(c, node->right); + else emit_op(c, OP_UNDEF); + emit_op(c, OP_YIELD); + } break; case N_TAGGED_TEMPLATE: { @@ -4853,8 +4878,11 @@ sv_func_t *sv_compile(ant_t *js, sv_ast_t *program, sv_compile_mode_t mode, cons return func; } -sv_func_t *sv_compile_function(ant_t *js, const char *source, size_t len, bool is_async) { - const char *prefix = is_async ? "(async function" : "(function"; +sv_func_t *sv_compile_function(ant_t *js, const char *source, size_t len, bool is_async, bool is_generator) { + const char *prefix = is_async + ? "(async function" + : (is_generator ? "(function*" : "(function"); + size_t prefix_len = strlen(prefix); size_t wrapped_len = prefix_len + len + 1; @@ -4873,10 +4901,8 @@ sv_func_t *sv_compile_function(ant_t *js, const char *source, size_t len, bool i sv_ast_t *func_node = NULL; if (program->args.count > 0) { sv_ast_t *stmt = program->args.items[0]; - if (stmt && stmt->type == N_FUNC) - func_node = stmt; - else if (stmt && stmt->left && stmt->left->type == N_FUNC) - func_node = stmt->left; + if (stmt && stmt->type == N_FUNC) func_node = stmt; + else if (stmt && stmt->left && stmt->left->type == N_FUNC) func_node = stmt->left; } if (!func_node) { free(wrapped); return NULL; } @@ -4959,5 +4985,6 @@ sv_func_t *sv_compile_function_with_params( sv_func_t *func = compile_function_body(&root, &top_fn, SV_COMPILE_SCRIPT); if (js->thrown_exists || !func) return NULL; + return func; } diff --git a/src/silver/engine.c b/src/silver/engine.c index 46e14ce..45d93fd 100644 --- a/src/silver/engine.c +++ b/src/silver/engine.c @@ -234,6 +234,148 @@ static inline ant_value_t sv_stage_frame_args( return js_mkundef(); } +static inline void sv_yield_star_store_state(sv_vm_t *vm, ant_value_t *lp, uint16_t base) { + lp[base + 0] = vm->stack[vm->sp - 3]; + lp[base + 1] = vm->stack[vm->sp - 2]; + lp[base + 2] = vm->stack[vm->sp - 1]; +} + +static inline void sv_yield_star_push_state(sv_vm_t *vm, ant_value_t *lp, uint16_t base) { + vm->stack[vm->sp++] = lp[base + 0]; + vm->stack[vm->sp++] = lp[base + 1]; + vm->stack[vm->sp++] = lp[base + 2]; +} + +static inline void sv_yield_star_clear_state(ant_t *js, ant_value_t *lp, uint16_t base) { + lp[base + 0] = js_mkundef(); + lp[base + 1] = js_mkundef(); + lp[base + 2] = js_mkundef(); + lp[base + 3] = js_false; +} + +static inline ant_value_t sv_yield_star_unpack_result( + ant_t *js, ant_value_t result, ant_value_t *out_value, bool *out_done +) { + if (!is_object_type(result)) + return js_mkerr_typed(js, JS_ERR_TYPE, "Iterator result is not an object"); + + ant_value_t done = js_mkundef(); + sv_iter_result_unpack(js, result, &done, out_value); + + if (is_err(done)) return done; + if (is_err(*out_value)) return *out_value; + *out_done = js_truthy(js, done); + + return js_mkundef(); +} + +static inline ant_value_t sv_yield_star_call_method( + sv_vm_t *vm, ant_t *js, ant_value_t iterator, + const char *name, ant_value_t arg, ant_value_t *out_value, bool *out_done +) { + ant_value_t method = js_getprop_fallback(js, iterator, name); + uint8_t mt = vtype(method); + + if (mt != T_FUNC && mt != T_CFUNC) { + *out_value = js_mkundef(); + *out_done = true; + return js_mkundef(); + } + + ant_value_t call_args[1] = { arg }; + ant_value_t result = sv_vm_call(vm, js, method, iterator, call_args, 1, NULL, false); + if (is_err(result)) return result; + + return sv_yield_star_unpack_result(js, result, out_value, out_done); +} + +static inline ant_value_t sv_yield_star_close_iterator( + sv_vm_t *vm, ant_t *js, ant_value_t iterator +) { + ant_value_t close_value = js_mkundef(); + bool close_done = true; + + return sv_yield_star_call_method( + vm, js, iterator, "return", js_mkundef(), + &close_value, &close_done + ); +} + +static inline ant_value_t sv_yield_star_next( + sv_vm_t *vm, ant_t *js, ant_value_t *lp, uint16_t base, + ant_value_t sent, ant_value_t *out_value, bool *out_done +) { + int tag = (int)js_getnum(lp[base + 2]); + bool first = js_truthy(js, lp[base + 3]); + lp[base + 3] = js_false; + + if (tag == SV_ITER_GENERIC) { + ant_value_t iterator = lp[base + 0]; + ant_value_t next_method = lp[base + 1]; + + uint8_t nt = vtype(next_method); + if (nt != T_FUNC && nt != T_CFUNC) + return js_mkerr(js, "iterator.next is not a function"); + + ant_value_t call_args[1] = { sent }; + ant_value_t result = sv_vm_call( + vm, js, next_method, iterator, first ? NULL : call_args, first ? 0 : 1, + NULL, false + ); + + if (is_err(result)) return result; + return sv_yield_star_unpack_result(js, result, out_value, out_done); + } + + sv_yield_star_push_state(vm, lp, base); + ant_value_t status = sv_iter_advance(vm, js, 0, out_value, out_done); + if (is_err(status)) return status; + + sv_yield_star_store_state(vm, lp, base); + vm->sp -= 3; + + return js_mkundef(); +} + +static inline ant_value_t sv_yield_star_throw( + sv_vm_t *vm, ant_t *js, ant_value_t *lp, uint16_t base, + ant_value_t thrown, ant_value_t *out_value, bool *out_done +) { + int tag = (int)js_getnum(lp[base + 2]); + if (tag != SV_ITER_GENERIC) return js_throw(js, thrown); + + ant_value_t iterator = lp[base + 0]; + ant_value_t throw_method = js_getprop_fallback(js, iterator, "throw"); + uint8_t tt = vtype(throw_method); + + if (tt != T_FUNC && tt != T_CFUNC) { + ant_value_t close_status = sv_yield_star_close_iterator(vm, js, iterator); + if (is_err(close_status)) return close_status; + return js_throw(js, thrown); + } + + ant_value_t call_args[1] = { thrown }; + ant_value_t result = sv_vm_call(vm, js, throw_method, iterator, call_args, 1, NULL, false); + if (is_err(result)) return result; + + return sv_yield_star_unpack_result(js, result, out_value, out_done); +} + +static inline ant_value_t sv_yield_star_return( + sv_vm_t *vm, ant_t *js, ant_value_t *lp, uint16_t base, + ant_value_t value, ant_value_t *out_value, bool *out_done +) { + int tag = (int)js_getnum(lp[base + 2]); + + if (tag != SV_ITER_GENERIC) { + *out_value = value; + *out_done = true; + return js_mkundef(); + } + + return sv_yield_star_call_method(vm, js, lp[base + 0], "return", value, out_value, out_done); +} + static inline ant_value_t sv_execute_entry_common( sv_vm_t *vm, sv_func_t *func, sv_upvalue_t **upvalues, int upvalue_count, ant_value_t callee_func, ant_value_t super_val, @@ -327,14 +469,18 @@ ant_value_t sv_execute_closure_entry( ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant_value_t super_val, ant_value_t *args, int argc) { ant_t *js = vm->js; + bool resuming = vm->suspended && vm->suspended_resume_pending; uint8_t *ip = resuming ? NULL : func->code; - int entry_fp = resuming ? vm->suspended_entry_fp : vm->fp; + int entry_fp = resuming + ? vm->suspended_entry_fp + : vm->fp; + ant_value_t vm_result = js_mkundef(); ant_value_t suspended_resume_value = js_mkundef(); + sv_resume_kind_t suspended_resume_kind = SV_RESUME_NEXT; - bool suspended_resume_is_error = false; js->vm_exec_depth++; // TODO: shorthand? @@ -356,10 +502,11 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant func = frame->func; ip = frame->ip; suspended_resume_value = vm->suspended_resume_value; - suspended_resume_is_error = vm->suspended_resume_is_error; + suspended_resume_kind = vm->suspended_resume_kind; vm->suspended = false; vm->suspended_resume_pending = false; vm->suspended_resume_is_error = false; + vm->suspended_resume_kind = SV_RESUME_NEXT; vm->suspended_resume_value = js_mkundef(); } @@ -498,10 +645,19 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant #define JIT_OSR_BACK_EDGE() ((void)0) #endif if (resuming) { - if (suspended_resume_is_error) { + bool yield_star_resume = ip && ( + *ip == OP_YIELD_STAR_NEXT || + *ip == OP_YIELD_STAR_THROW || + *ip == OP_YIELD_STAR_RETURN + ); + if (suspended_resume_kind == SV_RESUME_THROW && !yield_star_resume) { sv_err = js_throw(js, suspended_resume_value); goto sv_throw; } + if (suspended_resume_kind == SV_RESUME_RETURN && !yield_star_resume) { + vm->stack[vm->sp++] = suspended_resume_value; + goto L_RETURN; + } vm->stack[vm->sp++] = suspended_resume_value; } DISPATCH(); @@ -791,7 +947,7 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant if (closure->func != NULL) { if (closure->call_flags & (SV_CALL_HAS_BOUND_ARGS | SV_CALL_HAS_SUPER)) goto call_fallback; - if (closure->func->is_async) goto call_fallback; + if (closure->func->is_async || closure->func->is_generator) goto call_fallback; #ifdef ANT_JIT { ant_value_t jit_this = ( @@ -880,7 +1036,7 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant if (closure->func != NULL) { if (closure->call_flags & (SV_CALL_HAS_BOUND_ARGS | SV_CALL_HAS_SUPER)) goto call_method_fallback; - if (closure->func->is_async) goto call_method_fallback; + if (closure->func->is_async || closure->func->is_generator) goto call_method_fallback; #ifdef ANT_JIT { ant_value_t jit_this = ( @@ -989,7 +1145,7 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant vtype(call_func) == T_FUNC) { sv_closure_t *closure = js_func_closure(call_func); if (closure->func != NULL) { - if (!closure->func->is_async && + if (!closure->func->is_async && !closure->func->is_generator && !(closure->call_flags & (SV_CALL_HAS_BOUND_ARGS | SV_CALL_HAS_SUPER))) { if (closure->func->is_arrow || vtype(closure->bound_this) != T_UNDEF) tc_this = closure->bound_this; @@ -1018,7 +1174,7 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant vtype(call_func) == T_FUNC) { sv_closure_t *closure = js_func_closure(call_func); if (closure->func != NULL) { - if (!closure->func->is_async && + if (!closure->func->is_async && !closure->func->is_generator && !(closure->call_flags & (SV_CALL_HAS_BOUND_ARGS | SV_CALL_HAS_SUPER))) { if (closure->func->is_arrow || vtype(closure->bound_this) != T_UNDEF) tc_this = closure->bound_this; @@ -1242,10 +1398,73 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant NEXT(1); } - // TODO: implement - L_YIELD: { NEXT(1); } - L_YIELD_STAR: { NEXT(1); } - L_INITIAL_YIELD: { NEXT(1); } + L_YIELD: { + ant_value_t yielded = vm->stack[--vm->sp]; + coroutine_t *coro = js->active_async_coro; + if (!coro || coro->type != CORO_GENERATOR) { + sv_err = js_mkerr(js, "yield can only be used inside generator functions"); + goto sv_throw; + } + coro->yield_value = yielded; + coro->did_suspend = true; + vm->suspended = true; + vm->suspended_entry_fp = entry_fp; + vm->suspended_saved_fp = entry_fp - 1; + frame->ip = ip + 1; + vm_result = yielded; + goto sv_leave; + } + L_YIELD_STAR_INIT: { + uint16_t base = sv_get_u16(ip + 1); + ant_value_t iterable = vm->stack[--vm->sp]; + vm->stack[vm->sp++] = iterable; + VM_CHECK(sv_op_for_of(vm, js)); + sv_yield_star_store_state(vm, lp, base); + vm->sp -= 3; + lp[base + 3] = js_true; + NEXT(3); + } + + L_YIELD_STAR_NEXT: + L_YIELD_STAR_THROW: + L_YIELD_STAR_RETURN: { + coroutine_t *coro = js->active_async_coro; + if (!coro || coro->type != CORO_GENERATOR) { + sv_err = js_mkerr(js, "yield can only be used inside generator functions"); + goto sv_throw; + } + + uint16_t base = sv_get_u16(ip + 1); + ant_value_t resume_value = vm->stack[--vm->sp]; + ant_value_t yielded = js_mkundef(); + bool done = false; + + if (*ip == OP_YIELD_STAR_THROW || suspended_resume_kind == SV_RESUME_THROW) { + VM_CHECK(sv_yield_star_throw(vm, js, lp, base, resume_value, &yielded, &done)); + } else if (*ip == OP_YIELD_STAR_RETURN || suspended_resume_kind == SV_RESUME_RETURN) { + VM_CHECK(sv_yield_star_return(vm, js, lp, base, resume_value, &yielded, &done)); + if (done) { + sv_yield_star_clear_state(js, lp, base); + vm->stack[vm->sp++] = yielded; + goto L_RETURN; + } + } else VM_CHECK(sv_yield_star_next(vm, js, lp, base, resume_value, &yielded, &done)); + + if (done) { + sv_yield_star_clear_state(js, lp, base); + vm->stack[vm->sp++] = yielded; + NEXT(3); + } + + coro->yield_value = yielded; + coro->did_suspend = true; + vm->suspended = true; + vm->suspended_entry_fp = entry_fp; + vm->suspended_saved_fp = entry_fp - 1; + frame->ip = ip; + vm_result = yielded; + goto sv_leave; + } L_SPREAD: { VM_CHECK(sv_op_spread(vm, js)); NEXT(1); } L_DEFINE_METHOD: { sv_op_define_method(vm, js, func, ip); NEXT(6); } diff --git a/src/silver/glue.c b/src/silver/glue.c index fdf0ed1..365ec90 100644 --- a/src/silver/glue.c +++ b/src/silver/glue.c @@ -244,7 +244,7 @@ ant_value_t jit_helper_closure( closure->func_obj = func_obj; ant_value_t module_ctx = js_module_eval_active_ctx(js); - js_mark_constructor(func_obj, !child->is_arrow && !child->is_method); + js_mark_constructor(func_obj, !child->is_arrow && !child->is_method && !child->is_generator); js_setprop(js, func_obj, js->length_str, tov((double)child->param_count)); js_set_descriptor(js, func_obj, "length", 6, JS_DESC_C); @@ -252,13 +252,18 @@ ant_value_t jit_helper_closure( js_set_slot_wb(js, func_obj, SLOT_MODULE_CTX, module_ctx); ant_value_t func_val = mkval(T_FUNC, (uintptr_t)closure); - if (!child->is_arrow && !child->is_method) - sv_setup_function_prototype(js, func_obj, func_val); - + if (!child->is_arrow && !child->is_method) { + ant_value_t parent_proto = child->is_generator ? js->sym.generator_proto : js->sym.object_proto; + sv_setup_function_prototype_with_parent(js, func_obj, func_val, parent_proto); + } + if (child->is_async) { js_set_slot(func_obj, SLOT_ASYNC, js_true); ant_value_t async_proto = js_get_slot(js->global, SLOT_ASYNC_PROTO); if (vtype(async_proto) == T_FUNC) js_set_proto_init(func_obj, async_proto); + } else if (child->is_generator) { + ant_value_t generator_proto = js_get_slot(js->global, SLOT_GENERATOR_PROTO); + if (vtype(generator_proto) == T_FUNC) js_set_proto_init(func_obj, generator_proto); } else { ant_value_t func_proto = js_get_slot(js->global, SLOT_FUNC_PROTO); if (vtype(func_proto) == T_FUNC) js_set_proto_init(func_obj, func_proto); diff --git a/src/silver/ops/async.h b/src/silver/ops/async.h index 0f0fb66..f005cc1 100644 --- a/src/silver/ops/async.h +++ b/src/silver/ops/async.h @@ -35,8 +35,9 @@ static inline bool sv_async_func_supports_lazy_start(sv_func_t *func) { if ( op == OP_AWAIT_ITER_NEXT || op == OP_YIELD || - op == OP_YIELD_STAR || - op == OP_INITIAL_YIELD + op == OP_YIELD_STAR_NEXT || + op == OP_YIELD_STAR_THROW || + op == OP_YIELD_STAR_RETURN ) return false; int size = sv_op_size[op]; diff --git a/src/silver/ops/upvalues.h b/src/silver/ops/upvalues.h index 0c8bf4f..82ee493 100644 --- a/src/silver/ops/upvalues.h +++ b/src/silver/ops/upvalues.h @@ -4,24 +4,24 @@ #include "silver/engine.h" #include "descriptors.h" -static inline ant_value_t sv_setup_function_prototype( - ant_t *js, ant_value_t func_obj, - ant_value_t func_val +static inline ant_value_t sv_setup_function_prototype_with_parent( + ant_t *js, ant_value_t func_obj, + ant_value_t func_val, ant_value_t parent_proto ) { ant_value_t proto_obj = mkobj(js, 0); if (is_err(proto_obj)) return proto_obj; - - ant_value_t object_proto = js->sym.object_proto; - if (vtype(object_proto) == T_OBJ) js_set_proto_init(proto_obj, object_proto); + if (is_object_type(parent_proto)) js_set_proto_init(proto_obj, parent_proto); ant_value_t ctor_key = js_mkstr(js, "constructor", 11); if (is_err(ctor_key)) return ctor_key; + ant_value_t set_ctor = js_setprop(js, proto_obj, ctor_key, func_val); if (is_err(set_ctor)) return set_ctor; + js_set_descriptor(js, proto_obj, "constructor", 11, JS_DESC_W | JS_DESC_C); - ant_value_t proto_key = js_mkstr(js, "prototype", 9); if (is_err(proto_key)) return proto_key; + ant_value_t set_proto = js_setprop(js, func_obj, proto_key, proto_obj); if (is_err(set_proto)) return set_proto; js_set_descriptor(js, func_obj, "prototype", 9, JS_DESC_W); @@ -117,17 +117,23 @@ static inline void sv_op_closure( closure->func_obj = func_obj; ant_value_t module_ctx = js_module_eval_active_ctx(js); - js_mark_constructor(func_obj, !child->is_arrow && !child->is_method); + js_mark_constructor(func_obj, !child->is_arrow && !child->is_method && !child->is_generator); js_setprop(js, func_obj, js->length_str, tov((double)child->param_count)); js_set_descriptor(js, func_obj, "length", 6, JS_DESC_C); if (is_object_type(module_ctx)) js_set_slot_wb(js, func_obj, SLOT_MODULE_CTX, module_ctx); - if (!child->is_arrow && !child->is_method) sv_setup_function_prototype(js, func_obj, func_val); + if (!child->is_arrow && !child->is_method) { + ant_value_t parent_proto = child->is_generator ? js->sym.generator_proto : js->sym.object_proto; + sv_setup_function_prototype_with_parent(js, func_obj, func_val, parent_proto); + } if (child->is_async) { js_set_slot(func_obj, SLOT_ASYNC, js_true); ant_value_t async_proto = js_get_slot(js->global, SLOT_ASYNC_PROTO); if (vtype(async_proto) == T_FUNC) js_set_proto_init(func_obj, async_proto); + } else if (child->is_generator) { + ant_value_t generator_proto = js_get_slot(js->global, SLOT_GENERATOR_PROTO); + if (vtype(generator_proto) == T_FUNC) js_set_proto_init(func_obj, generator_proto); } else { ant_value_t func_proto = js_get_slot(js->global, SLOT_FUNC_PROTO); if (vtype(func_proto) == T_FUNC) js_set_proto_init(func_obj, func_proto); diff --git a/src/sugar.c b/src/sugar.c index 1fac1fa..93a83c9 100644 --- a/src/sugar.c +++ b/src/sugar.c @@ -101,6 +101,7 @@ static void resume_coroutine_if_suspended(ant_t *js, coroutine_t *coro) { coro->is_ready = false; coro->sv_vm->suspended_resume_value = coro->result; coro->sv_vm->suspended_resume_is_error = coro->is_error; + coro->sv_vm->suspended_resume_kind = coro->is_error ? SV_RESUME_THROW : SV_RESUME_NEXT; coro->sv_vm->suspended_resume_pending = true; coro->active_parent = js->active_async_coro;