Fixed bug in object code writer. Strings in the object code are now written correctly when the format is DC/GC.

This commit is contained in:
Daan Vanden Bosch 2020-01-16 22:55:47 +01:00
parent b276ba988e
commit 50d1ff7f93
7 changed files with 134 additions and 93 deletions

View File

@ -1,5 +1,6 @@
import { Kind, Opcode } from "./opcodes";
import { array_buffers_equal, arrays_equal } from "../../util";
import { BinFormat } from "../parsing/quest/BinFormat";
/**
* Instruction invocation.
@ -7,14 +8,6 @@ import { array_buffers_equal, arrays_equal } from "../../util";
export type Instruction = {
readonly opcode: Opcode;
readonly args: readonly Arg[];
/**
* Byte size of the argument list.
*/
readonly arg_size: number;
/**
* Byte size of the entire instruction, i.e. the sum of the opcode size and all argument sizes.
*/
readonly size: number;
/**
* Maps each parameter by index to its arguments.
*/
@ -25,7 +18,6 @@ export type Instruction = {
export function new_instruction(opcode: Opcode, args: Arg[], asm?: InstructionAsm): Instruction {
const len = Math.min(opcode.params.length, args.length);
const param_to_args: Arg[][] = [];
let arg_size = 0;
for (let i = 0; i < len; i++) {
const type = opcode.params[i].type;
@ -35,16 +27,12 @@ export function new_instruction(opcode: Opcode, args: Arg[], asm?: InstructionAs
switch (type.kind) {
case Kind.ILabelVar:
case Kind.RegRefVar:
arg_size++;
for (let j = i; j < args.length; j++) {
param_to_args[i].push(args[j]);
arg_size += args[j].size;
}
break;
default:
arg_size += arg.size;
param_to_args[i].push(arg);
break;
}
@ -53,13 +41,62 @@ export function new_instruction(opcode: Opcode, args: Arg[], asm?: InstructionAs
return {
opcode,
args,
arg_size,
size: opcode.size + arg_size,
param_to_args,
asm,
};
}
/**
* @returns The byte size of the entire instruction, i.e. the sum of the opcode size and all
* argument sizes.
*/
export function instruction_size(instruction: Instruction, format: BinFormat): number {
const opcode = instruction.opcode;
const p_len = Math.min(opcode.params.length, instruction.param_to_args.length);
let arg_size = 0;
for (let i = 0; i < p_len; i++) {
const type = opcode.params[i].type;
const args = instruction.param_to_args[i];
switch (type.kind) {
case Kind.Byte:
case Kind.RegRef:
case Kind.RegTupRef:
arg_size++;
break;
case Kind.Word:
case Kind.Label:
case Kind.ILabel:
case Kind.DLabel:
case Kind.SLabel:
arg_size += 2;
break;
case Kind.DWord:
case Kind.Float:
arg_size += 4;
break;
case Kind.String:
if (format == BinFormat.DC_GC) {
arg_size += (args[0].value as string).length + 1;
} else {
arg_size += 2 * (args[0].value as string).length + 2;
}
break;
case Kind.ILabelVar:
arg_size += 1 + 2 * args.length;
break;
case Kind.RegRefVar:
arg_size += 1 + args.length;
break;
default:
throw new Error(`Parameter type ${Kind[type.kind]} not implemented.`);
}
}
return opcode.size + arg_size;
}
function instructions_equal(a: Instruction, b: Instruction): boolean {
return a.opcode.code === b.opcode.code && arrays_equal(a.args, b.args, args_equal);
}
@ -68,8 +105,6 @@ export function clone_instruction(instr: Instruction): Instruction {
return {
opcode: instr.opcode,
args: instr.args.map(arg => ({ ...arg })),
arg_size: instr.arg_size,
size: instr.size,
param_to_args: instr.param_to_args.map(args => args.map(arg => ({ ...arg }))),
asm: instr.asm,
};
@ -80,18 +115,14 @@ export function clone_instruction(instr: Instruction): Instruction {
*/
export type Arg = {
readonly value: any;
readonly size: number;
};
export function new_arg(value: any, size: number): Arg {
return {
value,
size,
};
export function new_arg(value: any): Arg {
return { value };
}
function args_equal(a: Arg, b: Arg): boolean {
return a.value === b.value && a.size === b.size;
return a.value === b.value;
}
/**

View File

@ -5,6 +5,12 @@ import { ArrayBufferCursor } from "../../cursor/ArrayBufferCursor";
import { BufferCursor } from "../../cursor/BufferCursor";
import { parse_qst_to_quest, write_quest_qst } from "./index";
import { ObjectType } from "./object_types";
import {
DataSegment,
InstructionSegment,
SegmentType,
StringSegment,
} from "../../asm/instructions";
test("parse Towards the Future", () => {
const buffer = readFileSync("test/resources/quest118_e.qst");
@ -104,6 +110,35 @@ function round_trip_test(path: string, file_name: string, contents: Buffer): voi
expect(test_quest.object_code.length).toBe(orig_quest.object_code.length);
for (let i = 0; i < orig_quest.object_code.length; i++) {
const orig_segment = orig_quest.object_code[i];
const test_segment = test_quest.object_code[i];
expect(test_segment.type).toBe(orig_segment.type);
expect(test_segment.labels).toEqual(orig_segment.labels);
switch (orig_segment.type) {
case SegmentType.Instructions:
expect((test_segment as InstructionSegment).instructions.length).toBe(
orig_segment.instructions.length,
);
for (let j = 0; j < orig_segment.instructions.length; j++) {
const orig_inst = orig_segment.instructions[j];
const test_inst = (test_segment as InstructionSegment).instructions[j];
expect(test_inst.opcode.code).toBe(orig_inst.opcode.code);
expect(test_inst.args).toEqual(orig_inst.args);
}
break;
case SegmentType.Data:
expect((test_segment as DataSegment).data).toEqual(orig_segment.data);
break;
case SegmentType.String:
expect((test_segment as StringSegment).value).toBe(orig_segment.value);
break;
}
expect(test_quest.object_code[i]).toEqual(orig_quest.object_code[i]);
}
});

View File

@ -2,6 +2,7 @@ import {
Arg,
DataSegment,
Instruction,
instruction_size,
InstructionSegment,
new_arg,
new_instruction,
@ -110,9 +111,15 @@ export function write_object_code(
break;
case Kind.String:
if (format === BinFormat.DC_GC) {
cursor.write_string_ascii(arg.value, arg.size);
cursor.write_string_ascii(
arg.value,
(arg.value as string).length + 1,
);
} else {
cursor.write_string_utf16(arg.value, arg.size);
cursor.write_string_utf16(
arg.value,
2 * (arg.value as string).length + 2,
);
}
break;
case Kind.ILabelVar:
@ -229,7 +236,7 @@ function internal_parse_object_code(
switch (segment.type) {
case SegmentType.Instructions:
for (const instruction of segment.instructions) {
offset += instruction.size;
offset += instruction_size(instruction, format);
}
break;
@ -595,33 +602,31 @@ function parse_instruction_arguments(cursor: Cursor, opcode: Opcode, format: Bin
for (const param of opcode.params) {
switch (param.type.kind) {
case Kind.Byte:
args.push(new_arg(cursor.u8(), 1));
args.push(new_arg(cursor.u8()));
break;
case Kind.Word:
args.push(new_arg(cursor.u16(), 2));
args.push(new_arg(cursor.u16()));
break;
case Kind.DWord:
args.push(new_arg(cursor.i32(), 4));
args.push(new_arg(cursor.i32()));
break;
case Kind.Float:
args.push(new_arg(cursor.f32(), 4));
args.push(new_arg(cursor.f32()));
break;
case Kind.Label:
case Kind.ILabel:
case Kind.DLabel:
case Kind.SLabel:
args.push(new_arg(cursor.u16(), 2));
args.push(new_arg(cursor.u16()));
break;
case Kind.String:
{
const start_pos = cursor.position;
const max_bytes = Math.min(4096, cursor.bytes_left);
args.push(
new_arg(
format === BinFormat.DC_GC
? cursor.string_ascii(max_bytes, true, false)
: cursor.string_utf16(max_bytes, true, false),
cursor.position - start_pos,
),
);
}
@ -629,17 +634,17 @@ function parse_instruction_arguments(cursor: Cursor, opcode: Opcode, format: Bin
case Kind.ILabelVar:
{
const arg_size = cursor.u8();
args.push(...cursor.u16_array(arg_size).map(value => new_arg(value, 2)));
args.push(...cursor.u16_array(arg_size).map(value => new_arg(value)));
}
break;
case Kind.RegRef:
case Kind.RegTupRef:
args.push(new_arg(cursor.u8(), 1));
args.push(new_arg(cursor.u8()));
break;
case Kind.RegRefVar:
{
const arg_size = cursor.u8();
args.push(...cursor.u8_array(arg_size).map(value => new_arg(value, 1)));
args.push(...cursor.u8_array(arg_size).map(value => new_arg(value)));
}
break;
default:

View File

@ -37,7 +37,7 @@ test("basic script", () => {
expect(segment_0.instructions.length).toBe(9);
expect(segment_0.instructions[0].opcode).toBe(OP_SET_EPISODE);
expect(segment_0.instructions[0].args).toEqual([{ value: 0, size: 4 }]);
expect(segment_0.instructions[0].args).toEqual([{ value: 0 }]);
expect(segment_0.instructions[0].asm).toEqual({
mnemonic: { line_no: 2, col: 10, len: 11 },
args: [{ line_no: 2, col: 22, len: 1 }],
@ -46,10 +46,10 @@ test("basic script", () => {
expect(segment_0.instructions[1].opcode).toBe(OP_BB_MAP_DESIGNATE);
expect(segment_0.instructions[1].args).toEqual([
{ value: 1, size: 1 },
{ value: 2, size: 2 },
{ value: 3, size: 1 },
{ value: 4, size: 1 },
{ value: 1 },
{ value: 2 },
{ value: 3 },
{ value: 4 },
]);
expect(segment_0.instructions[1].asm).toEqual({
mnemonic: { line_no: 3, col: 10, len: 16 },
@ -63,13 +63,13 @@ test("basic script", () => {
});
expect(segment_0.instructions[2].opcode).toBe(OP_ARG_PUSHL);
expect(segment_0.instructions[2].args).toEqual([{ value: 0, size: 4 }]);
expect(segment_0.instructions[2].args).toEqual([{ value: 0 }]);
expect(segment_0.instructions[2].asm).toEqual({
args: [{ line_no: 4, col: 28, len: 1 }],
stack_args: [],
});
expect(segment_0.instructions[3].opcode).toBe(OP_ARG_PUSHW);
expect(segment_0.instructions[3].args).toEqual([{ value: 150, size: 2 }]);
expect(segment_0.instructions[3].args).toEqual([{ value: 150 }]);
expect(segment_0.instructions[3].asm).toEqual({
args: [{ line_no: 4, col: 31, len: 3 }],
stack_args: [],
@ -86,13 +86,13 @@ test("basic script", () => {
});
expect(segment_0.instructions[5].opcode).toBe(OP_ARG_PUSHL);
expect(segment_0.instructions[5].args).toEqual([{ value: 1, size: 4 }]);
expect(segment_0.instructions[5].args).toEqual([{ value: 1 }]);
expect(segment_0.instructions[5].asm).toEqual({
args: [{ line_no: 5, col: 28, len: 1 }],
stack_args: [],
});
expect(segment_0.instructions[6].opcode).toBe(OP_ARG_PUSHW);
expect(segment_0.instructions[6].args).toEqual([{ value: 151, size: 2 }]);
expect(segment_0.instructions[6].args).toEqual([{ value: 151 }]);
expect(segment_0.instructions[6].asm).toEqual({
args: [{ line_no: 5, col: 31, len: 3 }],
stack_args: [],
@ -117,7 +117,7 @@ test("basic script", () => {
expect(segment_1.instructions.length).toBe(3);
expect(segment_1.instructions[0].opcode).toBe(OP_ARG_PUSHL);
expect(segment_1.instructions[0].args).toEqual([{ value: 1, size: 4 }]);
expect(segment_1.instructions[0].args).toEqual([{ value: 1 }]);
expect(segment_1.instructions[0].asm).toEqual({
args: [{ line_no: 7, col: 23, len: 1 }],
stack_args: [],
@ -163,7 +163,7 @@ test("pass the value of a register via the stack", () => {
expect(segment_0.instructions.length).toBe(4);
expect(segment_0.instructions[1].opcode).toBe(OP_ARG_PUSHR);
expect(segment_0.instructions[1].args).toEqual([{ value: 255, size: 1 }]);
expect(segment_0.instructions[1].args).toEqual([{ value: 255 }]);
});
test("pass a register reference via the stack", () => {
@ -186,8 +186,8 @@ test("pass a register reference via the stack", () => {
expect(segment_0.instructions.length).toBe(4);
expect(segment_0.instructions[0].opcode).toBe(OP_ARG_PUSHB);
expect(segment_0.instructions[0].args).toEqual([{ value: 200, size: 1 }]);
expect(segment_0.instructions[0].args).toEqual([{ value: 200 }]);
expect(segment_0.instructions[1].opcode).toBe(OP_ARG_PUSHL);
expect(segment_0.instructions[1].args).toEqual([{ value: 3, size: 4 }]);
expect(segment_0.instructions[1].args).toEqual([{ value: 3 }]);
});

View File

@ -603,7 +603,7 @@ class Assembler {
case Kind.Float:
this.add_instruction(
OP_ARG_PUSHL,
[{ value: reinterpret_f32_as_i32(arg.value), size: 4 }],
[{ value: reinterpret_f32_as_i32(arg.value) }],
[],
undefined,
[arg_token],
@ -728,13 +728,7 @@ class Assembler {
break;
case Kind.Float:
match = true;
arg_and_tokens.push([
{
value: token.value,
size: 4,
},
token,
]);
arg_and_tokens.push([{ value: token.value }, token]);
break;
default:
match = false;
@ -745,13 +739,7 @@ class Assembler {
match = param.type.kind === Kind.Float;
if (match) {
arg_and_tokens.push([
{
value: token.value,
size: 4,
},
token,
]);
arg_and_tokens.push([{ value: token.value }, token]);
}
break;
@ -768,13 +756,7 @@ class Assembler {
match = param.type.kind === Kind.String;
if (match) {
arg_and_tokens.push([
{
value: token.value,
size: 2 * token.value.length + 2,
},
token,
]);
arg_and_tokens.push([{ value: token.value }, token]);
}
break;
@ -864,13 +846,7 @@ class Assembler {
message: `${bit_size}-Bit integer can't be greater than ${max_value}.`,
});
} else {
arg_and_tokens.push([
{
value,
size,
},
token,
]);
arg_and_tokens.push([{ value }, token]);
}
}
@ -884,13 +860,7 @@ class Assembler {
message: `Invalid register reference, expected r0-r255.`,
});
} else {
arg_and_tokens.push([
{
value,
size: 1,
},
token,
]);
arg_and_tokens.push([{ value }, token]);
}
}

View File

@ -34,10 +34,10 @@ test("vararg instructions should be disassembled correctly", () => {
labels: [0],
instructions: [
new_instruction(OP_SWITCH_JMP, [
new_arg(90, 1),
new_arg(100, 2),
new_arg(101, 2),
new_arg(102, 2),
new_arg(90),
new_arg(100),
new_arg(101),
new_arg(102),
]),
new_instruction(OP_RET, []),
],
@ -64,8 +64,8 @@ test("va list instructions should be disassembled correctly", () => {
labels: [0],
instructions: [
new_instruction(OP_VA_START, []),
new_instruction(OP_ARG_PUSHW, [new_arg(1337, 2)]),
new_instruction(OP_VA_CALL, [new_arg(100, 2)]),
new_instruction(OP_ARG_PUSHW, [new_arg(1337)]),
new_instruction(OP_VA_CALL, [new_arg(100)]),
new_instruction(OP_VA_END, []),
new_instruction(OP_RET, []),
],

View File

@ -1 +1 @@
47
48