MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Squashed commit of the following:

commit 1e8dbaf1a2df032a8ff1126175cde9f002664168
Author: theMackabu <theMackabu@gmail.com>
Date: Thu Jan 1 18:06:27 2026 -0800

remove debugging

commit 64c80ba424a92ebf9a877201b18e2eb30c62fc09
Author: theMackabu <theMackabu@gmail.com>
Date: Thu Jan 1 17:56:52 2026 -0800

??=, &&=, ||=

commit d1eab8acfbc84d3944bc777e7e94502640f5f8e8
Author: theMackabu <theMackabu@gmail.com>
Date: Thu Jan 1 17:52:30 2026 -0800

update router imports and usage

commit ddccd25e7a2027808156bd27042dbb8c3f432257
Author: theMackabu <theMackabu@gmail.com>
Date: Thu Jan 1 17:35:13 2026 -0800

remove log

commit 0a68bcec1aebf0897d37a85ce232555442e039a7
Author: theMackabu <theMackabu@gmail.com>
Date: Thu Jan 1 17:34:50 2026 -0800

support rou3

commit 9f6bfa48048aa648d0007eb038950d60e925720b
Author: theMackabu <theMackabu@gmail.com>
Date: Thu Jan 1 16:51:13 2026 -0800

fix async error logging

commit 9d6c48ee3a06824043b5d80dc40bb62408eda8a2
Author: theMackabu <theMackabu@gmail.com>
Date: Thu Jan 1 16:04:41 2026 -0800

revert to pre-templ

commit 7af6b78201dcbc1c5d5632ebd906e25d67704d56
Author: theMackabu <theMackabu@gmail.com>
Date: Thu Jan 1 15:20:57 2026 -0800

slot_name

commit b06cc558e850b60050f30be9a8842e1ac9395fca
Author: theMackabu <theMackabu@gmail.com>
Date: Thu Jan 1 14:53:11 2026 -0800

proto null obj

commit ca1fd92d70f4d6b4c515022650eddd59219b7fb0
Author: theMackabu <theMackabu@gmail.com>
Date: Thu Jan 1 14:36:50 2026 -0800

dry

commit 92fee347eca979538569eb3b7e663d04eda9a219
Author: theMackabu <theMackabu@gmail.com>
Date: Thu Jan 1 14:26:59 2026 -0800

slots & scope lookup

commit 838347edb6e3265088260f28fcce609933dcdeba
Author: theMackabu <theMackabu@gmail.com>
Date: Thu Jan 1 13:39:16 2026 -0800

mkprop-fast

commit 3314e3dc338fb40d27b8af24b2605ae83724fa59
Author: theMackabu <theMackabu@gmail.com>
Date: Thu Jan 1 13:21:24 2026 -0800

version slot

commit 4a888725bbffe72f7392daa07171a8ef5e652e0d
Author: theMackabu <theMackabu@gmail.com>
Date: Thu Jan 1 13:05:35 2026 -0800

scope changes

