Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 3 additions & 20 deletions src/jit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ export type Compiled = (t: JThread, f: Frame) => number;
const THRESHOLD = 500;
const BACKEDGE_THRESHOLD = 2000;
const MAX_CODE = 6000;
// compiled frames nested on one JS stack before calls go through the run loop
const MAX_DIRECT = 120;
const MAX_FALLBACKS = 5000;
// callees inlined as regions: bytecode outside throw-only blocks (HotSpot's
// MaxInlineSize), nesting, and bytecode inlined into one compiled method
Expand Down Expand Up @@ -191,20 +189,6 @@ const runtime = {
f.sp = sp;
return f;
},
// the pushed frame of a direct call to compiled m, or null when the call
// goes through the run loop: the stack is too deep or the monitor is taken
enter(t: JThread, m: JMethod, recv: JObject | null): Frame | null {
if (t.frames.length - t.base >= MAX_DIRECT || t.frames.length >= t.stackLimit) return null;
let lock: JObject | null = null;
if (m.isSynchronized) {
lock = m.isStatic ? t.vm.mirror(m.cls) : recv!;
if (!t.vm.tryMonitorEnter(t, lock)) return null;
}
const f = t.directFrame(m);
f.lock = lock;
t.frames.push(f);
return f;
},
// A call that is not direct: a native's result, PUSHED once a native that
// must wait is on the stack, or INTERPRET for the run loop to make the call.
slowCall(t: JThread, m: JMethod, args: any[]): any {
Expand Down Expand Up @@ -295,7 +279,6 @@ const runtime = {
},
// raises exc from the run loop, once every compiled caller has spilled
*rethrow(exc: JObject): Generator<never, void, unknown> { throw new JavaThrow(exc); },
MAX_DIRECT,
};

// ---- analysis ----------------------------------------------------------------
Expand Down Expand Up @@ -1187,7 +1170,7 @@ function genInvoke(
const res = (v: string) => (ret === "V" ? "" : `s${base}=${v};`);
// compiled callee: direct call
const recv = target.isStatic ? "null" : args[0];
parts.push(`if(tm.jit!==null&&(cf=rt.enter(t,tm,${recv}))!==null){`);
parts.push(`if(tm.jit!==null&&(cf=t.enter(tm,${recv}))!==null){`);
parts.push(`r=tm.jit(t,cf${args.map((x) => "," + x).join("")});if(r!==0){t.unpool(cf);${exit(pc, base, EXIT_SUSPEND)}}`);
parts.push(`${res("t.ret")}}`);
// a native callee runs here; anything else goes through the interpreter
Expand Down Expand Up @@ -1233,14 +1216,14 @@ function genPolymorphic(g: Gen, pc: number, next: number, d: number, target: JMe
const cell = K([null, null]);
const push = target.ret === "V" ? "" : `s${base}=t.ret;`;
return `if(${args[0]}!==null&&${args[0]}===${cell}[0]){f.pc=${pc};tm=${cell}[1];` +
`if(tm.jit!==null&&tm.cls.state===${INITIALIZED}&&(cf=rt.enter(t,tm,null))!==null){` +
`if(tm.jit!==null&&tm.cls.state===${INITIALIZED}&&(cf=t.enter(tm,null))!==null){` +
`r=tm.jit(t,cf${args.map((x) => "," + x).join("")});if(r!==0){t.unpool(cf);${exit(pc, base, EXIT_SUSPEND)}}${push}}` +
`else{${call(pc, d)}}}else{rt.vhTarget(t,${cell},${args[0]},${K(link.appendix)});${code}}`;
}
}
const push = target.ret === "V" ? "" : `s${base}=t.ret;`;
return `f.pc=${pc};${select}` +
`if(tm.jit!==null&&tm.cls.state===${INITIALIZED}&&(cf=rt.enter(t,tm,${callArgs[0] ?? "null"}))!==null){` +
`if(tm.jit!==null&&tm.cls.state===${INITIALIZED}&&(cf=t.enter(tm,${callArgs[0] ?? "null"}))!==null){` +
`r=tm.jit(t,cf${callArgs.map((x) => "," + x).join("")});if(r!==0){t.unpool(cf);${exit(pc, base, EXIT_SUSPEND)}}` +
`${push}}else{${call(pc, d)}}`;
}
Expand Down
9 changes: 6 additions & 3 deletions src/natives/lang.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// java.lang natives: Object, System, Thread, Throwable, String, Runtime, ...

