diff --git a/src/browser/http.ts b/src/browser/http.ts index 5e586d5..0ca8baa 100644 --- a/src/browser/http.ts +++ b/src/browser/http.ts @@ -14,6 +14,8 @@ export interface Pack { offsets: Map; } +const utf8 = new TextDecoder(); + // Pack layout: repeated [u16 path length][path][u32 size][bytes]. In the boot // pack, "/index.prefix" holds prefix-coded "d path" and "f path size // [image:offset] [pack]" lines. A file with an offset is read in ranges from @@ -24,11 +26,10 @@ export interface Pack { export function parsePack(buf: ArrayBuffer): Pack { const dv = new DataView(buf); const u8 = new Uint8Array(buf); - const dec = new TextDecoder(); const files = new Map(); for (let p = 0; p < buf.byteLength;) { const n = dv.getUint16(p); - const path = dec.decode(u8.subarray(p + 2, p + 2 + n)); + const path = utf8.decode(u8.subarray(p + 2, p + 2 + n)); p += 2 + n; const size = dv.getUint32(p); files.set(path, u8.subarray(p + 4, p + 4 + size)); @@ -38,7 +39,7 @@ export function parsePack(buf: ArrayBuffer): Pack { const raw = files.get("/index.prefix") ?? files.get("/index.txt"); if (raw) { pack.index = new PackIndex(pack); - addIndex(pack, dec.decode(raw), files.has("/index.prefix")); + addIndex(pack, utf8.decode(raw), files.has("/index.prefix")); files.delete("/index.txt"); files.delete("/index.prefix"); for (const [p, data] of files) { @@ -56,6 +57,9 @@ export function parsePack(buf: ArrayBuffer): Pack { export class PackIndex extends Map { readonly modules = new Map(); private readonly pack: Pack; + // directory -> names in it, built for the entries there were at the time + private dirs: Map | null = null; + private dirsSize = 0; constructor(pack: Pack) { super([["/", -1]]); @@ -72,6 +76,24 @@ export class PackIndex extends Map { return super.has(path); } + // The names in each directory. Entries are only ever added, so a change in + // size means new ones. + children(): Map { + if (this.dirs && this.dirsSize === this.size) return this.dirs; + const dirs = new Map(); + for (const p of this.keys()) { + if (p === "/") continue; + const i = p.lastIndexOf("/"); + const parent = i === 0 ? "/" : p.slice(0, i); + let names = dirs.get(parent); + if (!names) dirs.set(parent, (names = [])); + names.push(p.slice(i + 1)); + } + this.dirs = dirs; + this.dirsSize = this.size; + return dirs; + } + // Parses the lines of the module path is in. The module directories // themselves are in the main index. load(path: string) { @@ -82,7 +104,7 @@ export class PackIndex extends Map { const raw = this.modules.get(module); if (!raw) return; this.modules.delete(module); - addIndex(this.pack, new TextDecoder().decode(raw), true); + addIndex(this.pack, utf8.decode(raw), true); } } @@ -297,9 +319,5 @@ export function listIndex(idx: PackIndex, path: string): string[] | null { if (idx.get(path) !== -1) return null; const prefix = path === "/" ? "/" : path + "/"; idx.load(prefix); - const out: string[] = []; - for (const p of idx.keys()) { - if (p.startsWith(prefix) && p.length > prefix.length && !p.slice(prefix.length).includes("/")) out.push(p.slice(prefix.length)); - } - return out; + return [...(idx.children().get(path) ?? [])]; } diff --git a/src/jvm.ts b/src/jvm.ts index add6487..6b2f6c3 100644 --- a/src/jvm.ts +++ b/src/jvm.ts @@ -117,7 +117,8 @@ export class JVM { private natives = new Map(); private interned = new Map(); readonly prims = new Map(); - private slots = new Map(); + // owner -> field name -> slot; two lookups of literal strings beat building a key + private slots = new Map>(); readonly threads = new Set(); private runQueue: JThread[] = []; private queued = new Set(); @@ -716,14 +717,14 @@ export class JVM { // ---- fields by name ------------------------------------------------------ slotOf(owner: string, name: string): number { - const key = owner + "." + name; - let s = this.slots.get(key); + let byName = this.slots.get(owner); + if (!byName) this.slots.set(owner, (byName = new Map())); + let s = byName.get(name); if (s === undefined) { - const c = this.loadBootOrFail(owner); - const f = c.findField(name); - if (!f) throw new Error(`no field ${key}`); + const f = this.loadBootOrFail(owner).findField(name); + if (!f) throw new Error(`no field ${owner}.${name}`); s = f.slot; - this.slots.set(key, s); + byName.set(name, s); } return s; } diff --git a/src/natives/nio.ts b/src/natives/nio.ts index 5771d09..df7336c 100644 --- a/src/natives/nio.ts +++ b/src/natives/nio.ts @@ -88,10 +88,8 @@ function fillStat(t: JThread, attrs: JObject, st: Stat, writable: boolean, ino: vm.setField(attrs, A, "st_uid", 1000); vm.setField(attrs, A, "st_gid", 1000); vm.setField(attrs, A, "st_size", BigInt(size)); - for (const k of ["atime", "mtime", "ctime"]) { - vm.setField(attrs, A, `st_${k}_sec`, sec); - vm.setField(attrs, A, `st_${k}_nsec`, nsec); - } + for (const k of ["st_atime_sec", "st_mtime_sec", "st_ctime_sec"]) vm.setField(attrs, A, k, sec); + for (const k of ["st_atime_nsec", "st_mtime_nsec", "st_ctime_nsec"]) vm.setField(attrs, A, k, nsec); } // open mount table handles: whether the one entry has been read