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
45 changes: 33 additions & 12 deletions src/classfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ export interface CpEntry {
b: number;
}

// Utf8 and numeric values live in values. Other entries keep a and b as two
// u16 in one word of ab.
export interface ConstantPool {
length: number;
values: (string | number | bigint | undefined)[];
tags: Uint8Array;
ab: Uint32Array;
}

export interface ExceptionEntry {
start: number;
end: number;
Expand Down Expand Up @@ -52,7 +61,7 @@ export interface ClassFile {
renamedFrom?: string;
minor: number;
major: number;
cp: (CpEntry | string | undefined)[];
cp: ConstantPool;
access: number;
name: string;
superName: string | null;
Expand Down Expand Up @@ -181,22 +190,27 @@ function readCode(raw: Uint8Array, cp: ClassFile["cp"]): Code {
return { maxStack, maxLocals, code, handlers, lines, localVars };
}

// UTF-8 entries are stored as strings to avoid an object per constant.
export function cpEntry(cp: ClassFile["cp"], i: number): CpEntry | undefined {
const e = cp[i];
return typeof e === "string" ? { tag: Tag.Utf8, value: e, a: 0, b: 0 } : e;
const tag = cp.tags[i];
if (!tag) return undefined;
const value = cp.values[i];
return { tag, value, a: cp.ab[i] >>> 16, b: cp.ab[i] & 0xffff };
}

export function cpTag(cp: ClassFile["cp"], i: number): number { return cp.tags[i]; }
export function cpA(cp: ClassFile["cp"], i: number): number { return cp.ab[i] >>> 16; }
export function cpB(cp: ClassFile["cp"], i: number): number { return cp.ab[i] & 0xffff; }
export function cpValue(cp: ClassFile["cp"], i: number): number | bigint | undefined { return cp.values[i] as number | bigint | undefined; }

export function utf8(cp: ClassFile["cp"], i: number): string {
const e = cp[i];
if (typeof e !== "string") throw new ClassFormatError(`bad utf8 index ${i}`);
return e;
const s = cp.values[i];
if (typeof s !== "string") throw new ClassFormatError(`bad utf8 index ${i}`);
return s;
}

export function className(cp: ClassFile["cp"], i: number): string {
const e = cpEntry(cp, i);
if (!e || e.tag !== Tag.Class) throw new ClassFormatError(`bad class index ${i}`);
return utf8(cp, e.a);
if (cp.tags[i] !== Tag.Class) throw new ClassFormatError(`bad class index ${i}`);
return utf8(cp, cp.ab[i] >>> 16);
}

export function parseClass(bytes: Uint8Array, preview = false): ClassFile {
Expand All @@ -221,7 +235,12 @@ function parseBody(r: Reader, preview: boolean): ClassFile {
if (major < 45 || major > MAX_MAJOR) throw new UnsupportedVersion(major, minor);
if (major >= 56 && minor !== 0 && !(minor === 0xffff && major === MAX_MAJOR && preview)) throw new UnsupportedVersion(major, minor);
const count = r.u2();
const cp: ClassFile["cp"] = new Array(count);
const cp: ClassFile["cp"] = {
length: count,
values: new Array(count),
tags: new Uint8Array(count),
ab: new Uint32Array(count),
};
for (let i = 1; i < count; i++) {
const tag = r.u1();
let value: CpEntry["value"], a = 0, b = 0;
Expand All @@ -239,7 +258,9 @@ function parseBody(r: Reader, preview: boolean): ClassFile {
a = r.u2(); b = r.u2(); break;
default: throw new ClassFormatError(`bad constant pool tag ${tag} at ${i}`);
}
cp[i] = tag === Tag.Utf8 ? value as string : { tag, value, a, b };
cp.tags[i] = tag;
if (value !== undefined) cp.values[i] = value;
cp.ab[i] = a << 16 | b;
if (tag === Tag.Long || tag === Tag.Double) i++;
}
const access = r.u2();
Expand Down
79 changes: 38 additions & 41 deletions src/indy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// the same thing the JDK bootstraps do with the ClassFile API, minus the
// cost of running that machinery in the interpreter.

import { cpEntry, Tag, className, utf8 } from "./classfile.ts";
import { cpA, cpB, cpTag, cpValue, Tag, className, utf8 } from "./classfile.ts";
import { ClassWriter } from "./classwriter.ts";
import type { Code } from "./classwriter.ts";
import {
Expand Down Expand Up @@ -42,22 +42,22 @@ export type BsmArg =

export function cpArg(cls: JClass, idx: number): BsmArg {
const cp = cls.cf!.cp;
const e = cpEntry(cp, idx)!;
switch (e.tag) {
case Tag.Integer: case Tag.Float: case Tag.Long: case Tag.Double: return { kind: "value", tag: e.tag, value: e.value };
case Tag.String: return { kind: "value", tag: e.tag, value: utf8(cp, e.a) };
const tag = cpTag(cp, idx), a = cpA(cp, idx), b = cpB(cp, idx);
switch (tag) {
case Tag.Integer: case Tag.Float: case Tag.Long: case Tag.Double: return { kind: "value", tag, value: cpValue(cp, idx) };
case Tag.String: return { kind: "value", tag, value: utf8(cp, a) };
case Tag.Class: return { kind: "class", name: className(cp, idx) };
case Tag.MethodType: return { kind: "mt", desc: utf8(cp, e.a) };
case Tag.MethodType: return { kind: "mt", desc: utf8(cp, a) };
case Tag.MethodHandle: {
const ref = cpEntry(cp, e.b)!, nat = cpEntry(cp, ref.b)!;
const ref = b, nat = cpB(cp, ref);
return {
kind: "mh", refKind: e.a, owner: className(cp, ref.a), name: utf8(cp, nat.a), desc: utf8(cp, nat.b),
iface: ref.tag === Tag.InterfaceMethodref,
kind: "mh", refKind: a, owner: className(cp, cpA(cp, ref)), name: utf8(cp, cpA(cp, nat)), desc: utf8(cp, cpB(cp, nat)),
iface: cpTag(cp, ref) === Tag.InterfaceMethodref,
};
}
case Tag.Dynamic: return { kind: "condy", index: idx };
}
throw new PendingThrow("java/lang/InternalError", `bad bootstrap argument tag ${e.tag}`);
throw new PendingThrow("java/lang/InternalError", `bad bootstrap argument tag ${tag}`);
}

function wordSize(descs: string[]) {
Expand Down Expand Up @@ -434,15 +434,14 @@ function* methodHandleConstant(vm: JVM, t: JThread, cls: JClass, mh: MethodHandl

function* dynamicConstant(vm: JVM, t: JThread, cls: JClass, idx: number): NativeGen {
const cp = cls.cf!.cp;
const e = cpEntry(cp, idx)!;
const bsm = cls.cf!.bootstrap[e.a];
const nat = cpEntry(cp, e.b)!;
const bsm = cls.cf!.bootstrap[cpA(cp, idx)];
const nat = cpB(cp, idx);
const bsmHandle = yield* constantObject(vm, t, cls, bsm.ref);
const type: JClass = yield* typeOf(t, cls, utf8(cp, nat.b));
const type: JClass = yield* typeOf(t, cls, utf8(cp, cpB(cp, nat)));
const args = yield* staticArgs(vm, t, cls, bsm.args);
return yield* upcall(vm, t,
"linkDynamicConstant(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
vm.mirror(cls), bsmHandle, vm.intern(utf8(cp, nat.a)), vm.mirror(type), args);
vm.mirror(cls), bsmHandle, vm.intern(utf8(cp, cpA(cp, nat))), vm.mirror(type), args);
}

function* staticArgs(vm: JVM, t: JThread, cls: JClass, idxs: number[]): NativeGen {
Expand All @@ -455,70 +454,68 @@ function* staticArgs(vm: JVM, t: JThread, cls: JClass, idxs: number[]): NativeGe
// The Java object for a loadable constant, boxing primitives.
export function* constantObject(vm: JVM, t: JThread, cls: JClass, idx: number): NativeGen {
const cp = cls.cf!.cp;
const e = cpEntry(cp, idx)!;
switch (e.tag) {
case Tag.Integer: return yield* box(t, "I", e.value);
case Tag.Float: return yield* box(t, "F", e.value);
case Tag.Long: return yield* box(t, "J", e.value);
case Tag.Double: return yield* box(t, "D", e.value);
case Tag.String: return vm.intern(utf8(cp, e.a));
const tag = cpTag(cp, idx), a = cpA(cp, idx), b = cpB(cp, idx), value = cpValue(cp, idx);
switch (tag) {
case Tag.Integer: return yield* box(t, "I", value);
case Tag.Float: return yield* box(t, "F", value);
case Tag.Long: return yield* box(t, "J", value);
case Tag.Double: return yield* box(t, "D", value);
case Tag.String: return vm.intern(utf8(cp, a));
case Tag.Class: return vm.mirror(yield* vm.loadClassGen(t, cls.loader, className(cp, idx)));
}
const cached = cls.ldc[idx];
if (cached !== undefined) {
if (e.tag !== Tag.Dynamic) return cached;
const desc = utf8(cp, cpEntry(cp, e.b)!.b);
if (tag !== Tag.Dynamic) return cached;
const desc = utf8(cp, cpB(cp, b));
return isPrim(desc) ? yield* box(t, desc, cached) : cached;
}
let v: any;
if (e.tag === Tag.MethodType) v = yield* methodType(vm, t, cls.loader, utf8(cp, e.a));
else if (e.tag === Tag.MethodHandle) v = yield* methodHandleConstant(vm, t, cls, cpArg(cls, idx) as MethodHandleRef);
else if (e.tag === Tag.Dynamic) {
if (tag === Tag.MethodType) v = yield* methodType(vm, t, cls.loader, utf8(cp, a));
else if (tag === Tag.MethodHandle) v = yield* methodHandleConstant(vm, t, cls, cpArg(cls, idx) as MethodHandleRef);
else if (tag === Tag.Dynamic) {
v = yield* dynamicConstant(vm, t, cls, idx);
const desc = utf8(cp, cpEntry(cp, e.b)!.b);
const desc = utf8(cp, cpB(cp, b));
cls.ldc[idx] = isPrim(desc) && v !== null ? vm.getField(v, v.cls.name, "value") : v;
return v;
}
else throw new PendingThrow("java/lang/InternalError", `bad constant tag ${e.tag}`);
else throw new PendingThrow("java/lang/InternalError", `bad constant tag ${tag}`);
cls.ldc[idx] = v;
return v;
}

// ldc of a MethodType, MethodHandle or dynamic constant.
export function* resolveConstant(vm: JVM, t: JThread, cls: JClass, idx: number): NativeGen {
const cp = cls.cf!.cp;
const e = cpEntry(cp, idx)!;
const tag = cpTag(cp, idx), b = cpB(cp, idx);
const v = yield* constantObject(vm, t, cls, idx);
if (e.tag === Tag.Dynamic) {
const desc = utf8(cp, cpEntry(cp, e.b)!.b);
if (tag === Tag.Dynamic) {
const desc = utf8(cp, cpB(cp, b));
if (isPrim(desc)) return v === null ? null : vm.getField(v, v.cls.name, "value");
}
return v;
}

function* linkGeneral(vm: JVM, t: JThread, cls: JClass, idx: number): NativeGen {
const cp = cls.cf!.cp;
const e = cpEntry(cp, idx)!;
const bsm = cls.cf!.bootstrap[e.a];
const nat = cpEntry(cp, e.b)!;
const bsm = cls.cf!.bootstrap[cpA(cp, idx)];
const nat = cpB(cp, idx);
const bsmHandle = yield* constantObject(vm, t, cls, bsm.ref);
const type = yield* methodType(vm, t, cls.loader, utf8(cp, nat.b));
const type = yield* methodType(vm, t, cls.loader, utf8(cp, cpB(cp, nat)));
const args = yield* staticArgs(vm, t, cls, bsm.args);
const appendix = vm.newObjectArray("java/lang/Object", [null]);
const mn: JObject = yield* upcall(vm, t,
"linkCallSite(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/invoke/MemberName;",
vm.mirror(cls), bsmHandle, vm.intern(utf8(cp, nat.a)), type, args, appendix);
vm.mirror(cls), bsmHandle, vm.intern(utf8(cp, cpA(cp, nat))), type, args, appendix);
return { m: memberTarget(t, mn), appendix: appendix.a[0] as JObject | null };
}

// ---- linking ---------------------------------------------------------------

export function* linkCallSite(vm: JVM, t: JThread, cls: JClass, idx: number): Generator<any, CallSite, any> {
const cp = cls.cf!.cp;
const e = cpEntry(cp, idx)!;
const bsm = cls.cf!.bootstrap[e.a];
const nat = cpEntry(cp, e.b)!;
const name = utf8(cp, nat.a), desc = utf8(cp, nat.b);
const bsm = cls.cf!.bootstrap[cpA(cp, idx)];
const nat = cpB(cp, idx);
const name = utf8(cp, cpA(cp, nat)), desc = utf8(cp, cpB(cp, nat));
const ref = cpArg(cls, bsm.ref) as MethodHandleRef;
const args = bsm.args.map((a) => cpArg(cls, a));
let m: JMethod | null = null;
Expand Down
19 changes: 9 additions & 10 deletions src/jit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// caller spill too, and later delivers its result through the frame like an
// interpreted call.

import { cpEntry, Tag, utf8 } from "./classfile.ts";
import { cpB, cpTag, cpValue, Tag, utf8 } from "./classfile.ts";
import {
ACC_FINAL, INITIALIZED, JArray, JClass, JField, JMethod, JObject, isWide, longToFloat, newArrayData, parseMethodDesc,
} from "./runtime.ts";
Expand Down Expand Up @@ -708,9 +708,9 @@ function insn(g: Gen, b: Body, pc: number) {
case 0x11: emit(`${S(d)}=${s16(c, pc + 1)};`); break;
case 0x12: case 0x13: case 0x14: {
const i = op === 0x12 ? c[pc + 1] : idx2;
const e = cpEntry(cp, i)!;
if (e.tag === Tag.Integer || e.tag === Tag.Float || e.tag === Tag.Double) emit(`${S(d)}=${numLit(e.value as number)};`);
else if (e.tag === Tag.Long) emit(`${S(d)}=${e.value}n;`);
const tag = cpTag(cp, i), value = cpValue(cp, i);
if (tag === Tag.Integer || tag === Tag.Float || tag === Tag.Double) emit(`${S(d)}=${numLit(value as number)};`);
else if (tag === Tag.Long) emit(`${S(d)}=${value}n;`);
else {
const v = cls.ldc[i];
if (v === undefined) { emit(slow(pc, d)); break; }
Expand Down Expand Up @@ -966,7 +966,7 @@ function fuseConcat(g: Gen, b: Body, pc: number): { pieces: string[]; end: numbe
else if (op === 0x12 || op === 0x13) {
const i = op === 0x12 ? c[p + 1] : u16(c, p + 1);
const v = cls.ldc[i];
if (cpEntry(cls.cf!.cp, i)!.tag !== Tag.String || v === undefined) return null;
if (cpTag(cls.cf!.cp, i) !== Tag.String || v === undefined) return null;
pieces.push(() => g.K(v));
p += op === 0x12 ? 2 : 3;
} else return null;
Expand Down Expand Up @@ -1249,13 +1249,12 @@ function stackDelta(cls: JClass, c: Uint8Array, pc: number): number {
const op = c[pc];
const cp = cls.cf!.cp;
const fieldSize = (idx: number) => {
const nat = cpEntry(cp, cpEntry(cp, idx)!.b)!;
return isWide(utf8(cp, nat.b)) ? 2 : 1;
const nat = cpB(cp, idx);
return isWide(utf8(cp, cpB(cp, nat))) ? 2 : 1;
};
const methodSlots = (idx: number, withReceiver: boolean) => {
const e = cpEntry(cp, idx)!;
const nat = cpEntry(cp, e.b)!;
const { params, ret } = parseMethodDesc(utf8(cp, nat.b));
const nat = cpB(cp, idx);
const { params, ret } = parseMethodDesc(utf8(cp, cpB(cp, nat)));
let n = withReceiver ? 1 : 0;
for (const p of params) n += isWide(p) ? 2 : 1;
return (ret === "V" ? 0 : isWide(ret) ? 2 : 1) - n;
Expand Down
32 changes: 15 additions & 17 deletions src/jvm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// pool resolution, strings, monitors and the thread scheduler.

import type { Assertions } from "./assertions.ts";
import { cpEntry, BadMagic, ClassFormatError, MAX_MAJOR, Tag, UnsupportedVersion, className, parseClass, utf8 } from "./classfile.ts";
import { cpA, cpB, cpTag, cpValue, BadMagic, ClassFormatError, MAX_MAJOR, Tag, UnsupportedVersion, className, parseClass, utf8 } from "./classfile.ts";
import type { ClassFile } from "./classfile.ts";
import { deliver, raise, runThread } from "./interp.ts";
import { NativeMemory } from "./memory.ts";
Expand Down Expand Up @@ -432,24 +432,22 @@ export class JVM {

fieldRef(t: JThread, cls: JClass, idx: number): JField | undefined {
const cp = cls.cf!.cp;
const e = cpEntry(cp, idx)!;
const owner = cls.rcp[e.a] ?? this.classRef(t, cls, e.a);
const ownerIndex = cpA(cp, idx), natIndex = cpB(cp, idx);
const owner = cls.rcp[ownerIndex] ?? this.classRef(t, cls, ownerIndex);
if (!owner) return undefined;
const nat = cpEntry(cp, e.b)!;
const name = utf8(cp, nat.a);
const f = owner.findField(name, utf8(cp, nat.b));
const name = utf8(cp, cpA(cp, natIndex));
const f = owner.findField(name, utf8(cp, cpB(cp, natIndex)));
if (!f) throw new PendingThrow("java/lang/NoSuchFieldError", name);
cls.rcp[idx] = f;
return f;
}

methodRef(t: JThread, cls: JClass, idx: number): JMethod | undefined {
const cp = cls.cf!.cp;
const e = cpEntry(cp, idx)!;
const owner = cls.rcp[e.a] ?? this.classRef(t, cls, e.a);
const ownerIndex = cpA(cp, idx), natIndex = cpB(cp, idx);
const owner = cls.rcp[ownerIndex] ?? this.classRef(t, cls, ownerIndex);
if (!owner) return undefined;
const nat = cpEntry(cp, e.b)!;
const name = utf8(cp, nat.a), desc = utf8(cp, nat.b);
const name = utf8(cp, cpA(cp, natIndex)), desc = utf8(cp, cpB(cp, natIndex));
const m = this.resolveMethod(owner, name, desc);
cls.rcp[idx] = m;
return m;
Expand Down Expand Up @@ -485,11 +483,11 @@ export class JVM {
// ldc / ldc_w / ldc2_w
constant(t: JThread, cls: JClass, idx: number): any {
const cp = cls.cf!.cp;
const e = cpEntry(cp, idx)!;
const tag = cpTag(cp, idx), a = cpA(cp, idx);
let v: any;
switch (e.tag) {
case Tag.Integer: case Tag.Float: case Tag.Long: case Tag.Double: v = e.value; break;
case Tag.String: v = this.intern(utf8(cp, e.a)); break;
switch (tag) {
case Tag.Integer: case Tag.Float: case Tag.Long: case Tag.Double: v = cpValue(cp, idx); break;
case Tag.String: v = this.intern(utf8(cp, a)); break;
case Tag.Class: {
const c = this.classRef(t, cls, idx);
if (!c) return undefined;
Expand All @@ -500,7 +498,7 @@ export class JVM {
t.pushNative(this.resolveConstantGen(t, cls, idx), null, "V", true);
return undefined;
default:
throw new PendingThrow("java/lang/InternalError", `bad ldc constant tag ${e.tag}`);
throw new PendingThrow("java/lang/InternalError", `bad ldc constant tag ${tag}`);
}
cls.ldc[idx] = v;
return v;
Expand Down Expand Up @@ -624,8 +622,8 @@ export class JVM {
if (!f.isStatic) continue;
const raw = f.attrs.get("ConstantValue");
if (!raw) continue;
const e = cpEntry(cp, (raw[0] << 8) | raw[1])!;
c.statics[f.slot] = e.tag === Tag.String ? this.intern(utf8(cp, e.a)) : e.value;
const i = (raw[0] << 8) | raw[1], tag = cpTag(cp, i);
c.statics[f.slot] = tag === Tag.String ? this.intern(utf8(cp, cpA(cp, i))) : cpValue(cp, i);
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/natives/npe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// operand stack slot, then the message is built by walking back from the
// bytecode that hit null.

import { cpEntry, Tag, className, utf8 } from "../classfile.ts";
import { cpA, cpB, cpEntry, Tag, className, utf8 } from "../classfile.ts";
import type { ClassFile } from "../classfile.ts";
import type { JMethod } from "../runtime.ts";
import type { Natives } from "./util.ts";
Expand Down Expand Up @@ -248,10 +248,10 @@ class Builder {
}

private refDesc(i: number): string {
return utf8(this.cp, cpEntry(this.cp, cpEntry(this.cp, i)!.b)!.b);
return utf8(this.cp, cpB(this.cp, cpB(this.cp, i)));
}
private refName(i: number): string {
return utf8(this.cp, cpEntry(this.cp, cpEntry(this.cp, i)!.b)!.a);
return utf8(this.cp, cpA(this.cp, cpB(this.cp, i)));
}

private step(bci: number): number {
Expand Down
Loading