import { isHidden } from "./stackwalk.ts";
import { gcStats } from "./management.ts";
import { gcStats, javaHeap } from "./management.ts";
import { assertionsEnabled } from "../assertions.ts";
import { normalize } from "../fs.ts";
import { ACC_INTERFACE, JArray, JClass, JObject, MAX_HEAP, newArrayData } from "../runtime.ts";
Expand Down Expand Up @@ -316,8 +316,11 @@ export const natives: Natives = {
// Runtime
"java/lang/Runtime.availableProcessors()I": () => 1,
// free <= total <= max, as callers that compute available memory expect
"java/lang/Runtime.freeMemory()J": () => BigInt(Math.min(512 << 20, MAX_HEAP) / 2),
"java/lang/Runtime.totalMemory()J": () => BigInt(Math.min(512 << 20, MAX_HEAP)),
"java/lang/Runtime.freeMemory()J": () => {
const h = javaHeap();
return BigInt(h ? h.committed - h.used : Math.min(512 << 20, MAX_HEAP) / 2);
},
"java/lang/Runtime.totalMemory()J": () => BigInt(javaHeap()?.committed ?? Math.min(512 << 20, MAX_HEAP)),
"java/lang/Runtime.maxMemory()J": () => BigInt(MAX_HEAP),
// Collects where the engine allows it (node --expose-gc), in a new JS job:
// a WeakRef read earlier in a job keeps its target alive until the job ends.
Expand Down
23 changes: 15 additions & 8 deletions src/natives/management.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,21 @@ export function gcStats(vm: JVM): GcStats {
return s;
}

function jsHeap(): { used: number; committed: number } {
// The JS heap as Java sees it, with used <= committed <= -Xmx; primitive
// arrays are typed arrays, outside the JS heap. Null where the engine cannot tell.
export function javaHeap(): { used: number; committed: number } | null {
let used: number, committed: number;
const node = (globalThis as any).process?.memoryUsage?.();
if (node) return { used: node.heapUsed, committed: node.heapTotal };
const chrome = (globalThis as any).performance?.memory;
if (chrome) return { used: chrome.usedJSHeapSize, committed: chrome.totalJSHeapSize };
return { used: 0, committed: 0 };
if (node) {
used = node.heapUsed + node.arrayBuffers;
committed = node.heapTotal + node.arrayBuffers;
} else if (chrome) {
used = chrome.usedJSHeapSize;
committed = chrome.totalJSHeapSize;
} else return null;
committed = Math.min(Math.max(used, committed), MAX_HEAP);
return { used: Math.min(used, committed), committed };
}

function* beans(t: JThread): NativeGen {
Expand Down Expand Up @@ -94,11 +103,9 @@ function* poolUsage(t: JThread, self: JObject): NativeGen {
return yield* heapUsage(t);
}

// MemoryUsage needs used <= committed <= max; node's heap does not know -Xmx
function* heapUsage(t: JThread): NativeGen {
const h = jsHeap();
const committed = Math.min(Math.max(h.used, h.committed), MAX_HEAP);
return yield* usage(t, 0, Math.min(h.used, committed), committed, MAX_HEAP);
const h = javaHeap() ?? { used: 0, committed: 0 };
return yield* usage(t, 0, h.used, h.committed, MAX_HEAP);
}

function liveThreads(vm: JVM): JThread[] {
Expand Down
16 changes: 14 additions & 2 deletions src/thread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ export const STATUS_NEW = 0, STATUS_RUNNABLE = 5, STATUS_TERMINATED = 2;
export const STATUS_SLEEPING = 0x00e1, STATUS_WAITING = 0x0191, STATUS_TIMED_WAITING = 0x01a1;
export const STATUS_PARKED = 0x0291, STATUS_PARKED_TIMED = 0x02a1, STATUS_BLOCKED = 0x0401;

// compiled frames nested on one JS stack before calls go through the run loop
const MAX_DIRECT = 120;

let nextId = 1;

export class JThread {
Expand Down Expand Up @@ -139,8 +142,16 @@ export class JThread {
// it spilled.
private pool: (Frame | null)[] = [];

directFrame(m: JMethod): Frame {
// The pushed frame of a direct call to compiled m, or null when the call
// goes through the run loop: the stack is too deep or the monitor is taken.
enter(m: JMethod, recv: JObject | null): Frame | null {
const d = this.frames.length;
if (d - this.base >= MAX_DIRECT || d >= this.stackLimit) return null;
let lock: JObject | null = null;
if (m.isSynchronized) {
lock = m.isStatic ? this.vm.mirror(m.cls) : recv!;
if (!this.vm.tryMonitorEnter(this, lock)) return null;
}
// filled without holes, which would make V8 store it as a dictionary
while (this.pool.length <= d) this.pool.push(this.pooled(m, this.pool.length));
const f = this.pool[d] ?? (this.pool[d] = this.pooled(m, d));
Expand All @@ -150,9 +161,10 @@ export class JThread {
f.sp = 0;
f.locals = null as any;
f.stack = null as any;
f.lock = null;
f.lock = lock;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
f.needLock = false;
f.direct = true;
this.frames.push(f);
return f;
}

Expand Down
16 changes: 16 additions & 0 deletions test/java/RuntimeMemory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Runtime's memory figures follow what the program holds.
public class RuntimeMemory {
static long used(Runtime rt) {
return rt.totalMemory() - rt.freeMemory();
}

public static void main(String[] args) {
Runtime rt = Runtime.getRuntime();
long before = used(rt);
long[] held = new long[8 << 20];
long after = used(rt);
System.out.println("used grows by 32 MB holding 64 MB " + (after - before >= 32 << 20));
System.out.println("free <= total <= max " + (rt.freeMemory() <= rt.totalMemory() && rt.totalMemory() <= rt.maxMemory()));
System.out.println(held.length);
}
}
Loading