Refactored opcode types to make them structurally clonable.

This commit is contained in:
Daan Vanden Bosch 2019-08-07 08:43:47 +02:00
parent b928738d06
commit fbef785410
7 changed files with 1630 additions and 695 deletions

View File

@ -180,7 +180,9 @@ function params_to_code(params: any[]) {
type = "TYPE_REG_REF"; type = "TYPE_REG_REF";
break; break;
case "reg_tup_ref": case "reg_tup_ref":
type = `new RegTupRefType(${params_to_code(param.reg_tup)})`; type = `{ kind: Kind.RegTupRef, register_tuples: [${params_to_code(
param.reg_tup
)}] }`;
break; break;
case "reg_ref_var": case "reg_ref_var":
type = "TYPE_REG_REF_VAR"; type = "TYPE_REG_REF_VAR";

View File

@ -2,6 +2,7 @@ import Logger from "js-logger";
import { Endianness } from "../.."; import { Endianness } from "../..";
import { ControlFlowGraph } from "../../../scripting/data_flow_analysis/ControlFlowGraph"; import { ControlFlowGraph } from "../../../scripting/data_flow_analysis/ControlFlowGraph";
import { register_value } from "../../../scripting/data_flow_analysis/register_value"; import { register_value } from "../../../scripting/data_flow_analysis/register_value";
import { stack_value } from "../../../scripting/data_flow_analysis/stack_value";
import { import {
Arg, Arg,
DataSegment, DataSegment,
@ -11,31 +12,12 @@ import {
SegmentType, SegmentType,
StringSegment, StringSegment,
} from "../../../scripting/instructions"; } from "../../../scripting/instructions";
import { import { Kind, Opcode, OPCODES, StackInteraction } from "../../../scripting/opcodes";
Opcode,
OPCODES,
RegTupRefType,
StackInteraction,
TYPE_BYTE,
TYPE_DWORD,
TYPE_D_LABEL,
TYPE_FLOAT,
TYPE_I_LABEL,
TYPE_I_LABEL_VAR,
TYPE_LABEL,
TYPE_REF,
TYPE_REG_REF,
TYPE_REG_REF_VAR,
TYPE_STRING,
TYPE_S_LABEL,
TYPE_WORD,
} from "../../../scripting/opcodes";
import { ArrayBufferCursor } from "../../cursor/ArrayBufferCursor"; import { ArrayBufferCursor } from "../../cursor/ArrayBufferCursor";
import { Cursor } from "../../cursor/Cursor"; import { Cursor } from "../../cursor/Cursor";
import { ResizableBufferCursor } from "../../cursor/ResizableBufferCursor"; import { ResizableBufferCursor } from "../../cursor/ResizableBufferCursor";
import { WritableCursor } from "../../cursor/WritableCursor"; import { WritableCursor } from "../../cursor/WritableCursor";
import { ResizableBuffer } from "../../ResizableBuffer"; import { ResizableBuffer } from "../../ResizableBuffer";
import { stack_value } from "../../../scripting/data_flow_analysis/stack_value";
const logger = Logger.get("data_formats/parsing/quest/bin"); const logger = Logger.get("data_formats/parsing/quest/bin");
@ -370,8 +352,8 @@ function find_and_parse_segments(
for (let i = 0; i < instruction.opcode.params.length; i++) { for (let i = 0; i < instruction.opcode.params.length; i++) {
const param = instruction.opcode.params[i]; const param = instruction.opcode.params[i];
switch (param.type) { switch (param.type.kind) {
case TYPE_I_LABEL: case Kind.ILabel:
get_arg_label_values( get_arg_label_values(
cfg, cfg,
labels, labels,
@ -380,7 +362,7 @@ function find_and_parse_segments(
SegmentType.Instructions SegmentType.Instructions
); );
break; break;
case TYPE_I_LABEL_VAR: case Kind.ILabelVar:
// Never on the stack. // Never on the stack.
// Eat all remaining arguments. // Eat all remaining arguments.
for (; i < instruction.args.length; i++) { for (; i < instruction.args.length; i++) {
@ -388,21 +370,20 @@ function find_and_parse_segments(
} }
break; break;
case TYPE_D_LABEL: case Kind.DLabel:
get_arg_label_values(cfg, labels, instruction, i, SegmentType.Data); get_arg_label_values(cfg, labels, instruction, i, SegmentType.Data);
break; break;
case TYPE_S_LABEL: case Kind.SLabel:
get_arg_label_values(cfg, labels, instruction, i, SegmentType.String); get_arg_label_values(cfg, labels, instruction, i, SegmentType.String);
break; break;
default: case Kind.RegTupRef:
if (param.type instanceof RegTupRefType) {
// Never on the stack. // Never on the stack.
const arg = instruction.args[i]; const arg = instruction.args[i];
for (let j = 0; j < param.type.register_tuples.length; j++) { for (let j = 0; j < param.type.register_tuples.length; j++) {
const reg_tup = param.type.register_tuples[j]; const reg_tup = param.type.register_tuples[j];
if (reg_tup.type === TYPE_I_LABEL) { if (reg_tup.type.kind === Kind.ILabel) {
const label_values = register_value( const label_values = register_value(
cfg, cfg,
instruction, instruction,
@ -416,7 +397,6 @@ function find_and_parse_segments(
} }
} }
} }
}
break; break;
} }
@ -646,26 +626,26 @@ function parse_instruction_arguments(cursor: Cursor, opcode: Opcode): Arg[] {
if (opcode.stack !== StackInteraction.Pop) { if (opcode.stack !== StackInteraction.Pop) {
for (const param of opcode.params) { for (const param of opcode.params) {
switch (param.type) { switch (param.type.kind) {
case TYPE_BYTE: case Kind.Byte:
args.push({ value: cursor.u8(), size: 1 }); args.push({ value: cursor.u8(), size: 1 });
break; break;
case TYPE_WORD: case Kind.Word:
args.push({ value: cursor.u16(), size: 2 }); args.push({ value: cursor.u16(), size: 2 });
break; break;
case TYPE_DWORD: case Kind.DWord:
args.push({ value: cursor.i32(), size: 4 }); args.push({ value: cursor.i32(), size: 4 });
break; break;
case TYPE_FLOAT: case Kind.Float:
args.push({ value: cursor.f32(), size: 4 }); args.push({ value: cursor.f32(), size: 4 });
break; break;
case TYPE_LABEL: case Kind.Label:
case TYPE_I_LABEL: case Kind.ILabel:
case TYPE_D_LABEL: case Kind.DLabel:
case TYPE_S_LABEL: case Kind.SLabel:
args.push({ value: cursor.u16(), size: 2 }); args.push({ value: cursor.u16(), size: 2 });
break; break;
case TYPE_STRING: case Kind.String:
{ {
const start_pos = cursor.position; const start_pos = cursor.position;
args.push({ args.push({
@ -678,28 +658,24 @@ function parse_instruction_arguments(cursor: Cursor, opcode: Opcode): Arg[] {
}); });
} }
break; break;
case TYPE_I_LABEL_VAR: case Kind.ILabelVar:
{ {
const arg_size = cursor.u8(); const arg_size = cursor.u8();
args.push(...cursor.u16_array(arg_size).map(value => ({ value, size: 2 }))); args.push(...cursor.u16_array(arg_size).map(value => ({ value, size: 2 })));
} }
break; break;
case TYPE_REG_REF: case Kind.RegRef:
case Kind.RegTupRef:
args.push({ value: cursor.u8(), size: 1 }); args.push({ value: cursor.u8(), size: 1 });
break; break;
case TYPE_REG_REF_VAR: case Kind.RegRefVar:
{ {
const arg_size = cursor.u8(); const arg_size = cursor.u8();
args.push(...cursor.u8_array(arg_size).map(value => ({ value, size: 1 }))); args.push(...cursor.u8_array(arg_size).map(value => ({ value, size: 1 })));
} }
break; break;
default: default:
if (param.type instanceof RegTupRefType) { throw new Error(`Parameter type ${Kind[param.type.kind]} not implemented.`);
args.push({ value: cursor.u8(), size: 1 });
break;
} else {
throw new Error(`Parameter type ${param.type} not implemented.`);
}
} }
} }
} }
@ -736,65 +712,61 @@ function write_object_code(
const args = instruction.param_to_args[i]; const args = instruction.param_to_args[i];
const [arg] = args; const [arg] = args;
switch (param.type) { switch (param.type.kind) {
case TYPE_BYTE: case Kind.Byte:
if (arg.value >= 0) { if (arg.value >= 0) {
cursor.write_u8(arg.value); cursor.write_u8(arg.value);
} else { } else {
cursor.write_i8(arg.value); cursor.write_i8(arg.value);
} }
break; break;
case TYPE_WORD: case Kind.Word:
if (arg.value >= 0) { if (arg.value >= 0) {
cursor.write_u16(arg.value); cursor.write_u16(arg.value);
} else { } else {
cursor.write_i16(arg.value); cursor.write_i16(arg.value);
} }
break; break;
case TYPE_DWORD: case Kind.DWord:
if (arg.value >= 0) { if (arg.value >= 0) {
cursor.write_u32(arg.value); cursor.write_u32(arg.value);
} else { } else {
cursor.write_i32(arg.value); cursor.write_i32(arg.value);
} }
break; break;
case TYPE_FLOAT: case Kind.Float:
cursor.write_f32(arg.value); cursor.write_f32(arg.value);
break; break;
case TYPE_LABEL: // Abstract type case Kind.Label:
case TYPE_I_LABEL: case Kind.ILabel:
case TYPE_D_LABEL: case Kind.DLabel:
case TYPE_S_LABEL: case Kind.SLabel:
cursor.write_u16(arg.value); cursor.write_u16(arg.value);
break; break;
case TYPE_STRING: case Kind.String:
cursor.write_string_utf16(arg.value, arg.size); cursor.write_string_utf16(arg.value, arg.size);
break; break;
case TYPE_I_LABEL_VAR: case Kind.ILabelVar:
cursor.write_u8(args.length); cursor.write_u8(args.length);
cursor.write_u16_array(args.map(arg => arg.value)); cursor.write_u16_array(args.map(arg => arg.value));
break; break;
case TYPE_REF: // Abstract type case Kind.RegRef:
case TYPE_REG_REF: case Kind.RegTupRef:
cursor.write_u8(arg.value); cursor.write_u8(arg.value);
break; break;
case TYPE_REG_REF_VAR: case Kind.RegRefVar:
cursor.write_u8(args.length); cursor.write_u8(args.length);
cursor.write_u8_array(args.map(arg => arg.value)); cursor.write_u8_array(args.map(arg => arg.value));
break; break;
default: default:
if (param.type instanceof RegTupRefType) { // TYPE_ANY, TYPE_VALUE and TYPE_POINTER cannot be serialized.
cursor.write_u8(arg.value);
} else {
// TYPE_ANY and TYPE_POINTER cannot be serialized.
throw new Error( throw new Error(
`Parameter type ${param.type} not implemented.` `Parameter type ${Kind[param.type.kind]} not implemented.`
); );
} }
} }
} }
} }
}
} else if (segment.type === SegmentType.String) { } else if (segment.type === SegmentType.String) {
// String segments should be multiples of 4 bytes. // String segments should be multiples of 4 bytes.
const byte_length = 4 * Math.ceil((segment.value.length + 1) / 2); const byte_length = 4 * Math.ceil((segment.value.length + 1) / 2);

View File

@ -1,4 +1,5 @@
import Logger from "js-logger"; import Logger from "js-logger";
import { reinterpret_f32_as_i32 } from "../primitive_conversion";
import { import {
AssemblyLexer, AssemblyLexer,
CodeSectionToken, CodeSectionToken,
@ -7,40 +8,21 @@ import {
IntToken, IntToken,
LabelToken, LabelToken,
RegisterToken, RegisterToken,
Token,
TokenType,
StringSectionToken, StringSectionToken,
StringToken, StringToken,
Token,
TokenType,
} from "./AssemblyLexer"; } from "./AssemblyLexer";
import { import {
Segment,
Arg, Arg,
InstructionSegment,
SegmentType,
Instruction,
DataSegment, DataSegment,
Instruction,
InstructionSegment,
Segment,
SegmentType,
StringSegment, StringSegment,
} from "./instructions"; } from "./instructions";
import { import { Kind, Opcode, OPCODES_BY_MNEMONIC, Param, StackInteraction } from "./opcodes";
Opcode,
OPCODES_BY_MNEMONIC,
Param,
TYPE_I_LABEL_VAR,
TYPE_REG_REF_VAR,
StackInteraction,
TYPE_BYTE,
TYPE_DWORD,
TYPE_WORD,
TYPE_FLOAT,
TYPE_S_LABEL,
TYPE_D_LABEL,
TYPE_I_LABEL,
TYPE_LABEL,
TYPE_STRING,
TYPE_REG_REF,
RegTupRefType,
} from "./opcodes";
import { reinterpret_f32_as_i32 } from "../primitive_conversion";
const logger = Logger.get("scripting/assembly"); const logger = Logger.get("scripting/assembly");
@ -400,7 +382,7 @@ class Assembler {
} else { } else {
const varargs = const varargs =
opcode.params.findIndex( opcode.params.findIndex(
p => p.type === TYPE_I_LABEL_VAR || p.type === TYPE_REG_REF_VAR p => p.type.kind === Kind.ILabelVar || p.type.kind === Kind.RegRefVar
) !== -1; ) !== -1;
const param_count = const param_count =
@ -464,28 +446,29 @@ class Assembler {
const [arg, token] = arg_and_token; const [arg, token] = arg_and_token;
if (token.type === TokenType.Register) { if (token.type === TokenType.Register) {
if (param.type instanceof RegTupRefType) { if (param.type.kind === Kind.RegTupRef) {
this.add_instruction(Opcode.ARG_PUSHB, [arg]); this.add_instruction(Opcode.ARG_PUSHB, [arg]);
} else { } else {
this.add_instruction(Opcode.ARG_PUSHR, [arg]); this.add_instruction(Opcode.ARG_PUSHR, [arg]);
} }
} else { } else {
switch (param.type) { switch (param.type.kind) {
case TYPE_BYTE: case Kind.Byte:
case TYPE_REG_REF: case Kind.RegRef:
case Kind.RegTupRef:
this.add_instruction(Opcode.ARG_PUSHB, [arg]); this.add_instruction(Opcode.ARG_PUSHB, [arg]);
break; break;
case TYPE_WORD: case Kind.Word:
case TYPE_LABEL: case Kind.Label:
case TYPE_I_LABEL: case Kind.ILabel:
case TYPE_D_LABEL: case Kind.DLabel:
case TYPE_S_LABEL: case Kind.SLabel:
this.add_instruction(Opcode.ARG_PUSHW, [arg]); this.add_instruction(Opcode.ARG_PUSHW, [arg]);
break; break;
case TYPE_DWORD: case Kind.DWord:
this.add_instruction(Opcode.ARG_PUSHL, [arg]); this.add_instruction(Opcode.ARG_PUSHL, [arg]);
break; break;
case TYPE_FLOAT: case Kind.Float:
this.add_instruction(Opcode.ARG_PUSHL, [ this.add_instruction(Opcode.ARG_PUSHL, [
{ {
value: reinterpret_f32_as_i32(arg.value), value: reinterpret_f32_as_i32(arg.value),
@ -493,18 +476,15 @@ class Assembler {
}, },
]); ]);
break; break;
case TYPE_STRING: case Kind.String:
this.add_instruction(Opcode.ARG_PUSHS, [arg]); this.add_instruction(Opcode.ARG_PUSHS, [arg]);
break; break;
default: default:
if (param.type instanceof RegTupRefType) {
this.add_instruction(Opcode.ARG_PUSHB, [arg]);
} else {
logger.error( logger.error(
`Line ${this.line_no}: Type ${param.type} not implemented.` `Line ${this.line_no}: Type ${
Kind[param.type.kind]
} not implemented.`
); );
}
break; break;
} }
} }
@ -535,7 +515,7 @@ class Assembler {
message: "Expected an argument.", message: "Expected an argument.",
}); });
} else { } else {
if (param.type !== TYPE_I_LABEL_VAR && param.type !== TYPE_REG_REF_VAR) { if (param.type.kind !== Kind.ILabelVar && param.type.kind !== Kind.RegRefVar) {
param_i++; param_i++;
} }
} }
@ -559,25 +539,25 @@ class Assembler {
switch (token.type) { switch (token.type) {
case TokenType.Int: case TokenType.Int:
switch (param.type) { switch (param.type.kind) {
case TYPE_BYTE: case Kind.Byte:
match = true; match = true;
this.parse_int(1, token, arg_and_tokens); this.parse_int(1, token, arg_and_tokens);
break; break;
case TYPE_WORD: case Kind.Word:
case TYPE_LABEL: case Kind.Label:
case TYPE_I_LABEL: case Kind.ILabel:
case TYPE_D_LABEL: case Kind.DLabel:
case TYPE_S_LABEL: case Kind.SLabel:
case TYPE_I_LABEL_VAR: case Kind.ILabelVar:
match = true; match = true;
this.parse_int(2, token, arg_and_tokens); this.parse_int(2, token, arg_and_tokens);
break; break;
case TYPE_DWORD: case Kind.DWord:
match = true; match = true;
this.parse_int(4, token, arg_and_tokens); this.parse_int(4, token, arg_and_tokens);
break; break;
case TYPE_FLOAT: case Kind.Float:
match = true; match = true;
arg_and_tokens.push([ arg_and_tokens.push([
{ {
@ -593,7 +573,7 @@ class Assembler {
} }
break; break;
case TokenType.Float: case TokenType.Float:
match = param.type === TYPE_FLOAT; match = param.type.kind === Kind.Float;
if (match) { if (match) {
arg_and_tokens.push([ arg_and_tokens.push([
@ -609,14 +589,14 @@ class Assembler {
case TokenType.Register: case TokenType.Register:
match = match =
stack || stack ||
param.type === TYPE_REG_REF || param.type.kind === Kind.RegRef ||
param.type === TYPE_REG_REF_VAR || param.type.kind === Kind.RegRefVar ||
param.type instanceof RegTupRefType; param.type.kind === Kind.RegTupRef;
this.parse_register(token, arg_and_tokens); this.parse_register(token, arg_and_tokens);
break; break;
case TokenType.String: case TokenType.String:
match = param.type === TYPE_STRING; match = param.type.kind === Kind.String;
if (match) { if (match) {
arg_and_tokens.push([ arg_and_tokens.push([
@ -639,44 +619,40 @@ class Assembler {
let type_str: string | undefined; let type_str: string | undefined;
switch (param.type) { switch (param.type.kind) {
case TYPE_BYTE: case Kind.Byte:
type_str = "a 8-bit integer"; type_str = "a 8-bit integer";
break; break;
case TYPE_WORD: case Kind.Word:
type_str = "a 16-bit integer"; type_str = "a 16-bit integer";
break; break;
case TYPE_DWORD: case Kind.DWord:
type_str = "a 32-bit integer"; type_str = "a 32-bit integer";
break; break;
case TYPE_FLOAT: case Kind.Float:
type_str = "a float"; type_str = "a float";
break; break;
case TYPE_LABEL: case Kind.Label:
type_str = "a label"; type_str = "a label";
break; break;
case TYPE_I_LABEL: case Kind.ILabel:
case TYPE_I_LABEL_VAR: case Kind.ILabelVar:
type_str = "an instruction label"; type_str = "an instruction label";
break; break;
case TYPE_D_LABEL: case Kind.DLabel:
type_str = "a data label"; type_str = "a data label";
break; break;
case TYPE_S_LABEL: case Kind.SLabel:
type_str = "a string label"; type_str = "a string label";
break; break;
case TYPE_STRING: case Kind.String:
type_str = "a string"; type_str = "a string";
break; break;
case TYPE_REG_REF: case Kind.RegRef:
case TYPE_REG_REF_VAR: case Kind.RegRefVar:
case Kind.RegTupRef:
type_str = "a register reference"; type_str = "a register reference";
break; break;
default:
if (param.type instanceof RegTupRefType) {
type_str = "a register reference";
}
break;
} }
if (type_str) { if (type_str) {

View File

@ -1,11 +1,11 @@
import Logger from "js-logger"; import Logger from "js-logger";
import { Instruction } from "../instructions"; import { Instruction } from "../instructions";
import { import {
Kind,
MAX_SIGNED_DWORD_VALUE, MAX_SIGNED_DWORD_VALUE,
MIN_SIGNED_DWORD_VALUE, MIN_SIGNED_DWORD_VALUE,
Opcode, Opcode,
ParamAccess, ParamAccess,
RegTupRefType,
} from "../opcodes"; } from "../opcodes";
import { BasicBlock, ControlFlowGraph } from "./ControlFlowGraph"; import { BasicBlock, ControlFlowGraph } from "./ControlFlowGraph";
import { ValueSet } from "./ValueSet"; import { ValueSet } from "./ValueSet";
@ -167,7 +167,7 @@ function find_values(
for (let j = 0; j < arg_len; j++) { for (let j = 0; j < arg_len; j++) {
const param = params[j]; const param = params[j];
if (param.type instanceof RegTupRefType) { if (param.type.kind == Kind.RegTupRef) {
const reg_ref = args[j].value; const reg_ref = args[j].value;
let k = 0; let k = 0;

View File

@ -1,15 +1,6 @@
import { Arg, Segment, SegmentType } from "./instructions";
import {
Param,
StackInteraction,
TYPE_STRING,
TYPE_I_LABEL_VAR,
TYPE_REG_REF_VAR,
TYPE_REG_REF,
RegTupRefType,
TYPE_FLOAT,
} from "./opcodes";
import { reinterpret_i32_as_f32 } from "../primitive_conversion"; import { reinterpret_i32_as_f32 } from "../primitive_conversion";
import { Arg, Segment, SegmentType } from "./instructions";
import { Kind, Param, StackInteraction } from "./opcodes";
/** /**
* @param manual_stack If true, will output stack management instructions (argpush variants). Otherwise the arguments of stack management instructions will be output as arguments to the instruction that pops them from the stack. * @param manual_stack If true, will output stack management instructions (argpush variants). Otherwise the arguments of stack management instructions will be output as arguments to the instruction that pops them from the stack.
@ -123,8 +114,8 @@ function args_to_strings(params: Param[], args: Arg[], stack: boolean): string[]
continue; continue;
} }
switch (type) { switch (type.kind) {
case TYPE_FLOAT: case Kind.Float:
// Floats are pushed onto the stack as integers with arg_pushl. // Floats are pushed onto the stack as integers with arg_pushl.
if (stack) { if (stack) {
arg_strings.push(reinterpret_i32_as_f32(arg.value).toString()); arg_strings.push(reinterpret_i32_as_f32(arg.value).toString());
@ -132,28 +123,25 @@ function args_to_strings(params: Param[], args: Arg[], stack: boolean): string[]
arg_strings.push(arg.value.toString()); arg_strings.push(arg.value.toString());
} }
break; break;
case TYPE_I_LABEL_VAR: case Kind.ILabelVar:
for (; i < args.length; i++) { for (; i < args.length; i++) {
arg_strings.push(args[i].value.toString()); arg_strings.push(args[i].value.toString());
} }
break; break;
case TYPE_REG_REF_VAR: case Kind.RegRefVar:
for (; i < args.length; i++) { for (; i < args.length; i++) {
arg_strings.push("r" + args[i].value); arg_strings.push("r" + args[i].value);
} }
break; break;
case TYPE_REG_REF: case Kind.RegRef:
case Kind.RegTupRef:
arg_strings.push("r" + arg.value); arg_strings.push("r" + arg.value);
break; break;
case TYPE_STRING: case Kind.String:
arg_strings.push(JSON.stringify(arg.value)); arg_strings.push(JSON.stringify(arg.value));
break; break;
default: default:
if (type instanceof RegTupRefType) {
arg_strings.push("r" + arg.value);
} else {
arg_strings.push(arg.value.toString()); arg_strings.push(arg.value.toString());
}
break; break;
} }
} }

View File

@ -1,4 +1,4 @@
import { TYPE_I_LABEL_VAR, TYPE_REG_REF_VAR, Opcode } from "./opcodes"; import { Kind, Opcode } from "./opcodes";
/** /**
* Instruction invocation. * Instruction invocation.
@ -25,9 +25,9 @@ export class Instruction {
const arg = args[i]; const arg = args[i];
this.param_to_args[i] = []; this.param_to_args[i] = [];
switch (type) { switch (type.kind) {
case TYPE_I_LABEL_VAR: case Kind.ILabelVar:
case TYPE_REG_REF_VAR: case Kind.RegRefVar:
this.arg_size++; this.arg_size++;
for (let j = i; j < args.length; j++) { for (let j = i; j < args.length; j++) {

File diff suppressed because it is too large Load Diff