mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-05 15:28:29 +08:00
[VM] Implemented conditional jump opcodes.
jmp_on, jmp_off, jmp_=, jmpi_=, jmp_!=, jmpi_!=, ujmp_>, ujmpi_>, jmp_>, jmpi_>, ujmp_<, ujmpi_<, jmp_<, jmpi_<, ujmp_>=, ujmpi_>=, jmp_>=, jmpi_>=, ujmp_<=, ujmpi_<=, jmp_<=, jmpi_<=
This commit is contained in:
parent
efed622e94
commit
3a8b189b0a
@ -52,6 +52,28 @@ import {
|
||||
OP_THREAD,
|
||||
OP_XOR,
|
||||
OP_XORI,
|
||||
OP_JMP_E,
|
||||
OP_JMPI_E,
|
||||
OP_JMP_ON,
|
||||
OP_JMP_OFF,
|
||||
OP_JMP_NE,
|
||||
OP_JMPI_NE,
|
||||
OP_UJMP_G,
|
||||
OP_UJMPI_G,
|
||||
OP_JMP_G,
|
||||
OP_JMPI_G,
|
||||
OP_UJMP_L,
|
||||
OP_UJMPI_L,
|
||||
OP_JMP_L,
|
||||
OP_JMPI_L,
|
||||
OP_UJMP_GE,
|
||||
OP_UJMPI_GE,
|
||||
OP_JMP_GE,
|
||||
OP_JMPI_GE,
|
||||
OP_UJMP_LE,
|
||||
OP_UJMPI_LE,
|
||||
OP_JMP_LE,
|
||||
OP_JMPI_LE,
|
||||
} from "../opcodes";
|
||||
import Logger from "js-logger";
|
||||
|
||||
@ -85,6 +107,61 @@ const numeric_ops: Record<
|
||||
shr: (a, b) => a >>> b,
|
||||
};
|
||||
|
||||
type ComparisonOperation = (a: number, b: number) => boolean;
|
||||
|
||||
const comparison_ops: Record<
|
||||
"eq" | "neq" | "gt" | "lt" | "gte" | "lte",
|
||||
ComparisonOperation
|
||||
> = {
|
||||
eq: (a, b) => a === b,
|
||||
neq: (a, b) => a !== b,
|
||||
gt: (a, b) => a > b,
|
||||
lt: (a, b) => a < b,
|
||||
gte: (a, b) => a >= b,
|
||||
lte: (a, b) => a <= b,
|
||||
};
|
||||
|
||||
/**
|
||||
* Short-circuiting fold.
|
||||
*/
|
||||
function andfold<T, A>(fn: (acc: A, cur: T) => A | null, init: A, lst: T[]): A | null {
|
||||
let acc = init;
|
||||
|
||||
for (const item of lst) {
|
||||
const new_val = fn(acc, item);
|
||||
|
||||
if (new_val === null) {
|
||||
return null;
|
||||
} else {
|
||||
acc = new_val;
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Short-circuiting reduce.
|
||||
*/
|
||||
function andreduce<T>(fn: (acc: T, cur: T) => T | null, lst: T[]): T | null {
|
||||
return andfold(fn, lst[0], lst.slice(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the given arguments to the given function.
|
||||
* Returns the second argument if the function returns a truthy value, else null.
|
||||
*/
|
||||
function andsecond<T>(fn: (first: T, second: T) => any, first: T, second: T): T | null {
|
||||
if (fn(first, second)) {
|
||||
return second;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function rest<T>(lst: T[]): T[] {
|
||||
return lst.slice(1);
|
||||
}
|
||||
|
||||
export class VirtualMachine {
|
||||
private register_store = new ArrayBuffer(REGISTER_SIZE * REGISTER_COUNT);
|
||||
private register_uint8_view = new Uint8Array(this.register_store);
|
||||
@ -147,7 +224,13 @@ export class VirtualMachine {
|
||||
const exec = this.thread[this.thread_idx];
|
||||
const inst = this.get_next_instruction_from_thread(exec);
|
||||
|
||||
const [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7] = inst.args.map(arg => arg.value);
|
||||
const arg_vals = inst.args.map(arg => arg.value);
|
||||
const [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7] = arg_vals;
|
||||
|
||||
// helper for conditional jump opcodes
|
||||
const conditional_jump_args:
|
||||
(cond: ComparisonOperation) => [Thread, number, ComparisonOperation, number, number]
|
||||
= (cond) => [exec, arg2, cond, arg0, arg1];
|
||||
|
||||
switch (inst.opcode.code) {
|
||||
case OP_NOP.code:
|
||||
@ -269,6 +352,75 @@ export class VirtualMachine {
|
||||
case OP_SHIFT_RIGHT.code:
|
||||
this.do_numeric_op_with_register(arg0, arg1, numeric_ops.shr);
|
||||
break;
|
||||
// conditional jumps
|
||||
case OP_JMP_ON.code:
|
||||
// all eq 1?
|
||||
this.conditional_jump(exec, arg0, comparison_ops.eq, 1, ...rest(arg_vals).map(reg => this.get_sint(reg)));
|
||||
break;
|
||||
case OP_JMP_OFF.code:
|
||||
// all eq 0?
|
||||
this.conditional_jump(exec, arg0, comparison_ops.eq, 0, ...rest(arg_vals).map(reg => this.get_sint(reg)));
|
||||
break;
|
||||
case OP_JMP_E.code:
|
||||
this.signed_conditional_jump_with_register(...conditional_jump_args(comparison_ops.eq));
|
||||
break;
|
||||
case OP_JMPI_E.code:
|
||||
this.signed_conditional_jump_with_literal(...conditional_jump_args(comparison_ops.eq));
|
||||
break;
|
||||
case OP_JMP_NE.code:
|
||||
this.signed_conditional_jump_with_register(...conditional_jump_args(comparison_ops.neq));
|
||||
break;
|
||||
case OP_JMPI_NE.code:
|
||||
this.signed_conditional_jump_with_literal(...conditional_jump_args(comparison_ops.neq));
|
||||
break;
|
||||
case OP_UJMP_G.code:
|
||||
this.unsigned_conditional_jump_with_register(...conditional_jump_args(comparison_ops.gt));
|
||||
break;
|
||||
case OP_UJMPI_G.code:
|
||||
this.unsigned_conditional_jump_with_literal(...conditional_jump_args(comparison_ops.gt));
|
||||
break;
|
||||
case OP_JMP_G.code:
|
||||
this.signed_conditional_jump_with_register(...conditional_jump_args(comparison_ops.gt));
|
||||
break;
|
||||
case OP_JMPI_G.code:
|
||||
this.signed_conditional_jump_with_literal(...conditional_jump_args(comparison_ops.gt));
|
||||
break;
|
||||
case OP_UJMP_L.code:
|
||||
this.unsigned_conditional_jump_with_register(...conditional_jump_args(comparison_ops.lt));
|
||||
break;
|
||||
case OP_UJMPI_L.code:
|
||||
this.unsigned_conditional_jump_with_literal(...conditional_jump_args(comparison_ops.lt));
|
||||
break;
|
||||
case OP_JMP_L.code:
|
||||
this.signed_conditional_jump_with_register(...conditional_jump_args(comparison_ops.lt));
|
||||
break;
|
||||
case OP_JMPI_L.code:
|
||||
this.signed_conditional_jump_with_literal(...conditional_jump_args(comparison_ops.lt));
|
||||
break;
|
||||
case OP_UJMP_GE.code:
|
||||
this.unsigned_conditional_jump_with_register(...conditional_jump_args(comparison_ops.gte));
|
||||
break;
|
||||
case OP_UJMPI_GE.code:
|
||||
this.unsigned_conditional_jump_with_literal(...conditional_jump_args(comparison_ops.gte));
|
||||
break;
|
||||
case OP_JMP_GE.code:
|
||||
this.signed_conditional_jump_with_register(...conditional_jump_args(comparison_ops.gte));
|
||||
break;
|
||||
case OP_JMPI_GE.code:
|
||||
this.signed_conditional_jump_with_literal(...conditional_jump_args(comparison_ops.gte));
|
||||
break;
|
||||
case OP_UJMP_LE.code:
|
||||
this.unsigned_conditional_jump_with_register(...conditional_jump_args(comparison_ops.lte));
|
||||
break;
|
||||
case OP_UJMPI_LE.code:
|
||||
this.unsigned_conditional_jump_with_literal(...conditional_jump_args(comparison_ops.lte));
|
||||
break;
|
||||
case OP_JMP_LE.code:
|
||||
this.signed_conditional_jump_with_register(...conditional_jump_args(comparison_ops.lte));
|
||||
break;
|
||||
case OP_JMPI_LE.code:
|
||||
this.signed_conditional_jump_with_literal(...conditional_jump_args(comparison_ops.lte));
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported instruction: ${inst.opcode.mnemonic}.`);
|
||||
}
|
||||
@ -320,6 +472,10 @@ export class VirtualMachine {
|
||||
this.registers.setInt32(REGISTER_SIZE * reg, value);
|
||||
}
|
||||
|
||||
private get_uint(reg: number): number {
|
||||
return this.registers.getUint32(REGISTER_SIZE * reg);
|
||||
}
|
||||
|
||||
private set_uint(reg: number, value: number): void {
|
||||
this.registers.setUint32(REGISTER_SIZE * reg, value);
|
||||
}
|
||||
@ -385,11 +541,65 @@ export class VirtualMachine {
|
||||
if (seg_idx == undefined) {
|
||||
logger.warn(`Invalid jump label: ${label}.`);
|
||||
} else {
|
||||
console.log("Jumping to " + label);
|
||||
top.seg_idx = seg_idx;
|
||||
top.inst_idx = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private signed_conditional_jump_with_register(
|
||||
exec: Thread,
|
||||
label: number,
|
||||
condition: ComparisonOperation,
|
||||
reg1: number,
|
||||
reg2: number,
|
||||
): void {
|
||||
this.conditional_jump(exec, label, condition, this.get_sint(reg1), this.get_sint(reg2));
|
||||
}
|
||||
|
||||
private signed_conditional_jump_with_literal(
|
||||
exec: Thread,
|
||||
label: number,
|
||||
condition: ComparisonOperation,
|
||||
reg: number,
|
||||
literal: number,
|
||||
): void {
|
||||
this.conditional_jump(exec, label, condition, this.get_sint(reg), literal);
|
||||
}
|
||||
|
||||
private unsigned_conditional_jump_with_register(
|
||||
exec: Thread,
|
||||
label: number,
|
||||
condition: ComparisonOperation,
|
||||
reg1: number,
|
||||
reg2: number,
|
||||
): void {
|
||||
this.conditional_jump(exec, label, condition, this.get_uint(reg1), this.get_uint(reg2));
|
||||
}
|
||||
|
||||
private unsigned_conditional_jump_with_literal(
|
||||
exec: Thread,
|
||||
label: number,
|
||||
condition: ComparisonOperation,
|
||||
reg: number,
|
||||
literal: number,
|
||||
): void {
|
||||
this.conditional_jump(exec, label, condition, this.get_uint(reg), literal);
|
||||
}
|
||||
|
||||
private conditional_jump(
|
||||
exec: Thread,
|
||||
label: number,
|
||||
condition: ComparisonOperation,
|
||||
...vals: number[]
|
||||
): void {
|
||||
console.log(`Conditional jump to ${label} with vals "${vals}" using comparator: `, condition);
|
||||
const chain_cmp = andsecond.bind<null, ComparisonOperation, Parameters<ComparisonOperation>, any>(null, condition);
|
||||
if (andreduce(chain_cmp, vals) !== null) {
|
||||
this.jump_to_label(exec, label);
|
||||
}
|
||||
}
|
||||
|
||||
private push_arg_stack(exec: Thread, arg: Arg): void {
|
||||
exec.arg_stack.push(arg);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user