+677 -744
+13
examples/rou3/bench.js
··· 1 + import { createRouter, addRoute, findRoute } from './'; 2 + 3 + const router = createRouter(); 4 + 5 + addRoute(router, 'GET', '/path', { payload: 'this path' }); 6 + addRoute(router, 'POST', '/path/:name', { payload: 'named route' }); 7 + addRoute(router, 'GET', '/path/foo/**', { payload: 'wildcard route' }); 8 + addRoute(router, 'GET', '/path/foo/**:name', { payload: 'named wildcard route' }); 9 + 10 + console.log(findRoute(router, 'GET', '/path')); 11 + console.log(findRoute(router, 'POST', '/path/fooval')); 12 + console.log(findRoute(router, 'GET', '/path/foo/bar/baz')); 13 + console.log(findRoute(router, 'GET', '/'));
+234
examples/rou3/index.js
··· 1 + const NullProtoObj = /* @__PURE__ */ (() => { 2 + const e = function () {}; 3 + return ((e.prototype = Object.create(null)), Object.freeze(e.prototype), e); 4 + })(); 5 + 6 + function createRouter() { 7 + return { 8 + root: { key: '' }, 9 + static: new NullProtoObj() 10 + }; 11 + } 12 + 13 + function splitPath(path) { 14 + const [_, ...s] = path.split('/'); 15 + return s[s.length - 1] === '' ? s.slice(0, -1) : s; 16 + } 17 + function getMatchParams(segments, paramsMap) { 18 + const params = new NullProtoObj(); 19 + for (const [index, name] of paramsMap) { 20 + const segment = index < 0 ? segments.slice(-(index + 1)).join('/') : segments[index]; 21 + if (typeof name === 'string') params[name] = segment; 22 + else { 23 + const match = segment.match(name); 24 + if (match) for (const key in match.groups) params[key] = match.groups[key]; 25 + } 26 + } 27 + return params; 28 + } 29 + 30 + function addRoute(ctx, method = '', path, data) { 31 + method = method.toUpperCase(); 32 + if (path.charCodeAt(0) !== 47) path = `/${path}`; 33 + path = path.replace(/\\:/g, '%3A'); 34 + const segments = splitPath(path); 35 + let node = ctx.root; 36 + let _unnamedParamIndex = 0; 37 + const paramsMap = []; 38 + const paramsRegexp = []; 39 + for (let i = 0; i < segments.length; i++) { 40 + let segment = segments[i]; 41 + if (segment.startsWith('**')) { 42 + if (!node.wildcard) node.wildcard = { key: '**' }; 43 + node = node.wildcard; 44 + paramsMap.push([-(i + 1), segment.split(':')[1] || '_', segment.length === 2]); 45 + break; 46 + } 47 + if (segment === '*' || segment.includes(':')) { 48 + if (!node.param) node.param = { key: '*' }; 49 + node = node.param; 50 + if (segment === '*') paramsMap.push([i, `_${_unnamedParamIndex++}`, true]); 51 + else if (segment.includes(':', 1)) { 52 + const regexp = getParamRegexp(segment); 53 + paramsRegexp[i] = regexp; 54 + node.hasRegexParam = true; 55 + paramsMap.push([i, regexp, false]); 56 + } else paramsMap.push([i, segment.slice(1), false]); 57 + continue; 58 + } 59 + if (segment === '\\*') segment = segments[i] = '*'; 60 + else if (segment === '\\*\\*') segment = segments[i] = '**'; 61 + const child = node.static?.[segment]; 62 + if (child) node = child; 63 + else { 64 + const staticNode = { key: segment }; 65 + if (!node.static) node.static = new NullProtoObj(); 66 + node.static[segment] = staticNode; 67 + node = staticNode; 68 + } 69 + } 70 + const hasParams = paramsMap.length > 0; 71 + if (!node.methods) node.methods = new NullProtoObj(); 72 + node.methods[method] ??= []; 73 + node.methods[method].push({ 74 + data: data || null, 75 + paramsRegexp, 76 + paramsMap: hasParams ? paramsMap : void 0 77 + }); 78 + if (!hasParams) ctx.static['/' + segments.join('/')] = node; 79 + } 80 + function getParamRegexp(segment) { 81 + const regex = segment.replace(/:(\w+)/g, (_, id) => `(?<${id}>[^/]+)`).replace(/\./g, '\\.'); 82 + return /* @__PURE__ */ new RegExp(`^${regex}$`); 83 + } 84 + 85 + function findRoute(ctx, method = '', path, opts) { 86 + if (path.charCodeAt(path.length - 1) === 47) path = path.slice(0, -1); 87 + const staticNode = ctx.static[path]; 88 + if (staticNode && staticNode.methods) { 89 + const staticMatch = staticNode.methods[method] || staticNode.methods['']; 90 + if (staticMatch !== void 0) return staticMatch[0]; 91 + } 92 + const segments = splitPath(path); 93 + const match = _lookupTree(ctx, ctx.root, method, segments, 0)?.[0]; 94 + if (match === void 0) return; 95 + if (opts?.params === false) return match; 96 + return { 97 + data: match.data, 98 + params: match.paramsMap ? getMatchParams(segments, match.paramsMap) : void 0 99 + }; 100 + } 101 + function _lookupTree(ctx, node, method, segments, index) { 102 + if (index === segments.length) { 103 + if (node.methods) { 104 + const match = node.methods[method] || node.methods['']; 105 + if (match) return match; 106 + } 107 + if (node.param && node.param.methods) { 108 + const match = node.param.methods[method] || node.param.methods['']; 109 + if (match) { 110 + const pMap = match[0].paramsMap; 111 + if (pMap?.[pMap?.length - 1]?.[2]) return match; 112 + } 113 + } 114 + if (node.wildcard && node.wildcard.methods) { 115 + const match = node.wildcard.methods[method] || node.wildcard.methods['']; 116 + if (match) { 117 + const pMap = match[0].paramsMap; 118 + if (pMap?.[pMap?.length - 1]?.[2]) return match; 119 + } 120 + } 121 + return; 122 + } 123 + const segment = segments[index]; 124 + if (node.static) { 125 + const staticChild = node.static[segment]; 126 + if (staticChild) { 127 + const match = _lookupTree(ctx, staticChild, method, segments, index + 1); 128 + if (match) return match; 129 + } 130 + } 131 + if (node.param) { 132 + const match = _lookupTree(ctx, node.param, method, segments, index + 1); 133 + if (match) { 134 + if (node.param.hasRegexParam) { 135 + const exactMatch = match.find(m => m.paramsRegexp[index]?.test(segment)) || match.find(m => !m.paramsRegexp[index]); 136 + return exactMatch ? [exactMatch] : void 0; 137 + } 138 + return match; 139 + } 140 + } 141 + if (node.wildcard && node.wildcard.methods) return node.wildcard.methods[method] || node.wildcard.methods['']; 142 + } 143 + 144 + function removeRoute(ctx, method, path) { 145 + const segments = splitPath(path); 146 + return _remove(ctx.root, method || '', segments, 0); 147 + } 148 + function _remove(node, method, segments, index) { 149 + if (index === segments.length) { 150 + if (node.methods && method in node.methods) { 151 + delete node.methods[method]; 152 + if (Object.keys(node.methods).length === 0) node.methods = void 0; 153 + } 154 + return; 155 + } 156 + const segment = segments[index]; 157 + if (segment === '*') { 158 + if (node.param) { 159 + _remove(node.param, method, segments, index + 1); 160 + if (_isEmptyNode(node.param)) node.param = void 0; 161 + } 162 + return; 163 + } 164 + if (segment.startsWith('**')) { 165 + if (node.wildcard) { 166 + _remove(node.wildcard, method, segments, index + 1); 167 + if (_isEmptyNode(node.wildcard)) node.wildcard = void 0; 168 + } 169 + return; 170 + } 171 + const childNode = node.static?.[segment]; 172 + if (childNode) { 173 + _remove(childNode, method, segments, index + 1); 174 + if (_isEmptyNode(childNode)) { 175 + delete node.static[segment]; 176 + if (Object.keys(node.static).length === 0) node.static = void 0; 177 + } 178 + } 179 + } 180 + function _isEmptyNode(node) { 181 + return node.methods === void 0 && node.static === void 0 && node.param === void 0 && node.wildcard === void 0; 182 + } 183 + 184 + function findAllRoutes(ctx, method = '', path, opts) { 185 + if (path.charCodeAt(path.length - 1) === 47) path = path.slice(0, -1); 186 + const segments = splitPath(path); 187 + const matches = _findAll(ctx, ctx.root, method, segments, 0); 188 + if (opts?.params === false) return matches; 189 + return matches.map(m => { 190 + return { 191 + data: m.data, 192 + params: m.paramsMap ? getMatchParams(segments, m.paramsMap) : void 0 193 + }; 194 + }); 195 + } 196 + function _findAll(ctx, node, method, segments, index, matches = []) { 197 + const segment = segments[index]; 198 + if (node.wildcard && node.wildcard.methods) { 199 + const match = node.wildcard.methods[method] || node.wildcard.methods['']; 200 + if (match) matches.push(...match); 201 + } 202 + if (node.param) { 203 + _findAll(ctx, node.param, method, segments, index + 1, matches); 204 + if (index === segments.length && node.param.methods) { 205 + const match = node.param.methods[method] || node.param.methods['']; 206 + if (match) { 207 + const pMap = match[0].paramsMap; 208 + if (pMap?.[pMap?.length - 1]?.[2]) matches.push(...match); 209 + } 210 + } 211 + } 212 + const staticChild = node.static?.[segment]; 213 + if (staticChild) _findAll(ctx, staticChild, method, segments, index + 1, matches); 214 + if (index === segments.length && node.methods) { 215 + const match = node.methods[method] || node.methods['']; 216 + if (match) matches.push(...match); 217 + } 218 + return matches; 219 + } 220 + 221 + function routeToRegExp(route = '/') { 222 + const reSegments = []; 223 + let idCtr = 0; 224 + for (const segment of route.split('/')) { 225 + if (!segment) continue; 226 + if (segment === '*') reSegments.push(`(?<_${idCtr++}>[^/]*)`); 227 + else if (segment.startsWith('**')) reSegments.push(segment === '**' ? '?(?<_>.*)' : `?(?<${segment.slice(3)}>.+)`); 228 + else if (segment.includes(':')) reSegments.push(segment.replace(/:(\w+)/g, (_, id) => `(?<${id}>[^/]+)`).replace(/\./g, '\\.')); 229 + else reSegments.push(segment); 230 + } 231 + return /* @__PURE__ */ new RegExp(`^/${reSegments.join('/')}/?$`); 232 + } 233 + 234 + export { NullProtoObj, addRoute, createRouter, findAllRoutes, findRoute, removeRoute, routeToRegExp };
-306
examples/server/radix3.js
··· 1 - class Radix3Node { 2 - constructor(method) { 3 - this.method = method; 4 - this.prefix = ''; 5 - this.handler = undefined; 6 - this.children = []; 7 - this.paramChild = undefined; 8 - this.wildcardChild = undefined; 9 - this.paramName = undefined; 10 - } 11 - } 12 - 13 - export class Radix3 { 14 - constructor() { 15 - this.methods = {}; 16 - } 17 - 18 - longestCommonPrefix(a, b) { 19 - const minLen = a.length < b.length ? a.length : b.length; 20 - for (let i = 0; i < minLen; i = i + 1) { 21 - if (a[i] !== b[i]) return i; 22 - } 23 - return minLen; 24 - } 25 - 26 - insert(path, handler, method = 'GET') { 27 - if (this.methods[method] === undefined) { 28 - this.methods[method] = new Radix3Node(method); 29 - } 30 - this.insertPath(this.methods[method], path, handler, 0); 31 - } 32 - 33 - get(path, handler) { 34 - this.insert(path, handler, 'GET'); 35 - } 36 - 37 - post(path, handler) { 38 - this.insert(path, handler, 'POST'); 39 - } 40 - 41 - put(path, handler) { 42 - this.insert(path, handler, 'PUT'); 43 - } 44 - 45 - delete(path, handler) { 46 - this.insert(path, handler, 'DELETE'); 47 - } 48 - 49 - patch(path, handler) { 50 - this.insert(path, handler, 'PATCH'); 51 - } 52 - 53 - head(path, handler) { 54 - this.insert(path, handler, 'HEAD'); 55 - } 56 - 57 - options(path, handler) { 58 - this.insert(path, handler, 'OPTIONS'); 59 - } 60 - 61 - insertPath(node, path, handler, start) { 62 - if (start >= path.length) { 63 - node.handler = handler; 64 - return; 65 - } 66 - 67 - const char = path[start]; 68 - 69 - if (char === ':') { 70 - let end = start + 1; 71 - for (let i = end; i < path.length; i = i + 1) { 72 - if (path[i] === '/') break; 73 - end = i + 1; 74 - } 75 - 76 - const paramName = path.substring(start + 1, end); 77 - 78 - if (node.paramChild === undefined) { 79 - node.paramChild = new Radix3Node(); 80 - node.paramChild.paramName = paramName; 81 - } 82 - 83 - this.insertPath(node.paramChild, path, handler, end); 84 - return; 85 - } 86 - 87 - if (char === '*') { 88 - const paramName = path.substring(start + 1, path.length); 89 - 90 - if (node.wildcardChild === undefined) { 91 - node.wildcardChild = new Radix3Node(); 92 - node.wildcardChild.paramName = paramName; 93 - } 94 - 95 - node.wildcardChild.handler = handler; 96 - return; 97 - } 98 - 99 - let end = start; 100 - for (let i = start; i < path.length; i = i + 1) { 101 - if (path[i] === ':' || path[i] === '*') break; 102 - end = i + 1; 103 - } 104 - 105 - const segment = path.substring(start, end); 106 - 107 - for (let i = 0; i < node.children.length; i = i + 1) { 108 - const child = node.children[i]; 109 - const commonLen = this.longestCommonPrefix(child.prefix, segment); 110 - 111 - if (commonLen > 0) { 112 - if (commonLen < child.prefix.length) { 113 - const splitNode = new Radix3Node(); 114 - splitNode.prefix = child.prefix.substring(commonLen, child.prefix.length); 115 - splitNode.handler = child.handler; 116 - splitNode.children = child.children; 117 - splitNode.paramChild = child.paramChild; 118 - splitNode.wildcardChild = child.wildcardChild; 119 - 120 - child.prefix = child.prefix.substring(0, commonLen); 121 - child.handler = undefined; 122 - child.children = [splitNode]; 123 - child.paramChild = undefined; 124 - child.wildcardChild = undefined; 125 - } 126 - 127 - if (commonLen < segment.length) { 128 - this.insertPath(child, path, handler, start + commonLen); 129 - } else { 130 - this.insertPath(child, path, handler, end); 131 - } 132 - 133 - return; 134 - } 135 - } 136 - 137 - const newChild = new Radix3Node(); 138 - newChild.prefix = segment; 139 - node.children.push(newChild); 140 - this.insertPath(newChild, path, handler, end); 141 - } 142 - 143 - lookup(path, method = 'GET') { 144 - const methodRoot = this.methods[method]; 145 - if (methodRoot === undefined) return undefined; 146 - 147 - const params = {}; 148 - const handler = this.matchPath(methodRoot, path, 0, params); 149 - 150 - if (!handler) return undefined; 151 - return { handler, params }; 152 - } 153 - 154 - matchPath(node, path, depth, params) { 155 - if (depth >= path.length) { 156 - return node.handler; 157 - } 158 - 159 - const remaining = path.substring(depth, path.length); 160 - for (let i = 0; i < node.children.length; i = i + 1) { 161 - const child = node.children[i]; 162 - 163 - if (remaining.length >= child.prefix.length) { 164 - let matches = true; 165 - for (let j = 0; j < child.prefix.length; j = j + 1) { 166 - if (remaining[j] !== child.prefix[j]) { 167 - matches = false; 168 - break; 169 - } 170 - } 171 - 172 - if (matches) { 173 - const result = this.matchPath(child, path, depth + child.prefix.length, params); 174 - if (result !== undefined) return result; 175 - } 176 - } 177 - } 178 - 179 - if (node.paramChild !== undefined) { 180 - let paramValue = ''; 181 - let offset = depth; 182 - 183 - for (let i = depth; i < path.length; i = i + 1) { 184 - if (path[i] === '/') break; 185 - paramValue = paramValue + path[i]; 186 - offset = i + 1; 187 - } 188 - 189 - if (paramValue !== '') { 190 - params[node.paramChild.paramName] = paramValue; 191 - const result = this.matchPath(node.paramChild, path, offset, params); 192 - if (result !== undefined) return result; 193 - delete params[node.paramChild.paramName]; 194 - } 195 - } 196 - 197 - if (node.wildcardChild !== undefined) { 198 - const wildcardValue = path.substring(depth, path.length); 199 - params[node.wildcardChild.paramName] = wildcardValue; 200 - return node.wildcardChild.handler; 201 - } 202 - 203 - return undefined; 204 - } 205 - 206 - printTree() { 207 - const methods = Object.keys(this.methods); 208 - for (let i = 0; i < methods.length; i = i + 1) { 209 - const method = methods[i]; 210 - console.log('[' + method + ']'); 211 - 212 - const routes = []; 213 - this.collectRoutes(this.methods[method], '', routes); 214 - 215 - const tree = {}; 216 - for (let j = 0; j < routes.length; j = j + 1) { 217 - const route = routes[j]; 218 - const parts = this.splitPath(route.path); 219 - let current = tree; 220 - for (let k = 0; k < parts.length; k = k + 1) { 221 - const part = parts[k]; 222 - if (current[part] === undefined) { 223 - current[part] = { _children: {}, _handler: false }; 224 - } 225 - if (k === parts.length - 1) { 226 - current[part]._handler = route.hasHandler; 227 - } 228 - current = current[part]._children; 229 - } 230 - } 231 - 232 - this.printPathTree(tree, ''); 233 - if (i < methods.length - 1) console.log(''); 234 - } 235 - } 236 - 237 - splitPath(path) { 238 - const parts = []; 239 - let i = 0; 240 - 241 - while (i < path.length) { 242 - if (path[i] === '/') { 243 - let segment = '/'; 244 - i = i + 1; 245 - while (i < path.length && path[i] !== '/' && path[i] !== ':' && path[i] !== '*') { 246 - segment = segment + path[i]; 247 - i = i + 1; 248 - } 249 - if (segment !== '/') { 250 - parts.push(segment); 251 - } else if (parts.length === 0) { 252 - parts.push('/'); 253 - } 254 - } else if (path[i] === ':' || path[i] === '*') { 255 - let segment = path[i]; 256 - i = i + 1; 257 - while (i < path.length && path[i] !== '/') { 258 - segment = segment + path[i]; 259 - i = i + 1; 260 - } 261 - parts.push(segment); 262 - } else { 263 - i = i + 1; 264 - } 265 - } 266 - 267 - return parts; 268 - } 269 - 270 - printPathTree(tree, indent) { 271 - const keys = Object.keys(tree); 272 - for (let i = 0; i < keys.length; i = i + 1) { 273 - const key = keys[i]; 274 - const node = tree[key]; 275 - const isLast = i === keys.length - 1; 276 - const marker = isLast ? 'โ””โ”€ ' : 'โ”œโ”€ '; 277 - let line = indent + marker + key; 278 - if (node._handler) { 279 - line = line + ' [HANDLER]'; 280 - } 281 - console.log(line); 282 - const childIndent = indent + (isLast ? ' ' : 'โ”‚ '); 283 - this.printPathTree(node._children, childIndent); 284 - } 285 - } 286 - 287 - collectRoutes(node, currentPath, routes) { 288 - const path = currentPath + node.prefix; 289 - 290 - if (node.handler !== undefined) { 291 - routes.push({ path: path, hasHandler: true }); 292 - } 293 - 294 - for (let i = 0; i < node.children.length; i = i + 1) { 295 - this.collectRoutes(node.children[i], path, routes); 296 - } 297 - 298 - if (node.paramChild !== undefined) { 299 - this.collectRoutes(node.paramChild, path + ':' + node.paramChild.paramName, routes); 300 - } 301 - 302 - if (node.wildcardChild !== undefined) { 303 - routes.push({ path: path + '*' + node.wildcardChild.paramName, hasHandler: node.wildcardChild.handler !== undefined }); 304 - } 305 - } 306 - }
+19 -22
examples/server/server.js
··· 2 2 import { join } from 'ant:path'; 3 3 import { html } from './html'; 4 4 import { readFile } from 'ant:fs'; 5 - import { Radix3 } from './radix3'; 5 + import { createRouter, addRoute, findRoute } from '../rou3'; 6 6 7 - const router = new Radix3(); 7 + const router = createRouter(); 8 8 9 - router.get('/', c => c.res.body(`Welcome to Ant ${Ant.version}!`)); 9 + addRoute(router, 'GET', '/', c => c.res.body(`Welcome to Ant ${Ant.version}!`)); 10 10 11 - router.get('/meow', async c => { 11 + addRoute(router, 'GET', '/meow', async c => { 12 12 const userAgent = c.req.header('User-Agent'); 13 13 c.res.header('X-Ant', 'meow'); 14 14 15 15 return c.res.body(`${meow}\n\n${userAgent}`); 16 16 }); 17 17 18 - router.get('/echo', c => 18 + addRoute(router, 'GET', '/echo', c => 19 19 fetch('http://localhost:8000/meow').then(res => { 20 20 c.res.header('X-Ant', 'meow'); 21 21 c.res.body(res.body); 22 22 }) 23 23 ); 24 24 25 - router.get('/get', c => { 25 + addRoute(router, 'GET', '/get', c => { 26 26 console.log(c.get('meow')); 27 27 c.res.body(decodeURIComponent(c.get('meow'))); 28 28 }); 29 29 30 - router.get('/set/*val', c => { 30 + addRoute(router, 'GET', '/set/**:val', c => { 31 31 c.set('meow', c.params.val); 32 32 c.res.body(`meow = ${c.params.val}`); 33 33 }); 34 34 35 - router.get('/fs/meow', async c => { 35 + addRoute(router, 'GET', '/fs/meow', async c => { 36 36 const file = await readFile(join(import.meta.dirname, 'meow.txt')); 37 37 return c.res.body(file || 'none'); 38 38 }); 39 39 40 - router.get('/hello', async c => { 40 + addRoute(router, 'GET', '/hello', async c => { 41 41 return c.res.body('Hello, World!'); 42 42 }); 43 43 44 - router.get('/status', async c => { 44 + addRoute(router, 'GET', '/status', async c => { 45 45 await new Promise(resolve => setTimeout(resolve, 1000)); 46 46 const result = await Promise.resolve('Hello'); 47 47 return c.res.body(`server is responding with ${result}`); 48 48 }); 49 49 50 - router.post('/users/:id', async c => { 50 + addRoute(router, 'POST', '/users/:id', async c => { 51 51 return c.res.body(`User ID: ${c.params.id}`); 52 52 }); 53 53 54 - router.get('/users/:id/posts', async c => { 54 + addRoute(router, 'GET', '/users/:id/posts', async c => { 55 55 return c.res.body(`Posts for user: ${c.params.id}`); 56 56 }); 57 57 58 - router.get('/api/v1/users', async c => { 58 + addRoute(router, 'GET', '/api/v1/users', async c => { 59 59 return c.res.json({ users: [] }); 60 60 }); 61 61 62 - router.get('/zen', async c => { 62 + addRoute(router, 'GET', '/zen', async c => { 63 63 const response = await fetch('https://api.github.com/zen'); 64 64 return c.res.body(response.body); 65 65 }); 66 66 67 - router.get('/api/v2/demo', async c => { 67 + addRoute(router, 'GET', '/api/v2/demo', async c => { 68 68 const data = await fetch('https://themackabu.dev/test.json'); 69 69 return c.res.json(data.json()); 70 70 }); 71 71 72 - router.get('/files/*path', async c => { 72 + addRoute(router, 'GET', '/files/**:path', async c => { 73 73 return c.res.html(html`<div>${c.params.path}</div>`); 74 74 }); 75 75 76 - router.printTree(); 77 - console.log(''); 78 - 79 76 async function handleRequest(c) { 80 77 console.log('request:', c.req.method, c.req.uri); 81 - const result = router.lookup(c.req.uri, c.req.method); 78 + const result = findRoute(router, c.req.method, c.req.uri); 82 79 83 - if (result?.handler) { 80 + if (result?.data) { 84 81 c.params = result.params; 85 - return await result.handler(c); 82 + return await result.data(c); 86 83 } 87 84 88 85 c.res.body('not found: ' + c.req.uri, 404);
+11 -14
examples/spa/server.js
··· 1 1 import { open } from 'ant:fs'; 2 2 import { join, extname } from 'ant:path'; 3 - import { Radix3 } from '../server/radix3'; 3 + import { createRouter, addRoute, findRoute } from '../rou3'; 4 4 5 - const router = new Radix3(); 5 + const router = createRouter(); 6 6 7 7 const validPaths = new Set(); 8 8 const invalidPaths = new Set(); ··· 25 25 ['.woff2', 'font/woff2'] 26 26 ]); 27 27 28 - router.get('/api/version', async c => c.res.json({ version: Ant.version })); 28 + addRoute(router, 'GET', '/api/version', async c => c.res.json({ version: Ant.version })); 29 29 30 - router.get('*path', c => { 31 - const reqPath = c.params.path; 30 + addRoute(router, 'GET', '**', c => { 31 + const reqPath = c.req.uri; 32 32 if (reqPath === '/') return c.res.body(open(indexPath), 200, 'text/html'); 33 33 34 - const filePath = reqPath === '/' ? indexPath : join(basePath, reqPath); 34 + const filePath = join(basePath, reqPath); 35 35 36 36 if (validPaths.has(filePath)) { 37 37 const ext = extname(reqPath) || '.html'; ··· 54 54 } 55 55 }); 56 56 57 - router.printTree(); 58 - console.log(''); 59 - 60 57 async function handleRequest(c) { 61 58 console.log('request:', c.req.method, c.req.uri); 62 - const result = router.lookup(c.req.uri, c.req.method); 59 + const result = findRoute(router, c.req.method, c.req.uri); 63 60 64 - if (result?.handler) { 61 + if (result?.data) { 65 62 c.params = result.params; 66 - return await result.handler(c); 63 + return await result.data(c); 67 64 } 68 65 69 66 c.res.body('not found: ' + c.req.uri, 404); 70 67 } 71 68 72 - console.log('started on http://localhost:8000'); 73 - Ant.serve(8000, handleRequest); 69 + console.log('started on http://localhost:6369'); 70 + Ant.serve(6369, handleRequest);
+1
include/ant.h
··· 9 9 10 10 #define STR_PROTO "__proto__" 11 11 #define STR_PROTO_LEN 9 12 + #define ANT_LIMIT_SIZE_CACHE 16384 12 13 13 14 struct js; 14 15
+6 -1
include/config.h
··· 29 29 SLOT_THIS, 30 30 SLOT_BOUND_THIS, 31 31 SLOT_BOUND_ARGS, 32 - SLOT_STRICT, 33 32 SLOT_FIELD_COUNT, 33 + SLOT_SOURCE, 34 + SLOT_FIELDS, 35 + SLOT_STRICT, 34 36 SLOT_CODE, 35 37 SLOT_CFUNC, 36 38 SLOT_CORO, ··· 39 41 SLOT_SEALED, 40 42 SLOT_EXTENSIBLE, 41 43 SLOT_BUFFER, 44 + SLOT_TARGET_FUNC, 45 + SLOT_VERSION, 46 + SLOT_NAME, 42 47 SLOT_MAX = 255 43 48 } internal_slot_t; 44 49
+5
include/config.h.in
··· 22 22 SLOT_BOUND_THIS, 23 23 SLOT_BOUND_ARGS, 24 24 SLOT_FIELD_COUNT, 25 + SLOT_SOURCE, 26 + SLOT_FIELDS, 25 27 SLOT_STRICT, 26 28 SLOT_CODE, 27 29 SLOT_CFUNC, ··· 31 33 SLOT_SEALED, 32 34 SLOT_EXTENSIBLE, 33 35 SLOT_BUFFER, 36 + SLOT_TARGET_FUNC, 37 + SLOT_VERSION, 38 + SLOT_NAME, 34 39 SLOT_MAX = 255 35 40 } internal_slot_t; 36 41
+1 -1
meson.build
··· 79 79 build_date = run_command('date', '+%Y-%m-%d', check: true).stdout().strip() 80 80 81 81 version_conf = configuration_data() 82 - version_conf.set('ANT_VERSION', '0.2.2.39') 82 + version_conf.set('ANT_VERSION', '0.2.2.56') 83 83 version_conf.set('ANT_GIT_HASH', git_hash) 84 84 version_conf.set('ANT_BUILD_DATE', build_date) 85 85
+367 -399
src/ant.c
··· 37 37 #define CORO_MALLOC(size) calloc(1, size) 38 38 #define CORO_FREE(ptr) free(ptr) 39 39 40 - /* ========== PERFORMANCE INSTRUMENTATION ========== */ 41 - #define PERF_INSTRUMENTATION 1 42 - 43 - #if PERF_INSTRUMENTATION 44 - #include <mach/mach_time.h> 45 - 46 - static uint64_t perf_call_count = 0; 47 - static uint64_t perf_phase4_ns = 0; 48 - static uint64_t perf_phase4_sub1_ns = 0; // Parameter binding loop 49 - static uint64_t perf_phase4_sub2_ns = 0; // Rest parameter handling 50 - static uint64_t perf_phase4_sub3_ns = 0; // code_uses_arguments check 51 - static uint64_t perf_phase4_sub4_ns = 0; // setup_arguments 52 - static uint64_t perf_phase4_sub5_ns = 0; // NFE name binding 53 - static uint64_t perf_phase4_sub6_ns = 0; // Strict mode lookup + this handling 54 - static uint64_t perf_sub5_vstr_ns = 0; // vstr inside sub5 55 - static uint64_t perf_sub5_mkprop_ns = 0; // mkprop inside sub5 56 - static uint64_t perf_sub5_count = 0; // Times sub5 actually runs 57 - static uint64_t perf_mkprop_intern_ns = 0; // intern_string inside mkprop 58 - static uint64_t perf_mkprop_setkoff_ns = 0; // set_koff_intern inside mkprop 59 - static uint64_t perf_mkprop_mkentity_ns = 0; // mkentity inside mkprop 60 - static uint64_t perf_mkprop_setup_ns = 0; // setup (memcpy) inside mkprop 61 - static uint64_t perf_mkprop_count = 0; 62 - static uint64_t perf_lkp_calls = 0; 63 - static uint64_t perf_lkp_cache_hits = 0; 64 - static uint64_t perf_lkp_not_found = 0; 65 - static uint64_t perf_lkp_found_no_cache = 0; /* Found but wasn't in cache */ 66 - static uint64_t perf_lkp_trace_count = 0; 67 - #define PERF_LKP_TRACE 1 /* Set to 1 to trace lookups */ 68 - static uint64_t perf_report_interval = 100000; 69 - static mach_timebase_info_data_t perf_timebase = {0}; 70 - 71 - static inline uint64_t perf_now_ns(void) { 72 - if (perf_timebase.denom == 0) mach_timebase_info(&perf_timebase); 73 - return mach_absolute_time() * perf_timebase.numer / perf_timebase.denom; 74 - } 75 - 76 - static void perf_report(size_t heap_size) { 77 - if (perf_call_count == 0) return; 78 - fprintf(stderr, "\n[PERF] After %llu calls, heap=%zu KB\n", perf_call_count, heap_size / 1024); 79 - fprintf(stderr, "[PERF] phase4 total: %.1f ns/call\n", (double)perf_phase4_ns / perf_call_count); 80 - fprintf(stderr, "[PERF] sub1 (param bind): %.1f ns\n", (double)perf_phase4_sub1_ns / perf_call_count); 81 - fprintf(stderr, "[PERF] sub2 (rest params): %.1f ns\n", (double)perf_phase4_sub2_ns / perf_call_count); 82 - fprintf(stderr, "[PERF] sub3 (uses_arguments): %.1f ns\n", (double)perf_phase4_sub3_ns / perf_call_count); 83 - fprintf(stderr, "[PERF] sub4 (setup_args): %.1f ns\n", (double)perf_phase4_sub4_ns / perf_call_count); 84 - fprintf(stderr, "[PERF] sub5 (NFE bind): %.1f ns (runs=%llu, %.1f%%)\n", 85 - (double)perf_phase4_sub5_ns / perf_call_count, 86 - perf_sub5_count, 100.0 * perf_sub5_count / perf_call_count); 87 - if (perf_sub5_count > 0) { 88 - fprintf(stderr, "[PERF] sub5.vstr: %.1f ns/run\n", (double)perf_sub5_vstr_ns / perf_sub5_count); 89 - fprintf(stderr, "[PERF] sub5.mkprop: %.1f ns/run\n", (double)perf_sub5_mkprop_ns / perf_sub5_count); 90 - } 91 - fprintf(stderr, "[PERF] sub6 (strict+this): %.1f ns\n", (double)perf_phase4_sub6_ns / perf_call_count); 92 - if (perf_mkprop_count > 0) { 93 - fprintf(stderr, "[PERF] mkprop breakdown (%llu calls):\n", perf_mkprop_count); 94 - fprintf(stderr, "[PERF] setup: %.1f ns\n", (double)perf_mkprop_setup_ns / perf_mkprop_count); 95 - fprintf(stderr, "[PERF] intern: %.1f ns\n", (double)perf_mkprop_intern_ns / perf_mkprop_count); 96 - fprintf(stderr, "[PERF] setkoff: %.1f ns\n", (double)perf_mkprop_setkoff_ns / perf_mkprop_count); 97 - fprintf(stderr, "[PERF] mkentity: %.1f ns\n", (double)perf_mkprop_mkentity_ns / perf_mkprop_count); 98 - } 99 - if (perf_lkp_calls > 0) { 100 - fprintf(stderr, "[PERF] lkp_interned: %llu calls, %.1f%% cache hits, %.1f%% found (no cache), %.1f%% not found\n", 101 - perf_lkp_calls, 102 - 100.0 * perf_lkp_cache_hits / perf_lkp_calls, 103 - 100.0 * perf_lkp_found_no_cache / perf_lkp_calls, 104 - 100.0 * perf_lkp_not_found / perf_lkp_calls); 105 - fprintf(stderr, "[PERF] per function call: %.1f lookups\n", (double)perf_lkp_calls / perf_call_count); 106 - } 107 - } 108 - 109 - #define PERF_START(var) uint64_t var = perf_now_ns() 110 - #define PERF_END(var, accum) (accum) += (perf_now_ns() - (var)) 111 - #define PERF_CALL_INC() (++perf_call_count) 112 - #define PERF_SHOULD_REPORT() ((perf_call_count % perf_report_interval) == 0) 113 - #else 114 - #define PERF_START(var) (void)0 115 - #define PERF_END(var, accum) (void)0 116 - #define PERF_CALL_INC() (void)0 117 - #define PERF_SHOULD_REPORT() (false) 118 - #endif 119 - /* ========== END PERFORMANCE INSTRUMENTATION ========== */ 120 - 121 40 _Static_assert(sizeof(double) == 8, "NaN-boxing requires 64-bit IEEE 754 doubles"); 122 41 _Static_assert(sizeof(uint64_t) == 8, "NaN-boxing requires 64-bit integers"); 123 42 _Static_assert(sizeof(double) == sizeof(uint64_t), "double and uint64_t must have same size"); ··· 278 197 UT_hash_handle hh; 279 198 } obj_prop_cache_t; 280 199 281 - typedef struct interned_string { 282 - uint64_t hash; 283 - char *str; 284 - size_t len; 285 - struct interned_string *next; 286 - } interned_string_t; 287 - 288 - #define INTERN_BUCKETS 16384 289 - static interned_string_t *intern_buckets[INTERN_BUCKETS]; 290 - 291 200 static const char *INTERN_LENGTH = NULL; 292 201 static const char *INTERN_PROTOTYPE = NULL; 293 202 static const char *INTERN_CONSTRUCTOR = NULL; ··· 296 205 static const char *INTERN_VALUE = NULL; 297 206 static const char *INTERN_GET = NULL; 298 207 static const char *INTERN_SET = NULL; 299 - static const char *INTERN_TARGET_FUNC = NULL; 208 + 300 209 static const char *INTERN_ARGUMENTS = NULL; 301 210 static const char *INTERN_CALLEE = NULL; 302 211 static const char *INTERN_IDX[10] = {NULL}; 303 212 static const char *INTERN_PROMISE = NULL; 304 213 305 - #define INTERN_PROP_CACHE_SIZE 4096 214 + typedef struct interned_string { 215 + uint64_t hash; 216 + char *str; 217 + size_t len; 218 + struct interned_string *next; 219 + } interned_string_t; 220 + 221 + static interned_string_t *intern_buckets[ANT_LIMIT_SIZE_CACHE]; 222 + 306 223 typedef struct { 307 224 jsoff_t obj_off; 308 225 const char *intern_ptr; 309 226 jsoff_t prop_off; 227 + uint32_t obj_version; 310 228 } intern_prop_cache_entry_t; 311 - static intern_prop_cache_entry_t intern_prop_cache[INTERN_PROP_CACHE_SIZE]; 229 + static intern_prop_cache_entry_t intern_prop_cache[ANT_LIMIT_SIZE_CACHE]; 312 230 313 231 typedef struct promise_handler { 314 232 jsval_t onFulfilled; ··· 478 396 jsval_t tval; // holds last parsed numeric or string literal value 479 397 jsval_t scope; // current scope 480 398 jsval_t this_val; // 'this' value for currently executing function 399 + jsval_t module_ns; // current ESM module namespace 481 400 uint8_t *mem; // available JS memory 482 401 jsoff_t size; // memory size 483 402 jsoff_t brk; // current mem usage boundary ··· 509 428 TOK_COLON, TOK_Q, TOK_ASSIGN, TOK_PLUS_ASSIGN, TOK_MINUS_ASSIGN, 510 429 TOK_MUL_ASSIGN, TOK_DIV_ASSIGN, TOK_REM_ASSIGN, TOK_SHL_ASSIGN, 511 430 TOK_SHR_ASSIGN, TOK_ZSHR_ASSIGN, TOK_AND_ASSIGN, TOK_XOR_ASSIGN, 512 - TOK_OR_ASSIGN, TOK_COMMA, TOK_TEMPLATE, TOK_ARROW, TOK_HASH, 431 + TOK_OR_ASSIGN, TOK_LOR_ASSIGN, TOK_LAND_ASSIGN, TOK_NULLISH_ASSIGN, 432 + TOK_COMMA, TOK_TEMPLATE, TOK_ARROW, TOK_HASH, 513 433 TOK_MAX 514 434 }; 515 435 ··· 740 660 } 741 661 742 662 static bool is_assign(uint8_t tok) { 743 - return (tok >= TOK_ASSIGN && tok <= TOK_OR_ASSIGN); 663 + return (tok >= TOK_ASSIGN && tok <= TOK_OR_ASSIGN) || 664 + tok == TOK_LOR_ASSIGN || tok == TOK_LAND_ASSIGN || tok == TOK_NULLISH_ASSIGN; 744 665 } 745 666 746 667 static bool is_keyword_propname(uint8_t tok) { ··· 845 766 static jsval_t do_in(struct js *js, jsval_t l, jsval_t r); 846 767 static jsval_t resolveprop(struct js *js, jsval_t v); 847 768 static jsoff_t lkp(struct js *js, jsval_t obj, const char *buf, size_t len); 769 + static jsoff_t lkp_scope(struct js *js, jsval_t scope, const char *buf, size_t len); 848 770 static jsoff_t lkp_interned(struct js *js, jsval_t obj, const char *search_intern, size_t len); 849 771 static const char *intern_string(const char *str, size_t len); 850 772 static jsval_t get_slot(struct js *js, jsval_t obj, internal_slot_t slot); ··· 961 883 ctx->has_error = is_err(result); 962 884 963 885 if (ctx->has_error) { 964 - js_reject_promise(js, ctx->promise, result); 886 + jsval_t reject_value = js->thrown_value; 887 + if (vtype(reject_value) == T_UNDEF) { 888 + reject_value = js_mkstr(js, js->errmsg ? js->errmsg : "Unknown error", js->errmsg ? strlen(js->errmsg) : 13); 889 + } 890 + js->flags &= (uint8_t)~F_THROW; 891 + js->thrown_value = js_mkundef(); 892 + js_reject_promise(js, ctx->promise, reject_value); 965 893 } else { 966 894 js_resolve_promise(js, ctx->promise, result); 967 895 } ··· 1990 1918 jsoff_t proto_off = lkp_interned(js, func_obj, INTERN_PROTOTYPE, 9); 1991 1919 if (proto_off != 0) { 1992 1920 jsval_t proto_val = resolveprop(js, mkval(T_PROP, proto_off)); 1993 - if (vtype(proto_val) == T_OBJ) n += print_prototype(js, proto_val, buf + n, len - n, &first); 1921 + if (vtype(proto_val) == T_OBJ && !is_on_stack(proto_val)) n += print_prototype(js, proto_val, buf + n, len - n, &first); 1994 1922 } 1995 1923 1996 1924 stringify_indent--; ··· 2909 2837 2910 2838 static const char *intern_string(const char *str, size_t len) { 2911 2839 uint64_t h = hash_key(str, len); 2912 - uint32_t bucket = (uint32_t)(h & (INTERN_BUCKETS - 1)); 2840 + uint32_t bucket = (uint32_t)(h & (ANT_LIMIT_SIZE_CACHE - 1)); 2913 2841 2914 2842 for (interned_string_t *e = intern_buckets[bucket]; e; e = e->next) { 2915 2843 if (e->hash == h && e->len == len && memcmp(e->str, str, len) == 0) return e->str; ··· 2941 2869 INTERN_GET = intern_string("get", 3); 2942 2870 INTERN_SET = intern_string("set", 3); 2943 2871 INTERN_PROMISE = intern_string("promise", 7); 2944 - INTERN_TARGET_FUNC = intern_string("__target_func", 13); 2945 2872 INTERN_ARGUMENTS = intern_string("arguments", 9); 2946 2873 INTERN_CALLEE = intern_string("callee", 6); 2947 2874 INTERN_IDX[0] = intern_string("0", 1); ··· 2956 2883 INTERN_IDX[9] = intern_string("9", 1); 2957 2884 } 2958 2885 2959 - static void invalidate_prop_cache(jsoff_t obj_offset) { 2960 - for (uint32_t i = 0; i < INTERN_PROP_CACHE_SIZE; i++) { 2961 - if (intern_prop_cache[i].obj_off == obj_offset) { 2962 - intern_prop_cache[i].obj_off = 0; 2963 - intern_prop_cache[i].intern_ptr = NULL; 2964 - intern_prop_cache[i].prop_off = 0; 2965 - } 2966 - } 2886 + static void increment_version(struct js *js, jsoff_t obj_off) { 2887 + jsval_t version_val = get_slot(js, mkval(T_OBJ, obj_off), SLOT_VERSION); 2888 + uint32_t new_version = (vtype(version_val) == T_NUM) ? (uint32_t)tod(version_val) + 1 : 1; 2889 + set_slot(js, mkval(T_OBJ, obj_off), SLOT_VERSION, tov((double)new_version)); 2967 2890 } 2968 2891 2969 2892 static jsval_t mkprop(struct js *js, jsval_t obj, jsval_t k, jsval_t v, bool is_const) { 2970 - PERF_START(_mkprop_setup); 2971 2893 jsoff_t koff = (jsoff_t) vdata(k); 2972 2894 jsoff_t b, head = (jsoff_t) vdata(obj); 2973 2895 char buf[sizeof(koff) + sizeof(v)]; ··· 2978 2900 2979 2901 if (b & ARRMASK) brk |= ARRMASK; 2980 2902 memcpy(&js->mem[head], &brk, sizeof(brk)); 2981 - PERF_END(_mkprop_setup, perf_mkprop_setup_ns); 2982 2903 2983 - PERF_START(_mkprop_intern); 2984 2904 jsoff_t klen = (loadoff(js, koff) >> 2) - 1; 2985 2905 const char *p = (char *) &js->mem[koff + sizeof(koff)]; 2986 2906 (void)intern_string(p, klen); /* Ensure key is interned for fast lookup later */ 2987 - PERF_END(_mkprop_intern, perf_mkprop_intern_ns); 2988 2907 2989 - PERF_START(_mkprop_mkentity); 2990 2908 jsoff_t prop_header = (b & ~(3U | CONSTMASK | ARRMASK | SLOTMASK)) | T_PROP; 2991 2909 if (is_const) prop_header |= CONSTMASK; 2992 2910 jsval_t result = mkentity(js, prop_header, buf, sizeof(buf)); 2993 - PERF_END(_mkprop_mkentity, perf_mkprop_mkentity_ns); 2994 2911 2995 - perf_mkprop_count++; 2912 + increment_version(js, head); 2996 2913 return result; 2914 + } 2915 + 2916 + static inline jsval_t mkprop_fast(struct js *js, jsval_t obj, jsval_t k, jsval_t v, bool is_const) { 2917 + jsoff_t koff = (jsoff_t) vdata(k); 2918 + jsoff_t b, head = (jsoff_t) vdata(obj); 2919 + char buf[sizeof(koff) + sizeof(v)]; 2920 + memcpy(&b, &js->mem[head], sizeof(b)); 2921 + memcpy(buf, &koff, sizeof(koff)); 2922 + memcpy(buf + sizeof(koff), &v, sizeof(v)); 2923 + jsoff_t brk = js->brk | T_OBJ; 2924 + 2925 + if (b & ARRMASK) brk |= ARRMASK; 2926 + memcpy(&js->mem[head], &brk, sizeof(brk)); 2927 + 2928 + jsoff_t prop_header = (b & ~(3U | CONSTMASK | ARRMASK | SLOTMASK)) | T_PROP; 2929 + if (is_const) prop_header |= CONSTMASK; 2930 + return mkentity(js, prop_header, buf, sizeof(buf)); 2931 + } 2932 + 2933 + static inline jsval_t mkprop_new(struct js *js, jsval_t obj, jsoff_t koff, jsval_t v) { 2934 + jsoff_t b = loadoff(js, (jsoff_t)vdata(obj)); 2935 + jsoff_t brk = js->brk | T_OBJ; 2936 + 2937 + if (b & ARRMASK) brk |= ARRMASK; 2938 + memcpy(&js->mem[(jsoff_t)vdata(obj)], &brk, sizeof(brk)); 2939 + 2940 + char buf[sizeof(koff) + sizeof(v)]; 2941 + memcpy(buf, &koff, sizeof(koff)); 2942 + memcpy(buf + sizeof(koff), &v, sizeof(v)); 2943 + 2944 + jsoff_t prop_header = (b & ~(3U | CONSTMASK | ARRMASK | SLOTMASK)) | T_PROP; 2945 + return mkentity(js, prop_header, buf, sizeof(buf)); 2997 2946 } 2998 2947 2999 2948 static jsval_t mkslot(struct js *js, jsval_t obj, internal_slot_t slot, jsval_t v) { ··· 3168 3117 } 3169 3118 3170 3119 saveval(js, existing + sizeof(jsoff_t) * 2, v); 3120 + increment_version(js, (jsoff_t)vdata(obj)); 3171 3121 if (vtype(obj) != T_ARR || klen == 0 || key[0] < '0' || key[0] > '9') goto done_update; 3172 3122 3173 3123 char *endptr; ··· 3184 3134 3185 3135 jsval_t len_key = js_mkstr(js, "length", 6); 3186 3136 jsval_t new_len = tov((double)(update_idx + 1)); 3187 - if (len_off != 0) saveval(js, len_off + sizeof(jsoff_t) * 2, new_len); 3188 - else mkprop(js, obj, len_key, new_len, false); 3137 + if (len_off != 0) { 3138 + saveval(js, len_off + sizeof(jsoff_t) * 2, new_len); 3139 + increment_version(js, (jsoff_t)vdata(obj)); 3140 + } else mkprop(js, obj, len_key, new_len, false); 3189 3141 3190 3142 done_update: 3191 3143 return mkval(T_PROP, existing); ··· 3232 3184 jsval_t new_len = tov((double)(idx + 1)); 3233 3185 if (len_off != 0) { 3234 3186 saveval(js, len_off + sizeof(jsoff_t) * 2, new_len); 3187 + increment_version(js, (jsoff_t)vdata(obj)); 3235 3188 } else mkprop(js, obj, len_key, new_len, false); 3236 3189 } 3237 3190 ··· 3587 3540 #define LOOK(OFS, CH) js->toff + OFS < js->clen && buf[OFS] == CH 3588 3541 3589 3542 switch (buf[0]) { 3590 - case '?': if (LOOK(1, '?')) TOK(TOK_NULLISH, 2); if (LOOK(1, '.')) TOK(TOK_OPTIONAL_CHAIN, 2); TOK(TOK_Q, 1); 3543 + 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); 3591 3544 case ':': TOK(TOK_COLON, 1); 3592 3545 case '(': TOK(TOK_LPAREN, 1); 3593 3546 case ')': TOK(TOK_RPAREN, 1); ··· 3621 3574 case '*': if (LOOK(1, '*')) TOK(TOK_EXP, 2); if (LOOK(1, '=')) TOK(TOK_MUL_ASSIGN, 2); TOK(TOK_MUL, 1); 3622 3575 case '/': if (LOOK(1, '=')) TOK(TOK_DIV_ASSIGN, 2); TOK(TOK_DIV, 1); 3623 3576 case '%': if (LOOK(1, '=')) TOK(TOK_REM_ASSIGN, 2); TOK(TOK_REM, 1); 3624 - case '&': if (LOOK(1, '&')) TOK(TOK_LAND, 2); if (LOOK(1, '=')) TOK(TOK_AND_ASSIGN, 2); TOK(TOK_AND, 1); 3625 - case '|': if (LOOK(1, '|')) TOK(TOK_LOR, 2); if (LOOK(1, '=')) TOK(TOK_OR_ASSIGN, 2); TOK(TOK_OR, 1); 3577 + 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); 3578 + 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); 3626 3579 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); 3627 3580 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); 3628 3581 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 3882 } 3930 3883 3931 3884 static inline jsoff_t lkp_interned(struct js *js, jsval_t obj, const char *search_intern, size_t len) { 3932 - perf_lkp_calls++; 3933 - #if PERF_LKP_TRACE 3934 - if (perf_call_count >= 1 && perf_lkp_trace_count < 300) { 3935 - perf_lkp_trace_count++; 3936 - fprintf(stderr, "[LKP] '%.*s' on obj@%lu\n", (int)len, search_intern, (unsigned long)vdata(obj)); 3937 - } 3938 - #endif 3939 3885 jsoff_t obj_off = (jsoff_t)vdata(obj); 3940 3886 3941 - uint32_t cache_slot = (((uintptr_t)search_intern >> 3) ^ obj_off) & (INTERN_PROP_CACHE_SIZE - 1); 3887 + jsval_t version_val = get_slot(js, mkval(T_OBJ, obj_off), SLOT_VERSION); 3888 + uint32_t current_version = (vtype(version_val) == T_NUM) ? (uint32_t)tod(version_val) : 0; 3889 + 3890 + uint32_t cache_slot = (((uintptr_t)search_intern >> 3) ^ obj_off) & (ANT_LIMIT_SIZE_CACHE - 1); 3942 3891 intern_prop_cache_entry_t *ce = &intern_prop_cache[cache_slot]; 3943 - if (ce->obj_off == obj_off && ce->intern_ptr == search_intern) { 3944 - perf_lkp_cache_hits++; 3892 + if (ce->obj_off == obj_off && ce->intern_ptr == search_intern && ce->obj_version == current_version) { 3945 3893 return ce->prop_off; 3946 3894 } 3947 3895 ··· 3955 3903 if (klen == len) { 3956 3904 const char *p = (char *) &js->mem[koff + sizeof(koff)]; 3957 3905 if (intern_string(p, klen) == search_intern) { 3958 - perf_lkp_found_no_cache++; 3959 3906 ce->obj_off = obj_off; 3960 3907 ce->intern_ptr = search_intern; 3961 3908 ce->prop_off = off; 3909 + ce->obj_version = current_version; 3962 3910 return off; 3963 3911 } 3964 3912 } 3965 3913 off = next_prop(header); 3966 3914 } 3967 - perf_lkp_not_found++; 3915 + 3916 + ce->obj_off = obj_off; 3917 + ce->intern_ptr = search_intern; 3918 + ce->prop_off = 0; 3919 + ce->obj_version = current_version; 3920 + 3968 3921 return 0; 3969 3922 } 3970 3923 ··· 3974 3927 return lkp_interned(js, obj, search_intern, len); 3975 3928 } 3976 3929 3930 + static jsoff_t lkp_scope(struct js *js, jsval_t scope, const char *buf, size_t len) { 3931 + const char *search_intern = intern_string(buf, len); 3932 + if (!search_intern) return 0; 3933 + 3934 + jsoff_t scope_off = (jsoff_t)vdata(scope); 3935 + jsoff_t off = loadoff(js, scope_off) & ~(3U | CONSTMASK | ARRMASK | SLOTMASK); 3936 + 3937 + while (off < js->brk && off != 0) { 3938 + jsoff_t header = loadoff(js, off); 3939 + if (is_slot_prop(header)) { off = next_prop(header); continue; } 3940 + 3941 + jsoff_t koff = loadoff(js, (jsoff_t)(off + sizeof(off))); 3942 + jsoff_t klen = (loadoff(js, koff) >> 2) - 1; 3943 + if (klen == len) { 3944 + const char *p = (char *)&js->mem[koff + sizeof(koff)]; 3945 + if (intern_string(p, klen) == search_intern) return off; 3946 + } 3947 + off = next_prop(header); 3948 + } 3949 + return 0; 3950 + } 3951 + 3977 3952 static jsoff_t lkp(struct js *js, jsval_t obj, const char *buf, size_t len) { 3978 3953 return lkp_inline(js, obj, buf, len); 3979 3954 } 3980 - 3981 3955 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) { 3982 3956 *has_getter_out = false; 3983 3957 *getter_out = js_mkundef(); ··· 4030 4004 return 0; 4031 4005 } 4032 4006 4007 + 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) { 4008 + if (!has_accessor || (vtype(accessor) != T_FUNC && vtype(accessor) != T_CFUNC)) { 4009 + return is_setter ? js_mkundef() : js_mkundef(); 4010 + } 4011 + 4012 + js_parse_state_t saved; 4013 + JS_SAVE_STATE(js, saved); 4014 + uint8_t saved_flags = js->flags; 4015 + jsoff_t saved_toff = js->toff; 4016 + jsoff_t saved_tlen = js->tlen; 4017 + 4018 + push_this(prim); 4019 + jsval_t result = call_js_with_args(js, accessor, arg, arg_count); 4020 + pop_this(); 4021 + 4022 + JS_RESTORE_STATE(js, saved); 4023 + js->flags = saved_flags; 4024 + js->toff = saved_toff; 4025 + js->tlen = saved_tlen; 4026 + 4027 + if (is_setter) return is_err(result) ? result : (arg ? *arg : js_mkundef()); 4028 + return result; 4029 + } 4030 + 4033 4031 jsval_t js_get_proto(struct js *js, jsval_t obj) { 4034 4032 uint8_t t = vtype(obj); 4035 4033 ··· 4061 4059 } 4062 4060 4063 4061 jsval_t js_get_ctor_proto(struct js *js, const char *name, size_t len) { 4064 - jsoff_t ctor_off = lkp(js, js->scope, name, len); 4062 + jsoff_t ctor_off = lkp_scope(js, js->scope, name, len); 4065 4063 4066 4064 if (ctor_off == 0 && global_scope_stack) { 4067 4065 int stack_len = utarray_len(global_scope_stack); 4068 4066 for (int i = stack_len - 1; i >= 0 && ctor_off == 0; i--) { 4069 4067 jsoff_t *scope_off = (jsoff_t *)utarray_eltptr(global_scope_stack, i); 4070 4068 jsval_t scope = mkval(T_OBJ, *scope_off); 4071 - ctor_off = lkp(js, scope, name, len); 4069 + ctor_off = lkp_scope(js, scope, name, len); 4072 4070 } 4073 4071 } else if (ctor_off == 0) { 4074 4072 for (jsval_t scope = upper(js, js->scope); vdata(scope) != 0 && ctor_off == 0; scope = upper(js, scope)) { 4075 - ctor_off = lkp(js, scope, name, len); 4073 + ctor_off = lkp_scope(js, scope, name, len); 4076 4074 } 4077 4075 } 4078 4076 ··· 4193 4191 4194 4192 const char *key_intern = intern_string(key_str, key_len); 4195 4193 4196 - for (jsval_t scope = js->scope;;) { 4197 - jsoff_t off = lkp_interned(js, scope, key_intern, key_len); 4198 - if (off != 0) return mkval(T_PROP, off); 4194 + jsval_t parent_scope = upper(js, js->scope); 4195 + 4196 + jsoff_t off = lkp_interned(js, js->scope, key_intern, key_len); 4197 + if (off != 0) { 4198 + return mkval(T_PROP, off); 4199 + } 4200 + 4201 + jsval_t with_slot = get_slot(js, js->scope, SLOT_WITH); 4202 + if (vtype(with_slot) != T_UNDEF) { 4203 + jsval_t with_obj = ( 4204 + vtype(with_slot) == T_OBJ || 4205 + vtype(with_slot) == T_ARR || 4206 + vtype(with_slot) == T_FUNC) ? 4207 + with_slot : mkval(T_OBJ, vdata(with_slot) 4208 + ); 4209 + 4210 + jsoff_t prop_off = lkp_interned(js, with_obj, key_intern, key_len); 4211 + if (prop_off != 0) { 4212 + jsval_t key = js_mkstr(js, key_str, key_len); 4213 + if (is_err(key)) return key; 4214 + return mkpropref((jsoff_t)vdata(with_obj), (jsoff_t)vdata(key)); 4215 + } 4216 + } 4217 + 4218 + uint8_t depth = 1; 4219 + for (jsval_t scope = parent_scope; depth < 255; depth++) { 4220 + off = lkp_interned(js, scope, key_intern, key_len); 4221 + if (off != 0) { 4222 + return mkval(T_PROP, off); 4223 + } 4199 4224 4200 4225 jsval_t with_slot = get_slot(js, scope, SLOT_WITH); 4201 4226 if (vtype(with_slot) != T_UNDEF) { ··· 4234 4259 jsval_t getter = js_mkundef(); 4235 4260 bool has_getter = false; 4236 4261 lkp_with_getter(js, obj, key, key_len, &getter, &has_getter); 4237 - 4238 - if (!has_getter) return false; 4239 - if (vtype(getter) != T_FUNC && vtype(getter) != T_CFUNC) return false; 4240 - 4241 - js_parse_state_t saved; 4242 - JS_SAVE_STATE(js, saved); 4243 - uint8_t saved_flags = js->flags; 4244 - jsoff_t saved_toff = js->toff; 4245 - jsoff_t saved_tlen = js->tlen; 4246 - 4247 - jsval_t saved_this = js->this_val; 4248 - js->this_val = obj; 4249 - push_this(obj); 4250 - *out = call_js_with_args(js, getter, NULL, 0); 4251 - 4252 - pop_this(); 4253 - js->this_val = saved_this; 4254 - 4255 - JS_RESTORE_STATE(js, saved); 4256 - js->flags = saved_flags; 4257 - js->toff = saved_toff; 4258 - js->tlen = saved_tlen; 4259 - 4260 - return true; 4262 + 4263 + jsval_t result = call_proto_accessor(js, obj, getter, has_getter, NULL, 0, false); 4264 + if (vtype(result) != T_UNDEF) { 4265 + *out = result; 4266 + return true; 4267 + } 4268 + return false; 4261 4269 } 4262 4270 4263 4271 static jsval_t resolveprop(struct js *js, jsval_t v) { ··· 4271 4279 jsoff_t key_len; 4272 4280 const char *key_str = (const char *)&js->mem[vstr(js, key, &key_len)]; 4273 4281 4274 - jsval_t proto = get_prototype_for_type(js, vtype(prim)); 4275 - if (vtype(proto) == T_OBJ) { 4276 - jsval_t getter = js_mkundef(); 4277 - bool has_getter = false; 4278 - lkp_with_getter(js, proto, key_str, key_len, &getter, &has_getter); 4279 - if (has_getter && (vtype(getter) == T_FUNC || vtype(getter) == T_CFUNC)) { 4280 - js_parse_state_t saved; 4281 - JS_SAVE_STATE(js, saved); 4282 - uint8_t saved_flags = js->flags; 4283 - jsoff_t saved_toff = js->toff; 4284 - jsoff_t saved_tlen = js->tlen; 4285 - 4286 - push_this(prim); 4287 - jsval_t result = call_js_with_args(js, getter, NULL, 0); 4288 - pop_this(); 4289 - 4290 - JS_RESTORE_STATE(js, saved); 4291 - js->flags = saved_flags; 4292 - js->toff = saved_toff; 4293 - js->tlen = saved_tlen; 4294 - 4295 - return result; 4296 - } 4297 - 4298 - jsoff_t off = lkp_proto(js, prim, key_str, key_len); 4299 - if (off != 0) return resolveprop(js, mkval(T_PROP, off)); 4300 - } 4282 + jsval_t proto = get_prototype_for_type(js, vtype(prim)); 4283 + if (vtype(proto) == T_OBJ) { 4284 + jsval_t getter = js_mkundef(); 4285 + bool has_getter = false; 4286 + lkp_with_getter(js, proto, key_str, key_len, &getter, &has_getter); 4287 + jsval_t result = call_proto_accessor(js, prim, getter, has_getter, NULL, 0, false); 4288 + if (vtype(result) != T_UNDEF) return result; 4289 + 4290 + jsoff_t off = lkp_proto(js, prim, key_str, key_len); 4291 + if (off != 0) return resolveprop(js, mkval(T_PROP, off)); 4292 + } 4301 4293 4302 4294 return js_mkundef(); 4303 4295 } ··· 4353 4345 jsval_t setter = js_mkundef(); 4354 4346 bool has_setter = false; 4355 4347 lkp_with_setter(js, obj, key, key_len, &setter, &has_setter); 4356 - 4357 - if (!has_setter) return false; 4358 - if (vtype(setter) != T_FUNC && vtype(setter) != T_CFUNC) return false; 4359 - 4360 - js_parse_state_t saved; 4361 - JS_SAVE_STATE(js, saved); 4362 - uint8_t saved_flags = js->flags; 4363 - jsoff_t saved_toff = js->toff; 4364 - jsoff_t saved_tlen = js->tlen; 4365 - 4366 - jsval_t saved_this = js->this_val; 4367 - js->this_val = obj; 4368 - push_this(obj); 4369 - jsval_t result = call_js_with_args(js, setter, &val, 1); 4370 - pop_this(); 4371 - js->this_val = saved_this; 4372 - 4373 - JS_RESTORE_STATE(js, saved); 4374 - js->flags = saved_flags; 4375 - js->toff = saved_toff; 4376 - js->tlen = saved_tlen; 4377 - 4378 - *out = is_err(result) ? result : val; 4379 - return true; 4348 + 4349 + jsval_t result = call_proto_accessor(js, obj, setter, has_setter, &val, 1, true); 4350 + if (vtype(result) != T_UNDEF) { 4351 + *out = result; 4352 + return true; 4353 + } 4354 + return false; 4380 4355 } 4381 4356 4382 4357 static jsval_t assign(struct js *js, jsval_t lhs, jsval_t val) { ··· 4393 4368 jsoff_t key_len; 4394 4369 const char *key_str = (const char *)&js->mem[vstr(js, key, &key_len)]; 4395 4370 4396 - jsval_t proto = get_prototype_for_type(js, vtype(prim)); 4397 - if (vtype(proto) == T_OBJ) { 4398 - jsval_t setter = js_mkundef(); 4399 - bool has_setter = false; 4400 - lkp_with_setter(js, proto, key_str, key_len, &setter, &has_setter); 4401 - if (has_setter && (vtype(setter) == T_FUNC || vtype(setter) == T_CFUNC)) { 4402 - js_parse_state_t saved; 4403 - JS_SAVE_STATE(js, saved); 4404 - uint8_t saved_flags = js->flags; 4405 - jsoff_t saved_toff = js->toff; 4406 - jsoff_t saved_tlen = js->tlen; 4407 - 4408 - push_this(prim); 4409 - jsval_t result = call_js_with_args(js, setter, &val, 1); 4410 - pop_this(); 4411 - 4412 - JS_RESTORE_STATE(js, saved); 4413 - js->flags = saved_flags; 4414 - js->toff = saved_toff; 4415 - js->tlen = saved_tlen; 4416 - 4417 - return is_err(result) ? result : val; 4418 - } 4419 - } 4371 + jsval_t proto = get_prototype_for_type(js, vtype(prim)); 4372 + if (vtype(proto) == T_OBJ) { 4373 + jsval_t setter = js_mkundef(); 4374 + bool has_setter = false; 4375 + lkp_with_setter(js, proto, key_str, key_len, &setter, &has_setter); 4376 + jsval_t result = call_proto_accessor(js, prim, setter, has_setter, &val, 1, true); 4377 + if (vtype(result) != T_UNDEF) return result; 4378 + } 4420 4379 4421 4380 if (js->flags & F_STRICT) { 4422 4381 return js_mkerr_typed( ··· 5233 5192 return js_mkerr(js, "failed to parse function"); 5234 5193 } 5235 5194 5236 - /* ===== PHASE 4: BIND PHASE (instrumented) ===== */ 5237 - PERF_START(_p4_total); 5238 - 5239 - /* Sub1: Parameter binding loop */ 5240 - PERF_START(_p4_sub1); 5241 5195 int argi = 0; 5242 5196 for (int i = 0; i < pf->param_count; i++) { 5243 5197 parsed_param_t *pp = (parsed_param_t *)utarray_eltptr(pf->params, i); ··· 5263 5217 } else { 5264 5218 v = js_mkundef(); 5265 5219 } 5266 - setprop_fast(js, function_scope, &fn[pp->name_off], pp->name_len, v); 5220 + jsval_t k = js_mkstr(js, &fn[pp->name_off], pp->name_len); 5221 + if (!is_err(k)) mkprop_fast(js, function_scope, k, v, false); 5267 5222 } 5268 5223 } 5269 - PERF_END(_p4_sub1, perf_phase4_sub1_ns); 5270 5224 5271 - /* Sub2: Rest parameter handling */ 5272 - PERF_START(_p4_sub2); 5273 5225 if (pf->has_rest && pf->rest_param_len > 0) { 5274 5226 jsval_t rest_array = mkarr(js); 5275 5227 if (!is_err(rest_array)) { ··· 5292 5244 setprop(js, function_scope, js_mkstr(js, &fn[pf->rest_param_start], pf->rest_param_len), rest_array); 5293 5245 } 5294 5246 } 5295 - PERF_END(_p4_sub2, perf_phase4_sub2_ns); 5296 5247 5297 - /* Sub3: code_uses_arguments check (THIS IS SUSPECT!) */ 5298 - PERF_START(_p4_sub3); 5299 5248 bool needs_arguments = code_uses_arguments(&fn[pf->body_start], pf->body_len); 5300 - PERF_END(_p4_sub3, perf_phase4_sub3_ns); 5301 - 5302 - /* Sub4: setup_arguments if needed */ 5303 - PERF_START(_p4_sub4); 5304 5249 bool func_strict = pf->is_strict; 5305 5250 5306 5251 if (!func_strict && vtype(func_val) == T_FUNC) { ··· 5312 5257 if (needs_arguments) { 5313 5258 setup_arguments(js, function_scope, args, argc, func_strict); 5314 5259 } 5315 - PERF_END(_p4_sub4, perf_phase4_sub4_ns); 5316 5260 5317 - /* Sub5: NFE name binding */ 5318 - PERF_START(_p4_sub5); 5319 - if (vtype(func_name) == T_STR && vtype(func_val) == T_FUNC) { 5320 - PERF_START(_sub5_vstr); 5321 - jsoff_t len; 5322 - (void)vstr(js, func_name, &len); 5323 - PERF_END(_sub5_vstr, perf_sub5_vstr_ns); 5324 - if (len > 0) { 5325 - PERF_START(_sub5_mkprop); 5326 - jsval_t prop = mkprop(js, function_scope, func_name, func_val, true); 5327 - PERF_END(_sub5_mkprop, perf_sub5_mkprop_ns); 5328 - (void)prop; 5329 - perf_sub5_count++; 5330 - } 5331 - } 5332 - PERF_END(_p4_sub5, perf_phase4_sub5_ns); 5261 + jsval_t slot_name = get_slot(js, func_val, SLOT_NAME); 5262 + if (vtype(func_name) == T_STR && vtype(func_val) == T_FUNC && vtype(slot_name) == T_UNDEF) { 5263 + jsoff_t len; 5264 + (void)vstr(js, func_name, &len); 5265 + if (len > 0) mkprop_fast(js, function_scope, func_name, func_val, true); 5266 + } 5333 5267 5334 - /* Sub6: Strict mode + this handling */ 5335 - PERF_START(_p4_sub6); 5336 5268 if (func_strict && (vtype(target_this) == T_UNDEF || vtype(target_this) == T_NULL || 5337 5269 (vtype(target_this) == T_OBJ && vdata(target_this) == 0))) { 5338 5270 js->this_val = js_mkundef(); ··· 5340 5272 js->this_val = target_this; 5341 5273 } 5342 5274 js->flags = F_CALL | (func_strict ? F_STRICT : 0); 5343 - PERF_END(_p4_sub6, perf_phase4_sub6_ns); 5344 - 5345 - PERF_END(_p4_total, perf_phase4_ns); 5346 - PERF_CALL_INC(); 5347 - if (PERF_SHOULD_REPORT()) perf_report(js->brk); 5348 - /* ===== END PHASE 4 ===== */ 5349 5275 5350 5276 jsval_t res = js_eval(js, &fn[pf->body_start], pf->body_len); 5351 5277 if (!is_err(res) && !(js->flags & F_RETURN)) res = js_mkundef(); ··· 5475 5401 jsval_t saved_scope = js->scope; 5476 5402 if (global_scope_stack == NULL) utarray_new(global_scope_stack, &jsoff_icd); 5477 5403 utarray_push_back(global_scope_stack, &parent_scope_offset); 5478 - jsval_t function_scope = mkobj(js, parent_scope_offset); 5479 - js->scope = function_scope; 5480 - 5481 - if (vtype(func_name) == T_STR && vtype(func_val) == T_FUNC) { 5482 - jsoff_t len; 5483 - (void)vstr(js, func_name, &len); 5484 - if (len > 0) { 5485 - jsval_t prop = mkprop(js, function_scope, func_name, func_val, true); 5486 - (void)prop; 5487 - } 5488 - } 5404 + jsval_t function_scope = mkobj(js, parent_scope_offset); 5405 + js->scope = function_scope; 5406 + 5407 + jsval_t slot_name = get_slot(js, func_val, SLOT_NAME); 5408 + if (vtype(func_name) == T_STR && vtype(func_val) == T_FUNC && vtype(slot_name) == T_UNDEF) { 5409 + jsoff_t len; 5410 + (void)vstr(js, func_name, &len); 5411 + if (len > 0) { 5412 + jsval_t prop = mkprop(js, function_scope, func_name, func_val, true); 5413 + (void)prop; 5414 + } 5415 + } 5489 5416 5490 5417 jsoff_t fnpos = 1; 5491 5418 int arg_idx = 0; ··· 5654 5581 if (vtype(target_proto) == T_UNDEF) { 5655 5582 jsval_t func_obj = mkval(T_OBJ, vdata(func)); 5656 5583 5657 - jsoff_t target_func_off = lkp_interned(js, func_obj, INTERN_TARGET_FUNC, 13); 5584 + jsval_t target_func = get_slot(js, func_obj, SLOT_TARGET_FUNC); 5658 5585 jsval_t proto_source = func_obj; 5659 - if (target_func_off != 0) { 5660 - jsval_t target_func = resolveprop(js, mkval(T_PROP, target_func_off)); 5661 - if (vtype(target_func) == T_FUNC) { 5662 - proto_source = mkval(T_OBJ, vdata(target_func)); 5663 - } 5586 + if (vtype(target_func) == T_FUNC) { 5587 + proto_source = mkval(T_OBJ, vdata(target_func)); 5664 5588 } 5665 5589 5666 5590 jsoff_t proto_off = lkp_interned(js, proto_source, INTERN_PROTOTYPE, 9); ··· 5739 5663 } 5740 5664 5741 5665 jsval_t nfe_name_val = js_mkundef(); 5742 - jsoff_t nfe_name_off = lkp(js, func_obj, "name", 4); 5743 - if (nfe_name_off != 0) { 5744 - nfe_name_val = resolveprop(js, mkval(T_PROP, nfe_name_off)); 5666 + jsval_t slot_name = get_slot(js, func_obj, SLOT_NAME); 5667 + if (vtype(slot_name) == T_STR) { 5668 + nfe_name_val = slot_name; 5669 + } else { 5670 + jsoff_t nfe_name_off = lkp(js, func_obj, "name", 4); 5671 + if (nfe_name_off != 0) { 5672 + nfe_name_val = resolveprop(js, mkval(T_PROP, nfe_name_off)); 5673 + } 5745 5674 } 5746 5675 5747 5676 if (fnlen == 16 && memcmp(code_str, "__builtin_Object", 16) == 0) { ··· 5812 5741 if (vtype(count_val) != T_NUM || vtype(target_this) != T_OBJ) goto skip_fields; 5813 5742 5814 5743 int field_count = (int)tod(count_val); 5815 - jsoff_t src_off = lkp(js, func_obj, "__source", 8); 5816 - jsoff_t fields_off = lkp(js, func_obj, "__fields", 8); 5817 - if (src_off == 0 || fields_off == 0) goto skip_fields; 5818 - 5819 - jsval_t src_val = resolveprop(js, mkval(T_PROP, src_off)); 5820 - jsval_t fields_meta = resolveprop(js, mkval(T_PROP, fields_off)); 5821 - if (vtype(src_val) != T_STR || vtype(fields_meta) != T_STR) goto skip_fields; 5744 + jsval_t src_val = get_slot(js, func_obj, SLOT_SOURCE); 5745 + jsval_t fields_meta = get_slot(js, func_obj, SLOT_FIELDS); 5746 + if (vtype(src_val) == T_UNDEF || vtype(fields_meta) == T_UNDEF) goto skip_fields; 5822 5747 5823 5748 jsoff_t src_len, src_ptr_off = vstr(js, src_val, &src_len); 5824 5749 const char *source = (const char *)(&js->mem[src_ptr_off]); ··· 6624 6549 } 6625 6550 } 6626 6551 6627 - jsoff_t existing = lkp(js, js->scope, var_name, var_len); 6552 + jsoff_t existing = lkp_scope(js, js->scope, var_name, var_len); 6628 6553 if (existing != 0) { 6629 6554 jsval_t res = setprop(js, js->scope, js_mkstr(js, var_name, var_len), prop_val); 6630 6555 if (is_err(res)) return res; ··· 7209 7134 } 7210 7135 7211 7136 if (name_len > 0) { 7137 + jsval_t name_val = js_mkstr(js, &js->code[name_off], name_len); 7138 + if (is_err(name_val)) return name_val; 7139 + set_slot(js, func_obj, SLOT_NAME, name_val); 7212 7140 jsval_t name_key = js_mkstr(js, "name", 4); 7213 7141 if (is_err(name_key)) return name_key; 7214 - jsval_t name_val = js_mkstr(js, &js->code[name_off], name_len); 7215 - if (is_err(name_val)) return name_val; 7216 7142 jsval_t res3 = setprop(js, func_obj, name_key, name_val); 7217 7143 if (is_err(res3)) return res3; 7218 7144 } ··· 7816 7742 jsoff_t deleted_next = loadoff(js, prop_off) & ~(CONSTMASK | ARRMASK | SLOTMASK); 7817 7743 jsoff_t current = loadoff(js, obj_off); 7818 7744 saveoff(js, obj_off, (deleted_next & ~3U) | (current & (CONSTMASK | ARRMASK | SLOTMASK | 3U))); 7819 - invalidate_prop_cache(obj_off); 7745 + increment_version(js, obj_off); 7820 7746 return js_mktrue(); 7821 7747 } 7822 7748 jsoff_t prev = first_prop; ··· 7826 7752 jsoff_t deleted_next = loadoff(js, prop_off) & ~(CONSTMASK | ARRMASK | SLOTMASK); 7827 7753 jsoff_t current = loadoff(js, prev); 7828 7754 saveoff(js, prev, (deleted_next & ~3U) | (current & (CONSTMASK | ARRMASK | SLOTMASK | 3U))); 7829 - invalidate_prop_cache(obj_off); 7755 + increment_version(js, obj_off); 7830 7756 return js_mktrue(); 7831 7757 } 7832 7758 prev = next_prop; ··· 7904 7830 jsoff_t current = loadoff(js, prev_prop_off); 7905 7831 saveoff(js, prev_prop_off, (deleted_next & ~3U) | (current & (CONSTMASK | ARRMASK | SLOTMASK | 3U))); 7906 7832 } 7907 - invalidate_prop_cache(owner_obj_off); 7833 + increment_version(js, owner_obj_off); 7908 7834 } 7909 7835 (void) save_pos; 7910 7836 (void) save_tok; ··· 8230 8156 return mkval(T_FUNC, (unsigned long) vdata(func_obj)); 8231 8157 } 8232 8158 8233 - while (!is_err(res) && (next(js) == TOK_ASSIGN || js->tok == TOK_PLUS_ASSIGN || 8234 - js->tok == TOK_MINUS_ASSIGN || js->tok == TOK_MUL_ASSIGN || 8235 - js->tok == TOK_DIV_ASSIGN || js->tok == TOK_REM_ASSIGN || 8236 - js->tok == TOK_SHL_ASSIGN || js->tok == TOK_SHR_ASSIGN || 8237 - js->tok == TOK_ZSHR_ASSIGN || js->tok == TOK_AND_ASSIGN || 8238 - js->tok == TOK_XOR_ASSIGN || js->tok == TOK_OR_ASSIGN)) { 8159 + while ( 8160 + !is_err(res) && (next(js) == TOK_ASSIGN || js->tok == TOK_PLUS_ASSIGN || 8161 + js->tok == TOK_MINUS_ASSIGN || js->tok == TOK_MUL_ASSIGN || 8162 + js->tok == TOK_DIV_ASSIGN || js->tok == TOK_REM_ASSIGN || 8163 + js->tok == TOK_SHL_ASSIGN || js->tok == TOK_SHR_ASSIGN || 8164 + js->tok == TOK_ZSHR_ASSIGN || js->tok == TOK_AND_ASSIGN || 8165 + js->tok == TOK_XOR_ASSIGN || js->tok == TOK_OR_ASSIGN || 8166 + js->tok == TOK_LOR_ASSIGN || js->tok == TOK_LAND_ASSIGN || 8167 + js->tok == TOK_NULLISH_ASSIGN) 8168 + ) { 8239 8169 uint8_t op = js->tok; 8240 8170 js->consumed = 1; 8241 8171 8172 + if (op == TOK_LOR_ASSIGN || op == TOK_LAND_ASSIGN || op == TOK_NULLISH_ASSIGN) { 8173 + jsval_t lhs_val = js_mkundef(); 8174 + bool should_assign = true; 8175 + 8176 + if (!(js->flags & F_NOEXEC)) { 8177 + lhs_val = resolveprop(js, res); 8178 + if (is_err(lhs_val)) return lhs_val; 8179 + 8180 + if (op == TOK_LOR_ASSIGN) { 8181 + should_assign = !js_truthy(js, lhs_val); 8182 + } else if (op == TOK_LAND_ASSIGN) { 8183 + should_assign = js_truthy(js, lhs_val); 8184 + } else should_assign = is_null(lhs_val) || is_undefined(lhs_val); 8185 + } 8186 + 8187 + if (should_assign || (js->flags & F_NOEXEC)) { 8188 + jsval_t rhs = js_assignment(js); 8189 + if (is_err(rhs)) return rhs; 8190 + 8191 + if (!(js->flags & F_NOEXEC) && should_assign) { 8192 + jsval_t rhs_resolved = resolveprop(js, rhs); 8193 + if (is_err(rhs_resolved)) return rhs_resolved; 8194 + res = assign(js, res, rhs_resolved); 8195 + } 8196 + } else { 8197 + uint8_t saved_flags = js->flags; 8198 + js->flags |= F_NOEXEC; 8199 + jsval_t rhs = js_assignment(js); 8200 + js->flags = saved_flags; 8201 + if (is_err(rhs)) return rhs; 8202 + res = lhs_val; 8203 + } 8204 + continue; 8205 + } 8206 + 8242 8207 jsval_t lhs_val = js_mkundef(); 8243 8208 if (op != TOK_ASSIGN && !(js->flags & F_NOEXEC)) { 8244 8209 lhs_val = resolveprop(js, res); ··· 8408 8373 } 8409 8374 { 8410 8375 const char *vn = &js->code[var_off]; 8411 - if (lkp(js, js->scope, vn, var_len) > 0) return js_mkerr(js, "'%.*s' already declared", (int)var_len, vn); 8376 + if (lkp_scope(js, js->scope, vn, var_len) > 0) return js_mkerr(js, "'%.*s' already declared", (int)var_len, vn); 8412 8377 jsval_t x = mkprop(js, js->scope, js_mkstr(js, vn, var_len), rest_obj, is_const); 8413 8378 if (is_err(x)) return x; 8414 8379 } ··· 8448 8413 } 8449 8414 8450 8415 const char *ivn = &js->code[ivoff]; 8451 - if (lkp(js, js->scope, ivn, ivlen) > 0) return js_mkerr(js, "'%.*s' already declared", (int)ivlen, ivn); 8416 + if (lkp_scope(js, js->scope, ivn, ivlen) > 0) return js_mkerr(js, "'%.*s' already declared", (int)ivlen, ivn); 8452 8417 8453 8418 jsoff_t ipoff = lkp(js, nobj, &js->code[isoff], islen); 8454 8419 jsval_t ival = ipoff > 0 ? resolveprop(js, mkval(T_PROP, ipoff)) : js_mkundef(); ··· 8470 8435 obj_destruct_simple:; 8471 8436 { 8472 8437 const char *vn = &js->code[var_off]; 8473 - if (lkp(js, js->scope, vn, var_len) > 0) return js_mkerr(js, "'%.*s' already declared", (int)var_len, vn); 8438 + if (lkp_scope(js, js->scope, vn, var_len) > 0) return js_mkerr(js, "'%.*s' already declared", (int)var_len, vn); 8474 8439 8475 8440 jsval_t sk = js_mkstr(js, &js->code[src_off], src_len); 8476 8441 if (is_err(sk)) return sk; ··· 8564 8529 8565 8530 if (exe) { 8566 8531 const char *var_name = &js->code[var_off]; 8567 - if (lkp(js, js->scope, var_name, var_len) > 0) { 8532 + if (lkp_scope(js, js->scope, var_name, var_len) > 0) { 8568 8533 return js_mkerr(js, "'%.*s' already declared", (int)var_len, var_name); 8569 8534 } 8570 8535 ··· 8643 8608 char decoded_name[256]; 8644 8609 size_t decoded_len = decode_ident_escapes(name, nlen, decoded_name, sizeof(decoded_name)); 8645 8610 8646 - if (lkp(js, js->scope, decoded_name, decoded_len) > 0) return js_mkerr(js, "'%.*s' already declared", (int) decoded_len, decoded_name); 8611 + if (lkp_scope(js, js->scope, decoded_name, decoded_len) > 0) return js_mkerr(js, "'%.*s' already declared", (int) decoded_len, decoded_name); 8647 8612 jsval_t x = mkprop(js, js->scope, js_mkstr(js, decoded_name, decoded_len), resolveprop(js, v), is_const); 8648 8613 if (is_err(x)) return x; 8649 8614 } ··· 8737 8702 return res; 8738 8703 } 8739 8704 js->flags = flags; 8740 - jsval_t str = js_mkstr(js, &js->code[pos], js->pos - pos); 8741 - jsval_t func_obj = mkobj(js, 0); 8742 - if (is_err(func_obj)) return func_obj; 8743 - set_slot(js, func_obj, SLOT_CODE, str); 8744 - jsval_t len_key = js_mkstr(js, "length", 6); 8745 - if (is_err(len_key)) return len_key; 8746 - jsval_t res_len = setprop(js, func_obj, len_key, tov(param_count)); 8747 - if (is_err(res_len)) return res_len; 8748 - js_set_descriptor(js, func_obj, "length", 6, JS_DESC_C); 8749 - jsval_t name_key = js_mkstr(js, "name", 4); 8750 - if (is_err(name_key)) return name_key; 8751 - jsval_t name_val = js_mkstr(js, name, nlen); 8752 - if (is_err(name_val)) return name_val; 8753 - jsval_t res3 = setprop(js, func_obj, name_key, name_val); 8754 - if (is_err(res3)) return res3; 8755 - if (exe) { 8756 - set_slot(js, func_obj, SLOT_SCOPE, js->scope); 8757 - if (flags & F_STRICT) { 8758 - set_slot(js, func_obj, SLOT_STRICT, js_mktrue()); 8759 - } 8760 - } 8761 - jsval_t func = mkval(T_FUNC, (unsigned long) vdata(func_obj)); 8705 + jsval_t str = js_mkstr(js, &js->code[pos], js->pos - pos); 8706 + jsval_t func_obj = mkobj(js, 0); 8707 + if (is_err(func_obj)) return func_obj; 8708 + set_slot(js, func_obj, SLOT_CODE, str); 8709 + jsval_t len_key = js_mkstr(js, "length", 6); 8710 + if (is_err(len_key)) return len_key; 8711 + jsval_t res_len = setprop(js, func_obj, len_key, tov(param_count)); 8712 + if (is_err(res_len)) return res_len; 8713 + js_set_descriptor(js, func_obj, "length", 6, JS_DESC_C); 8714 + jsval_t name_key = js_mkstr(js, "name", 4); 8715 + if (is_err(name_key)) return name_key; 8716 + jsval_t name_val = js_mkstr(js, name, nlen); 8717 + if (is_err(name_val)) return name_val; 8718 + set_slot(js, func_obj, SLOT_NAME, name_val); 8719 + jsval_t res3 = setprop(js, func_obj, name_key, name_val); 8720 + if (is_err(res3)) return res3; 8721 + if (exe) { 8722 + set_slot(js, func_obj, SLOT_SCOPE, js->scope); 8723 + if (flags & F_STRICT) { 8724 + set_slot(js, func_obj, SLOT_STRICT, js_mktrue()); 8725 + } 8726 + } 8727 + jsval_t func = mkval(T_FUNC, (unsigned long) vdata(func_obj)); 8762 8728 8763 8729 jsval_t proto_setup = setup_func_prototype(js, func); 8764 8730 if (is_err(proto_setup)) return proto_setup; 8765 8731 8766 8732 if (exe) { 8767 - jsoff_t existing = lkp(js, js->scope, name, nlen); 8733 + jsoff_t existing = lkp_scope(js, js->scope, name, nlen); 8768 8734 if (existing > 0) { 8769 8735 saveval(js, existing + sizeof(jsoff_t) * 2, func); 8770 8736 } else { ··· 8803 8769 jsval_t str = js_mkstr(js, &js->code[pos], js->pos - pos); 8804 8770 jsval_t func_obj = mkobj(js, 0); 8805 8771 if (is_err(func_obj)) return func_obj; 8806 - set_slot(js, func_obj, SLOT_CODE, str); 8807 - set_slot(js, func_obj, SLOT_ASYNC, js_mktrue()); 8808 - jsval_t len_key = js_mkstr(js, "length", 6); 8809 - if (is_err(len_key)) return len_key; 8810 - jsval_t res_len = setprop(js, func_obj, len_key, tov(0)); 8811 - if (is_err(res_len)) return res_len; 8812 - js_set_descriptor(js, func_obj, "length", 6, JS_DESC_C); 8813 - jsval_t name_key = js_mkstr(js, "name", 4); 8814 - if (is_err(name_key)) return name_key; 8815 - jsval_t name_val = js_mkstr(js, name, nlen); 8816 - if (is_err(name_val)) return name_val; 8817 - jsval_t res3 = setprop(js, func_obj, name_key, name_val); 8818 - if (is_err(res3)) return res3; 8772 + set_slot(js, func_obj, SLOT_CODE, str); 8773 + set_slot(js, func_obj, SLOT_ASYNC, js_mktrue()); 8774 + jsval_t len_key = js_mkstr(js, "length", 6); 8775 + if (is_err(len_key)) return len_key; 8776 + jsval_t res_len = setprop(js, func_obj, len_key, tov(0)); 8777 + if (is_err(res_len)) return res_len; 8778 + js_set_descriptor(js, func_obj, "length", 6, JS_DESC_C); 8779 + jsval_t name_key = js_mkstr(js, "name", 4); 8780 + if (is_err(name_key)) return name_key; 8781 + jsval_t name_val = js_mkstr(js, name, nlen); 8782 + if (is_err(name_val)) return name_val; 8783 + set_slot(js, func_obj, SLOT_NAME, name_val); 8784 + jsval_t res3 = setprop(js, func_obj, name_key, name_val); 8785 + if (is_err(res3)) return res3; 8819 8786 if (exe) { 8820 8787 set_slot(js, func_obj, SLOT_SCOPE, js->scope); 8821 8788 if (flags & F_STRICT) { ··· 8828 8795 if (is_err(proto_setup)) return proto_setup; 8829 8796 8830 8797 if (exe) { 8831 - jsoff_t existing = lkp(js, js->scope, name, nlen); 8798 + jsoff_t existing = lkp_scope(js, js->scope, name, nlen); 8832 8799 if (existing > 0) { 8833 8800 saveval(js, existing + sizeof(jsoff_t) * 2, func); 8834 8801 } else { ··· 8913 8880 return bind_destruct_pattern(js, &js->code[ctx->destructure_off], ctx->destructure_len, value, js->scope); 8914 8881 } 8915 8882 const char *var_name = &js->code[ctx->var_name_off]; 8916 - jsoff_t existing = lkp(js, js->scope, var_name, ctx->var_name_len); 8883 + jsoff_t existing = lkp_scope(js, js->scope, var_name, ctx->var_name_len); 8917 8884 if (existing > 0) { 8918 8885 saveval(js, existing + sizeof(jsoff_t) * 2, value); 8919 8886 return js_mkundef(); ··· 9221 9188 9222 9189 if (exe) { 9223 9190 jsval_t obj = resolveprop(js, obj_expr); 9224 - if (vtype(obj) != T_OBJ && vtype(obj) != T_ARR && vtype(obj) != T_FUNC) { 9191 + uint8_t obj_type = vtype(obj); 9192 + 9193 + if (obj_type == T_NULL || obj_type == T_UNDEF) { 9194 + res = js_mkundef(); 9195 + goto done; 9196 + } 9197 + 9198 + if (obj_type != T_OBJ && obj_type != T_ARR && obj_type != T_FUNC) { 9225 9199 res = js_mkerr(js, "for-in requires object"); 9226 9200 goto done; 9227 9201 } ··· 9264 9238 jsval_t key_str = js_mkstr(js, key, klen); 9265 9239 9266 9240 const char *var_name = &js->code[var_name_off]; 9267 - jsoff_t existing = lkp(js, js->scope, var_name, var_name_len); 9241 + jsoff_t existing = lkp_scope(js, js->scope, var_name, var_name_len); 9268 9242 if (existing > 0) { 9269 9243 saveval(js, existing + sizeof(jsoff_t) * 2, key_str); 9270 9244 } else { ··· 9372 9346 jsval_t iter_scope = js_mkundef(); 9373 9347 jsval_t loop_var_val = js_mkundef(); 9374 9348 if (is_let_loop && let_var_len > 0) { 9375 - jsoff_t var_off = lkp(js, js->scope, &js->code[let_var_off], let_var_len); 9349 + jsoff_t var_off = lkp_scope(js, js->scope, &js->code[let_var_off], let_var_len); 9376 9350 if (var_off != 0) { 9377 9351 loop_var_val = resolveprop(js, mkval(T_PROP, var_off)); 9378 9352 } ··· 9397 9371 loop_var_val = resolveprop(js, mkval(T_PROP, iter_var_off)); 9398 9372 } 9399 9373 delscope(js); 9400 - jsoff_t outer_var_off = lkp(js, js->scope, &js->code[let_var_off], let_var_len); 9374 + jsoff_t outer_var_off = lkp_scope(js, js->scope, &js->code[let_var_off], let_var_len); 9401 9375 if (outer_var_off != 0) { 9402 9376 saveval(js, outer_var_off + sizeof(jsoff_t) * 2, loop_var_val); 9403 9377 } ··· 10630 10604 js->mem[meta_off + sizeof(meta_header) + metadata_size] = 0; 10631 10605 jsval_t fields_meta = mkval(T_STR, meta_off); 10632 10606 10633 - jsval_t fields_key = js_mkstr(js, "__fields", 8); 10634 - if (is_err(fields_key)) return fields_key; 10635 - jsval_t res_fields = setprop(js, func_obj, fields_key, fields_meta); 10636 - if (is_err(res_fields)) return res_fields; 10637 - 10638 10607 set_slot(js, func_obj, SLOT_FIELD_COUNT, tov((double)instance_field_count)); 10608 + set_slot(js, func_obj, SLOT_FIELDS, fields_meta); 10639 10609 10640 - jsval_t src_key = js_mkstr(js, "__source", 8); 10641 - if (is_err(src_key)) return src_key; 10642 10610 jsval_t src_ref = js_mkstr(js, js->code, js->clen); 10643 10611 if (is_err(src_ref)) return src_ref; 10644 - jsval_t res_src = setprop(js, func_obj, src_key, src_ref); 10645 - if (is_err(res_src)) return res_src; 10612 + set_slot(js, func_obj, SLOT_SOURCE, src_ref); 10646 10613 } 10647 10614 10648 10615 set_slot(js, func_obj, SLOT_SCOPE, func_scope); ··· 10668 10635 js_set_descriptor(js, proto, "constructor", 11, JS_DESC_W | JS_DESC_C); 10669 10636 10670 10637 if (class_name_len > 0) { 10671 - if (lkp(js, js->scope, class_name, class_name_len) > 0) { 10638 + if (lkp_scope(js, js->scope, class_name, class_name_len) > 0) { 10672 10639 return js_mkerr(js, "'%.*s' already declared", (int) class_name_len, class_name); 10673 10640 } 10674 10641 jsval_t x = mkprop(js, js->scope, js_mkstr(js, class_name, class_name_len), constructor, false); ··· 11477 11444 set_slot(js, bound_func, SLOT_ASYNC, js_mktrue()); 11478 11445 } 11479 11446 11480 - setprop(js, bound_func, js_mkstr(js, "__target_func", 13), func); 11447 + set_slot(js, bound_func, SLOT_TARGET_FUNC, func); 11481 11448 set_slot(js, bound_func, SLOT_BOUND_THIS, this_arg); 11482 11449 11483 11450 if (bound_argc > 0) { ··· 18151 18118 static char *esm_try_resolve(const char *dir, const char *spec, const char *suffix) { 18152 18119 char path[PATH_MAX]; 18153 18120 snprintf(path, PATH_MAX, "%s/%s%s", dir, spec, suffix); 18154 - return realpath(path, NULL); 18121 + char *resolved = realpath(path, NULL); 18122 + if (resolved) { 18123 + struct stat st; 18124 + if (stat(resolved, &st) == 0 && S_ISREG(st.st_mode)) return resolved; 18125 + free(resolved); 18126 + } 18127 + return NULL; 18155 18128 } 18156 18129 18157 18130 static bool esm_has_extension(const char *spec) { ··· 18475 18448 jsval_t ns = mkobj(js, 0); 18476 18449 mod->namespace_obj = ns; 18477 18450 18478 - jsval_t glob = js_glob(js); 18479 - jsval_t module_scope = js_get(js, glob, "__esm_module_scope"); 18480 - jsval_t prev_module = module_scope; 18481 - js_set(js, glob, "__esm_module_scope", ns); 18451 + jsval_t prev_module = js->module_ns; 18452 + js->module_ns = ns; 18482 18453 18483 18454 const char *prev_filename = js->filename; 18484 18455 jsval_t saved_scope = js->scope; ··· 18492 18463 18493 18464 js->scope = saved_scope; 18494 18465 js_set_filename(js, prev_filename); 18495 - js_set(js, glob, "__esm_module_scope", prev_module); 18466 + js->module_ns = prev_module; 18496 18467 18497 18468 if (is_err(result)) { 18498 18469 mod->is_loading = false; ··· 18923 18894 static jsval_t js_export_stmt(struct js *js) { 18924 18895 js->consumed = 1; 18925 18896 18926 - jsval_t glob = js_glob(js); 18927 - jsval_t module_ns = js_get(js, glob, "__esm_module_scope"); 18928 - 18929 - if (vtype(module_ns) != T_OBJ) { 18930 - module_ns = mkobj(js, 0); 18931 - js_set(js, glob, "__esm_module_scope", module_ns); 18897 + if (vtype(js->module_ns) != T_OBJ) { 18898 + js->module_ns = mkobj(js, 0); 18932 18899 } 18933 18900 18934 18901 if (next(js) == TOK_DEFAULT) { ··· 18936 18903 jsval_t value = js_assignment(js); 18937 18904 if (is_err(value)) return value; 18938 18905 18939 - setprop(js, module_ns, js_mkstr(js, "default", 7), resolveprop(js, value)); 18906 + setprop(js, js->module_ns, js_mkstr(js, "default", 7), resolveprop(js, value)); 18940 18907 return value; 18941 18908 } 18942 18909 ··· 18958 18925 18959 18926 jsval_t key = js_mkstr(js, name, name_len); 18960 18927 mkprop(js, js->scope, key, resolveprop(js, value), is_const); 18961 - setprop(js, module_ns, key, resolveprop(js, value)); 18928 + setprop(js, js->module_ns, key, resolveprop(js, value)); 18962 18929 18963 18930 return value; 18964 18931 } ··· 18973 18940 jsval_t name_val = resolveprop(js, mkval(T_PROP, name_off)); 18974 18941 if (vtype(name_val) == T_STR) { 18975 18942 setprop(js, js->scope, name_val, func); 18976 - setprop(js, module_ns, name_val, func); 18943 + setprop(js, js->module_ns, name_val, func); 18977 18944 } 18978 18945 } 18979 18946 ··· 18988 18955 jsoff_t name_off = lkp(js, cls_obj, "name", 4); 18989 18956 if (name_off != 0) { 18990 18957 jsval_t name_val = resolveprop(js, mkval(T_PROP, name_off)); 18991 - if (vtype(name_val) == T_STR) { 18992 - setprop(js, js->scope, name_val, cls); 18993 - setprop(js, module_ns, name_val, cls); 18994 - } 18958 + if (vtype(name_val) == T_STR) { 18959 + setprop(js, js->scope, name_val, cls); 18960 + setprop(js, js->module_ns, name_val, cls); 18961 + } 18995 18962 } 18996 18963 18997 18964 return cls; ··· 19020 18987 jsval_t local_val = lookup(js, local_name, local_len); 19021 18988 if (is_err(local_val)) return local_val; 19022 18989 19023 - setprop(js, module_ns, js_mkstr(js, export_name, export_len), resolveprop(js, local_val)); 18990 + setprop(js, js->module_ns, js_mkstr(js, export_name, export_len), resolveprop(js, local_val)); 19024 18991 if (next(js) == TOK_COMMA) js->consumed = 1; 19025 18992 } 19026 18993 ··· 19914 19881 19915 19882 jsval_t glob = js->scope; 19916 19883 jsval_t object_proto = js_mkobj(js); 19884 + set_proto(js, object_proto, js_mknull()); 19917 19885 setprop(js, object_proto, js_mkstr(js, "toString", 8), js_mkfun(builtin_object_toString)); 19918 19886 setprop(js, object_proto, js_mkstr(js, "valueOf", 7), js_mkfun(builtin_object_valueOf)); 19919 19887 setprop(js, object_proto, js_mkstr(js, "toLocaleString", 14), js_mkfun(builtin_object_toLocaleString)); ··· 20399 20367 20400 20368 set_slot(js, import_obj, SLOT_CFUNC, js_mkfun(builtin_import)); 20401 20369 setprop(js, glob, js_mkstr(js, "import", 6), mkval(T_FUNC, vdata(import_obj))); 20402 - setprop(js, glob, js_mkstr(js, "__esm_module_scope", 18), js_mkundef()); 20370 + js->module_ns = js_mkundef(); 20403 20371 20404 20372 setprop(js, object_proto, js_mkstr(js, "constructor", 11), mkval(T_FUNC, vdata(obj_func_obj))); 20405 20373 setprop(js, function_proto, js_mkstr(js, "constructor", 11), mkval(T_FUNC, vdata(func_ctor_obj))); ··· 20526 20494 jsoff_t deleted_next = loadoff(js, prop_off) & ~(CONSTMASK | ARRMASK | SLOTMASK); 20527 20495 jsoff_t current = loadoff(js, obj_off); 20528 20496 saveoff(js, obj_off, (deleted_next & ~3U) | (current & (CONSTMASK | ARRMASK | SLOTMASK | 3U))); 20529 - invalidate_prop_cache(obj_off); 20497 + increment_version(js, obj_off); 20530 20498 return true; 20531 20499 } 20532 20500 ··· 20537 20505 jsoff_t deleted_next = loadoff(js, prop_off) & ~(CONSTMASK | ARRMASK | SLOTMASK); 20538 20506 jsoff_t prev_flags = loadoff(js, prev) & (CONSTMASK | ARRMASK | SLOTMASK); 20539 20507 saveoff(js, prev, deleted_next | prev_flags); 20540 - invalidate_prop_cache(obj_off); 20508 + increment_version(js, obj_off); 20541 20509 return true; 20542 20510 } 20543 20511 prev = next_prop;
+1 -1
tests/loop.async.js
··· 13 13 await server(); 14 14 } 15 15 16 - for (let i = 0; i < 40000; i++) { 16 + for (let i = 0; i < 100000; i++) { 17 17 void main(); 18 18 } 19 19
+6
tests/null.js
··· 1 + const NullProtoObj = (() => { 2 + const e = function () {}; 3 + return ((e.prototype = Object.create(null)), Object.freeze(e.prototype), e); 4 + })(); 5 + 6 + console.log(new NullProtoObj());
+13
tests/test_error_catch.js
··· 1 + Promise.reject(new Error('Promise Test error')).catch(console.error); 2 + 3 + try { 4 + throw new Error('try-catch test error'); 5 + } catch (err) { 6 + console.error(err); 7 + } 8 + 9 + async function testAsync() { 10 + throw new Error('Async test error'); 11 + } 12 + 13 + testAsync().catch(console.error);