aboutsummaryrefslogtreecommitdiffgithub
diff options
context:
space:
mode:
authorAustin Adams <git@austinjadams.com>2019-01-09 13:06:42 -0800
committerAustin Adams <git@austinjadams.com>2019-01-09 13:06:42 -0800
commitd82178b128009a5830338b139bf55712bfba9d9c (patch)
tree9a89a1f5238f073e7745e2767f8c7e17802570ee
parenta565e56d6868692dcbe6656d6a4b4b995122ef1a (diff)
downloadnovice-d82178b128009a5830338b139bf55712bfba9d9c.tar.gz
novice-d82178b128009a5830338b139bf55712bfba9d9c.tar.xz
Add basic second pass to assembler
-rw-r--r--novice/assembler/assembler.ts15
-rw-r--r--novice/assembler/codegen/base.ts178
-rw-r--r--novice/assembler/codegen/codegen.ts4
-rw-r--r--novice/assembler/index.ts8
-rw-r--r--novice/assembler/isa/index.ts4
-rw-r--r--novice/assembler/isa/isa.ts2
-rw-r--r--novice/assembler/parsers/index.ts4
-rw-r--r--novice/cli.ts26
8 files changed, 217 insertions, 24 deletions
diff --git a/novice/assembler/assembler.ts b/novice/assembler/assembler.ts
index 7075b68..76b226d 100644
--- a/novice/assembler/assembler.ts
+++ b/novice/assembler/assembler.ts
@@ -1,4 +1,5 @@
import { Readable } from 'stream';
+import { MachineCodeGenerator, MachineCodeSection } from './codegen';
import { Isa } from './isa';
import { ParsedAssembly, Parser } from './parsers';
import { Scanner } from './scanner';
@@ -6,17 +7,29 @@ import { Scanner } from './scanner';
class Assembler {
private scanner: Scanner;
private parser: Parser;
+ private generator: MachineCodeGenerator;
private isa: Isa;
- public constructor(parser: Parser, isa: Isa) {
+ public constructor(parser: Parser,
+ generator: MachineCodeGenerator,
+ isa: Isa) {
this.scanner = new Scanner();
this.parser = parser;
+ this.generator = generator;
this.isa = isa;
}
public async parse(fp: Readable): Promise<ParsedAssembly> {
return this.parser.parse(this.isa, await this.scanner.scan(fp));
}
+
+ public codegen(asm: ParsedAssembly): MachineCodeSection[] {
+ return this.generator.gen(this.isa, asm);
+ }
+
+ public async assemble(fp: Readable): Promise<MachineCodeSection[]> {
+ return this.codegen(await this.parse(fp));
+ }
}
export { Assembler };
diff --git a/novice/assembler/codegen/base.ts b/novice/assembler/codegen/base.ts
index c67e60c..39a8146 100644
--- a/novice/assembler/codegen/base.ts
+++ b/novice/assembler/codegen/base.ts
@@ -1,9 +1,181 @@
-import { ParsedAssembly, Section } from '../parsers';
+import { Instruction as IsaInstruction, Isa } from '../isa';
+import { Instruction, ParsedAssembly, PseudoOp, Section } from '../parsers';
import { MachineCodeGenerator, MachineCodeSection } from './codegen';
+interface ReassembleVictim {
+ // Index in words array in MachineCodeSection
+ index: number;
+ sectionIdx: number;
+ instrIdx: number;
+}
+
+type SymbTable = {[s: string]: number};
+
class BaseMachineCodeGenerator implements MachineCodeGenerator {
- public gen(asm: ParsedAssembly): MachineCodeSection[] {
- return [];
+ public gen(isa: Isa, asm: ParsedAssembly): MachineCodeSection[] {
+ const sections: MachineCodeSection[] = [];
+ const symbtable: SymbTable = {};
+ const reassemble: ReassembleVictim[] = [];
+
+ for (let i = 0; i < asm.sections.length; i++) {
+ const asmSection = asm.sections[i];
+ sections.push({startAddr: asmSection.startAddr, words: []});
+
+ for (let j = 0; j < asmSection.instructions.length; j++) {
+ const pc = sections[i].startAddr + sections[i].words.length;
+
+ // TODO: super inefficient. sort the list of labels first.
+ // Add to symbol table if needed
+ for (const label of Object.keys(asm.labels)) {
+ const [sectionIdx, instrIdx] = asm.labels[label];
+ if (sectionIdx === i && instrIdx === j) {
+ symbtable[label] = pc;
+ }
+ }
+
+ const instr = asmSection.instructions[j];
+ const [words, hasLabel] = this.inflateInstr(isa, instr, pc, null);
+
+ // If this instruction uses a label, we'll need to
+ // reassmble it
+ if (hasLabel) {
+ reassemble.push({index: sections[i].words.length,
+ sectionIdx: i,
+ instrIdx: j});
+ }
+
+ sections[i].words = sections[i].words.concat(words);
+ }
+ }
+
+ for (const victim of reassemble) {
+ const section = asm.sections[victim.sectionIdx];
+ const instr = section.instructions[victim.instrIdx];
+ const pc = sections[victim.sectionIdx].startAddr + victim.index;
+ const [words, _] = this.inflateInstr(isa, instr, pc, symbtable);
+
+ for (let k = 0; k < words.length; k++) {
+ sections[victim.sectionIdx].words[victim.index + k] = words[k];
+ }
+ }
+
+ // TODO: check for overlapping sections
+
+ return sections;
+ }
+
+ private inflateInstr(isa: Isa,
+ instr: Instruction|PseudoOp,
+ pc: number,
+ symbtable: SymbTable|null): [number[], boolean] {
+ // TODO: actually implement pseudoops
+ if (instr.kind === 'pseudoop') {
+ return [[0, 0, 0, 0], false];
+ } else {
+ let match: IsaInstruction|null = null;
+
+ // TODO: Figure out a more efficient way than this. just a
+ // hashmap right?
+ for (const isaInstr of isa.instructions) {
+ if (this.instrMatch(instr, isaInstr)) {
+ match = isaInstr;
+ break;
+ }
+ }
+
+ if (!match) {
+ // TODO: include reg prefixes
+ const operands = (instr.operands.length > 0) ?
+ 'operands' + instr.operands.map(operand => operand.kind)
+ .join(', ')
+ : 'no operands';
+
+ // TODO: line numbers
+ throw new Error(`unknown instruction ${instr.op} with ` +
+ operands);
+ }
+
+ const skip = !symbtable &&
+ instr.operands.some(op => op.kind === 'label');
+ const instrBin = skip ? 0 : this.genInstrBin(instr, match, pc,
+ symbtable as SymbTable,
+ isa);
+ return [[instrBin], skip];
+ }
+ }
+
+ private genInstrBin(instr: Instruction, isaInstr: IsaInstruction,
+ pc: number, symbtable: SymbTable, isa: Isa): number {
+ let bin = 0;
+ let o = 0;
+
+ for (const field of isaInstr.fields) {
+ if (field.kind === 'const') {
+ const numBits = field.bits[0] - field.bits[1] + 1;
+ const masked = field.val & ~(-1 << numBits);
+ bin |= masked << field.bits[1];
+ } else {
+ const operand = instr.operands[o++];
+ const numBits = field.bits[0] - field.bits[1] + 1;
+
+ if (field.kind === 'reg' && operand.kind === 'reg') {
+ // TODO: check for reg too big
+ // TODO: support other prefixes etc
+ bin |= operand.num << field.bits[1];
+ } else if (field.kind === 'imm' && operand.kind === 'int') {
+ // TODO: check if too big
+ const masked = operand.val & ~(-1 << numBits);
+ bin |= masked << field.bits[1];
+ } else if (field.kind === 'imm' && operand.kind === 'label') {
+ const actualPc = pc + isa.pc.increment;
+ // TODO: check if too big
+ // TODO: check if nonexistent labels
+ // TODO: check if offset is negative but sext is false
+ const offset = symbtable[operand.label] - actualPc;
+ // TODO: make this a helper function
+ const masked = offset & ~(-1 << numBits);
+ bin |= masked << field.bits[1];
+ } else {
+ // TODO: non-garbage error message
+ // TODO: line numbers
+ throw new Error(`unknown operand ${operand.kind}`);
+ }
+ }
+ }
+
+ return bin;
+ }
+
+ private instrMatch(instr: Instruction, isaInstr: IsaInstruction): boolean {
+ if (instr.op !== isaInstr.op) {
+ return false;
+ }
+
+ let ok = true;
+ let o = 0;
+ let f = 0;
+
+ // while (ok && o < instr.operands.length && f < isaInstr.fields.length) {
+ while (ok && f < isaInstr.fields.length) {
+ const field = isaInstr.fields[f];
+
+ if (field.kind === 'const') {
+ f++;
+ } else if (ok = o < instr.operands.length) {
+ const operand = instr.operands[o];
+
+ // TODO: check reg prefixes
+ ok = operand.kind === 'reg' && field.kind === 'reg' ||
+ operand.kind === 'int' && field.kind === 'imm' ||
+ operand.kind === 'label' && field.kind === 'imm'
+ && field.label;
+ f++;
+ o++;
+ }
+ }
+
+ ok = ok && o === instr.operands.length && f === isaInstr.fields.length;
+ return ok;
}
}
diff --git a/novice/assembler/codegen/codegen.ts b/novice/assembler/codegen/codegen.ts
index cf34315..d151c00 100644
--- a/novice/assembler/codegen/codegen.ts
+++ b/novice/assembler/codegen/codegen.ts
@@ -1,13 +1,13 @@
+import { Isa } from '../isa';
import { ParsedAssembly } from '../parsers';
interface MachineCodeSection {
startAddr: number;
- length: number;
words: number[];
}
interface MachineCodeGenerator {
- gen(asm: ParsedAssembly): MachineCodeSection[];
+ gen(isa: Isa, asm: ParsedAssembly): MachineCodeSection[];
}
export { MachineCodeSection, MachineCodeGenerator };
diff --git a/novice/assembler/index.ts b/novice/assembler/index.ts
index 3a04dc2..f408641 100644
--- a/novice/assembler/index.ts
+++ b/novice/assembler/index.ts
@@ -1,4 +1,5 @@
import { Assembler } from './assembler';
+import { BaseMachineCodeGenerator, MachineCodeGenerator } from './codegen';
import { Isa, isas } from './isa';
import { Parser, parsers } from './parsers';
@@ -10,6 +11,11 @@ function getParser(parserName: string): Parser {
return new parsers[parserName]();
}
+function getGenerator(): MachineCodeGenerator {
+ // Go ahead and return only this lil fella for now
+ return new BaseMachineCodeGenerator();
+}
+
function getIsa(isaName: string): Isa {
if (!isas.hasOwnProperty(isaName)) {
throw new Error(`no such isa \`${isaName}'\n`);
@@ -18,4 +24,4 @@ function getIsa(isaName: string): Isa {
return isas[isaName];
}
-export { Assembler, getParser, getIsa };
+export { Assembler, getParser, getGenerator, getIsa };
diff --git a/novice/assembler/isa/index.ts b/novice/assembler/isa/index.ts
index 04e6c8e..53a5623 100644
--- a/novice/assembler/isa/index.ts
+++ b/novice/assembler/isa/index.ts
@@ -1,4 +1,4 @@
-import { Isa } from './isa';
+import { Instruction, Isa } from './isa';
import { Lc3Isa } from './lc3';
import { MachineState, MachineStateUpdate } from './state';
@@ -6,4 +6,4 @@ const isas: {[s: string]: Isa} = {
lc3: Lc3Isa,
};
-export { Isa, isas, Lc3Isa, MachineState, MachineStateUpdate };
+export { Isa, Instruction, isas, Lc3Isa, MachineState, MachineStateUpdate };
diff --git a/novice/assembler/isa/isa.ts b/novice/assembler/isa/isa.ts
index 985b38d..450c693 100644
--- a/novice/assembler/isa/isa.ts
+++ b/novice/assembler/isa/isa.ts
@@ -68,4 +68,4 @@ interface Isa {
instructions: Instruction[];
}
-export { Isa, Fields };
+export { Isa, Fields, Instruction };
diff --git a/novice/assembler/parsers/index.ts b/novice/assembler/parsers/index.ts
index d21a455..19a9265 100644
--- a/novice/assembler/parsers/index.ts
+++ b/novice/assembler/parsers/index.ts
@@ -1,8 +1,8 @@
import ComplxParser from './complx';
-import { ParsedAssembly, Parser, Section } from './parser';
+import { Instruction, ParsedAssembly, Parser, PseudoOp, Section } from './parser';
const parsers: {[s: string]: new() => Parser} = {
complx: ComplxParser,
};
-export { ParsedAssembly, Section, Parser, parsers };
+export { ParsedAssembly, Section, Parser, parsers, Instruction, PseudoOp };
diff --git a/novice/cli.ts b/novice/cli.ts
index d070fac..26a4665 100644
--- a/novice/cli.ts
+++ b/novice/cli.ts
@@ -1,18 +1,18 @@
import * as fs from 'fs';
import { Writable } from 'stream';
-import { Assembler, getIsa, getParser } from './assembler';
+import { Assembler, getGenerator, getIsa, getParser } from './assembler';
-async function main(args: string[], stdout: Writable, stderr: Writable):
- Promise<number> {
+async function main(args: string[], stdout: Writable,
+ stderr: Writable): Promise<number> {
const subcommand = args[0];
switch (subcommand) {
- case 'asm-pass1':
+ case 'asm':
if (args.length === 4) {
const parser = args[1];
const isa = args[2];
const path = args[3];
- return await asmPass1(parser, isa, path, stdout, stderr);
+ return await asm(parser, isa, path, stdout, stderr);
} else {
return usage(stderr);
}
@@ -29,26 +29,28 @@ async function main(args: string[], stdout: Writable, stderr: Writable):
}
function usage(stderr: Writable): number {
- stderr.write('usage: novice asm-pass1 <parser> <isa> <file>\n' +
- ' novice tablegen <parser>\n');
+ stderr.write('usage: novice asm <parser> <isa> <file>\n' +
+ ' novice tablegen <parser>\n');
return 1;
}
-async function asmPass1(parserName: string, isaName: string, path: string, stdout: Writable,
- stderr: Writable): Promise<number> {
+async function asm(parserName: string, isaName: string, path: string,
+ stdout: Writable, stderr: Writable): Promise<number> {
try {
const parser = getParser(parserName);
+ const generator = getGenerator();
const isa = getIsa(isaName);
const fp = fs.createReadStream(path);
await new Promise((resolve, reject) => {
fp.on('readable', resolve);
fp.on('error', reject);
});
- const assembly = await new Assembler(parser, isa).parse(fp);
- stdout.write(JSON.stringify(assembly));
+ const assembler = new Assembler(parser, generator, isa);
+ const machineCode = await assembler.assemble(fp);
+ stdout.write(JSON.stringify(machineCode));
return 0;
} catch (err) {
- stderr.write(`asm-pass1 error: ${err.message}\n`);
+ stderr.write(`asm error: ${err.message}\n`);
return 1;
}
}