UNPKG

x86

Version:

Generates x86_64 native code from assembly instructions

383 lines (319 loc) 9.89 kB
import {UInt64} from './util'; export const enum SIZE { BYTE = 8, WORD = 16, DOUBLE = 32, QUAD = 64, } // # General operand used in our assembly "language". export abstract class Operand { // Convenience method to get `Register` associated with `Register` or `Memory`. reg(): Register { return null; } isRegister(): boolean { return this instanceof Register; } isMemory(): boolean { return this instanceof Memory; } toString(): string { return '[operand]'; } } // ## Constant // // Constants are everything where we directly type in a `number` value. export type number64 = [number, number]; export class Constant extends Operand { static sizeClass(value) { if((value <= 0x7f) && (value >= -0x80)) return SIZE.BYTE; if((value <= 0x7fff) && (value >= -0x8000)) return SIZE.WORD; if((value <= 0x7fffffff) && (value >= -0x80000000)) return SIZE.DOUBLE; return SIZE.QUAD; } // Size in bits. size: number = 32; value: number|number64 = 0; // Each byte as a `number` in reverse order. octets: number[] = []; constructor(value: number|number64) { super(); this.Value = value; } set Value(value: number|number64) { if(value instanceof Array) { if(value.length !== 2) throw TypeError('number64 must be a 2-tuple, given: ' + value); this.setValue64(value as number64); } else if(typeof value === 'number') { /* JS integers are 53-bit, so split here `number`s over 32 bits into [number, number]. */ if(Constant.sizeClass(value) === SIZE.QUAD) this.setValue64([UInt64.lo(value), UInt64.hi(value)]); else this.setValue32(value); } else throw TypeError('Constant value must be of type number|number64.'); } protected setValue32(value: number) { var size = Constant.sizeClass(value); this.size = size; this.value = value; this.octets = []; this.octets[0] = value & 0xFF; if(size > SIZE.BYTE) this.octets[1] = (value >> 8) & 0xFF; if(size > SIZE.WORD) { this.octets[2] = (value >> 16) & 0xFF; this.octets[3] = (value >> 24) & 0xFF; } } protected setValue64(value: number64) { this.size = 64; this.value = value; this.octets = []; var [lo, hi] = value; this.octets[0] = (lo) & 0xFF; this.octets[1] = (lo >> 8) & 0xFF; this.octets[2] = (lo >> 16) & 0xFF; this.octets[3] = (lo >> 24) & 0xFF; this.octets[4] = (hi) & 0xFF; this.octets[5] = (hi >> 8) & 0xFF; this.octets[6] = (hi >> 16) & 0xFF; this.octets[7] = (hi >> 24) & 0xFF; } zeroExtend(size) { if(this.size > size) throw Error(`Already larger than ${size} bits, cannot zero-extend.`); var missing_bytes = (size - this.size) / 8; this.size = size; for(var i = 0; i < missing_bytes; i++) this.octets.push(0); } toString() { return `const[${this.size}]: ${this.value}`; } } export class ImmediateValue extends Constant {} export class DisplacementValue extends Constant { static SIZE = { DISP8: SIZE.BYTE, DISP32: SIZE.DOUBLE, }; size = DisplacementValue.SIZE.DISP8; constructor(value: number) { super(value); } protected setValue32(value: number) { super.setValue32(value); /* Make sure `Displacement` is 1 or 4 bytes, not 2. */ if(this.size > DisplacementValue.SIZE.DISP8) this.zeroExtend(DisplacementValue.SIZE.DISP32); } protected setValue64() { throw TypeError(`Displacement can be only of these sizes: ${DisplacementValue.SIZE.DISP8} and ${DisplacementValue.SIZE.DISP32}.`); } } // # Scale // // `Scale` used in SIB byte in two bit `SCALE` field. export class Scale extends Operand { static VALUES = [1, 2, 4, 8]; value: number; constructor(scale: number = 1) { super(); if(Scale.VALUES.indexOf(scale) < 0) throw TypeError(`Scale must be one of [1, 2, 4, 8].`); this.value = scale; } toString() { return '' + this.value; } } // ## Registers // // `Register` represents one of `%rax`, `%rbx`, etc. registers. export enum R64 { RAX = 0, RCX, RDX, RBX, RSP, RBP, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15, } export enum R32 { EAX = 0, ECX, EDX, EBX, ESP, EBP, ESI, EDI, R8D, R9D, R10D, R11D, R12D, R13D, R14D, R15D, } export enum R8 { AL = 0, CL, DL, BL, SPL, BPL, SIL, DIL, R8B, R9B, R10B, R11B, R12B, R13B, R14B, R15B, } export class Register extends Operand { id: number = 0; // Number value of register. size: SIZE = SIZE.QUAD; // Size in bits constructor(id: number, size: SIZE) { super(); this.id = id; this.size = size; } reg(): Register { return this; } ref(): Memory { return (new Memory).ref(this); } disp(value: number): Memory { return (new Memory).ref(this).disp(value); } // Whether the register is one of `%r8`, `%r9`, etc. extended registers. isExtended() { return this.id > 0b111; } get3bitId() { return this.id & 0b111; } getName() { switch(this.size) { case SIZE.QUAD: return R64[this.id].toLowerCase(); case SIZE.DOUBLE: return R32[this.id].toLowerCase(); case SIZE.BYTE: return R8[this.id].toLowerCase(); default: return 'unknown'; } } toString() { return '%' + this.getName(); } } export class Register64 extends Register { constructor(id: number) { super(id, SIZE.QUAD); } } export class Register32 extends Register { constructor(id: number) { super(id, SIZE.DOUBLE); } } export class Register16 extends Register { constructor(id: number) { super(id, SIZE.WORD); } } export class Register8 extends Register { constructor(id: number) { super(id, SIZE.BYTE); } } export var rax = new Register64(R64.RAX); export var rbx = new Register64(R64.RBX); export var rcx = new Register64(R64.RCX); export var rdx = new Register64(R64.RDX); export var rsi = new Register64(R64.RSI); export var rdi = new Register64(R64.RDI); export var rbp = new Register64(R64.RBP); export var rsp = new Register64(R64.RSP); export var r8 = new Register64(R64.R8); export var r9 = new Register64(R64.R9); export var r10 = new Register64(R64.R10); export var r11 = new Register64(R64.R11); export var r12 = new Register64(R64.R12); export var r13 = new Register64(R64.R13); export var r14 = new Register64(R64.R14); export var r15 = new Register64(R64.R15); export var eax = new Register32(R32.EAX); export var ebx = new Register32(R32.EBX); export var ecx = new Register32(R32.ECX); export var edx = new Register32(R32.EDX); export var esi = new Register32(R32.ESI); export var edi = new Register32(R32.EDI); export var ebp = new Register32(R32.EBP); export var esp = new Register32(R32.ESP); export var r8d = new Register32(R32.R8D); export var r9d = new Register32(R32.R9D); export var r10d = new Register32(R32.R10D); export var r11d = new Register32(R32.R11D); export var r12d = new Register32(R32.R12D); export var r13d = new Register32(R32.R13D); export var r14d = new Register32(R32.R14D); export var r15d = new Register32(R32.R15D); export var al = new Register8(R8.AL); export var bl = new Register8(R8.BL); export var cl = new Register8(R8.CL); export var dl = new Register8(R8.DL); export var sil = new Register8(R8.SIL); export var dil = new Register8(R8.DIL); export var bpl = new Register8(R8.BPL); export var spl = new Register8(R8.SPL); export var r8b = new Register8(R8.R8B); export var r9b = new Register8(R8.R9B); export var r10b = new Register8(R8.R10B); export var r11b = new Register8(R8.R11B); export var r12b = new Register8(R8.R12B); export var r13b = new Register8(R8.R13B); export var r14b = new Register8(R8.R14B); export var r15b = new Register8(R8.R15B); // ## Memory // // `Memory` is RAM addresses which `Register`s can *dereference*. export class Memory extends Operand { base: Register = null; index: Register = null; scale: Scale = null; displacement: DisplacementValue = null; reg(): Register { if(this.base) return this.base; if(this.index) return this.index; // throw Error('No backing register.'); return null; } needsSib() { return !!this.index || !!this.scale; } ref(base: Register): this { this.base = base; return this; } disp(value: number): this { this.displacement = new DisplacementValue(value); return this; } toString() { var base = this.base ? this.base.toString() : ''; var index = this.index ? this.index.toString() : ''; var scale = this.scale ? this.scale.toString() : ''; var disp = this.disp ? this.disp.toString() : ''; return `[%${base} + %{index} * ${scale} + ${disp}]`; } }