/* Copyright (C) 2023-2025 anonymous This file is part of PSFree. PSFree is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. PSFree is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import { Int, lohi_from_one } from "./int64.mjs"; import { view_m_vector, view_m_length } from "./offset.mjs"; export let mem = null; // cache some constants const off_vector = view_m_vector / 4; const off_vector2 = (view_m_vector + 4) / 4; const isInteger = Number.isInteger; function init_module(memory) { mem = memory; } function add_and_set_addr(mem, offset, base_lo, base_hi) { const values = lohi_from_one(offset); const main = mem._main; const low = base_lo + values[0]; // no need to use ">>> 0" to convert to unsigned here main[off_vector] = low; main[off_vector2] = base_hi + values[1] + (low > 0xffffffff); } export class Addr extends Int { read8(offset) { const m = mem; if (isInteger(offset) && 0 <= offset && offset <= 0xffffffff) { m._set_addr_direct(this); } else { add_and_set_addr(m, offset, this.lo, this.hi); offset = 0; } return m.read8_at(offset); } read16(offset) { const m = mem; if (isInteger(offset) && 0 <= offset && offset <= 0xffffffff) { m._set_addr_direct(this); } else { add_and_set_addr(m, offset, this.lo, this.hi); offset = 0; } return m.read16_at(offset); } read32(offset) { const m = mem; if (isInteger(offset) && 0 <= offset && offset <= 0xffffffff) { m._set_addr_direct(this); } else { add_and_set_addr(m, offset, this.lo, this.hi); offset = 0; } return m.read32_at(offset); } read64(offset) { const m = mem; if (isInteger(offset) && 0 <= offset && offset <= 0xffffffff) { m._set_addr_direct(this); } else { add_and_set_addr(m, offset, this.lo, this.hi); offset = 0; } return m.read64_at(offset); } readp(offset) { const m = mem; if (isInteger(offset) && 0 <= offset && offset <= 0xffffffff) { m._set_addr_direct(this); } else { add_and_set_addr(m, offset, this.lo, this.hi); offset = 0; } return m.readp_at(offset); } write8(offset, value) { const m = mem; if (isInteger(offset) && 0 <= offset && offset <= 0xffffffff) { m._set_addr_direct(this); } else { add_and_set_addr(m, offset, this.lo, this.hi); offset = 0; } m.write8_at(offset, value); } write16(offset, value) { const m = mem; if (isInteger(offset) && 0 <= offset && offset <= 0xffffffff) { m._set_addr_direct(this); } else { add_and_set_addr(m, offset, this.lo, this.hi); offset = 0; } m.write16_at(offset, value); } write32(offset, value) { const m = mem; if (isInteger(offset) && 0 <= offset && offset <= 0xffffffff) { m._set_addr_direct(this); } else { add_and_set_addr(m, offset, this.lo, this.hi); offset = 0; } m.write32_at(offset, value); } write64(offset, value) { const m = mem; if (isInteger(offset) && 0 <= offset && offset <= 0xffffffff) { m._set_addr_direct(this); } else { add_and_set_addr(m, offset, this.lo, this.hi); offset = 0; } m.write64_at(offset, value); } } // expected: // * main - Uint32Array whose m_vector points to worker // * worker - DataView // // addrof()/fakeobj() expectations: // * obj - has a "addr" property and a 0 index. // * addr_addr - Int, the address of the slot of obj.addr // * fake_addr - Int, the address of the slot of obj[0] // // a valid example for "obj" is "{addr: null, 0: 0}". note that this example // has [0] be 0 so that the butterfly's indexing type is ArrayWithInt32. this // prevents the garbage collector from incorrectly treating the slot's value as // a JSObject and then crash // // the relative read/write methods expect the offset to be a unsigned 32-bit // integer export class Memory { constructor(main, worker, obj, addr_addr, fake_addr) { this._main = main; this._worker = worker; this._obj = obj; this._addr_low = addr_addr.lo; this._addr_high = addr_addr.hi; this._fake_low = fake_addr.lo; this._fake_high = fake_addr.hi; main[view_m_length / 4] = 0xffffffff; init_module(this); const off_mvec = view_m_vector; // use this to create WastefulTypedArrays to avoid a GC crash const buf = new ArrayBuffer(0); const src = new Uint8Array(buf); const sset = new Uint32Array(buf); const sset_p = this.addrof(sset); sset_p.write64(off_mvec, this.addrof(src).add(off_mvec)); sset_p.write32(view_m_length, 3); this._cpysrc = src; this._src_setter = sset; const dst = new Uint8Array(buf); const dset = new Uint32Array(buf); const dset_p = this.addrof(dset); dset_p.write64(off_mvec, this.addrof(dst).add(off_mvec)); dset_p.write32(view_m_length, 3); dset[2] = 0xffffffff; this._cpydst = dst; this._dst_setter = dset; } // dst and src may overlap cpy(dst, src, len) { if (!(isInteger(len) && 0 <= len && len <= 0xffffffff)) { throw TypeError("len not a unsigned 32-bit integer"); } const dvals = lohi_from_one(dst); const svals = lohi_from_one(src); const dset = this._dst_setter; const sset = this._src_setter; dset[0] = dvals[0]; dset[1] = dvals[1]; sset[0] = svals[0]; sset[1] = svals[1]; sset[2] = len; this._cpydst.set(this._cpysrc); } // allocate Garbage Collector managed memory. returns [address_of_memory, // backer]. backer is the JSCell that is keeping the returned memory alive, // you can drop it once you have another GC object reference the address. // the backer is an implementation detail. don't use it to mutate the // memory gc_alloc(size) { if (!isInteger(size)) { throw TypeError("size not a integer"); } if (size < 0) { throw RangeError("size is negative"); } const fastLimit = 1000; size = ((size + 7) & ~7) >> 3; if (size > fastLimit) { throw RangeError("size is too large"); } const backer = new Float64Array(size); return [mem.addrof(backer).readp(view_m_vector), backer]; } fakeobj(addr) { const values = lohi_from_one(addr); const worker = this._worker; const main = this._main; main[off_vector] = this._fake_low; main[off_vector2] = this._fake_high; worker.setUint32(0, values[0], true); worker.setUint32(4, values[1], true); return this._obj[0]; } addrof(object) { // typeof considers null as a object. blacklist it as it isn't a // JSObject if (object === null || (typeof object !== "object" && typeof object !== "function")) { throw TypeError("argument not a JS object"); } const obj = this._obj; const worker = this._worker; const main = this._main; obj.addr = object; main[off_vector] = this._addr_low; main[off_vector2] = this._addr_high; const res = new Addr(worker.getUint32(0, true), worker.getUint32(4, true)); obj.addr = null; return res; } // expects addr to be a Int _set_addr_direct(addr) { const main = this._main; main[off_vector] = addr.lo; main[off_vector2] = addr.hi; } set_addr(addr) { const values = lohi_from_one(addr); const main = this._main; main[off_vector] = values[0]; main[off_vector2] = values[1]; } get_addr() { const main = this._main; return new Addr(main[off_vector], main[off_vector2]); } read8(addr) { this.set_addr(addr); return this._worker.getUint8(0); } read16(addr) { this.set_addr(addr); return this._worker.getUint16(0, true); } read32(addr) { this.set_addr(addr); return this._worker.getUint32(0, true); } read64(addr) { this.set_addr(addr); const worker = this._worker; return new Int(worker.getUint32(0, true), worker.getUint32(4, true)); } // returns a pointer instead of an Int readp(addr) { this.set_addr(addr); const worker = this._worker; return new Addr(worker.getUint32(0, true), worker.getUint32(4, true)); } read8_at(offset) { if (!isInteger(offset)) { throw TypeError("offset not a integer"); } return this._worker.getUint8(offset); } read16_at(offset) { if (!isInteger(offset)) { throw TypeError("offset not a integer"); } return this._worker.getUint16(offset, true); } read32_at(offset) { if (!isInteger(offset)) { throw TypeError("offset not a integer"); } return this._worker.getUint32(offset, true); } read64_at(offset) { if (!isInteger(offset)) { throw TypeError("offset not a integer"); } const worker = this._worker; return new Int(worker.getUint32(offset, true), worker.getUint32(offset + 4, true)); } readp_at(offset) { if (!isInteger(offset)) { throw TypeError("offset not a integer"); } const worker = this._worker; return new Addr(worker.getUint32(offset, true), worker.getUint32(offset + 4, true)); } write8(addr, value) { this.set_addr(addr); this._worker.setUint8(0, value); } write16(addr, value) { this.set_addr(addr); this._worker.setUint16(0, value, true); } write32(addr, value) { this.set_addr(addr); this._worker.setUint32(0, value, true); } write64(addr, value) { const values = lohi_from_one(value); this.set_addr(addr); const worker = this._worker; worker.setUint32(0, values[0], true); worker.setUint32(4, values[1], true); } write8_at(offset, value) { if (!isInteger(offset)) { throw TypeError("offset not a integer"); } this._worker.setUint8(offset, value); } write16_at(offset, value) { if (!isInteger(offset)) { throw TypeError("offset not a integer"); } this._worker.setUint16(offset, value, true); } write32_at(offset, value) { if (!isInteger(offset)) { throw TypeError("offset not a integer"); } this._worker.setUint32(offset, value, true); } write64_at(offset, value) { if (!isInteger(offset)) { throw TypeError("offset not a integer"); } const values = lohi_from_one(value); const worker = this._worker; worker.setUint32(offset, values[0], true); worker.setUint32(offset + 4, values[1], true); } }