diff --git a/examples/rou3/bench.js b/examples/rou3/bench.js
new file mode 100644
index 0000000..ab240c8
--- /dev/null
+++ b/examples/rou3/bench.js
@@ -0,0 +1,13 @@
+import { createRouter, addRoute, findRoute } from './';
+
+const router = createRouter();
+
+addRoute(router, 'GET', '/path', { payload: 'this path' });
+addRoute(router, 'POST', '/path/:name', { payload: 'named route' });
+addRoute(router, 'GET', '/path/foo/**', { payload: 'wildcard route' });
+addRoute(router, 'GET', '/path/foo/**:name', { payload: 'named wildcard route' });
+
+console.log(findRoute(router, 'GET', '/path'));
+console.log(findRoute(router, 'POST', '/path/fooval'));
+console.log(findRoute(router, 'GET', '/path/foo/bar/baz'));
+console.log(findRoute(router, 'GET', '/'));
diff --git a/examples/rou3/index.js b/examples/rou3/index.js
new file mode 100644
index 0000000..40ee741
--- /dev/null
+++ b/examples/rou3/index.js
@@ -0,0 +1,234 @@
+const NullProtoObj = /* @__PURE__ */ (() => {
+ const e = function () {};
+ return ((e.prototype = Object.create(null)), Object.freeze(e.prototype), e);
+})();
+
+function createRouter() {
+ return {
+ root: { key: '' },
+ static: new NullProtoObj()
+ };
+}
+
+function splitPath(path) {
+ const [_, ...s] = path.split('/');
+ return s[s.length - 1] === '' ? s.slice(0, -1) : s;
+}
+function getMatchParams(segments, paramsMap) {
+ const params = new NullProtoObj();
+ for (const [index, name] of paramsMap) {
+ const segment = index < 0 ? segments.slice(-(index + 1)).join('/') : segments[index];
+ if (typeof name === 'string') params[name] = segment;
+ else {
+ const match = segment.match(name);
+ if (match) for (const key in match.groups) params[key] = match.groups[key];
+ }
+ }
+ return params;
+}
+
+function addRoute(ctx, method = '', path, data) {
+ method = method.toUpperCase();
+ if (path.charCodeAt(0) !== 47) path = `/${path}`;
+ path = path.replace(/\\:/g, '%3A');
+ const segments = splitPath(path);
+ let node = ctx.root;
+ let _unnamedParamIndex = 0;
+ const paramsMap = [];
+ const paramsRegexp = [];
+ for (let i = 0; i < segments.length; i++) {
+ let segment = segments[i];
+ if (segment.startsWith('**')) {
+ if (!node.wildcard) node.wildcard = { key: '**' };
+ node = node.wildcard;
+ paramsMap.push([-(i + 1), segment.split(':')[1] || '_', segment.length === 2]);
+ break;
+ }
+ if (segment === '*' || segment.includes(':')) {
+ if (!node.param) node.param = { key: '*' };
+ node = node.param;
+ if (segment === '*') paramsMap.push([i, `_${_unnamedParamIndex++}`, true]);
+ else if (segment.includes(':', 1)) {
+ const regexp = getParamRegexp(segment);
+ paramsRegexp[i] = regexp;
+ node.hasRegexParam = true;
+ paramsMap.push([i, regexp, false]);
+ } else paramsMap.push([i, segment.slice(1), false]);
+ continue;
+ }
+ if (segment === '\\*') segment = segments[i] = '*';
+ else if (segment === '\\*\\*') segment = segments[i] = '**';
+ const child = node.static?.[segment];
+ if (child) node = child;
+ else {
+ const staticNode = { key: segment };
+ if (!node.static) node.static = new NullProtoObj();
+ node.static[segment] = staticNode;
+ node = staticNode;
+ }
+ }
+ const hasParams = paramsMap.length > 0;
+ if (!node.methods) node.methods = new NullProtoObj();
+ node.methods[method] ??= [];
+ node.methods[method].push({
+ data: data || null,
+ paramsRegexp,
+ paramsMap: hasParams ? paramsMap : void 0
+ });
+ if (!hasParams) ctx.static['/' + segments.join('/')] = node;
+}
+function getParamRegexp(segment) {
+ const regex = segment.replace(/:(\w+)/g, (_, id) => `(?<${id}>[^/]+)`).replace(/\./g, '\\.');
+ return /* @__PURE__ */ new RegExp(`^${regex}$`);
+}
+
+function findRoute(ctx, method = '', path, opts) {
+ if (path.charCodeAt(path.length - 1) === 47) path = path.slice(0, -1);
+ const staticNode = ctx.static[path];
+ if (staticNode && staticNode.methods) {
+ const staticMatch = staticNode.methods[method] || staticNode.methods[''];
+ if (staticMatch !== void 0) return staticMatch[0];
+ }
+ const segments = splitPath(path);
+ const match = _lookupTree(ctx, ctx.root, method, segments, 0)?.[0];
+ if (match === void 0) return;
+ if (opts?.params === false) return match;
+ return {
+ data: match.data,
+ params: match.paramsMap ? getMatchParams(segments, match.paramsMap) : void 0
+ };
+}
+function _lookupTree(ctx, node, method, segments, index) {
+ if (index === segments.length) {
+ if (node.methods) {
+ const match = node.methods[method] || node.methods[''];
+ if (match) return match;
+ }
+ if (node.param && node.param.methods) {
+ const match = node.param.methods[method] || node.param.methods[''];
+ if (match) {
+ const pMap = match[0].paramsMap;
+ if (pMap?.[pMap?.length - 1]?.[2]) return match;
+ }
+ }
+ if (node.wildcard && node.wildcard.methods) {
+ const match = node.wildcard.methods[method] || node.wildcard.methods[''];
+ if (match) {
+ const pMap = match[0].paramsMap;
+ if (pMap?.[pMap?.length - 1]?.[2]) return match;
+ }
+ }
+ return;
+ }
+ const segment = segments[index];
+ if (node.static) {
+ const staticChild = node.static[segment];
+ if (staticChild) {
+ const match = _lookupTree(ctx, staticChild, method, segments, index + 1);
+ if (match) return match;
+ }
+ }
+ if (node.param) {
+ const match = _lookupTree(ctx, node.param, method, segments, index + 1);
+ if (match) {
+ if (node.param.hasRegexParam) {
+ const exactMatch = match.find(m => m.paramsRegexp[index]?.test(segment)) || match.find(m => !m.paramsRegexp[index]);
+ return exactMatch ? [exactMatch] : void 0;
+ }
+ return match;
+ }
+ }
+ if (node.wildcard && node.wildcard.methods) return node.wildcard.methods[method] || node.wildcard.methods[''];
+}
+
+function removeRoute(ctx, method, path) {
+ const segments = splitPath(path);
+ return _remove(ctx.root, method || '', segments, 0);
+}
+function _remove(node, method, segments, index) {
+ if (index === segments.length) {
+ if (node.methods && method in node.methods) {
+ delete node.methods[method];
+ if (Object.keys(node.methods).length === 0) node.methods = void 0;
+ }
+ return;
+ }
+ const segment = segments[index];
+ if (segment === '*') {
+ if (node.param) {
+ _remove(node.param, method, segments, index + 1);
+ if (_isEmptyNode(node.param)) node.param = void 0;
+ }
+ return;
+ }
+ if (segment.startsWith('**')) {
+ if (node.wildcard) {
+ _remove(node.wildcard, method, segments, index + 1);
+ if (_isEmptyNode(node.wildcard)) node.wildcard = void 0;
+ }
+ return;
+ }
+ const childNode = node.static?.[segment];
+ if (childNode) {
+ _remove(childNode, method, segments, index + 1);
+ if (_isEmptyNode(childNode)) {
+ delete node.static[segment];
+ if (Object.keys(node.static).length === 0) node.static = void 0;
+ }
+ }
+}
+function _isEmptyNode(node) {
+ return node.methods === void 0 && node.static === void 0 && node.param === void 0 && node.wildcard === void 0;
+}
+
+function findAllRoutes(ctx, method = '', path, opts) {
+ if (path.charCodeAt(path.length - 1) === 47) path = path.slice(0, -1);
+ const segments = splitPath(path);
+ const matches = _findAll(ctx, ctx.root, method, segments, 0);
+ if (opts?.params === false) return matches;
+ return matches.map(m => {
+ return {
+ data: m.data,
+ params: m.paramsMap ? getMatchParams(segments, m.paramsMap) : void 0
+ };
+ });
+}
+function _findAll(ctx, node, method, segments, index, matches = []) {
+ const segment = segments[index];
+ if (node.wildcard && node.wildcard.methods) {
+ const match = node.wildcard.methods[method] || node.wildcard.methods[''];
+ if (match) matches.push(...match);
+ }
+ if (node.param) {
+ _findAll(ctx, node.param, method, segments, index + 1, matches);
+ if (index === segments.length && node.param.methods) {
+ const match = node.param.methods[method] || node.param.methods[''];
+ if (match) {
+ const pMap = match[0].paramsMap;
+ if (pMap?.[pMap?.length - 1]?.[2]) matches.push(...match);
+ }
+ }
+ }
+ const staticChild = node.static?.[segment];
+ if (staticChild) _findAll(ctx, staticChild, method, segments, index + 1, matches);
+ if (index === segments.length && node.methods) {
+ const match = node.methods[method] || node.methods[''];
+ if (match) matches.push(...match);
+ }
+ return matches;
+}
+
+function routeToRegExp(route = '/') {
+ const reSegments = [];
+ let idCtr = 0;
+ for (const segment of route.split('/')) {
+ if (!segment) continue;
+ if (segment === '*') reSegments.push(`(?<_${idCtr++}>[^/]*)`);
+ else if (segment.startsWith('**')) reSegments.push(segment === '**' ? '?(?<_>.*)' : `?(?<${segment.slice(3)}>.+)`);
+ else if (segment.includes(':')) reSegments.push(segment.replace(/:(\w+)/g, (_, id) => `(?<${id}>[^/]+)`).replace(/\./g, '\\.'));
+ else reSegments.push(segment);
+ }
+ return /* @__PURE__ */ new RegExp(`^/${reSegments.join('/')}/?$`);
+}
+
+export { NullProtoObj, addRoute, createRouter, findAllRoutes, findRoute, removeRoute, routeToRegExp };
diff --git a/examples/server/radix3.js b/examples/server/radix3.js
deleted file mode 100644
index 72a0e45..0000000
--- a/examples/server/radix3.js
+++ /dev/null
@@ -1,306 +0,0 @@
-class Radix3Node {
- constructor(method) {
- this.method = method;
- this.prefix = '';
- this.handler = undefined;
- this.children = [];
- this.paramChild = undefined;
- this.wildcardChild = undefined;
- this.paramName = undefined;
- }
-}
-
-export class Radix3 {
- constructor() {
- this.methods = {};
- }
-
- longestCommonPrefix(a, b) {
- const minLen = a.length < b.length ? a.length : b.length;
- for (let i = 0; i < minLen; i = i + 1) {
- if (a[i] !== b[i]) return i;
- }
- return minLen;
- }
-
- insert(path, handler, method = 'GET') {
- if (this.methods[method] === undefined) {
- this.methods[method] = new Radix3Node(method);
- }
- this.insertPath(this.methods[method], path, handler, 0);
- }
-
- get(path, handler) {
- this.insert(path, handler, 'GET');
- }
-
- post(path, handler) {
- this.insert(path, handler, 'POST');
- }
-
- put(path, handler) {
- this.insert(path, handler, 'PUT');
- }
-
- delete(path, handler) {
- this.insert(path, handler, 'DELETE');
- }
-
- patch(path, handler) {
- this.insert(path, handler, 'PATCH');
- }
-
- head(path, handler) {
- this.insert(path, handler, 'HEAD');
- }
-
- options(path, handler) {
- this.insert(path, handler, 'OPTIONS');
- }
-
- insertPath(node, path, handler, start) {
- if (start >= path.length) {
- node.handler = handler;
- return;
- }
-
- const char = path[start];
-
- if (char === ':') {
- let end = start + 1;
- for (let i = end; i < path.length; i = i + 1) {
- if (path[i] === '/') break;
- end = i + 1;
- }
-
- const paramName = path.substring(start + 1, end);
-
- if (node.paramChild === undefined) {
- node.paramChild = new Radix3Node();
- node.paramChild.paramName = paramName;
- }
-
- this.insertPath(node.paramChild, path, handler, end);
- return;
- }
-
- if (char === '*') {
- const paramName = path.substring(start + 1, path.length);
-
- if (node.wildcardChild === undefined) {
- node.wildcardChild = new Radix3Node();
- node.wildcardChild.paramName = paramName;
- }
-
- node.wildcardChild.handler = handler;
- return;
- }
-
- let end = start;
- for (let i = start; i < path.length; i = i + 1) {
- if (path[i] === ':' || path[i] === '*') break;
- end = i + 1;
- }
-
- const segment = path.substring(start, end);
-
- for (let i = 0; i < node.children.length; i = i + 1) {
- const child = node.children[i];
- const commonLen = this.longestCommonPrefix(child.prefix, segment);
-
- if (commonLen > 0) {
- if (commonLen < child.prefix.length) {
- const splitNode = new Radix3Node();
- splitNode.prefix = child.prefix.substring(commonLen, child.prefix.length);
- splitNode.handler = child.handler;
- splitNode.children = child.children;
- splitNode.paramChild = child.paramChild;
- splitNode.wildcardChild = child.wildcardChild;
-
- child.prefix = child.prefix.substring(0, commonLen);
- child.handler = undefined;
- child.children = [splitNode];
- child.paramChild = undefined;
- child.wildcardChild = undefined;
- }
-
- if (commonLen < segment.length) {
- this.insertPath(child, path, handler, start + commonLen);
- } else {
- this.insertPath(child, path, handler, end);
- }
-
- return;
- }
- }
-
- const newChild = new Radix3Node();
- newChild.prefix = segment;
- node.children.push(newChild);
- this.insertPath(newChild, path, handler, end);
- }
-
- lookup(path, method = 'GET') {
- const methodRoot = this.methods[method];
- if (methodRoot === undefined) return undefined;
-
- const params = {};
- const handler = this.matchPath(methodRoot, path, 0, params);
-
- if (!handler) return undefined;
- return { handler, params };
- }
-
- matchPath(node, path, depth, params) {
- if (depth >= path.length) {
- return node.handler;
- }
-
- const remaining = path.substring(depth, path.length);
- for (let i = 0; i < node.children.length; i = i + 1) {
- const child = node.children[i];
-
- if (remaining.length >= child.prefix.length) {
- let matches = true;
- for (let j = 0; j < child.prefix.length; j = j + 1) {
- if (remaining[j] !== child.prefix[j]) {
- matches = false;
- break;
- }
- }
-
- if (matches) {
- const result = this.matchPath(child, path, depth + child.prefix.length, params);
- if (result !== undefined) return result;
- }
- }
- }
-
- if (node.paramChild !== undefined) {
- let paramValue = '';
- let offset = depth;
-
- for (let i = depth; i < path.length; i = i + 1) {
- if (path[i] === '/') break;
- paramValue = paramValue + path[i];
- offset = i + 1;
- }
-
- if (paramValue !== '') {
- params[node.paramChild.paramName] = paramValue;
- const result = this.matchPath(node.paramChild, path, offset, params);
- if (result !== undefined) return result;
- delete params[node.paramChild.paramName];
- }
- }
-
- if (node.wildcardChild !== undefined) {
- const wildcardValue = path.substring(depth, path.length);
- params[node.wildcardChild.paramName] = wildcardValue;
- return node.wildcardChild.handler;
- }
-
- return undefined;
- }
-
- printTree() {
- const methods = Object.keys(this.methods);
- for (let i = 0; i < methods.length; i = i + 1) {
- const method = methods[i];
- console.log('[' + method + ']');
-
- const routes = [];
- this.collectRoutes(this.methods[method], '', routes);
-
- const tree = {};
- for (let j = 0; j < routes.length; j = j + 1) {
- const route = routes[j];
- const parts = this.splitPath(route.path);
- let current = tree;
- for (let k = 0; k < parts.length; k = k + 1) {
- const part = parts[k];
- if (current[part] === undefined) {
- current[part] = { _children: {}, _handler: false };
- }
- if (k === parts.length - 1) {
- current[part]._handler = route.hasHandler;
- }
- current = current[part]._children;
- }
- }
-
- this.printPathTree(tree, '');
- if (i < methods.length - 1) console.log('');
- }
- }
-
- splitPath(path) {
- const parts = [];
- let i = 0;
-
- while (i < path.length) {
- if (path[i] === '/') {
- let segment = '/';
- i = i + 1;
- while (i < path.length && path[i] !== '/' && path[i] !== ':' && path[i] !== '*') {
- segment = segment + path[i];
- i = i + 1;
- }
- if (segment !== '/') {
- parts.push(segment);
- } else if (parts.length === 0) {
- parts.push('/');
- }
- } else if (path[i] === ':' || path[i] === '*') {
- let segment = path[i];
- i = i + 1;
- while (i < path.length && path[i] !== '/') {
- segment = segment + path[i];
- i = i + 1;
- }
- parts.push(segment);
- } else {
- i = i + 1;
- }
- }
-
- return parts;
- }
-
- printPathTree(tree, indent) {
- const keys = Object.keys(tree);
- for (let i = 0; i < keys.length; i = i + 1) {
- const key = keys[i];
- const node = tree[key];
- const isLast = i === keys.length - 1;
- const marker = isLast ? '└─ ' : '├─ ';
- let line = indent + marker + key;
- if (node._handler) {
- line = line + ' [HANDLER]';
- }
- console.log(line);
- const childIndent = indent + (isLast ? ' ' : '│ ');
- this.printPathTree(node._children, childIndent);
- }
- }
-
- collectRoutes(node, currentPath, routes) {
- const path = currentPath + node.prefix;
-
- if (node.handler !== undefined) {
- routes.push({ path: path, hasHandler: true });
- }
-
- for (let i = 0; i < node.children.length; i = i + 1) {
- this.collectRoutes(node.children[i], path, routes);
- }
-
- if (node.paramChild !== undefined) {
- this.collectRoutes(node.paramChild, path + ':' + node.paramChild.paramName, routes);
- }
-
- if (node.wildcardChild !== undefined) {
- routes.push({ path: path + '*' + node.wildcardChild.paramName, hasHandler: node.wildcardChild.handler !== undefined });
- }
- }
-}
diff --git a/examples/server/server.js b/examples/server/server.js
index 3b0a840..3f24ba5 100644
--- a/examples/server/server.js
+++ b/examples/server/server.js
@@ -2,87 +2,84 @@ import meow from './meow.txt';
import { join } from 'ant:path';
import { html } from './html';
import { readFile } from 'ant:fs';
-import { Radix3 } from './radix3';
+import { createRouter, addRoute, findRoute } from '../rou3';
-const router = new Radix3();
+const router = createRouter();
-router.get('/', c => c.res.body(`Welcome to Ant ${Ant.version}!`));
+addRoute(router, 'GET', '/', c => c.res.body(`Welcome to Ant ${Ant.version}!`));
-router.get('/meow', async c => {
+addRoute(router, 'GET', '/meow', async c => {
const userAgent = c.req.header('User-Agent');
c.res.header('X-Ant', 'meow');
return c.res.body(`${meow}\n\n${userAgent}`);
});
-router.get('/echo', c =>
+addRoute(router, 'GET', '/echo', c =>
fetch('http://localhost:8000/meow').then(res => {
c.res.header('X-Ant', 'meow');
c.res.body(res.body);
})
);
-router.get('/get', c => {
+addRoute(router, 'GET', '/get', c => {
console.log(c.get('meow'));
c.res.body(decodeURIComponent(c.get('meow')));
});
-router.get('/set/*val', c => {
+addRoute(router, 'GET', '/set/**:val', c => {
c.set('meow', c.params.val);
c.res.body(`meow = ${c.params.val}`);
});
-router.get('/fs/meow', async c => {
+addRoute(router, 'GET', '/fs/meow', async c => {
const file = await readFile(join(import.meta.dirname, 'meow.txt'));
return c.res.body(file || 'none');
});
-router.get('/hello', async c => {
+addRoute(router, 'GET', '/hello', async c => {
return c.res.body('Hello, World!');
});
-router.get('/status', async c => {
+addRoute(router, 'GET', '/status', async c => {
await new Promise(resolve => setTimeout(resolve, 1000));
const result = await Promise.resolve('Hello');
return c.res.body(`server is responding with ${result}`);
});
-router.post('/users/:id', async c => {
+addRoute(router, 'POST', '/users/:id', async c => {
return c.res.body(`User ID: ${c.params.id}`);
});
-router.get('/users/:id/posts', async c => {
+addRoute(router, 'GET', '/users/:id/posts', async c => {
return c.res.body(`Posts for user: ${c.params.id}`);
});
-router.get('/api/v1/users', async c => {
+addRoute(router, 'GET', '/api/v1/users', async c => {
return c.res.json({ users: [] });
});
-router.get('/zen', async c => {
+addRoute(router, 'GET', '/zen', async c => {
const response = await fetch('https://api.github.com/zen');
return c.res.body(response.body);
});
-router.get('/api/v2/demo', async c => {
+addRoute(router, 'GET', '/api/v2/demo', async c => {
const data = await fetch('https://themackabu.dev/test.json');
return c.res.json(data.json());
});
-router.get('/files/*path', async c => {
+addRoute(router, 'GET', '/files/**:path', async c => {
return c.res.html(html`
${c.params.path}
`);
});
-router.printTree();
-console.log('');
-
async function handleRequest(c) {
console.log('request:', c.req.method, c.req.uri);
- const result = router.lookup(c.req.uri, c.req.method);
+ const result = findRoute(router, c.req.method, c.req.uri);
- if (result?.handler) {
+ if (result?.data) {
c.params = result.params;
- return await result.handler(c);
+ return await result.data(c);
}
c.res.body('not found: ' + c.req.uri, 404);
diff --git a/examples/spa/server.js b/examples/spa/server.js
index 9bcec28..a87972c 100644
--- a/examples/spa/server.js
+++ b/examples/spa/server.js
@@ -1,8 +1,8 @@
import { open } from 'ant:fs';
import { join, extname } from 'ant:path';
-import { Radix3 } from '../server/radix3';
+import { createRouter, addRoute, findRoute } from '../rou3';
-const router = new Radix3();
+const router = createRouter();
const validPaths = new Set();
const invalidPaths = new Set();
@@ -25,13 +25,13 @@ const mimeTypes = new Map([
['.woff2', 'font/woff2']
]);
-router.get('/api/version', async c => c.res.json({ version: Ant.version }));
+addRoute(router, 'GET', '/api/version', async c => c.res.json({ version: Ant.version }));
-router.get('*path', c => {
- const reqPath = c.params.path;
+addRoute(router, 'GET', '**', c => {
+ const reqPath = c.req.uri;
if (reqPath === '/') return c.res.body(open(indexPath), 200, 'text/html');
- const filePath = reqPath === '/' ? indexPath : join(basePath, reqPath);
+ const filePath = join(basePath, reqPath);
if (validPaths.has(filePath)) {
const ext = extname(reqPath) || '.html';
@@ -54,20 +54,17 @@ router.get('*path', c => {
}
});
-router.printTree();
-console.log('');
-
async function handleRequest(c) {
console.log('request:', c.req.method, c.req.uri);
- const result = router.lookup(c.req.uri, c.req.method);
+ const result = findRoute(router, c.req.method, c.req.uri);
- if (result?.handler) {
+ if (result?.data) {
c.params = result.params;
- return await result.handler(c);
+ return await result.data(c);
}
c.res.body('not found: ' + c.req.uri, 404);
}
-console.log('started on http://localhost:8000');
-Ant.serve(8000, handleRequest);
+console.log('started on http://localhost:6369');
+Ant.serve(6369, handleRequest);
diff --git a/include/ant.h b/include/ant.h
index 097b6c7..c920400 100644
--- a/include/ant.h
+++ b/include/ant.h
@@ -9,6 +9,7 @@
#define STR_PROTO "__proto__"
#define STR_PROTO_LEN 9
+#define ANT_LIMIT_SIZE_CACHE 16384
struct js;
diff --git a/include/config.h b/include/config.h
index ea47e03..7f6df77 100644
--- a/include/config.h
+++ b/include/config.h
@@ -29,8 +29,10 @@ typedef enum {
SLOT_THIS,
SLOT_BOUND_THIS,
SLOT_BOUND_ARGS,
- SLOT_STRICT,
SLOT_FIELD_COUNT,
+ SLOT_SOURCE,
+ SLOT_FIELDS,
+ SLOT_STRICT,
SLOT_CODE,
SLOT_CFUNC,
SLOT_CORO,
@@ -39,6 +41,9 @@ typedef enum {
SLOT_SEALED,
SLOT_EXTENSIBLE,
SLOT_BUFFER,
+ SLOT_TARGET_FUNC,
+ SLOT_VERSION,
+ SLOT_NAME,
SLOT_MAX = 255
} internal_slot_t;
diff --git a/include/config.h.in b/include/config.h.in
index 4cb8d67..bf328af 100644
--- a/include/config.h.in
+++ b/include/config.h.in
@@ -22,6 +22,8 @@ typedef enum {
SLOT_BOUND_THIS,
SLOT_BOUND_ARGS,
SLOT_FIELD_COUNT,
+ SLOT_SOURCE,
+ SLOT_FIELDS,
SLOT_STRICT,
SLOT_CODE,
SLOT_CFUNC,
@@ -31,6 +33,9 @@ typedef enum {
SLOT_SEALED,
SLOT_EXTENSIBLE,
SLOT_BUFFER,
+ SLOT_TARGET_FUNC,
+ SLOT_VERSION,
+ SLOT_NAME,
SLOT_MAX = 255
} internal_slot_t;
diff --git a/meson.build b/meson.build
index 2a96f55..6320b38 100644
--- a/meson.build
+++ b/meson.build
@@ -79,7 +79,7 @@ endif
build_date = run_command('date', '+%Y-%m-%d', check: true).stdout().strip()
version_conf = configuration_data()
-version_conf.set('ANT_VERSION', '0.2.2.39')
+version_conf.set('ANT_VERSION', '0.2.2.56')
version_conf.set('ANT_GIT_HASH', git_hash)
version_conf.set('ANT_BUILD_DATE', build_date)
diff --git a/src/ant.c b/src/ant.c
index cecac42..80bab55 100644
--- a/src/ant.c
+++ b/src/ant.c
@@ -37,87 +37,6 @@
#define CORO_MALLOC(size) calloc(1, size)
#define CORO_FREE(ptr) free(ptr)
-/* ========== PERFORMANCE INSTRUMENTATION ========== */
-#define PERF_INSTRUMENTATION 1
-
-#if PERF_INSTRUMENTATION
-#include
-
-static uint64_t perf_call_count = 0;
-static uint64_t perf_phase4_ns = 0;
-static uint64_t perf_phase4_sub1_ns = 0; // Parameter binding loop
-static uint64_t perf_phase4_sub2_ns = 0; // Rest parameter handling
-static uint64_t perf_phase4_sub3_ns = 0; // code_uses_arguments check
-static uint64_t perf_phase4_sub4_ns = 0; // setup_arguments
-static uint64_t perf_phase4_sub5_ns = 0; // NFE name binding
-static uint64_t perf_phase4_sub6_ns = 0; // Strict mode lookup + this handling
-static uint64_t perf_sub5_vstr_ns = 0; // vstr inside sub5
-static uint64_t perf_sub5_mkprop_ns = 0; // mkprop inside sub5
-static uint64_t perf_sub5_count = 0; // Times sub5 actually runs
-static uint64_t perf_mkprop_intern_ns = 0; // intern_string inside mkprop
-static uint64_t perf_mkprop_setkoff_ns = 0; // set_koff_intern inside mkprop
-static uint64_t perf_mkprop_mkentity_ns = 0; // mkentity inside mkprop
-static uint64_t perf_mkprop_setup_ns = 0; // setup (memcpy) inside mkprop
-static uint64_t perf_mkprop_count = 0;
-static uint64_t perf_lkp_calls = 0;
-static uint64_t perf_lkp_cache_hits = 0;
-static uint64_t perf_lkp_not_found = 0;
-static uint64_t perf_lkp_found_no_cache = 0; /* Found but wasn't in cache */
-static uint64_t perf_lkp_trace_count = 0;
-#define PERF_LKP_TRACE 1 /* Set to 1 to trace lookups */
-static uint64_t perf_report_interval = 100000;
-static mach_timebase_info_data_t perf_timebase = {0};
-
-static inline uint64_t perf_now_ns(void) {
- if (perf_timebase.denom == 0) mach_timebase_info(&perf_timebase);
- return mach_absolute_time() * perf_timebase.numer / perf_timebase.denom;
-}
-
-static void perf_report(size_t heap_size) {
- if (perf_call_count == 0) return;
- fprintf(stderr, "\n[PERF] After %llu calls, heap=%zu KB\n", perf_call_count, heap_size / 1024);
- fprintf(stderr, "[PERF] phase4 total: %.1f ns/call\n", (double)perf_phase4_ns / perf_call_count);
- fprintf(stderr, "[PERF] sub1 (param bind): %.1f ns\n", (double)perf_phase4_sub1_ns / perf_call_count);
- fprintf(stderr, "[PERF] sub2 (rest params): %.1f ns\n", (double)perf_phase4_sub2_ns / perf_call_count);
- fprintf(stderr, "[PERF] sub3 (uses_arguments): %.1f ns\n", (double)perf_phase4_sub3_ns / perf_call_count);
- fprintf(stderr, "[PERF] sub4 (setup_args): %.1f ns\n", (double)perf_phase4_sub4_ns / perf_call_count);
- fprintf(stderr, "[PERF] sub5 (NFE bind): %.1f ns (runs=%llu, %.1f%%)\n",
- (double)perf_phase4_sub5_ns / perf_call_count,
- perf_sub5_count, 100.0 * perf_sub5_count / perf_call_count);
- if (perf_sub5_count > 0) {
- fprintf(stderr, "[PERF] sub5.vstr: %.1f ns/run\n", (double)perf_sub5_vstr_ns / perf_sub5_count);
- fprintf(stderr, "[PERF] sub5.mkprop: %.1f ns/run\n", (double)perf_sub5_mkprop_ns / perf_sub5_count);
- }
- fprintf(stderr, "[PERF] sub6 (strict+this): %.1f ns\n", (double)perf_phase4_sub6_ns / perf_call_count);
- if (perf_mkprop_count > 0) {
- fprintf(stderr, "[PERF] mkprop breakdown (%llu calls):\n", perf_mkprop_count);
- fprintf(stderr, "[PERF] setup: %.1f ns\n", (double)perf_mkprop_setup_ns / perf_mkprop_count);
- fprintf(stderr, "[PERF] intern: %.1f ns\n", (double)perf_mkprop_intern_ns / perf_mkprop_count);
- fprintf(stderr, "[PERF] setkoff: %.1f ns\n", (double)perf_mkprop_setkoff_ns / perf_mkprop_count);
- fprintf(stderr, "[PERF] mkentity: %.1f ns\n", (double)perf_mkprop_mkentity_ns / perf_mkprop_count);
- }
- if (perf_lkp_calls > 0) {
- fprintf(stderr, "[PERF] lkp_interned: %llu calls, %.1f%% cache hits, %.1f%% found (no cache), %.1f%% not found\n",
- perf_lkp_calls,
- 100.0 * perf_lkp_cache_hits / perf_lkp_calls,
- 100.0 * perf_lkp_found_no_cache / perf_lkp_calls,
- 100.0 * perf_lkp_not_found / perf_lkp_calls);
- fprintf(stderr, "[PERF] per function call: %.1f lookups\n", (double)perf_lkp_calls / perf_call_count);
- }
-}
-
-#define PERF_START(var) uint64_t var = perf_now_ns()
-#define PERF_END(var, accum) (accum) += (perf_now_ns() - (var))
-#define PERF_CALL_INC() (++perf_call_count)
-#define PERF_SHOULD_REPORT() ((perf_call_count % perf_report_interval) == 0)
-#else
-#define PERF_START(var) (void)0
-#define PERF_END(var, accum) (void)0
-#define PERF_CALL_INC() (void)0
-#define PERF_SHOULD_REPORT() (false)
-#endif
-/* ========== END PERFORMANCE INSTRUMENTATION ========== */
-
_Static_assert(sizeof(double) == 8, "NaN-boxing requires 64-bit IEEE 754 doubles");
_Static_assert(sizeof(uint64_t) == 8, "NaN-boxing requires 64-bit integers");
_Static_assert(sizeof(double) == sizeof(uint64_t), "double and uint64_t must have same size");
@@ -278,16 +197,6 @@ typedef struct {
UT_hash_handle hh;
} obj_prop_cache_t;
-typedef struct interned_string {
- uint64_t hash;
- char *str;
- size_t len;
- struct interned_string *next;
-} interned_string_t;
-
-#define INTERN_BUCKETS 16384
-static interned_string_t *intern_buckets[INTERN_BUCKETS];
-
static const char *INTERN_LENGTH = NULL;
static const char *INTERN_PROTOTYPE = NULL;
static const char *INTERN_CONSTRUCTOR = NULL;
@@ -296,19 +205,28 @@ static const char *INTERN_MESSAGE = NULL;
static const char *INTERN_VALUE = NULL;
static const char *INTERN_GET = NULL;
static const char *INTERN_SET = NULL;
-static const char *INTERN_TARGET_FUNC = NULL;
+
static const char *INTERN_ARGUMENTS = NULL;
static const char *INTERN_CALLEE = NULL;
static const char *INTERN_IDX[10] = {NULL};
static const char *INTERN_PROMISE = NULL;
-#define INTERN_PROP_CACHE_SIZE 4096
+typedef struct interned_string {
+ uint64_t hash;
+ char *str;
+ size_t len;
+ struct interned_string *next;
+} interned_string_t;
+
+static interned_string_t *intern_buckets[ANT_LIMIT_SIZE_CACHE];
+
typedef struct {
jsoff_t obj_off;
const char *intern_ptr;
jsoff_t prop_off;
+ uint32_t obj_version;
} intern_prop_cache_entry_t;
-static intern_prop_cache_entry_t intern_prop_cache[INTERN_PROP_CACHE_SIZE];
+static intern_prop_cache_entry_t intern_prop_cache[ANT_LIMIT_SIZE_CACHE];
typedef struct promise_handler {
jsval_t onFulfilled;
@@ -478,6 +396,7 @@ struct js {
jsval_t tval; // holds last parsed numeric or string literal value
jsval_t scope; // current scope
jsval_t this_val; // 'this' value for currently executing function
+ jsval_t module_ns; // current ESM module namespace
uint8_t *mem; // available JS memory
jsoff_t size; // memory size
jsoff_t brk; // current mem usage boundary
@@ -509,7 +428,8 @@ enum {
TOK_COLON, TOK_Q, TOK_ASSIGN, TOK_PLUS_ASSIGN, TOK_MINUS_ASSIGN,
TOK_MUL_ASSIGN, TOK_DIV_ASSIGN, TOK_REM_ASSIGN, TOK_SHL_ASSIGN,
TOK_SHR_ASSIGN, TOK_ZSHR_ASSIGN, TOK_AND_ASSIGN, TOK_XOR_ASSIGN,
- TOK_OR_ASSIGN, TOK_COMMA, TOK_TEMPLATE, TOK_ARROW, TOK_HASH,
+ TOK_OR_ASSIGN, TOK_LOR_ASSIGN, TOK_LAND_ASSIGN, TOK_NULLISH_ASSIGN,
+ TOK_COMMA, TOK_TEMPLATE, TOK_ARROW, TOK_HASH,
TOK_MAX
};
@@ -740,7 +660,8 @@ static bool is_unary(uint8_t tok) {
}
static bool is_assign(uint8_t tok) {
- return (tok >= TOK_ASSIGN && tok <= TOK_OR_ASSIGN);
+ return (tok >= TOK_ASSIGN && tok <= TOK_OR_ASSIGN) ||
+ tok == TOK_LOR_ASSIGN || tok == TOK_LAND_ASSIGN || tok == TOK_NULLISH_ASSIGN;
}
static bool is_keyword_propname(uint8_t tok) {
@@ -845,6 +766,7 @@ static jsval_t do_instanceof(struct js *js, jsval_t l, jsval_t r);
static jsval_t do_in(struct js *js, jsval_t l, jsval_t r);
static jsval_t resolveprop(struct js *js, jsval_t v);
static jsoff_t lkp(struct js *js, jsval_t obj, const char *buf, size_t len);
+static jsoff_t lkp_scope(struct js *js, jsval_t scope, const char *buf, size_t len);
static jsoff_t lkp_interned(struct js *js, jsval_t obj, const char *search_intern, size_t len);
static const char *intern_string(const char *str, size_t len);
static jsval_t get_slot(struct js *js, jsval_t obj, internal_slot_t slot);
@@ -961,7 +883,13 @@ static void mco_async_entry(mco_coro* mco) {
ctx->has_error = is_err(result);
if (ctx->has_error) {
- js_reject_promise(js, ctx->promise, result);
+ jsval_t reject_value = js->thrown_value;
+ if (vtype(reject_value) == T_UNDEF) {
+ reject_value = js_mkstr(js, js->errmsg ? js->errmsg : "Unknown error", js->errmsg ? strlen(js->errmsg) : 13);
+ }
+ js->flags &= (uint8_t)~F_THROW;
+ js->thrown_value = js_mkundef();
+ js_reject_promise(js, ctx->promise, reject_value);
} else {
js_resolve_promise(js, ctx->promise, result);
}
@@ -1990,7 +1918,7 @@ static size_t strfunc_ctor(struct js *js, jsval_t func_obj, char *buf, size_t le
jsoff_t proto_off = lkp_interned(js, func_obj, INTERN_PROTOTYPE, 9);
if (proto_off != 0) {
jsval_t proto_val = resolveprop(js, mkval(T_PROP, proto_off));
- if (vtype(proto_val) == T_OBJ) n += print_prototype(js, proto_val, buf + n, len - n, &first);
+ if (vtype(proto_val) == T_OBJ && !is_on_stack(proto_val)) n += print_prototype(js, proto_val, buf + n, len - n, &first);
}
stringify_indent--;
@@ -2909,7 +2837,7 @@ static inline uint64_t hash_key(const char *key, size_t len) {
static const char *intern_string(const char *str, size_t len) {
uint64_t h = hash_key(str, len);
- uint32_t bucket = (uint32_t)(h & (INTERN_BUCKETS - 1));
+ uint32_t bucket = (uint32_t)(h & (ANT_LIMIT_SIZE_CACHE - 1));
for (interned_string_t *e = intern_buckets[bucket]; e; e = e->next) {
if (e->hash == h && e->len == len && memcmp(e->str, str, len) == 0) return e->str;
@@ -2941,7 +2869,6 @@ static void intern_init(void) {
INTERN_GET = intern_string("get", 3);
INTERN_SET = intern_string("set", 3);
INTERN_PROMISE = intern_string("promise", 7);
- INTERN_TARGET_FUNC = intern_string("__target_func", 13);
INTERN_ARGUMENTS = intern_string("arguments", 9);
INTERN_CALLEE = intern_string("callee", 6);
INTERN_IDX[0] = intern_string("0", 1);
@@ -2956,18 +2883,13 @@ static void intern_init(void) {
INTERN_IDX[9] = intern_string("9", 1);
}
-static void invalidate_prop_cache(jsoff_t obj_offset) {
- for (uint32_t i = 0; i < INTERN_PROP_CACHE_SIZE; i++) {
- if (intern_prop_cache[i].obj_off == obj_offset) {
- intern_prop_cache[i].obj_off = 0;
- intern_prop_cache[i].intern_ptr = NULL;
- intern_prop_cache[i].prop_off = 0;
- }
- }
+static void increment_version(struct js *js, jsoff_t obj_off) {
+ jsval_t version_val = get_slot(js, mkval(T_OBJ, obj_off), SLOT_VERSION);
+ uint32_t new_version = (vtype(version_val) == T_NUM) ? (uint32_t)tod(version_val) + 1 : 1;
+ set_slot(js, mkval(T_OBJ, obj_off), SLOT_VERSION, tov((double)new_version));
}
static jsval_t mkprop(struct js *js, jsval_t obj, jsval_t k, jsval_t v, bool is_const) {
- PERF_START(_mkprop_setup);
jsoff_t koff = (jsoff_t) vdata(k);
jsoff_t b, head = (jsoff_t) vdata(obj);
char buf[sizeof(koff) + sizeof(v)];
@@ -2978,24 +2900,51 @@ static jsval_t mkprop(struct js *js, jsval_t obj, jsval_t k, jsval_t v, bool is_
if (b & ARRMASK) brk |= ARRMASK;
memcpy(&js->mem[head], &brk, sizeof(brk));
- PERF_END(_mkprop_setup, perf_mkprop_setup_ns);
- PERF_START(_mkprop_intern);
jsoff_t klen = (loadoff(js, koff) >> 2) - 1;
const char *p = (char *) &js->mem[koff + sizeof(koff)];
(void)intern_string(p, klen); /* Ensure key is interned for fast lookup later */
- PERF_END(_mkprop_intern, perf_mkprop_intern_ns);
- PERF_START(_mkprop_mkentity);
jsoff_t prop_header = (b & ~(3U | CONSTMASK | ARRMASK | SLOTMASK)) | T_PROP;
if (is_const) prop_header |= CONSTMASK;
jsval_t result = mkentity(js, prop_header, buf, sizeof(buf));
- PERF_END(_mkprop_mkentity, perf_mkprop_mkentity_ns);
- perf_mkprop_count++;
+ increment_version(js, head);
return result;
}
+static inline jsval_t mkprop_fast(struct js *js, jsval_t obj, jsval_t k, jsval_t v, bool is_const) {
+ jsoff_t koff = (jsoff_t) vdata(k);
+ jsoff_t b, head = (jsoff_t) vdata(obj);
+ char buf[sizeof(koff) + sizeof(v)];
+ memcpy(&b, &js->mem[head], sizeof(b));
+ memcpy(buf, &koff, sizeof(koff));
+ memcpy(buf + sizeof(koff), &v, sizeof(v));
+ jsoff_t brk = js->brk | T_OBJ;
+
+ if (b & ARRMASK) brk |= ARRMASK;
+ memcpy(&js->mem[head], &brk, sizeof(brk));
+
+ jsoff_t prop_header = (b & ~(3U | CONSTMASK | ARRMASK | SLOTMASK)) | T_PROP;
+ if (is_const) prop_header |= CONSTMASK;
+ return mkentity(js, prop_header, buf, sizeof(buf));
+}
+
+static inline jsval_t mkprop_new(struct js *js, jsval_t obj, jsoff_t koff, jsval_t v) {
+ jsoff_t b = loadoff(js, (jsoff_t)vdata(obj));
+ jsoff_t brk = js->brk | T_OBJ;
+
+ if (b & ARRMASK) brk |= ARRMASK;
+ memcpy(&js->mem[(jsoff_t)vdata(obj)], &brk, sizeof(brk));
+
+ char buf[sizeof(koff) + sizeof(v)];
+ memcpy(buf, &koff, sizeof(koff));
+ memcpy(buf + sizeof(koff), &v, sizeof(v));
+
+ jsoff_t prop_header = (b & ~(3U | CONSTMASK | ARRMASK | SLOTMASK)) | T_PROP;
+ return mkentity(js, prop_header, buf, sizeof(buf));
+}
+
static jsval_t mkslot(struct js *js, jsval_t obj, internal_slot_t slot, jsval_t v) {
jsoff_t b, head = (jsoff_t) vdata(obj);
char buf[sizeof(jsoff_t) + sizeof(v)];
@@ -3168,6 +3117,7 @@ no_descriptor:
}
saveval(js, existing + sizeof(jsoff_t) * 2, v);
+ increment_version(js, (jsoff_t)vdata(obj));
if (vtype(obj) != T_ARR || klen == 0 || key[0] < '0' || key[0] > '9') goto done_update;
char *endptr;
@@ -3184,8 +3134,10 @@ no_descriptor:
jsval_t len_key = js_mkstr(js, "length", 6);
jsval_t new_len = tov((double)(update_idx + 1));
- if (len_off != 0) saveval(js, len_off + sizeof(jsoff_t) * 2, new_len);
- else mkprop(js, obj, len_key, new_len, false);
+ if (len_off != 0) {
+ saveval(js, len_off + sizeof(jsoff_t) * 2, new_len);
+ increment_version(js, (jsoff_t)vdata(obj));
+ } else mkprop(js, obj, len_key, new_len, false);
done_update:
return mkval(T_PROP, existing);
@@ -3232,6 +3184,7 @@ create_new:
jsval_t new_len = tov((double)(idx + 1));
if (len_off != 0) {
saveval(js, len_off + sizeof(jsoff_t) * 2, new_len);
+ increment_version(js, (jsoff_t)vdata(obj));
} else mkprop(js, obj, len_key, new_len, false);
}
@@ -3587,7 +3540,7 @@ static uint8_t next(struct js *js) {
#define LOOK(OFS, CH) js->toff + OFS < js->clen && buf[OFS] == CH
switch (buf[0]) {
- case '?': if (LOOK(1, '?')) TOK(TOK_NULLISH, 2); if (LOOK(1, '.')) TOK(TOK_OPTIONAL_CHAIN, 2); TOK(TOK_Q, 1);
+ case '?': if (LOOK(1, '?') && LOOK(2, '=')) TOK(TOK_NULLISH_ASSIGN, 3); if (LOOK(1, '?')) TOK(TOK_NULLISH, 2); if (LOOK(1, '.')) TOK(TOK_OPTIONAL_CHAIN, 2); TOK(TOK_Q, 1);
case ':': TOK(TOK_COLON, 1);
case '(': TOK(TOK_LPAREN, 1);
case ')': TOK(TOK_RPAREN, 1);
@@ -3621,8 +3574,8 @@ static uint8_t next(struct js *js) {
case '*': if (LOOK(1, '*')) TOK(TOK_EXP, 2); if (LOOK(1, '=')) TOK(TOK_MUL_ASSIGN, 2); TOK(TOK_MUL, 1);
case '/': if (LOOK(1, '=')) TOK(TOK_DIV_ASSIGN, 2); TOK(TOK_DIV, 1);
case '%': if (LOOK(1, '=')) TOK(TOK_REM_ASSIGN, 2); TOK(TOK_REM, 1);
- case '&': if (LOOK(1, '&')) TOK(TOK_LAND, 2); if (LOOK(1, '=')) TOK(TOK_AND_ASSIGN, 2); TOK(TOK_AND, 1);
- case '|': if (LOOK(1, '|')) TOK(TOK_LOR, 2); if (LOOK(1, '=')) TOK(TOK_OR_ASSIGN, 2); TOK(TOK_OR, 1);
+ case '&': if (LOOK(1, '&') && LOOK(2, '=')) TOK(TOK_LAND_ASSIGN, 3); if (LOOK(1, '&')) TOK(TOK_LAND, 2); if (LOOK(1, '=')) TOK(TOK_AND_ASSIGN, 2); TOK(TOK_AND, 1);
+ case '|': if (LOOK(1, '|') && LOOK(2, '=')) TOK(TOK_LOR_ASSIGN, 3); if (LOOK(1, '|')) TOK(TOK_LOR, 2); if (LOOK(1, '=')) TOK(TOK_OR_ASSIGN, 2); TOK(TOK_OR, 1);
case '=': if (LOOK(1, '=') && LOOK(2, '=')) TOK(TOK_SEQ, 3); if (LOOK(1, '=')) TOK(TOK_EQ, 2); if (LOOK(1, '>')) TOK(TOK_ARROW, 2); TOK(TOK_ASSIGN, 1);
case '<': if (LOOK(1, '<') && LOOK(2, '=')) TOK(TOK_SHL_ASSIGN, 3); if (LOOK(1, '<')) TOK(TOK_SHL, 2); if (LOOK(1, '=')) TOK(TOK_LE, 2); TOK(TOK_LT, 1);
case '>': if (LOOK(1, '>') && LOOK(2, '>') && LOOK(3, '=')) TOK(TOK_ZSHR_ASSIGN, 4); if (LOOK(1, '>') && LOOK(2, '>')) TOK(TOK_ZSHR, 3); if (LOOK(1, '>') && LOOK(2, '=')) TOK(TOK_SHR_ASSIGN, 3); if (LOOK(1, '>')) TOK(TOK_SHR, 2); if (LOOK(1, '=')) TOK(TOK_GE, 2); TOK(TOK_GT, 1);
@@ -3929,19 +3882,14 @@ static jsval_t js_block(struct js *js, bool create_scope) {
}
static inline jsoff_t lkp_interned(struct js *js, jsval_t obj, const char *search_intern, size_t len) {
- perf_lkp_calls++;
-#if PERF_LKP_TRACE
- if (perf_call_count >= 1 && perf_lkp_trace_count < 300) {
- perf_lkp_trace_count++;
- fprintf(stderr, "[LKP] '%.*s' on obj@%lu\n", (int)len, search_intern, (unsigned long)vdata(obj));
- }
-#endif
jsoff_t obj_off = (jsoff_t)vdata(obj);
- uint32_t cache_slot = (((uintptr_t)search_intern >> 3) ^ obj_off) & (INTERN_PROP_CACHE_SIZE - 1);
+ jsval_t version_val = get_slot(js, mkval(T_OBJ, obj_off), SLOT_VERSION);
+ uint32_t current_version = (vtype(version_val) == T_NUM) ? (uint32_t)tod(version_val) : 0;
+
+ uint32_t cache_slot = (((uintptr_t)search_intern >> 3) ^ obj_off) & (ANT_LIMIT_SIZE_CACHE - 1);
intern_prop_cache_entry_t *ce = &intern_prop_cache[cache_slot];
- if (ce->obj_off == obj_off && ce->intern_ptr == search_intern) {
- perf_lkp_cache_hits++;
+ if (ce->obj_off == obj_off && ce->intern_ptr == search_intern && ce->obj_version == current_version) {
return ce->prop_off;
}
@@ -3955,16 +3903,21 @@ static inline jsoff_t lkp_interned(struct js *js, jsval_t obj, const char *searc
if (klen == len) {
const char *p = (char *) &js->mem[koff + sizeof(koff)];
if (intern_string(p, klen) == search_intern) {
- perf_lkp_found_no_cache++;
ce->obj_off = obj_off;
ce->intern_ptr = search_intern;
ce->prop_off = off;
+ ce->obj_version = current_version;
return off;
}
}
off = next_prop(header);
}
- perf_lkp_not_found++;
+
+ ce->obj_off = obj_off;
+ ce->intern_ptr = search_intern;
+ ce->prop_off = 0;
+ ce->obj_version = current_version;
+
return 0;
}
@@ -3974,10 +3927,31 @@ static inline jsoff_t lkp_inline(struct js *js, jsval_t obj, const char *buf, si
return lkp_interned(js, obj, search_intern, len);
}
+static jsoff_t lkp_scope(struct js *js, jsval_t scope, const char *buf, size_t len) {
+ const char *search_intern = intern_string(buf, len);
+ if (!search_intern) return 0;
+
+ jsoff_t scope_off = (jsoff_t)vdata(scope);
+ jsoff_t off = loadoff(js, scope_off) & ~(3U | CONSTMASK | ARRMASK | SLOTMASK);
+
+ while (off < js->brk && off != 0) {
+ jsoff_t header = loadoff(js, off);
+ if (is_slot_prop(header)) { off = next_prop(header); continue; }
+
+ jsoff_t koff = loadoff(js, (jsoff_t)(off + sizeof(off)));
+ jsoff_t klen = (loadoff(js, koff) >> 2) - 1;
+ if (klen == len) {
+ const char *p = (char *)&js->mem[koff + sizeof(koff)];
+ if (intern_string(p, klen) == search_intern) return off;
+ }
+ off = next_prop(header);
+ }
+ return 0;
+}
+
static jsoff_t lkp(struct js *js, jsval_t obj, const char *buf, size_t len) {
return lkp_inline(js, obj, buf, len);
}
-
static jsoff_t lkp_with_getter(struct js *js, jsval_t obj, const char *buf, size_t len, jsval_t *getter_out, bool *has_getter_out) {
*has_getter_out = false;
*getter_out = js_mkundef();
@@ -4030,6 +4004,30 @@ static jsoff_t lkp_with_setter(struct js *js, jsval_t obj, const char *buf, size
return 0;
}
+static jsval_t call_proto_accessor(struct js *js, jsval_t prim, jsval_t accessor, bool has_accessor, jsval_t *arg, int arg_count, bool is_setter) {
+ if (!has_accessor || (vtype(accessor) != T_FUNC && vtype(accessor) != T_CFUNC)) {
+ return is_setter ? js_mkundef() : js_mkundef();
+ }
+
+ js_parse_state_t saved;
+ JS_SAVE_STATE(js, saved);
+ uint8_t saved_flags = js->flags;
+ jsoff_t saved_toff = js->toff;
+ jsoff_t saved_tlen = js->tlen;
+
+ push_this(prim);
+ jsval_t result = call_js_with_args(js, accessor, arg, arg_count);
+ pop_this();
+
+ JS_RESTORE_STATE(js, saved);
+ js->flags = saved_flags;
+ js->toff = saved_toff;
+ js->tlen = saved_tlen;
+
+ if (is_setter) return is_err(result) ? result : (arg ? *arg : js_mkundef());
+ return result;
+}
+
jsval_t js_get_proto(struct js *js, jsval_t obj) {
uint8_t t = vtype(obj);
@@ -4061,18 +4059,18 @@ static void set_proto(struct js *js, jsval_t obj, jsval_t proto) {
}
jsval_t js_get_ctor_proto(struct js *js, const char *name, size_t len) {
- jsoff_t ctor_off = lkp(js, js->scope, name, len);
+ jsoff_t ctor_off = lkp_scope(js, js->scope, name, len);
if (ctor_off == 0 && global_scope_stack) {
int stack_len = utarray_len(global_scope_stack);
for (int i = stack_len - 1; i >= 0 && ctor_off == 0; i--) {
jsoff_t *scope_off = (jsoff_t *)utarray_eltptr(global_scope_stack, i);
jsval_t scope = mkval(T_OBJ, *scope_off);
- ctor_off = lkp(js, scope, name, len);
+ ctor_off = lkp_scope(js, scope, name, len);
}
} else if (ctor_off == 0) {
for (jsval_t scope = upper(js, js->scope); vdata(scope) != 0 && ctor_off == 0; scope = upper(js, scope)) {
- ctor_off = lkp(js, scope, name, len);
+ ctor_off = lkp_scope(js, scope, name, len);
}
}
@@ -4193,9 +4191,36 @@ static jsval_t lookup(struct js *js, const char *buf, size_t len) {
const char *key_intern = intern_string(key_str, key_len);
- for (jsval_t scope = js->scope;;) {
- jsoff_t off = lkp_interned(js, scope, key_intern, key_len);
- if (off != 0) return mkval(T_PROP, off);
+ jsval_t parent_scope = upper(js, js->scope);
+
+ jsoff_t off = lkp_interned(js, js->scope, key_intern, key_len);
+ if (off != 0) {
+ return mkval(T_PROP, off);
+ }
+
+ jsval_t with_slot = get_slot(js, js->scope, SLOT_WITH);
+ if (vtype(with_slot) != T_UNDEF) {
+ jsval_t with_obj = (
+ vtype(with_slot) == T_OBJ ||
+ vtype(with_slot) == T_ARR ||
+ vtype(with_slot) == T_FUNC) ?
+ with_slot : mkval(T_OBJ, vdata(with_slot)
+ );
+
+ jsoff_t prop_off = lkp_interned(js, with_obj, key_intern, key_len);
+ if (prop_off != 0) {
+ jsval_t key = js_mkstr(js, key_str, key_len);
+ if (is_err(key)) return key;
+ return mkpropref((jsoff_t)vdata(with_obj), (jsoff_t)vdata(key));
+ }
+ }
+
+ uint8_t depth = 1;
+ for (jsval_t scope = parent_scope; depth < 255; depth++) {
+ off = lkp_interned(js, scope, key_intern, key_len);
+ if (off != 0) {
+ return mkval(T_PROP, off);
+ }
jsval_t with_slot = get_slot(js, scope, SLOT_WITH);
if (vtype(with_slot) != T_UNDEF) {
@@ -4234,30 +4259,13 @@ static bool try_accessor_getter(struct js *js, jsval_t obj, const char *key, siz
jsval_t getter = js_mkundef();
bool has_getter = false;
lkp_with_getter(js, obj, key, key_len, &getter, &has_getter);
-
- if (!has_getter) return false;
- if (vtype(getter) != T_FUNC && vtype(getter) != T_CFUNC) return false;
-
- js_parse_state_t saved;
- JS_SAVE_STATE(js, saved);
- uint8_t saved_flags = js->flags;
- jsoff_t saved_toff = js->toff;
- jsoff_t saved_tlen = js->tlen;
-
- jsval_t saved_this = js->this_val;
- js->this_val = obj;
- push_this(obj);
- *out = call_js_with_args(js, getter, NULL, 0);
-
- pop_this();
- js->this_val = saved_this;
-
- JS_RESTORE_STATE(js, saved);
- js->flags = saved_flags;
- js->toff = saved_toff;
- js->tlen = saved_tlen;
-
- return true;
+
+ jsval_t result = call_proto_accessor(js, obj, getter, has_getter, NULL, 0, false);
+ if (vtype(result) != T_UNDEF) {
+ *out = result;
+ return true;
+ }
+ return false;
}
static jsval_t resolveprop(struct js *js, jsval_t v) {
@@ -4271,33 +4279,17 @@ static jsval_t resolveprop(struct js *js, jsval_t v) {
jsoff_t key_len;
const char *key_str = (const char *)&js->mem[vstr(js, key, &key_len)];
- jsval_t proto = get_prototype_for_type(js, vtype(prim));
- if (vtype(proto) == T_OBJ) {
- jsval_t getter = js_mkundef();
- bool has_getter = false;
- lkp_with_getter(js, proto, key_str, key_len, &getter, &has_getter);
- if (has_getter && (vtype(getter) == T_FUNC || vtype(getter) == T_CFUNC)) {
- js_parse_state_t saved;
- JS_SAVE_STATE(js, saved);
- uint8_t saved_flags = js->flags;
- jsoff_t saved_toff = js->toff;
- jsoff_t saved_tlen = js->tlen;
-
- push_this(prim);
- jsval_t result = call_js_with_args(js, getter, NULL, 0);
- pop_this();
-
- JS_RESTORE_STATE(js, saved);
- js->flags = saved_flags;
- js->toff = saved_toff;
- js->tlen = saved_tlen;
-
- return result;
- }
-
- jsoff_t off = lkp_proto(js, prim, key_str, key_len);
- if (off != 0) return resolveprop(js, mkval(T_PROP, off));
- }
+ jsval_t proto = get_prototype_for_type(js, vtype(prim));
+ if (vtype(proto) == T_OBJ) {
+ jsval_t getter = js_mkundef();
+ bool has_getter = false;
+ lkp_with_getter(js, proto, key_str, key_len, &getter, &has_getter);
+ jsval_t result = call_proto_accessor(js, prim, getter, has_getter, NULL, 0, false);
+ if (vtype(result) != T_UNDEF) return result;
+
+ jsoff_t off = lkp_proto(js, prim, key_str, key_len);
+ if (off != 0) return resolveprop(js, mkval(T_PROP, off));
+ }
return js_mkundef();
}
@@ -4353,30 +4345,13 @@ static bool try_accessor_setter(struct js *js, jsval_t obj, const char *key, siz
jsval_t setter = js_mkundef();
bool has_setter = false;
lkp_with_setter(js, obj, key, key_len, &setter, &has_setter);
-
- if (!has_setter) return false;
- if (vtype(setter) != T_FUNC && vtype(setter) != T_CFUNC) return false;
-
- js_parse_state_t saved;
- JS_SAVE_STATE(js, saved);
- uint8_t saved_flags = js->flags;
- jsoff_t saved_toff = js->toff;
- jsoff_t saved_tlen = js->tlen;
-
- jsval_t saved_this = js->this_val;
- js->this_val = obj;
- push_this(obj);
- jsval_t result = call_js_with_args(js, setter, &val, 1);
- pop_this();
- js->this_val = saved_this;
-
- JS_RESTORE_STATE(js, saved);
- js->flags = saved_flags;
- js->toff = saved_toff;
- js->tlen = saved_tlen;
-
- *out = is_err(result) ? result : val;
- return true;
+
+ jsval_t result = call_proto_accessor(js, obj, setter, has_setter, &val, 1, true);
+ if (vtype(result) != T_UNDEF) {
+ *out = result;
+ return true;
+ }
+ return false;
}
static jsval_t assign(struct js *js, jsval_t lhs, jsval_t val) {
@@ -4393,30 +4368,14 @@ static jsval_t assign(struct js *js, jsval_t lhs, jsval_t val) {
jsoff_t key_len;
const char *key_str = (const char *)&js->mem[vstr(js, key, &key_len)];
- jsval_t proto = get_prototype_for_type(js, vtype(prim));
- if (vtype(proto) == T_OBJ) {
- jsval_t setter = js_mkundef();
- bool has_setter = false;
- lkp_with_setter(js, proto, key_str, key_len, &setter, &has_setter);
- if (has_setter && (vtype(setter) == T_FUNC || vtype(setter) == T_CFUNC)) {
- js_parse_state_t saved;
- JS_SAVE_STATE(js, saved);
- uint8_t saved_flags = js->flags;
- jsoff_t saved_toff = js->toff;
- jsoff_t saved_tlen = js->tlen;
-
- push_this(prim);
- jsval_t result = call_js_with_args(js, setter, &val, 1);
- pop_this();
-
- JS_RESTORE_STATE(js, saved);
- js->flags = saved_flags;
- js->toff = saved_toff;
- js->tlen = saved_tlen;
-
- return is_err(result) ? result : val;
- }
- }
+ jsval_t proto = get_prototype_for_type(js, vtype(prim));
+ if (vtype(proto) == T_OBJ) {
+ jsval_t setter = js_mkundef();
+ bool has_setter = false;
+ lkp_with_setter(js, proto, key_str, key_len, &setter, &has_setter);
+ jsval_t result = call_proto_accessor(js, prim, setter, has_setter, &val, 1, true);
+ if (vtype(result) != T_UNDEF) return result;
+ }
if (js->flags & F_STRICT) {
return js_mkerr_typed(
@@ -5233,11 +5192,6 @@ static jsval_t call_js_internal_nfe(struct js *js, const char *fn, jsoff_t fnlen
return js_mkerr(js, "failed to parse function");
}
- /* ===== PHASE 4: BIND PHASE (instrumented) ===== */
- PERF_START(_p4_total);
-
- /* Sub1: Parameter binding loop */
- PERF_START(_p4_sub1);
int argi = 0;
for (int i = 0; i < pf->param_count; i++) {
parsed_param_t *pp = (parsed_param_t *)utarray_eltptr(pf->params, i);
@@ -5263,13 +5217,11 @@ static jsval_t call_js_internal_nfe(struct js *js, const char *fn, jsoff_t fnlen
} else {
v = js_mkundef();
}
- setprop_fast(js, function_scope, &fn[pp->name_off], pp->name_len, v);
+ jsval_t k = js_mkstr(js, &fn[pp->name_off], pp->name_len);
+ if (!is_err(k)) mkprop_fast(js, function_scope, k, v, false);
}
}
- PERF_END(_p4_sub1, perf_phase4_sub1_ns);
- /* Sub2: Rest parameter handling */
- PERF_START(_p4_sub2);
if (pf->has_rest && pf->rest_param_len > 0) {
jsval_t rest_array = mkarr(js);
if (!is_err(rest_array)) {
@@ -5292,15 +5244,8 @@ static jsval_t call_js_internal_nfe(struct js *js, const char *fn, jsoff_t fnlen
setprop(js, function_scope, js_mkstr(js, &fn[pf->rest_param_start], pf->rest_param_len), rest_array);
}
}
- PERF_END(_p4_sub2, perf_phase4_sub2_ns);
- /* Sub3: code_uses_arguments check (THIS IS SUSPECT!) */
- PERF_START(_p4_sub3);
bool needs_arguments = code_uses_arguments(&fn[pf->body_start], pf->body_len);
- PERF_END(_p4_sub3, perf_phase4_sub3_ns);
-
- /* Sub4: setup_arguments if needed */
- PERF_START(_p4_sub4);
bool func_strict = pf->is_strict;
if (!func_strict && vtype(func_val) == T_FUNC) {
@@ -5312,27 +5257,14 @@ static jsval_t call_js_internal_nfe(struct js *js, const char *fn, jsoff_t fnlen
if (needs_arguments) {
setup_arguments(js, function_scope, args, argc, func_strict);
}
- PERF_END(_p4_sub4, perf_phase4_sub4_ns);
- /* Sub5: NFE name binding */
- PERF_START(_p4_sub5);
- if (vtype(func_name) == T_STR && vtype(func_val) == T_FUNC) {
- PERF_START(_sub5_vstr);
- jsoff_t len;
- (void)vstr(js, func_name, &len);
- PERF_END(_sub5_vstr, perf_sub5_vstr_ns);
- if (len > 0) {
- PERF_START(_sub5_mkprop);
- jsval_t prop = mkprop(js, function_scope, func_name, func_val, true);
- PERF_END(_sub5_mkprop, perf_sub5_mkprop_ns);
- (void)prop;
- perf_sub5_count++;
- }
- }
- PERF_END(_p4_sub5, perf_phase4_sub5_ns);
+ jsval_t slot_name = get_slot(js, func_val, SLOT_NAME);
+ if (vtype(func_name) == T_STR && vtype(func_val) == T_FUNC && vtype(slot_name) == T_UNDEF) {
+ jsoff_t len;
+ (void)vstr(js, func_name, &len);
+ if (len > 0) mkprop_fast(js, function_scope, func_name, func_val, true);
+ }
- /* Sub6: Strict mode + this handling */
- PERF_START(_p4_sub6);
if (func_strict && (vtype(target_this) == T_UNDEF || vtype(target_this) == T_NULL ||
(vtype(target_this) == T_OBJ && vdata(target_this) == 0))) {
js->this_val = js_mkundef();
@@ -5340,12 +5272,6 @@ static jsval_t call_js_internal_nfe(struct js *js, const char *fn, jsoff_t fnlen
js->this_val = target_this;
}
js->flags = F_CALL | (func_strict ? F_STRICT : 0);
- PERF_END(_p4_sub6, perf_phase4_sub6_ns);
-
- PERF_END(_p4_total, perf_phase4_ns);
- PERF_CALL_INC();
- if (PERF_SHOULD_REPORT()) perf_report(js->brk);
- /* ===== END PHASE 4 ===== */
jsval_t res = js_eval(js, &fn[pf->body_start], pf->body_len);
if (!is_err(res) && !(js->flags & F_RETURN)) res = js_mkundef();
@@ -5475,17 +5401,18 @@ static jsval_t call_js_code_with_args_nfe(struct js *js, const char *fn, jsoff_t
jsval_t saved_scope = js->scope;
if (global_scope_stack == NULL) utarray_new(global_scope_stack, &jsoff_icd);
utarray_push_back(global_scope_stack, &parent_scope_offset);
- jsval_t function_scope = mkobj(js, parent_scope_offset);
- js->scope = function_scope;
-
- if (vtype(func_name) == T_STR && vtype(func_val) == T_FUNC) {
- jsoff_t len;
- (void)vstr(js, func_name, &len);
- if (len > 0) {
- jsval_t prop = mkprop(js, function_scope, func_name, func_val, true);
- (void)prop;
- }
- }
+ jsval_t function_scope = mkobj(js, parent_scope_offset);
+ js->scope = function_scope;
+
+ jsval_t slot_name = get_slot(js, func_val, SLOT_NAME);
+ if (vtype(func_name) == T_STR && vtype(func_val) == T_FUNC && vtype(slot_name) == T_UNDEF) {
+ jsoff_t len;
+ (void)vstr(js, func_name, &len);
+ if (len > 0) {
+ jsval_t prop = mkprop(js, function_scope, func_name, func_val, true);
+ (void)prop;
+ }
+ }
jsoff_t fnpos = 1;
int arg_idx = 0;
@@ -5654,13 +5581,10 @@ static jsval_t do_call_op(struct js *js, jsval_t func, jsval_t args) {
if (vtype(target_proto) == T_UNDEF) {
jsval_t func_obj = mkval(T_OBJ, vdata(func));
- jsoff_t target_func_off = lkp_interned(js, func_obj, INTERN_TARGET_FUNC, 13);
+ jsval_t target_func = get_slot(js, func_obj, SLOT_TARGET_FUNC);
jsval_t proto_source = func_obj;
- if (target_func_off != 0) {
- jsval_t target_func = resolveprop(js, mkval(T_PROP, target_func_off));
- if (vtype(target_func) == T_FUNC) {
- proto_source = mkval(T_OBJ, vdata(target_func));
- }
+ if (vtype(target_func) == T_FUNC) {
+ proto_source = mkval(T_OBJ, vdata(target_func));
}
jsoff_t proto_off = lkp_interned(js, proto_source, INTERN_PROTOTYPE, 9);
@@ -5739,9 +5663,14 @@ static jsval_t do_call_op(struct js *js, jsval_t func, jsval_t args) {
}
jsval_t nfe_name_val = js_mkundef();
- jsoff_t nfe_name_off = lkp(js, func_obj, "name", 4);
- if (nfe_name_off != 0) {
- nfe_name_val = resolveprop(js, mkval(T_PROP, nfe_name_off));
+ jsval_t slot_name = get_slot(js, func_obj, SLOT_NAME);
+ if (vtype(slot_name) == T_STR) {
+ nfe_name_val = slot_name;
+ } else {
+ jsoff_t nfe_name_off = lkp(js, func_obj, "name", 4);
+ if (nfe_name_off != 0) {
+ nfe_name_val = resolveprop(js, mkval(T_PROP, nfe_name_off));
+ }
}
if (fnlen == 16 && memcmp(code_str, "__builtin_Object", 16) == 0) {
@@ -5812,13 +5741,9 @@ static jsval_t do_call_op(struct js *js, jsval_t func, jsval_t args) {
if (vtype(count_val) != T_NUM || vtype(target_this) != T_OBJ) goto skip_fields;
int field_count = (int)tod(count_val);
- jsoff_t src_off = lkp(js, func_obj, "__source", 8);
- jsoff_t fields_off = lkp(js, func_obj, "__fields", 8);
- if (src_off == 0 || fields_off == 0) goto skip_fields;
-
- jsval_t src_val = resolveprop(js, mkval(T_PROP, src_off));
- jsval_t fields_meta = resolveprop(js, mkval(T_PROP, fields_off));
- if (vtype(src_val) != T_STR || vtype(fields_meta) != T_STR) goto skip_fields;
+ jsval_t src_val = get_slot(js, func_obj, SLOT_SOURCE);
+ jsval_t fields_meta = get_slot(js, func_obj, SLOT_FIELDS);
+ if (vtype(src_val) == T_UNDEF || vtype(fields_meta) == T_UNDEF) goto skip_fields;
jsoff_t src_len, src_ptr_off = vstr(js, src_val, &src_len);
const char *source = (const char *)(&js->mem[src_ptr_off]);
@@ -6624,7 +6549,7 @@ static jsval_t js_arr_destruct_assign(struct js *js) {
}
}
- jsoff_t existing = lkp(js, js->scope, var_name, var_len);
+ jsoff_t existing = lkp_scope(js, js->scope, var_name, var_len);
if (existing != 0) {
jsval_t res = setprop(js, js->scope, js_mkstr(js, var_name, var_len), prop_val);
if (is_err(res)) return res;
@@ -7209,10 +7134,11 @@ static jsval_t js_func_literal(struct js *js, bool is_async) {
}
if (name_len > 0) {
- jsval_t name_key = js_mkstr(js, "name", 4);
- if (is_err(name_key)) return name_key;
jsval_t name_val = js_mkstr(js, &js->code[name_off], name_len);
if (is_err(name_val)) return name_val;
+ set_slot(js, func_obj, SLOT_NAME, name_val);
+ jsval_t name_key = js_mkstr(js, "name", 4);
+ if (is_err(name_key)) return name_key;
jsval_t res3 = setprop(js, func_obj, name_key, name_val);
if (is_err(res3)) return res3;
}
@@ -7816,7 +7742,7 @@ static jsval_t js_unary(struct js *js) {
jsoff_t deleted_next = loadoff(js, prop_off) & ~(CONSTMASK | ARRMASK | SLOTMASK);
jsoff_t current = loadoff(js, obj_off);
saveoff(js, obj_off, (deleted_next & ~3U) | (current & (CONSTMASK | ARRMASK | SLOTMASK | 3U)));
- invalidate_prop_cache(obj_off);
+ increment_version(js, obj_off);
return js_mktrue();
}
jsoff_t prev = first_prop;
@@ -7826,7 +7752,7 @@ static jsval_t js_unary(struct js *js) {
jsoff_t deleted_next = loadoff(js, prop_off) & ~(CONSTMASK | ARRMASK | SLOTMASK);
jsoff_t current = loadoff(js, prev);
saveoff(js, prev, (deleted_next & ~3U) | (current & (CONSTMASK | ARRMASK | SLOTMASK | 3U)));
- invalidate_prop_cache(obj_off);
+ increment_version(js, obj_off);
return js_mktrue();
}
prev = next_prop;
@@ -7904,7 +7830,7 @@ static jsval_t js_unary(struct js *js) {
jsoff_t current = loadoff(js, prev_prop_off);
saveoff(js, prev_prop_off, (deleted_next & ~3U) | (current & (CONSTMASK | ARRMASK | SLOTMASK | 3U)));
}
- invalidate_prop_cache(owner_obj_off);
+ increment_version(js, owner_obj_off);
}
(void) save_pos;
(void) save_tok;
@@ -8230,15 +8156,54 @@ static jsval_t js_assignment(struct js *js) {
return mkval(T_FUNC, (unsigned long) vdata(func_obj));
}
- while (!is_err(res) && (next(js) == TOK_ASSIGN || js->tok == TOK_PLUS_ASSIGN ||
- js->tok == TOK_MINUS_ASSIGN || js->tok == TOK_MUL_ASSIGN ||
- js->tok == TOK_DIV_ASSIGN || js->tok == TOK_REM_ASSIGN ||
- js->tok == TOK_SHL_ASSIGN || js->tok == TOK_SHR_ASSIGN ||
- js->tok == TOK_ZSHR_ASSIGN || js->tok == TOK_AND_ASSIGN ||
- js->tok == TOK_XOR_ASSIGN || js->tok == TOK_OR_ASSIGN)) {
+ while (
+ !is_err(res) && (next(js) == TOK_ASSIGN || js->tok == TOK_PLUS_ASSIGN ||
+ js->tok == TOK_MINUS_ASSIGN || js->tok == TOK_MUL_ASSIGN ||
+ js->tok == TOK_DIV_ASSIGN || js->tok == TOK_REM_ASSIGN ||
+ js->tok == TOK_SHL_ASSIGN || js->tok == TOK_SHR_ASSIGN ||
+ js->tok == TOK_ZSHR_ASSIGN || js->tok == TOK_AND_ASSIGN ||
+ js->tok == TOK_XOR_ASSIGN || js->tok == TOK_OR_ASSIGN ||
+ js->tok == TOK_LOR_ASSIGN || js->tok == TOK_LAND_ASSIGN ||
+ js->tok == TOK_NULLISH_ASSIGN)
+ ) {
uint8_t op = js->tok;
js->consumed = 1;
+ if (op == TOK_LOR_ASSIGN || op == TOK_LAND_ASSIGN || op == TOK_NULLISH_ASSIGN) {
+ jsval_t lhs_val = js_mkundef();
+ bool should_assign = true;
+
+ if (!(js->flags & F_NOEXEC)) {
+ lhs_val = resolveprop(js, res);
+ if (is_err(lhs_val)) return lhs_val;
+
+ if (op == TOK_LOR_ASSIGN) {
+ should_assign = !js_truthy(js, lhs_val);
+ } else if (op == TOK_LAND_ASSIGN) {
+ should_assign = js_truthy(js, lhs_val);
+ } else should_assign = is_null(lhs_val) || is_undefined(lhs_val);
+ }
+
+ if (should_assign || (js->flags & F_NOEXEC)) {
+ jsval_t rhs = js_assignment(js);
+ if (is_err(rhs)) return rhs;
+
+ if (!(js->flags & F_NOEXEC) && should_assign) {
+ jsval_t rhs_resolved = resolveprop(js, rhs);
+ if (is_err(rhs_resolved)) return rhs_resolved;
+ res = assign(js, res, rhs_resolved);
+ }
+ } else {
+ uint8_t saved_flags = js->flags;
+ js->flags |= F_NOEXEC;
+ jsval_t rhs = js_assignment(js);
+ js->flags = saved_flags;
+ if (is_err(rhs)) return rhs;
+ res = lhs_val;
+ }
+ continue;
+ }
+
jsval_t lhs_val = js_mkundef();
if (op != TOK_ASSIGN && !(js->flags & F_NOEXEC)) {
lhs_val = resolveprop(js, res);
@@ -8408,7 +8373,7 @@ obj_destruct_rest:;
}
{
const char *vn = &js->code[var_off];
- if (lkp(js, js->scope, vn, var_len) > 0) return js_mkerr(js, "'%.*s' already declared", (int)var_len, vn);
+ if (lkp_scope(js, js->scope, vn, var_len) > 0) return js_mkerr(js, "'%.*s' already declared", (int)var_len, vn);
jsval_t x = mkprop(js, js->scope, js_mkstr(js, vn, var_len), rest_obj, is_const);
if (is_err(x)) return x;
}
@@ -8448,7 +8413,7 @@ obj_destruct_nested:;
}
const char *ivn = &js->code[ivoff];
- if (lkp(js, js->scope, ivn, ivlen) > 0) return js_mkerr(js, "'%.*s' already declared", (int)ivlen, ivn);
+ if (lkp_scope(js, js->scope, ivn, ivlen) > 0) return js_mkerr(js, "'%.*s' already declared", (int)ivlen, ivn);
jsoff_t ipoff = lkp(js, nobj, &js->code[isoff], islen);
jsval_t ival = ipoff > 0 ? resolveprop(js, mkval(T_PROP, ipoff)) : js_mkundef();
@@ -8470,7 +8435,7 @@ nested_done:
obj_destruct_simple:;
{
const char *vn = &js->code[var_off];
- if (lkp(js, js->scope, vn, var_len) > 0) return js_mkerr(js, "'%.*s' already declared", (int)var_len, vn);
+ if (lkp_scope(js, js->scope, vn, var_len) > 0) return js_mkerr(js, "'%.*s' already declared", (int)var_len, vn);
jsval_t sk = js_mkstr(js, &js->code[src_off], src_len);
if (is_err(sk)) return sk;
@@ -8564,7 +8529,7 @@ obj_destruct_next:
if (exe) {
const char *var_name = &js->code[var_off];
- if (lkp(js, js->scope, var_name, var_len) > 0) {
+ if (lkp_scope(js, js->scope, var_name, var_len) > 0) {
return js_mkerr(js, "'%.*s' already declared", (int)var_len, var_name);
}
@@ -8643,7 +8608,7 @@ obj_destruct_next:
char decoded_name[256];
size_t decoded_len = decode_ident_escapes(name, nlen, decoded_name, sizeof(decoded_name));
- if (lkp(js, js->scope, decoded_name, decoded_len) > 0) return js_mkerr(js, "'%.*s' already declared", (int) decoded_len, decoded_name);
+ if (lkp_scope(js, js->scope, decoded_name, decoded_len) > 0) return js_mkerr(js, "'%.*s' already declared", (int) decoded_len, decoded_name);
jsval_t x = mkprop(js, js->scope, js_mkstr(js, decoded_name, decoded_len), resolveprop(js, v), is_const);
if (is_err(x)) return x;
}
@@ -8737,34 +8702,35 @@ static jsval_t js_func_decl(struct js *js) {
return res;
}
js->flags = flags;
- jsval_t str = js_mkstr(js, &js->code[pos], js->pos - pos);
- jsval_t func_obj = mkobj(js, 0);
- if (is_err(func_obj)) return func_obj;
- set_slot(js, func_obj, SLOT_CODE, str);
- jsval_t len_key = js_mkstr(js, "length", 6);
- if (is_err(len_key)) return len_key;
- jsval_t res_len = setprop(js, func_obj, len_key, tov(param_count));
- if (is_err(res_len)) return res_len;
- js_set_descriptor(js, func_obj, "length", 6, JS_DESC_C);
- jsval_t name_key = js_mkstr(js, "name", 4);
- if (is_err(name_key)) return name_key;
- jsval_t name_val = js_mkstr(js, name, nlen);
- if (is_err(name_val)) return name_val;
- jsval_t res3 = setprop(js, func_obj, name_key, name_val);
- if (is_err(res3)) return res3;
- if (exe) {
- set_slot(js, func_obj, SLOT_SCOPE, js->scope);
- if (flags & F_STRICT) {
- set_slot(js, func_obj, SLOT_STRICT, js_mktrue());
- }
- }
- jsval_t func = mkval(T_FUNC, (unsigned long) vdata(func_obj));
+ jsval_t str = js_mkstr(js, &js->code[pos], js->pos - pos);
+ jsval_t func_obj = mkobj(js, 0);
+ if (is_err(func_obj)) return func_obj;
+ set_slot(js, func_obj, SLOT_CODE, str);
+ jsval_t len_key = js_mkstr(js, "length", 6);
+ if (is_err(len_key)) return len_key;
+ jsval_t res_len = setprop(js, func_obj, len_key, tov(param_count));
+ if (is_err(res_len)) return res_len;
+ js_set_descriptor(js, func_obj, "length", 6, JS_DESC_C);
+ jsval_t name_key = js_mkstr(js, "name", 4);
+ if (is_err(name_key)) return name_key;
+ jsval_t name_val = js_mkstr(js, name, nlen);
+ if (is_err(name_val)) return name_val;
+ set_slot(js, func_obj, SLOT_NAME, name_val);
+ jsval_t res3 = setprop(js, func_obj, name_key, name_val);
+ if (is_err(res3)) return res3;
+ if (exe) {
+ set_slot(js, func_obj, SLOT_SCOPE, js->scope);
+ if (flags & F_STRICT) {
+ set_slot(js, func_obj, SLOT_STRICT, js_mktrue());
+ }
+ }
+ jsval_t func = mkval(T_FUNC, (unsigned long) vdata(func_obj));
jsval_t proto_setup = setup_func_prototype(js, func);
if (is_err(proto_setup)) return proto_setup;
if (exe) {
- jsoff_t existing = lkp(js, js->scope, name, nlen);
+ jsoff_t existing = lkp_scope(js, js->scope, name, nlen);
if (existing > 0) {
saveval(js, existing + sizeof(jsoff_t) * 2, func);
} else {
@@ -8803,19 +8769,20 @@ static jsval_t js_func_decl_async(struct js *js) {
jsval_t str = js_mkstr(js, &js->code[pos], js->pos - pos);
jsval_t func_obj = mkobj(js, 0);
if (is_err(func_obj)) return func_obj;
- set_slot(js, func_obj, SLOT_CODE, str);
- set_slot(js, func_obj, SLOT_ASYNC, js_mktrue());
- jsval_t len_key = js_mkstr(js, "length", 6);
- if (is_err(len_key)) return len_key;
- jsval_t res_len = setprop(js, func_obj, len_key, tov(0));
- if (is_err(res_len)) return res_len;
- js_set_descriptor(js, func_obj, "length", 6, JS_DESC_C);
- jsval_t name_key = js_mkstr(js, "name", 4);
- if (is_err(name_key)) return name_key;
- jsval_t name_val = js_mkstr(js, name, nlen);
- if (is_err(name_val)) return name_val;
- jsval_t res3 = setprop(js, func_obj, name_key, name_val);
- if (is_err(res3)) return res3;
+ set_slot(js, func_obj, SLOT_CODE, str);
+ set_slot(js, func_obj, SLOT_ASYNC, js_mktrue());
+ jsval_t len_key = js_mkstr(js, "length", 6);
+ if (is_err(len_key)) return len_key;
+ jsval_t res_len = setprop(js, func_obj, len_key, tov(0));
+ if (is_err(res_len)) return res_len;
+ js_set_descriptor(js, func_obj, "length", 6, JS_DESC_C);
+ jsval_t name_key = js_mkstr(js, "name", 4);
+ if (is_err(name_key)) return name_key;
+ jsval_t name_val = js_mkstr(js, name, nlen);
+ if (is_err(name_val)) return name_val;
+ set_slot(js, func_obj, SLOT_NAME, name_val);
+ jsval_t res3 = setprop(js, func_obj, name_key, name_val);
+ if (is_err(res3)) return res3;
if (exe) {
set_slot(js, func_obj, SLOT_SCOPE, js->scope);
if (flags & F_STRICT) {
@@ -8828,7 +8795,7 @@ static jsval_t js_func_decl_async(struct js *js) {
if (is_err(proto_setup)) return proto_setup;
if (exe) {
- jsoff_t existing = lkp(js, js->scope, name, nlen);
+ jsoff_t existing = lkp_scope(js, js->scope, name, nlen);
if (existing > 0) {
saveval(js, existing + sizeof(jsoff_t) * 2, func);
} else {
@@ -8913,7 +8880,7 @@ static jsval_t for_iter_bind_var(struct js *js, for_iter_ctx_t *ctx, jsval_t val
return bind_destruct_pattern(js, &js->code[ctx->destructure_off], ctx->destructure_len, value, js->scope);
}
const char *var_name = &js->code[ctx->var_name_off];
- jsoff_t existing = lkp(js, js->scope, var_name, ctx->var_name_len);
+ jsoff_t existing = lkp_scope(js, js->scope, var_name, ctx->var_name_len);
if (existing > 0) {
saveval(js, existing + sizeof(jsoff_t) * 2, value);
return js_mkundef();
@@ -9221,7 +9188,14 @@ static jsval_t js_for(struct js *js) {
if (exe) {
jsval_t obj = resolveprop(js, obj_expr);
- if (vtype(obj) != T_OBJ && vtype(obj) != T_ARR && vtype(obj) != T_FUNC) {
+ uint8_t obj_type = vtype(obj);
+
+ if (obj_type == T_NULL || obj_type == T_UNDEF) {
+ res = js_mkundef();
+ goto done;
+ }
+
+ if (obj_type != T_OBJ && obj_type != T_ARR && obj_type != T_FUNC) {
res = js_mkerr(js, "for-in requires object");
goto done;
}
@@ -9264,7 +9238,7 @@ static jsval_t js_for(struct js *js) {
jsval_t key_str = js_mkstr(js, key, klen);
const char *var_name = &js->code[var_name_off];
- jsoff_t existing = lkp(js, js->scope, var_name, var_name_len);
+ jsoff_t existing = lkp_scope(js, js->scope, var_name, var_name_len);
if (existing > 0) {
saveval(js, existing + sizeof(jsoff_t) * 2, key_str);
} else {
@@ -9372,7 +9346,7 @@ static jsval_t js_for(struct js *js) {
jsval_t iter_scope = js_mkundef();
jsval_t loop_var_val = js_mkundef();
if (is_let_loop && let_var_len > 0) {
- jsoff_t var_off = lkp(js, js->scope, &js->code[let_var_off], let_var_len);
+ jsoff_t var_off = lkp_scope(js, js->scope, &js->code[let_var_off], let_var_len);
if (var_off != 0) {
loop_var_val = resolveprop(js, mkval(T_PROP, var_off));
}
@@ -9397,7 +9371,7 @@ static jsval_t js_for(struct js *js) {
loop_var_val = resolveprop(js, mkval(T_PROP, iter_var_off));
}
delscope(js);
- jsoff_t outer_var_off = lkp(js, js->scope, &js->code[let_var_off], let_var_len);
+ jsoff_t outer_var_off = lkp_scope(js, js->scope, &js->code[let_var_off], let_var_len);
if (outer_var_off != 0) {
saveval(js, outer_var_off + sizeof(jsoff_t) * 2, loop_var_val);
}
@@ -10630,19 +10604,12 @@ static jsval_t js_class_expr(struct js *js, bool is_expression) {
js->mem[meta_off + sizeof(meta_header) + metadata_size] = 0;
jsval_t fields_meta = mkval(T_STR, meta_off);
- jsval_t fields_key = js_mkstr(js, "__fields", 8);
- if (is_err(fields_key)) return fields_key;
- jsval_t res_fields = setprop(js, func_obj, fields_key, fields_meta);
- if (is_err(res_fields)) return res_fields;
-
set_slot(js, func_obj, SLOT_FIELD_COUNT, tov((double)instance_field_count));
+ set_slot(js, func_obj, SLOT_FIELDS, fields_meta);
- jsval_t src_key = js_mkstr(js, "__source", 8);
- if (is_err(src_key)) return src_key;
jsval_t src_ref = js_mkstr(js, js->code, js->clen);
if (is_err(src_ref)) return src_ref;
- jsval_t res_src = setprop(js, func_obj, src_key, src_ref);
- if (is_err(res_src)) return res_src;
+ set_slot(js, func_obj, SLOT_SOURCE, src_ref);
}
set_slot(js, func_obj, SLOT_SCOPE, func_scope);
@@ -10668,7 +10635,7 @@ static jsval_t js_class_expr(struct js *js, bool is_expression) {
js_set_descriptor(js, proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
if (class_name_len > 0) {
- if (lkp(js, js->scope, class_name, class_name_len) > 0) {
+ if (lkp_scope(js, js->scope, class_name, class_name_len) > 0) {
return js_mkerr(js, "'%.*s' already declared", (int) class_name_len, class_name);
}
jsval_t x = mkprop(js, js->scope, js_mkstr(js, class_name, class_name_len), constructor, false);
@@ -11477,7 +11444,7 @@ static jsval_t builtin_function_bind(struct js *js, jsval_t *args, int nargs) {
set_slot(js, bound_func, SLOT_ASYNC, js_mktrue());
}
- setprop(js, bound_func, js_mkstr(js, "__target_func", 13), func);
+ set_slot(js, bound_func, SLOT_TARGET_FUNC, func);
set_slot(js, bound_func, SLOT_BOUND_THIS, this_arg);
if (bound_argc > 0) {
@@ -18151,7 +18118,13 @@ static char *esm_get_extension(const char *path) {
static char *esm_try_resolve(const char *dir, const char *spec, const char *suffix) {
char path[PATH_MAX];
snprintf(path, PATH_MAX, "%s/%s%s", dir, spec, suffix);
- return realpath(path, NULL);
+ char *resolved = realpath(path, NULL);
+ if (resolved) {
+ struct stat st;
+ if (stat(resolved, &st) == 0 && S_ISREG(st.st_mode)) return resolved;
+ free(resolved);
+ }
+ return NULL;
}
static bool esm_has_extension(const char *spec) {
@@ -18475,10 +18448,8 @@ static jsval_t esm_load_module(struct js *js, esm_module_t *mod) {
jsval_t ns = mkobj(js, 0);
mod->namespace_obj = ns;
- jsval_t glob = js_glob(js);
- jsval_t module_scope = js_get(js, glob, "__esm_module_scope");
- jsval_t prev_module = module_scope;
- js_set(js, glob, "__esm_module_scope", ns);
+ jsval_t prev_module = js->module_ns;
+ js->module_ns = ns;
const char *prev_filename = js->filename;
jsval_t saved_scope = js->scope;
@@ -18492,7 +18463,7 @@ static jsval_t esm_load_module(struct js *js, esm_module_t *mod) {
js->scope = saved_scope;
js_set_filename(js, prev_filename);
- js_set(js, glob, "__esm_module_scope", prev_module);
+ js->module_ns = prev_module;
if (is_err(result)) {
mod->is_loading = false;
@@ -18923,12 +18894,8 @@ static jsval_t js_import_stmt(struct js *js) {
static jsval_t js_export_stmt(struct js *js) {
js->consumed = 1;
- jsval_t glob = js_glob(js);
- jsval_t module_ns = js_get(js, glob, "__esm_module_scope");
-
- if (vtype(module_ns) != T_OBJ) {
- module_ns = mkobj(js, 0);
- js_set(js, glob, "__esm_module_scope", module_ns);
+ if (vtype(js->module_ns) != T_OBJ) {
+ js->module_ns = mkobj(js, 0);
}
if (next(js) == TOK_DEFAULT) {
@@ -18936,7 +18903,7 @@ static jsval_t js_export_stmt(struct js *js) {
jsval_t value = js_assignment(js);
if (is_err(value)) return value;
- setprop(js, module_ns, js_mkstr(js, "default", 7), resolveprop(js, value));
+ setprop(js, js->module_ns, js_mkstr(js, "default", 7), resolveprop(js, value));
return value;
}
@@ -18958,7 +18925,7 @@ static jsval_t js_export_stmt(struct js *js) {
jsval_t key = js_mkstr(js, name, name_len);
mkprop(js, js->scope, key, resolveprop(js, value), is_const);
- setprop(js, module_ns, key, resolveprop(js, value));
+ setprop(js, js->module_ns, key, resolveprop(js, value));
return value;
}
@@ -18973,7 +18940,7 @@ static jsval_t js_export_stmt(struct js *js) {
jsval_t name_val = resolveprop(js, mkval(T_PROP, name_off));
if (vtype(name_val) == T_STR) {
setprop(js, js->scope, name_val, func);
- setprop(js, module_ns, name_val, func);
+ setprop(js, js->module_ns, name_val, func);
}
}
@@ -18988,10 +18955,10 @@ static jsval_t js_export_stmt(struct js *js) {
jsoff_t name_off = lkp(js, cls_obj, "name", 4);
if (name_off != 0) {
jsval_t name_val = resolveprop(js, mkval(T_PROP, name_off));
- if (vtype(name_val) == T_STR) {
- setprop(js, js->scope, name_val, cls);
- setprop(js, module_ns, name_val, cls);
- }
+ if (vtype(name_val) == T_STR) {
+ setprop(js, js->scope, name_val, cls);
+ setprop(js, js->module_ns, name_val, cls);
+ }
}
return cls;
@@ -19020,7 +18987,7 @@ static jsval_t js_export_stmt(struct js *js) {
jsval_t local_val = lookup(js, local_name, local_len);
if (is_err(local_val)) return local_val;
- setprop(js, module_ns, js_mkstr(js, export_name, export_len), resolveprop(js, local_val));
+ setprop(js, js->module_ns, js_mkstr(js, export_name, export_len), resolveprop(js, local_val));
if (next(js) == TOK_COMMA) js->consumed = 1;
}
@@ -19914,6 +19881,7 @@ struct js *js_create(void *buf, size_t len) {
jsval_t glob = js->scope;
jsval_t object_proto = js_mkobj(js);
+ set_proto(js, object_proto, js_mknull());
setprop(js, object_proto, js_mkstr(js, "toString", 8), js_mkfun(builtin_object_toString));
setprop(js, object_proto, js_mkstr(js, "valueOf", 7), js_mkfun(builtin_object_valueOf));
setprop(js, object_proto, js_mkstr(js, "toLocaleString", 14), js_mkfun(builtin_object_toLocaleString));
@@ -20399,7 +20367,7 @@ struct js *js_create(void *buf, size_t len) {
set_slot(js, import_obj, SLOT_CFUNC, js_mkfun(builtin_import));
setprop(js, glob, js_mkstr(js, "import", 6), mkval(T_FUNC, vdata(import_obj)));
- setprop(js, glob, js_mkstr(js, "__esm_module_scope", 18), js_mkundef());
+ js->module_ns = js_mkundef();
setprop(js, object_proto, js_mkstr(js, "constructor", 11), mkval(T_FUNC, vdata(obj_func_obj)));
setprop(js, function_proto, js_mkstr(js, "constructor", 11), mkval(T_FUNC, vdata(func_ctor_obj)));
@@ -20526,7 +20494,7 @@ bool js_del(struct js *js, jsval_t obj, const char *key) {
jsoff_t deleted_next = loadoff(js, prop_off) & ~(CONSTMASK | ARRMASK | SLOTMASK);
jsoff_t current = loadoff(js, obj_off);
saveoff(js, obj_off, (deleted_next & ~3U) | (current & (CONSTMASK | ARRMASK | SLOTMASK | 3U)));
- invalidate_prop_cache(obj_off);
+ increment_version(js, obj_off);
return true;
}
@@ -20537,7 +20505,7 @@ bool js_del(struct js *js, jsval_t obj, const char *key) {
jsoff_t deleted_next = loadoff(js, prop_off) & ~(CONSTMASK | ARRMASK | SLOTMASK);
jsoff_t prev_flags = loadoff(js, prev) & (CONSTMASK | ARRMASK | SLOTMASK);
saveoff(js, prev, deleted_next | prev_flags);
- invalidate_prop_cache(obj_off);
+ increment_version(js, obj_off);
return true;
}
prev = next_prop;
diff --git a/tests/loop.async.js b/tests/loop.async.js
index 367d299..2b93165 100644
--- a/tests/loop.async.js
+++ b/tests/loop.async.js
@@ -13,7 +13,7 @@ async function main() {
await server();
}
-for (let i = 0; i < 40000; i++) {
+for (let i = 0; i < 100000; i++) {
void main();
}
diff --git a/tests/null.js b/tests/null.js
new file mode 100644
index 0000000..d14fe52
--- /dev/null
+++ b/tests/null.js
@@ -0,0 +1,6 @@
+const NullProtoObj = (() => {
+ const e = function () {};
+ return ((e.prototype = Object.create(null)), Object.freeze(e.prototype), e);
+})();
+
+console.log(new NullProtoObj());
diff --git a/tests/test_error_catch.js b/tests/test_error_catch.js
new file mode 100644
index 0000000..e5d6857
--- /dev/null
+++ b/tests/test_error_catch.js
@@ -0,0 +1,13 @@
+Promise.reject(new Error('Promise Test error')).catch(console.error);
+
+try {
+ throw new Error('try-catch test error');
+} catch (err) {
+ console.error(err);
+}
+
+async function testAsync() {
+ throw new Error('Async test error');
+}
+
+testAsync().catch(console.error);