├── src ├── constants.ts ├── passes │ ├── index.ts │ ├── base.ts │ └── verify.ts ├── utils.ts ├── types │ ├── struct │ │ ├── field.ts │ │ └── index.ts │ ├── label.ts │ ├── void.ts │ ├── index.ts │ ├── metadata.ts │ ├── int.ts │ ├── pointer.ts │ ├── array.ts │ ├── signature.ts │ └── base.ts ├── linkage.ts ├── values │ ├── instructions │ │ ├── unreachable.ts │ │ ├── jump.ts │ │ ├── ret.ts │ │ ├── extractvalue.ts │ │ ├── load.ts │ │ ├── insertvalue.ts │ │ ├── index.ts │ │ ├── branch.ts │ │ ├── binop.ts │ │ ├── icmp.ts │ │ ├── switch.ts │ │ ├── store.ts │ │ ├── common.ts │ │ ├── phi.ts │ │ ├── getelementptr.ts │ │ ├── base.ts │ │ ├── cast.ts │ │ └── call.ts │ ├── index.ts │ ├── constants │ │ ├── index.ts │ │ ├── null.ts │ │ ├── undef.ts │ │ ├── int.ts │ │ ├── struct.ts │ │ ├── array.ts │ │ ├── declaration.ts │ │ ├── metadata.ts │ │ ├── base.ts │ │ └── function.ts │ ├── argument.ts │ ├── global.ts │ ├── base.ts │ └── basic-block.ts ├── calling-conv.ts ├── bitcode.ts └── attribute-list.ts ├── .gitignore ├── .travis.yml ├── tsconfig.json ├── tslint.json ├── package.json ├── README.md └── test ├── types-test.ts ├── function-test.ts ├── constants-test.ts ├── instructions-test.ts └── passes-verify-test.ts /src/constants.ts: -------------------------------------------------------------------------------- 1 | export let BOOL_WIDTH = 1; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm_debug.log 3 | lib/ 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "stable" 5 | -------------------------------------------------------------------------------- /src/passes/index.ts: -------------------------------------------------------------------------------- 1 | export { IPassInput, Pass } from './base'; 2 | export { Verify } from './verify'; 3 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | // CHAR6 2 | export function validateName(name: string): boolean { 3 | return /^[a-zA-Z0-9_.]+$/.test(name); 4 | } 5 | -------------------------------------------------------------------------------- /src/types/struct/field.ts: -------------------------------------------------------------------------------- 1 | import { Type } from '../base'; 2 | 3 | export class Field { 4 | constructor(public readonly ty: Type, public readonly name: string, 5 | public readonly index: number) { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/linkage.ts: -------------------------------------------------------------------------------- 1 | export type Linkage = 2 | 'external' | 'weak' | 'appending' | 'internal' | 'linkonce' | 'dllimport' | 3 | 'dllexport' | 'extern_weak' | 'common' | 'private' | 'weak_odr' | 4 | 'linkonce_odr' | 'available_externally'; 5 | -------------------------------------------------------------------------------- /src/values/instructions/unreachable.ts: -------------------------------------------------------------------------------- 1 | import { Void } from '../../types'; 2 | import { Instruction } from './base'; 3 | 4 | export class Unreachable extends Instruction { 5 | constructor() { 6 | super(new Void(), 'unreachable', []); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/calling-conv.ts: -------------------------------------------------------------------------------- 1 | export type CallingConv = 2 | 'ccc' | 'fastcc' | 'coldcc' | 'webkit_jscc' | 'anyregcc' | 'preserve_mostcc' | 3 | 'preserve_allcc' | 'swiftcc' | 'cxx_fast_tlscc' | 'x86_stdcallcc' | 4 | 'x86_fastcallcc' | 'arm_apcscc' | 'arm_aapcscc' | 'arm_aapcs_vfpcc'; 5 | -------------------------------------------------------------------------------- /src/types/label.ts: -------------------------------------------------------------------------------- 1 | import { Type } from './base'; 2 | 3 | export class Label extends Type { 4 | constructor() { 5 | super('label'); 6 | } 7 | 8 | public isEqual(to: Type): boolean { 9 | if (this === to) { 10 | return true; 11 | } 12 | return false; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "target": "es2017", 5 | "module": "commonjs", 6 | "outDir": "./lib", 7 | "declaration": true, 8 | "pretty": true, 9 | "sourceMap": true 10 | }, 11 | "include": [ 12 | "src/**/*.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": {}, 7 | "rules": { 8 | "quotemark": [ 9 | true, "single", "avoid-escape", "avoid-template" 10 | ] 11 | }, 12 | "rulesDirectory": [] 13 | } 14 | -------------------------------------------------------------------------------- /src/values/instructions/jump.ts: -------------------------------------------------------------------------------- 1 | import * as values from '../'; 2 | import { Void } from '../../types'; 3 | import { Instruction } from './base'; 4 | 5 | export class Jump extends Instruction { 6 | constructor(public readonly target: values.BasicBlock) { 7 | super(new Void(), 'jmp', [ target ]); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/values/index.ts: -------------------------------------------------------------------------------- 1 | import * as constants from './constants'; 2 | import * as instructions from './instructions'; 3 | 4 | export { Argument } from './argument'; 5 | export { Value } from './base'; 6 | export { BasicBlock } from './basic-block'; 7 | export { Global } from './global'; 8 | 9 | export { constants, instructions }; 10 | -------------------------------------------------------------------------------- /src/types/void.ts: -------------------------------------------------------------------------------- 1 | import { Type } from './base'; 2 | 3 | export class Void extends Type { 4 | constructor() { 5 | super('void'); 6 | } 7 | 8 | public ptr(): never { throw new Error('Can\'t create pointer to void'); } 9 | 10 | public isEqual(to: Type): boolean { 11 | return this === to || to.isVoid(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/values/instructions/ret.ts: -------------------------------------------------------------------------------- 1 | import * as values from '../'; 2 | import { Void } from '../../types'; 3 | import { Instruction } from './base'; 4 | 5 | export class Ret extends Instruction { 6 | constructor(public readonly operand?: values.Value) { 7 | super(new Void(), 'ret', operand === undefined ? [] : [ operand ]); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export { Array } from './array'; 2 | export { Type } from './base'; 3 | export { Int } from './int'; 4 | export { Label } from './label'; 5 | export { Metadata } from './metadata'; 6 | export { Pointer } from './pointer'; 7 | export { Signature } from './signature'; 8 | export { Field, Struct } from './struct'; 9 | export { Void } from './void'; 10 | -------------------------------------------------------------------------------- /src/values/constants/index.ts: -------------------------------------------------------------------------------- 1 | export { Array } from './array'; 2 | export { Constant } from './base'; 3 | export { Declaration } from './declaration'; 4 | export { Func } from './function'; 5 | export { Int } from './int'; 6 | export { Metadata, MetadataValue } from './metadata'; 7 | export { Null } from './null'; 8 | export { Struct } from './struct'; 9 | export { Undef } from './undef'; 10 | -------------------------------------------------------------------------------- /src/values/argument.ts: -------------------------------------------------------------------------------- 1 | import { Type } from '../types'; 2 | import { Value } from './base'; 3 | 4 | export class Argument extends Value { 5 | constructor(ty: Type, public readonly index: number, 6 | public readonly name: string) { 7 | super(ty); 8 | } 9 | 10 | public toString(): string { 11 | return `[argument index=${this.index} name=${this.name}]`; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/passes/base.ts: -------------------------------------------------------------------------------- 1 | import * as values from '../values'; 2 | 3 | export interface IPassInput { 4 | readonly declarations: values.constants.Declaration[]; 5 | readonly functions: values.constants.Func[]; 6 | readonly globals: values.Global[]; 7 | } 8 | 9 | export abstract class Pass { 10 | constructor(protected readonly input: IPassInput) { 11 | } 12 | 13 | public abstract run(): void; 14 | } 15 | -------------------------------------------------------------------------------- /src/values/instructions/extractvalue.ts: -------------------------------------------------------------------------------- 1 | import * as values from '../'; 2 | import { Instruction } from './base'; 3 | import { getAggrFieldType } from './common'; 4 | 5 | // TODO(indutny): support more indices? 6 | export class ExtractValue extends Instruction { 7 | constructor(public readonly aggr: values.Value, 8 | public readonly index: number) { 9 | super(getAggrFieldType(aggr.ty, index), 'extractvalue', [ aggr ]); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/types/metadata.ts: -------------------------------------------------------------------------------- 1 | import * as values from '../values'; 2 | import { Type } from './base'; 3 | 4 | export class Metadata extends Type { 5 | constructor() { 6 | super('metadata'); 7 | } 8 | 9 | public ptr(): never { throw new Error('Can\'t create pointer to metadata'); } 10 | 11 | public isEqual(to: Type): boolean { 12 | return this === to || to.isMetadata(); 13 | } 14 | 15 | public val(value: values.constants.MetadataValue): values.constants.Metadata { 16 | return new values.constants.Metadata(value); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/values/constants/null.ts: -------------------------------------------------------------------------------- 1 | import * as types from '../../types'; 2 | import { Constant } from './base'; 3 | 4 | export class Null extends Constant { 5 | constructor(ty: types.Pointer) { 6 | super(ty); 7 | } 8 | 9 | public isEqual(to: Constant): boolean { 10 | if (this === to) { 11 | return true; 12 | } 13 | 14 | if (!to.isNull()) { 15 | return false; 16 | } 17 | 18 | const toNull = to as Null; 19 | return toNull.ty.isEqual(this.ty); 20 | } 21 | 22 | public toString(): string { 23 | return `[null type=${this.ty.typeString}]`; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/values/constants/undef.ts: -------------------------------------------------------------------------------- 1 | import * as types from '../../types'; 2 | import { Constant } from './base'; 3 | 4 | export class Undef extends Constant { 5 | constructor(ty: types.Type) { 6 | super(ty); 7 | } 8 | 9 | public isEqual(to: Constant): boolean { 10 | if (this === to) { 11 | return true; 12 | } 13 | 14 | if (!to.isUndef()) { 15 | return false; 16 | } 17 | 18 | const toUndef = to as Undef; 19 | return toUndef.ty.isEqual(this.ty); 20 | } 21 | 22 | public toString(): string { 23 | return `[undef type=${this.ty.typeString}]`; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/types/int.ts: -------------------------------------------------------------------------------- 1 | import * as values from '../values'; 2 | import { Type } from './base'; 3 | 4 | export class Int extends Type { 5 | constructor(public readonly width: number) { 6 | super('i' + width); 7 | } 8 | 9 | public isEqual(to: Type): boolean { 10 | if (this === to) { 11 | return true; 12 | } 13 | 14 | if (!to.isInt()) { 15 | return false; 16 | } 17 | 18 | const toInt = to as Int; 19 | return toInt.width === this.width; 20 | } 21 | 22 | public val(num: number): values.constants.Int { 23 | return new values.constants.Int(this, num); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/values/instructions/load.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | import * as values from '../'; 4 | import { Instruction } from './base'; 5 | import { checkAlignment } from './common'; 6 | 7 | // TODO(indutny): separate instruction for atomic load? 8 | export class Load extends Instruction { 9 | constructor(public readonly ptr: values.Value, 10 | public readonly alignment?: number, 11 | public readonly isVolatile: boolean = false) { 12 | super(ptr.ty.toPointer().to, 'load', [ ptr ]); 13 | 14 | if (alignment !== undefined) { 15 | checkAlignment(alignment); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/values/constants/int.ts: -------------------------------------------------------------------------------- 1 | import * as types from '../../types'; 2 | import { Constant } from './base'; 3 | 4 | export class Int extends Constant { 5 | // TODO(indutny): 64bit values through Array, or Int64 class? 6 | constructor(ty: types.Int, public readonly value: number) { 7 | super(ty); 8 | } 9 | 10 | public isEqual(to: Constant): boolean { 11 | if (this === to) { 12 | return true; 13 | } 14 | 15 | if (!to.isInt() || !to.ty.isEqual(this.ty)) { 16 | return false; 17 | } 18 | 19 | const toInt = to as Int; 20 | return toInt.value === this.value; 21 | } 22 | 23 | public toString(): string { 24 | return `[int value=${this.value}]`; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/values/instructions/insertvalue.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | import * as values from '../'; 4 | import { Instruction } from './base'; 5 | import { getAggrFieldType } from './common'; 6 | 7 | // TODO(indutny): support more indices? 8 | export class InsertValue extends Instruction { 9 | constructor(public readonly aggr: values.Value, 10 | public readonly element: values.Value, 11 | public readonly index: number) { 12 | super(aggr.ty, 'insertvalue', [ aggr, element ]); 13 | 14 | const fieldType = getAggrFieldType(aggr.ty, index); 15 | assert(element.ty.isEqual(fieldType), 16 | 'element type doesn\'t match field type in `insertvalue`'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/values/instructions/index.ts: -------------------------------------------------------------------------------- 1 | export { Instruction } from './base'; 2 | export { Binop, BinopType } from './binop'; 3 | export { Branch } from './branch'; 4 | export { Call, CallType } from './call'; 5 | export { Cast, CastType } from './cast'; 6 | export { ExtractValue } from './extractvalue'; 7 | export { GetElementPtr } from './getelementptr'; 8 | export { ICmp, ICmpPredicate } from './icmp'; 9 | export { InsertValue } from './insertvalue'; 10 | export { Jump } from './jump'; 11 | export { Load } from './load'; 12 | export { IPhiEdge, Phi } from './phi'; 13 | export { Ret } from './ret'; 14 | export { Store } from './store'; 15 | export { ISwitchCase, Switch } from './switch'; 16 | export { Unreachable } from './unreachable'; 17 | -------------------------------------------------------------------------------- /src/values/instructions/branch.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | import * as values from '../'; 4 | import { BOOL_WIDTH } from '../../constants'; 5 | import { Void } from '../../types'; 6 | import { Instruction } from './base'; 7 | 8 | export class Branch extends Instruction { 9 | constructor(public readonly condition: values.Value, 10 | public readonly onTrue: values.BasicBlock, 11 | public readonly onFalse: values.BasicBlock) { 12 | super(new Void(), 'branch', [ condition, onTrue, onFalse ]); 13 | 14 | assert(condition.ty.isInt(), 'Branch `condition` must have Int type'); 15 | assert.strictEqual(condition.ty.toInt().width, BOOL_WIDTH, 16 | 'Branch `condition` must have a boolean width'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/types/pointer.ts: -------------------------------------------------------------------------------- 1 | import * as values from '../values'; 2 | import { Type } from './base'; 3 | 4 | export class Pointer extends Type { 5 | // TODO(indutny): addrspace 6 | constructor(public readonly to: Type) { 7 | super(to.typeString + '*'); 8 | } 9 | 10 | public isEqual(to: Type): boolean { 11 | if (this === to) { 12 | return true; 13 | } 14 | 15 | if (!to.isPointer()) { 16 | // Signatures might be equal to pointers, better check 17 | if (to.isSignature()) { 18 | return to.isEqual(this); 19 | } 20 | return false; 21 | } 22 | 23 | const toPtr = to as Pointer; 24 | return toPtr.to.isEqual(this.to); 25 | } 26 | 27 | public val(_: null): values.constants.Null { 28 | return new values.constants.Null(this); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/values/constants/struct.ts: -------------------------------------------------------------------------------- 1 | import * as types from '../../types'; 2 | import { Constant } from './base'; 3 | 4 | export class Struct extends Constant { 5 | constructor(ty: types.Struct, 6 | public readonly fields: ReadonlyArray) { 7 | super(ty); 8 | } 9 | 10 | public isEqual(to: Constant): boolean { 11 | if (this === to) { 12 | return true; 13 | } 14 | 15 | if (!to.isStruct()) { 16 | return false; 17 | } 18 | 19 | const toStruct = to as Struct; 20 | return toStruct.ty.isEqual(this.ty) && 21 | toStruct.fields.length === this.fields.length && 22 | toStruct.fields.every((field, i) => field.isEqual(this.fields[i])); 23 | } 24 | 25 | public toString(): string { 26 | // TODO(indutny): print more 27 | return '[struct]'; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/values/constants/array.ts: -------------------------------------------------------------------------------- 1 | import * as types from '../../types'; 2 | import { Constant } from './base'; 3 | 4 | class ArrayVal extends Constant { 5 | constructor(ty: types.Array, public readonly elems: ReadonlyArray) { 6 | super(ty); 7 | } 8 | 9 | public isEqual(to: Constant): boolean { 10 | if (this === to) { 11 | return true; 12 | } 13 | 14 | if (!to.isArray() || !to.ty.isEqual(this.ty)) { 15 | return false; 16 | } 17 | 18 | const toArray = to as ArrayVal; 19 | return toArray.elems.length === this.elems.length && 20 | toArray.elems.every((elem, i) => elem.isEqual(this.elems[i])); 21 | } 22 | 23 | public toString(): string { 24 | const elems = this.elems.map((elem) => elem.toString()); 25 | return `[const.array elems={${elems.join(', ')}}]`; 26 | } 27 | } 28 | 29 | export { ArrayVal as Array }; 30 | -------------------------------------------------------------------------------- /src/values/instructions/binop.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | import * as values from '../'; 4 | import { Instruction } from './base'; 5 | 6 | // TODO(indutny): floating point 7 | export type BinopType = 8 | 'add' | 'sub' | 'mul' | 'udiv' | 'sdiv' | 'urem' | 'srem' | 'shl' | 'lshr' | 9 | 'ashr' | 'and' | 'or' | 'xor'; 10 | 11 | // TODO(indutny): optimization (nsw, nuw, ...) 12 | export class Binop extends Instruction { 13 | constructor(public readonly binopType: BinopType, 14 | public readonly left: values.Value, 15 | public readonly right: values.Value) { 16 | super(left.ty, 'binop.' + binopType, [ left, right ]); 17 | assert(left.ty.isInt() && right.ty.isInt(), 18 | 'Only Int type is supported at the moment'); 19 | assert(left.ty.isEqual(right.ty), 20 | 'Left and right operands of Binop have different types'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/values/instructions/icmp.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | import * as values from '../'; 4 | import { BOOL_WIDTH } from '../../constants'; 5 | import { Int } from '../../types'; 6 | import { Instruction } from './base'; 7 | 8 | // TODO(indutny): floating point 9 | export type ICmpPredicate = 10 | 'eq' | 'ne' | 'ugt' | 'uge' | 'ult' | 'ule' | 'sgt' | 'sge' | 'slt' | 'sle'; 11 | 12 | export class ICmp extends Instruction { 13 | constructor(public readonly predicate: ICmpPredicate, 14 | public readonly left: values.Value, 15 | public readonly right: values.Value) { 16 | super(new Int(BOOL_WIDTH), 'icmp', [ left, right ]); 17 | 18 | assert(left.ty.isInt() || left.ty.isPointer(), 19 | 'Only integer types are supported at the moment'); 20 | assert(left.ty.isEqual(right.ty), 21 | 'Left and right operands of ICmp have different types'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/values/instructions/switch.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | import * as values from '../'; 4 | import { Void } from '../../types'; 5 | import { Instruction } from './base'; 6 | 7 | export interface ISwitchCase { 8 | readonly value: values.constants.Int; 9 | readonly block: values.BasicBlock; 10 | } 11 | 12 | export class Switch extends Instruction { 13 | constructor(public readonly condition: values.Value, 14 | public readonly otherwise: values.BasicBlock, 15 | public readonly cases: ISwitchCase[]) { 16 | super(new Void(), 'switch', [ condition, otherwise ]); 17 | 18 | assert(condition.ty.isInt(), 'Switch `condition` must have Int type'); 19 | cases.forEach((c, index) => { 20 | assert(condition.ty.isEqual(c.value.ty), 21 | `Type mismatch for switch clause :${index}`); 22 | this.operands.push(c.value, c.block); 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/values/instructions/store.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | import * as values from '../'; 4 | import { Void } from '../../types'; 5 | import { Instruction } from './base'; 6 | import { checkAlignment } from './common'; 7 | 8 | // TODO(indutny): separate instruction for atomic store? 9 | export class Store extends Instruction { 10 | constructor(public readonly value: values.Value, 11 | public readonly ptr: values.Value, 12 | public readonly alignment?: number, 13 | public readonly isVolatile: boolean = false) { 14 | super(new Void(), 'store', [ value, ptr ]); 15 | 16 | if (alignment !== undefined) { 17 | checkAlignment(alignment); 18 | } 19 | 20 | const slotType = ptr.ty.toPointer().to; 21 | 22 | // It's okay to store functions inside a pointer field 23 | assert(value.ty.isEqual(slotType), 24 | 'Invalid value type for Store instruction'); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/values/instructions/common.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | import * as values from '../'; 4 | import { Type } from '../../types'; 5 | 6 | export function getAggrFieldType(aggrTy: Type, index: number): Type { 7 | if (aggrTy.isStruct()) { 8 | const field = aggrTy.toStruct().getField(index); 9 | 10 | return field.ty; 11 | } else if (aggrTy.isArray()) { 12 | const arrayTy = aggrTy.toArray(); 13 | assert(0 <= index && index < arrayTy.length, 14 | 'OOB array index in `extractvalue`'); 15 | 16 | return arrayTy.elemType; 17 | } else { 18 | throw new Error('Expected aggregate type, but got: ' + aggrTy.typeString); 19 | } 20 | } 21 | 22 | export function checkAlignment(alignment: number): void { 23 | assert(1 < alignment, 'Alignment must be greater than zero if present'); 24 | assert.strictEqual(alignment, Math.pow(2, Math.floor(Math.log2(alignment))), 25 | 'Alignment must be a power of two if present'); 26 | } 27 | -------------------------------------------------------------------------------- /src/values/instructions/phi.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | import * as values from '../'; 4 | import { Type } from '../../types'; 5 | import { Instruction } from './base'; 6 | 7 | export interface IPhiEdge { 8 | readonly fromBlock: values.BasicBlock; 9 | readonly value: values.Value; 10 | } 11 | 12 | export class Phi extends Instruction { 13 | private readonly privEdges: IPhiEdge[] = []; 14 | 15 | constructor(edgeOrTy: IPhiEdge | Type) { 16 | super(edgeOrTy instanceof Type ? edgeOrTy : edgeOrTy.value.ty, 'phi', []); 17 | 18 | if (!(edgeOrTy instanceof Type)) { 19 | this.addEdge(edgeOrTy); 20 | } 21 | } 22 | 23 | public get edges(): ReadonlyArray { 24 | return this.privEdges; 25 | } 26 | 27 | public addEdge(edge: IPhiEdge) { 28 | assert(this.ty.isEqual(edge.value.ty), 'Type mismatch for Phi edge'); 29 | 30 | this.operands.push(edge.fromBlock, edge.value); 31 | this.privEdges.push(edge); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/types/array.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import { Buffer } from 'buffer'; 3 | 4 | import * as values from '../values'; 5 | import { Type } from './base'; 6 | 7 | class ArrayTy extends Type { 8 | constructor(public readonly length: number, public readonly elemType: Type) { 9 | super(`[${length} x ${elemType.typeString}]`); 10 | 11 | assert(!elemType.isVoid(), 'Can\'t create Array of Void'); 12 | assert(!elemType.isSignature(), 13 | 'Array can\'t have signature elements, please use `sig.ptr()`'); 14 | } 15 | 16 | public isEqual(to: Type): boolean { 17 | if (this === to) { 18 | return true; 19 | } 20 | 21 | if (!to.isArray()) { 22 | return false; 23 | } 24 | 25 | const toArray = to as ArrayTy; 26 | return toArray.length === this.length && 27 | toArray.elemType.isEqual(this.elemType); 28 | } 29 | 30 | public val(elems: values.constants.Constant[]): values.constants.Array { 31 | assert.strictEqual(elems.length, this.length, 'Invalid elements count'); 32 | return new values.constants.Array(this, elems); 33 | } 34 | } 35 | export { ArrayTy as Array }; 36 | -------------------------------------------------------------------------------- /src/values/instructions/getelementptr.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | import * as values from '../'; 4 | import { Instruction } from './base'; 5 | import { getAggrFieldType } from './common'; 6 | 7 | function getElementPtrType(value: values.Value, index?: values.Value) { 8 | const ptr = value.ty.toPointer(); 9 | 10 | if (index === undefined) { 11 | return ptr; 12 | } 13 | 14 | if (ptr.to.isArray() && !index.isConstant()) { 15 | return ptr.to.toArray().elemType.ptr(); 16 | } 17 | 18 | const indexConst = index.toConstant(); 19 | assert(indexConst.isInt(), 20 | 'Expected integer constant offset for `getelementptr`'); 21 | return getAggrFieldType(ptr.to, indexConst.toInt().value).ptr(); 22 | } 23 | 24 | // TODO(indutny): support `inrange` 25 | // TODO(indutny): support more indices? 26 | export class GetElementPtr extends Instruction { 27 | constructor(public readonly ptr: values.Value, 28 | public readonly ptrIndex: values.Value, 29 | public readonly index?: values.Value, 30 | public readonly inbounds: boolean = false) { 31 | super( 32 | getElementPtrType(ptr, index), 33 | 'getelementptr', 34 | index === undefined ? [ ptr, ptrIndex ] : [ ptr, ptrIndex, index ]); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bitcode-builder", 3 | "version": "1.2.0", 4 | "description": "API for building CFG graph for compilation to LLVM bitcode", 5 | "main": "lib/bitcode.js", 6 | "types": "lib/bitcode.d.ts", 7 | "files": [ 8 | "lib", 9 | "src" 10 | ], 11 | "scripts": { 12 | "build": "tsc", 13 | "clean": "rm -rf lib", 14 | "prepare": "npm run clean && npm run build", 15 | "mocha": "mocha -r ts-node/register/type-check --reporter spec test/*-test.ts", 16 | "lint": "tslint -c tslint.json src/*.ts src/**/*.ts src/**/**/*.ts test/*.ts", 17 | "test": "npm run mocha && npm run lint" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+ssh://git@github.com/indutny/bitcode-builder.git" 22 | }, 23 | "keywords": [ 24 | "bitcode", 25 | "llvm", 26 | "ir", 27 | "cfg" 28 | ], 29 | "author": "Fedor Indutny (http://darksi.de/)", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/indutny/bitcode-builder/issues" 33 | }, 34 | "homepage": "https://github.com/indutny/bitcode-builder#readme", 35 | "devDependencies": { 36 | "@types/mocha": "^2.2.48", 37 | "@types/node": "^9.4.7", 38 | "mocha": "^5.0.4", 39 | "ts-node": "^5.0.1", 40 | "tslint": "^5.9.1", 41 | "typescript": "^2.7.2" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/values/instructions/base.ts: -------------------------------------------------------------------------------- 1 | import { Type } from '../../types'; 2 | import { Value } from '../base'; 3 | import { Metadata } from '../constants'; 4 | import * as instructions from './'; 5 | 6 | export abstract class Instruction extends Value { 7 | public readonly metadata: Map = new Map(); 8 | 9 | constructor(ty: Type, public readonly opcode: string, 10 | protected readonly operands: Value[]) { 11 | super(ty); 12 | } 13 | 14 | public isExtractValue(): boolean { 15 | return this instanceof instructions.ExtractValue; 16 | } 17 | 18 | public isInsertValue(): boolean { 19 | return this instanceof instructions.InsertValue; 20 | } 21 | 22 | public isGetElementPtr(): boolean { 23 | return this instanceof instructions.GetElementPtr; 24 | } 25 | 26 | public isUnreachable(): boolean { 27 | return this instanceof instructions.Unreachable; 28 | } 29 | 30 | public *[Symbol.iterator](): Iterator { 31 | yield* this.operands; 32 | } 33 | 34 | public toString(): string { 35 | const operands = this.operands.map((op) => { 36 | if (op instanceof Instruction) { 37 | return `[instr opcode=${op.opcode}]`; 38 | } 39 | return op.toString(); 40 | }); 41 | return `[instr opcode=${this.opcode} operands={${operands.join(', ')}}]`; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/values/global.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import { validateName } from '../utils'; 3 | 4 | import { AttributeList } from '../attribute-list'; 5 | import { Linkage } from '../linkage'; 6 | import { Type } from '../types'; 7 | import { Value } from './base'; 8 | import { Constant } from './constants'; 9 | 10 | export class Global extends Value { 11 | public attrs: AttributeList = new AttributeList(); 12 | public linkage: Linkage = 'external'; 13 | private privIsConstant: boolean = false; 14 | 15 | constructor(ty: Type, public readonly name: string, 16 | public readonly init?: Constant) { 17 | super(ty); 18 | assert(ty.isPointer(), 'Can\'t declare global with non-pointer type'); 19 | assert(validateName(name), `Invalid characters in Global name: "${name}"`); 20 | if (init !== undefined) { 21 | assert(init.ty.isEqual(ty.toPointer().to), 22 | 'Incompatible type of initialization value for global variable'); 23 | } 24 | } 25 | 26 | public hasConstantValue(): boolean { return this.privIsConstant; } 27 | 28 | public markConstant(): void { 29 | assert(this.init !== undefined, 30 | 'Can\'t mark global without init value as constant'); 31 | 32 | this.privIsConstant = true; 33 | } 34 | 35 | public toString(): string { 36 | return `[global ${this.name}]`; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/values/constants/declaration.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | import { AttributeList } from '../../attribute-list'; 4 | import { CallingConv } from '../../calling-conv'; 5 | import { Linkage } from '../../linkage'; 6 | import { Signature } from '../../types'; 7 | import { validateName } from '../../utils'; 8 | import { Constant } from './base'; 9 | 10 | export class Declaration extends Constant { 11 | public readonly returnAttrs: AttributeList = new AttributeList(); 12 | public readonly attrs: AttributeList = new AttributeList(); 13 | public linkage: Linkage = 'external'; 14 | public cconv: CallingConv = 'ccc'; 15 | public readonly paramAttrs: ReadonlyArray; 16 | 17 | constructor(signature: Signature, public readonly name: string) { 18 | super(signature); 19 | 20 | assert(validateName(name), 21 | `Invalid characters in function name: "${name}"`); 22 | this.paramAttrs = signature.params.map(() => new AttributeList()); 23 | } 24 | 25 | public isEqual(to: Constant): boolean { 26 | if (this === to) { 27 | return true; 28 | } 29 | 30 | if (!to.isDeclaration()) { 31 | return false; 32 | } 33 | 34 | // TODO(indutny): verify cconv, linkage, paramAttrs, etc 35 | return to.ty.isEqual(this.ty); 36 | } 37 | 38 | public toString(): string { 39 | return `[declaration name=${this.name}]`; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/types/signature.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | import * as values from '../values'; 4 | import { Type } from './base'; 5 | import { Pointer } from './pointer'; 6 | 7 | export class Signature extends Type { 8 | constructor(public readonly returnType: Type, 9 | public readonly params: ReadonlyArray) { 10 | super(`${returnType.typeString} ` + 11 | `(${params.map((p) => p.typeString).join(', ')})`); 12 | } 13 | 14 | public isEqual(to: Type): boolean { 15 | if (this === to) { 16 | return true; 17 | } 18 | 19 | // Pointer to signature is equal to the signature 20 | if (to.isPointer()) { 21 | const toPtr = to as Pointer; 22 | if (toPtr.to.isSignature()) { 23 | return this.isEqual(toPtr.to as Signature); 24 | } 25 | return false; 26 | } 27 | 28 | if (!to.isSignature()) { 29 | return false; 30 | } 31 | 32 | const toSig = to as Signature; 33 | 34 | return toSig.returnType.isEqual(this.returnType) && 35 | toSig.params.length === this.params.length && 36 | toSig.params.every((param, i) => param.isEqual(this.params[i])); 37 | } 38 | 39 | public declareFunction(name: string): values.constants.Declaration { 40 | return new values.constants.Declaration(this, name); 41 | } 42 | 43 | public defineFunction(name: string, paramNames: ReadonlyArray) 44 | : values.constants.Func { 45 | assert.strictEqual(paramNames.length, this.params.length, 46 | 'Invalid parameter count for `.defineFunction()`'); 47 | return new values.constants.Func(this, name, paramNames); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/values/base.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | import { Type } from '../types'; 4 | import * as values from './'; 5 | 6 | export abstract class Value { 7 | constructor(public readonly ty: Type) { 8 | } 9 | 10 | public abstract toString(): string; 11 | 12 | // Node.js REPL 13 | public inspect(): string { 14 | return this.toString(); 15 | } 16 | 17 | public isArgument(): boolean { 18 | return this instanceof values.Argument; 19 | } 20 | 21 | public isGlobal(): boolean { 22 | return this instanceof values.Global; 23 | } 24 | 25 | public isBasicBlock(): boolean { 26 | return this instanceof values.BasicBlock; 27 | } 28 | 29 | public isConstant(): boolean { 30 | return this instanceof values.constants.Constant; 31 | } 32 | 33 | public isInstruction(): boolean { 34 | return this instanceof values.instructions.Instruction; 35 | } 36 | 37 | public toArgument(): values.Argument { 38 | assert(this.isArgument(), 'Value is not an Argument instance'); 39 | return this as any; 40 | } 41 | 42 | public toGlobal(): values.Global { 43 | assert(this.isGlobal(), 'Value is not an Global instance'); 44 | return this as any; 45 | } 46 | 47 | public toBasicBlock(): values.BasicBlock { 48 | assert(this.isBasicBlock(), 'Value is not an BasicBlock instance'); 49 | return this as any; 50 | } 51 | 52 | public toConstant(): values.constants.Constant { 53 | assert(this.isConstant(), 'Value is not an Constant instance'); 54 | return this as any; 55 | } 56 | 57 | public toInstruction(): values.instructions.Instruction { 58 | assert(this.isInstruction(), 'Value is not an Instruction instance'); 59 | return this as any; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/values/instructions/cast.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | import * as values from '../'; 4 | import { Type } from '../../types'; 5 | import { Instruction } from './base'; 6 | 7 | export type CastType = 'trunc' | 'zext' | 'sext' | 'fptoui' | 'fptosi' | 8 | 'uitofp' | 'sitofp' | 'fptrunc' | 'fpext' | 'ptrtoint' | 'inttoptr' | 9 | 'bitcast' | 'addrspacecast'; 10 | 11 | export class Cast extends Instruction { 12 | constructor(public readonly castType: CastType, 13 | public readonly operand: values.Value, 14 | public readonly targetType: Type) { 15 | super(targetType, 'cast.' + castType, [ operand ]); 16 | 17 | if (castType === 'trunc' || castType === 'zext' || castType === 'sext') { 18 | assert(operand.ty.isInt() && targetType.isInt(), 19 | `Invalid types for \`${castType}\` cast`); 20 | 21 | if (castType === 'trunc') { 22 | assert(operand.ty.toInt().width >= targetType.toInt().width, 23 | '`trunc` should reduce bit width`'); 24 | } else { 25 | assert(operand.ty.toInt().width <= targetType.toInt().width, 26 | '`zext`/`sext` should extend bit width`'); 27 | } 28 | } else if (castType === 'ptrtoint') { 29 | assert(operand.ty.isPointer() && targetType.isInt(), 30 | 'Invalid types for `ptrtoint` cast'); 31 | } else if (castType === 'inttoptr') { 32 | assert(operand.ty.isInt() && targetType.isPointer(), 33 | 'Invalid types for `inttoptr` cast'); 34 | } else if (castType === 'bitcast') { 35 | // TODO(indutny): check what we're doing with signatures here 36 | assert( 37 | (operand.ty.isSignature() || operand.ty.isPointer()) && 38 | (targetType.isSignature() || targetType.isPointer()), 39 | 'Invalid types for `bitcast` cast'); 40 | } else { 41 | throw new Error(`Sorry, but \`${castType}\` cast is not implemented yet`); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/values/instructions/call.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | import * as values from '../'; 4 | import { CallingConv } from '../../calling-conv'; 5 | import { Signature } from '../../types'; 6 | import { Instruction } from './base'; 7 | 8 | export type CallType = 'normal' | 'tail' | 'musttail' | 'notail'; 9 | 10 | function getCalleeType(callee: values.Value): Signature { 11 | const ty = callee.ty; 12 | 13 | // Calling function or declaration 14 | if (ty.isSignature()) { 15 | return ty.toSignature(); 16 | } 17 | 18 | // A pointer to function or declaration (indirect call) 19 | return ty.toPointer().to.toSignature(); 20 | } 21 | 22 | // TODO(indutny): vararg, optimizations, ret attributes 23 | export class Call extends Instruction { 24 | public readonly calleeSignature: Signature; 25 | 26 | constructor(public readonly callee: values.Value, 27 | public readonly args: ReadonlyArray, 28 | public readonly callType: CallType = 'normal', 29 | public readonly cconv: CallingConv = 'ccc') { 30 | super(getCalleeType(callee).returnType, 'call', [ callee ].concat(args)); 31 | 32 | // TODO(indutny): de-duplicate it 33 | this.calleeSignature = getCalleeType(callee); 34 | if (!callee.isConstant()) { 35 | return; 36 | } 37 | 38 | const constCallee = callee as values.constants.Constant; 39 | if (constCallee.isDeclaration()) { 40 | const calleeFn = constCallee as values.constants.Declaration; 41 | assert.strictEqual(this.cconv, calleeFn.cconv, 42 | 'Calling convention mismatch'); 43 | } 44 | 45 | assert.strictEqual(args.length, this.calleeSignature.params.length, 46 | 'Invalid number of arguments for function signature'); 47 | this.calleeSignature.params.forEach((param, index) => { 48 | assert(param.isEqual(args[index]!.ty), 49 | `Invalid type of call parameter #${index}`); 50 | }); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/values/constants/metadata.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | import * as types from '../../types'; 4 | import { Constant } from './base'; 5 | 6 | export type MetadataValue = string | Constant | ReadonlyArray; 7 | 8 | export class Metadata extends Constant { 9 | private privSelfReference: boolean = false; 10 | private privDistinct: boolean = false; 11 | 12 | constructor(public readonly value: MetadataValue) { 13 | super(new types.Metadata()); 14 | } 15 | 16 | public get selfReference(): boolean { return this.privSelfReference; } 17 | public get distinct(): boolean { return this.privDistinct; } 18 | 19 | public addSelfReference(): this { 20 | assert(Array.isArray(this.value), 21 | 'Can\'t add self-reference to non-tuple metadata'); 22 | this.privSelfReference = true; 23 | return this; 24 | } 25 | 26 | public markDistinct(): this { 27 | assert(Array.isArray(this.value), 28 | 'Can\'t have distinct non-tuple metadata'); 29 | this.privDistinct = true; 30 | return this; 31 | } 32 | 33 | public isEqual(to: Constant): boolean { 34 | if (this === to) { 35 | return true; 36 | } 37 | 38 | if (!to.isMetadata()) { 39 | return false; 40 | } 41 | 42 | const toValue = (to as Metadata).value; 43 | if (typeof toValue === 'string') { 44 | return toValue === this.value; 45 | } 46 | 47 | if (toValue instanceof Constant) { 48 | return this.value instanceof Constant && toValue.isEqual(this.value); 49 | } 50 | 51 | // `toValue` is an Array 52 | if (!Array.isArray(this.value)) { 53 | return false; 54 | } 55 | 56 | if (toValue.length !== this.value.length) { 57 | return false; 58 | } 59 | 60 | return toValue.every((subValue, index) => { 61 | return subValue.isEqual((this.value as Metadata[])[index]); 62 | }); 63 | } 64 | 65 | public toString(): string { 66 | // TODO(indutny): print more 67 | return '[metadata]'; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bitcode-builder 2 | [![Build Status](https://secure.travis-ci.org/indutny/bitcode-builder.svg)](http://travis-ci.org/indutny/bitcode-builder) 3 | [![NPM version](https://badge.fury.io/js/bitcode-builder.svg)](https://badge.fury.io/js/bitcode-builder) 4 | 5 | A helper for [`bitcode`][0] module for building the LLVM Intermediate 6 | Representation. 7 | 8 | ## Usage 9 | 10 | ```typescript 11 | import { Builder } from 'bitcode-builder'; 12 | 13 | const b = new Builder(); 14 | 15 | const i32 = b.i(32); 16 | const sig = b.signature(i32, [ i32, i32 ]); 17 | 18 | const fn = sig.defineFunction(sig, [ 'param1', 'param2' ]); 19 | 20 | const sum = fn.body.binop('add', 21 | fn.getArgument('param1'), 22 | fn.getArgument('param2')); 23 | fn.body.ret(sum); 24 | 25 | // Pass `fn` to `bitcode` module 26 | ``` 27 | 28 | See [`bitcode`][0] module for compiling the CFG to binary format. 29 | 30 | #### LICENSE 31 | 32 | This software is licensed under the MIT License. 33 | 34 | Copyright Fedor Indutny, 2018. 35 | 36 | Permission is hereby granted, free of charge, to any person obtaining a 37 | copy of this software and associated documentation files (the 38 | "Software"), to deal in the Software without restriction, including 39 | without limitation the rights to use, copy, modify, merge, publish, 40 | distribute, sublicense, and/or sell copies of the Software, and to permit 41 | persons to whom the Software is furnished to do so, subject to the 42 | following conditions: 43 | 44 | The above copyright notice and this permission notice shall be included 45 | in all copies or substantial portions of the Software. 46 | 47 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 48 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 49 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 50 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 51 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 52 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 53 | USE OR OTHER DEALINGS IN THE SOFTWARE. 54 | 55 | [0]: https://github.com/indutny/bitcode 56 | -------------------------------------------------------------------------------- /src/values/constants/base.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | import { Value } from '../base'; 4 | import * as constants from './'; 5 | 6 | export abstract class Constant extends Value { 7 | public abstract isEqual(to: Constant): boolean; 8 | 9 | public isInt(): boolean { 10 | return this instanceof constants.Int; 11 | } 12 | 13 | public isArray(): boolean { 14 | return this instanceof constants.Array; 15 | } 16 | 17 | public isStruct(): boolean { 18 | return this instanceof constants.Struct; 19 | } 20 | 21 | public isNull(): boolean { 22 | return this instanceof constants.Null; 23 | } 24 | 25 | public isUndef(): boolean { 26 | return this instanceof constants.Undef; 27 | } 28 | 29 | public isMetadata(): boolean { 30 | return this instanceof constants.Metadata; 31 | } 32 | 33 | public isDeclaration(): boolean { 34 | return this instanceof constants.Declaration; 35 | } 36 | 37 | public isFunction(): boolean { 38 | return this instanceof constants.Func; 39 | } 40 | 41 | public toInt(): constants.Int { 42 | assert(this.isInt(), 'Constant is not an Int instance'); 43 | return this as any; 44 | } 45 | 46 | public toArray(): constants.Array { 47 | assert(this.isArray(), 'Constant is not an Array instance'); 48 | return this as any; 49 | } 50 | 51 | public toStruct(): constants.Struct { 52 | assert(this.isStruct(), 'Constant is not a Struct instance'); 53 | return this as any; 54 | } 55 | 56 | public toNull(): constants.Null { 57 | assert(this.isNull(), 'Constant is not a Null instance'); 58 | return this as any; 59 | } 60 | 61 | public toUndef(): constants.Undef { 62 | assert(this.isUndef(), 'Constant is not a Undef instance'); 63 | return this as any; 64 | } 65 | 66 | public toMetadata(): constants.Metadata { 67 | assert(this.isMetadata(), 'Constant is not a Metadata instance'); 68 | return this as any; 69 | } 70 | 71 | public toDeclaration(): constants.Declaration { 72 | assert(this.isDeclaration(), 'Constant is not a Declaration instance'); 73 | return this as any; 74 | } 75 | 76 | public toFunction(): constants.Func { 77 | assert(this.isFunction(), 'Constant is not a Func instance'); 78 | return this as any; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/types/base.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | import * as types from '../types'; 4 | import * as values from '../values'; 5 | 6 | export abstract class Type { 7 | constructor(private privTypeString: string) { 8 | } 9 | 10 | public get typeString(): string { 11 | return this.privTypeString; 12 | } 13 | 14 | public ptr(): types.Pointer { 15 | return new types.Pointer(this); 16 | } 17 | 18 | public val(_: any): values.Value { 19 | throw new Error('Not supported'); 20 | } 21 | 22 | public undef(): values.constants.Undef { 23 | return new values.constants.Undef(this); 24 | } 25 | 26 | public isVoid(): boolean { return this instanceof types.Void; } 27 | public isInt(): boolean { return this instanceof types.Int; } 28 | public isLabel(): boolean { return this instanceof types.Label; } 29 | public isMetadata(): boolean { return this instanceof types.Metadata; } 30 | public isPointer(): boolean { return this instanceof types.Pointer; } 31 | public isSignature(): boolean { return this instanceof types.Signature; } 32 | public isStruct(): boolean { return this instanceof types.Struct; } 33 | public isArray(): boolean { return this instanceof types.Array; } 34 | 35 | public toVoid(): types.Void { 36 | assert(this.isVoid(), 'Type is not a Void instance'); 37 | return this as any; 38 | } 39 | 40 | public toInt(): types.Int { 41 | assert(this.isInt(), 'Type is not an Int instance'); 42 | return this as any; 43 | } 44 | 45 | public toLabel(): types.Label { 46 | assert(this.isLabel(), 'Type is not a Label instance'); 47 | return this as any; 48 | } 49 | 50 | public toMetadata(): types.Metadata { 51 | assert(this.isMetadata(), 'Type is not a Metadata instance'); 52 | return this as any; 53 | } 54 | 55 | public toPointer(): types.Pointer { 56 | assert(this.isPointer(), 'Type is not a Pointer instance'); 57 | return this as any; 58 | } 59 | 60 | public toSignature(): types.Signature { 61 | assert(this.isSignature(), 'Type is not a Signature instance'); 62 | return this as any; 63 | } 64 | 65 | public toStruct(): types.Struct { 66 | assert(this.isStruct(), 'Type is not a Struct instance'); 67 | return this as any; 68 | } 69 | 70 | public toArray(): types.Array { 71 | assert(this.isArray(), 'Type is not an Array instance'); 72 | return this as any; 73 | } 74 | 75 | public abstract isEqual(to: Type): boolean; 76 | } 77 | -------------------------------------------------------------------------------- /src/values/constants/function.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | import { Signature } from '../../types'; 4 | import { validateName } from '../../utils'; 5 | import { Argument, BasicBlock } from '../../values'; 6 | import { Declaration } from './declaration'; 7 | import { Metadata } from './metadata'; 8 | 9 | // TODO(indutny): `.verify()` method 10 | export class Func extends Declaration { 11 | public readonly args: ReadonlyArray; 12 | public readonly body: BasicBlock = this.createBlock(); 13 | public readonly metadata: Map = new Map(); 14 | 15 | private readonly paramMap: Map = new Map(); 16 | private blockList: ReadonlyArray | undefined = undefined; 17 | 18 | constructor(signature: Signature, name: string, 19 | private readonly paramNames: ReadonlyArray) { 20 | super(signature, name); 21 | assert.strictEqual(paramNames.length, signature.params.length, 22 | 'Invalid number of parameter names, doesn\'t match signature'); 23 | 24 | paramNames.forEach((paramName, i) => { 25 | if (this.paramMap.has(paramName)) { 26 | throw new Error(`Duplicate parameter name: "${paramName}"`); 27 | } 28 | 29 | assert(validateName(paramName), 30 | `Invalid characters in parameter name: "${paramName}"`); 31 | this.paramMap.set(paramName, i); 32 | }); 33 | 34 | this.args = signature.params.map((param, i) => { 35 | return new Argument(param, i, this.paramNames[i]); 36 | }); 37 | } 38 | 39 | public toString(): string { 40 | return `[function name=${this.name}]`; 41 | } 42 | 43 | public createBlock(name?: string) { 44 | return new BasicBlock(this, name); 45 | } 46 | 47 | public getArgument(name: string): Argument { 48 | assert(this.paramMap.has(name), `Unknown parameter name: "${name}"`); 49 | 50 | const index = this.paramMap.get(name)!; 51 | return this.args[index]; 52 | } 53 | 54 | public *[Symbol.iterator](): Iterator { 55 | if (this.blockList !== undefined) { 56 | yield* this.blockList; 57 | return; 58 | } 59 | 60 | const visited = new Set(); 61 | const queue = [ this.body ]; 62 | let canCache = true; 63 | 64 | const list = []; 65 | while (queue.length !== 0) { 66 | const bb = queue.pop() as BasicBlock; 67 | if (visited.has(bb)) { 68 | continue; 69 | } 70 | visited.add(bb); 71 | 72 | if (!bb.isTerminated()) { 73 | canCache = false; 74 | } 75 | list.push(bb); 76 | yield bb; 77 | 78 | // Push successors in reverse order, so that we'll pop the first on 79 | // next iteration 80 | for (let i = bb.successors.length - 1; i >= 0; i--) { 81 | queue.push(bb.successors[i]!); 82 | } 83 | } 84 | 85 | if (canCache) { 86 | this.blockList = list; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/bitcode.ts: -------------------------------------------------------------------------------- 1 | import { Buffer } from 'buffer'; 2 | 3 | import { Attribute, AttributeList } from './attribute-list'; 4 | import { CallingConv } from './calling-conv'; 5 | import { Linkage } from './linkage'; 6 | import * as passes from './passes'; 7 | import * as types from './types'; 8 | import * as values from './values'; 9 | 10 | import Type = types.Type; 11 | import constants = values.constants; 12 | 13 | const CHAR_WIDTH = 8; 14 | 15 | export class Builder { 16 | // Types 17 | 18 | public static void(): types.Void { 19 | return new types.Void(); 20 | } 21 | 22 | public static i(width: number): types.Int { 23 | return new types.Int(width); 24 | } 25 | 26 | public static signature(returnType: Type, params: Type[]): types.Signature { 27 | return new types.Signature(returnType, params); 28 | } 29 | 30 | public static array(length: number, elemType: Type): types.Array { 31 | return new types.Array(length, elemType); 32 | } 33 | 34 | public static struct(name?: string): types.Struct { 35 | return new types.Struct(name); 36 | } 37 | 38 | // Values 39 | 40 | public static global(ty: types.Type, name: string, init?: constants.Constant) 41 | : values.Global { 42 | return new values.Global(ty, name, init); 43 | } 44 | 45 | public static cstring(value: string): constants.Array { 46 | const len = Buffer.byteLength(value); 47 | const blob = Buffer.alloc(len + 1); 48 | blob.write(value); 49 | return Builder.blob(Buffer.from(blob)); 50 | } 51 | 52 | public static blob(buffer: Buffer): constants.Array { 53 | const elemTy = Builder.i(CHAR_WIDTH); 54 | const ty = Builder.array(buffer.length, elemTy); 55 | 56 | const elems = Array.from(buffer).map((ch) => elemTy.val(ch)); 57 | return ty.val(elems); 58 | } 59 | 60 | // Metadata 61 | 62 | public static metadata(value: constants.MetadataValue): constants.Metadata { 63 | return new constants.Metadata(value); 64 | } 65 | 66 | // Convenience methods 67 | 68 | public void(): types.Void { return Builder.void(); } 69 | 70 | public i(width: number): types.Int { return Builder.i(width); } 71 | 72 | public signature(returnType: Type, params: Type[]): types.Signature { 73 | return Builder.signature(returnType, params); 74 | } 75 | 76 | public array(length: number, elemType: Type): types.Array { 77 | return Builder.array(length, elemType); 78 | } 79 | 80 | public struct(name?: string): types.Struct { return Builder.struct(name); } 81 | 82 | public global(ty: types.Type, name: string, init?: constants.Constant) 83 | : values.Global { 84 | return Builder.global(ty, name, init); 85 | } 86 | 87 | public cstring(value: string): constants.Array { 88 | return Builder.cstring(value); 89 | } 90 | 91 | public blob(buffer: Buffer): constants.Array { 92 | // TODO(indutny): cache results? 93 | return Builder.blob(buffer); 94 | } 95 | 96 | public metadata(value: constants.MetadataValue): constants.Metadata { 97 | return Builder.metadata(value); 98 | } 99 | } 100 | 101 | export { 102 | passes, types, values, Attribute, AttributeList, CallingConv, Linkage, 103 | }; 104 | -------------------------------------------------------------------------------- /test/types-test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import { Builder } from '../src/bitcode'; 3 | 4 | describe('bitcode/types', () => { 5 | let b: Builder; 6 | beforeEach(() => { 7 | b = new Builder(); 8 | }); 9 | 10 | it('should create Void', () => { 11 | const t = b.void(); 12 | 13 | assert(t.isVoid()); 14 | assert.strictEqual(t.typeString, 'void'); 15 | assert(t.isEqual(b.void())); 16 | }); 17 | 18 | it('should create Int', () => { 19 | const i32 = b.i(32); 20 | 21 | assert(i32.isInt()); 22 | assert.strictEqual(i32.typeString, 'i32'); 23 | assert(i32.isEqual(b.i(32))); 24 | assert(!i32.isEqual(b.i(16))); 25 | }); 26 | 27 | it('should create Signature', () => { 28 | const sig = b.signature(b.void(), [ b.i(32), b.i(8) ]); 29 | 30 | assert(sig.isSignature()); 31 | assert.strictEqual(sig.typeString, 'void (i32, i8)'); 32 | assert(sig.isEqual(sig)); 33 | assert(!sig.isEqual(b.signature(b.void(), [ b.i(32) ]))); 34 | assert(!sig.isEqual(b.signature(b.i(32), [ b.i(32), b.i(8) ]))); 35 | assert(!sig.isEqual(b.signature(b.void(), [ b.i(32), b.i(16) ]))); 36 | 37 | assert(sig.isEqual(sig.ptr())); 38 | }); 39 | 40 | describe('Array', () => { 41 | it('should create Array', () => { 42 | const arr = b.array(4, b.i(32)); 43 | 44 | assert(arr.isArray()); 45 | assert.strictEqual(arr.typeString, '[4 x i32]'); 46 | assert(arr.isEqual(arr)); 47 | assert(!arr.isEqual(b.array(5, b.i(32)))); 48 | assert(!arr.isEqual(b.array(4, b.i(64)))); 49 | }); 50 | 51 | it('should not create Array of Void', () => { 52 | assert.throws(() => b.array(4, b.void()), /Can't create Array of Void/); 53 | }); 54 | }); 55 | 56 | describe('Struct', () => { 57 | it('should create Struct', () => { 58 | const s = b.struct(); 59 | 60 | assert(s.isStruct()); 61 | s.addField(b.i(32), 'f1'); 62 | s.addField(b.i(8), 'f2'); 63 | s.finalize(); 64 | assert.strictEqual(s.typeString, '{ i32, i8 }'); 65 | 66 | assert(s.isEqual(s)); 67 | 68 | const diff1 = b.struct(); 69 | diff1.addField(b.i(32), 'a1'); 70 | diff1.finalize(); 71 | assert(!s.isEqual(diff1)); 72 | 73 | const diff2 = b.struct(); 74 | diff2.addField(b.i(32), 'a1'); 75 | diff2.addField(b.i(16), 'b2'); 76 | diff2.finalize(); 77 | assert(!s.isEqual(diff1)); 78 | }); 79 | 80 | }); 81 | 82 | describe('Pointer', () => { 83 | it('should not point to Void', () => { 84 | assert.throws(() => b.void().ptr(), /Can't create pointer to void/); 85 | }); 86 | 87 | it('should point to Int', () => { 88 | const p = b.i(32).ptr(); 89 | 90 | assert(p.isPointer()); 91 | assert.strictEqual(p.typeString, 'i32*'); 92 | assert(p.to.isInt()); 93 | }); 94 | 95 | it('should point to Signature', () => { 96 | const p = b.signature(b.void(), [ b.i(32), b.i(8) ]).ptr(); 97 | 98 | assert(p.isPointer()); 99 | assert.strictEqual(p.typeString, 'void (i32, i8)*'); 100 | assert(p.to.isSignature()); 101 | }); 102 | 103 | it('should point to Pointer', () => { 104 | const p = b.i(32).ptr().ptr(); 105 | 106 | assert(p.isPointer()); 107 | assert.strictEqual(p.typeString, 'i32**'); 108 | assert(p.to.isPointer()); 109 | }); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /src/attribute-list.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | // TODO(indutny): allocsize 4 | export type Attribute = 5 | { 6 | key: 'align' | 'alignstack' | 'dereferenceable' | 'dereferenceable_or_null', 7 | value: number, 8 | } | 9 | { 10 | key: string, 11 | value: string, 12 | } | 13 | 'alwaysinline' | 'byval' | 'inlinehint' | 'inreg' | 'minsize' | 'naked' | 14 | 'nest' | 'noalias' | 'nobuiltin' | 'nocapture' | 'noduplicates' | 15 | 'noimplictfloat' | 'noinline' | 'nonlazybind' | 'noredzone' | 'noreturn' | 16 | 'nounwind' | 'optsize' | 'readnone' | 'readonly' | 'returned' | 17 | 'returns_twice' | 'signext' | 'ssp' | 'sspreq' | 'sspstrong' | 'sret' | 18 | 'sanitize_address' | 'sanitize_thread' | 'sanitize_memory' | 'uwtable' | 19 | 'zeroext' | 'builtin' | 'cold' | 'optnone' | 'inalloca' | 'nonnull' | 20 | 'jumptable' | 'convergent' | 'safestack' | 'argmemonly' | 'swiftself' | 21 | 'swifterror' | 'norecurse' | 'inaccessiblememonly' | 22 | 'inaccessiblememonly_or_argmemonly' | 'writeonly' | 'speculatable' | 23 | 'strictfp' | 'sanitize_hwaddress' | 24 | string; 25 | 26 | export class AttributeList { 27 | private list: Attribute[] = []; 28 | 29 | public add(attr: Attribute | ReadonlyArray): boolean { 30 | if (Array.isArray(attr)) { 31 | let changed = false; 32 | attr.forEach((sub) => { 33 | if (this.add(sub)) { 34 | changed = true; 35 | } 36 | }); 37 | return changed; 38 | } 39 | 40 | // XXX(indutny): `Array.isArray` above doesn't work as a type guard 41 | const single: Attribute = attr as Attribute; 42 | 43 | if (typeof single === 'string') { 44 | if (this.list.includes(single)) { 45 | return false; 46 | } 47 | 48 | } else { 49 | const found = this.list.some((entry) => { 50 | if (typeof entry === 'string') { 51 | return false; 52 | } 53 | if (entry.key !== single.key) { 54 | return false; 55 | } 56 | 57 | assert.strictEqual(entry.value, single.value, 58 | `Conflicting attribute value for "${entry.key}"`); 59 | return true; 60 | }); 61 | 62 | if (found) { 63 | return false; 64 | } 65 | } 66 | 67 | this.list.push(single); 68 | return true; 69 | } 70 | 71 | public delete(attr: Attribute | ReadonlyArray): boolean { 72 | if (Array.isArray(attr)) { 73 | let changed = false; 74 | attr.forEach((sub) => { 75 | if (this.delete(sub)) { 76 | changed = true; 77 | } 78 | }); 79 | return changed; 80 | } 81 | 82 | const single = attr as Attribute; 83 | if (typeof single === 'string') { 84 | const index = this.list.indexOf(single); 85 | if (index === -1) { 86 | return false; 87 | } 88 | this.list.splice(index, 1); 89 | return true; 90 | } 91 | 92 | let foundAt: number | boolean = false; 93 | this.list.some((entry, i) => { 94 | if (typeof entry === 'string') { 95 | return false; 96 | } 97 | if (entry.key !== single.key) { 98 | return false; 99 | } 100 | if (entry.value !== single.value) { 101 | return false; 102 | } 103 | 104 | foundAt = i; 105 | return true; 106 | }); 107 | 108 | if (foundAt === false) { 109 | return false; 110 | } 111 | 112 | this.list.splice(foundAt, 1); 113 | return true; 114 | } 115 | 116 | public *[Symbol.iterator](): Iterator { 117 | yield* this.list; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/types/struct/index.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import { validateName } from '../../utils'; 3 | import * as values from '../../values'; 4 | 5 | import { Type } from '../base'; 6 | import { Field } from './field'; 7 | 8 | export class Struct extends Type { 9 | private readonly privFields: Field[] = []; 10 | private fieldMap: Map = new Map(); 11 | private finalized: boolean = false; 12 | 13 | constructor(public readonly name?: string) { 14 | super('struct'); 15 | if (name !== undefined) { 16 | assert(validateName(name), 17 | `Invalid characters in Struct name: "${name}"`); 18 | } 19 | } 20 | 21 | public get typeString(): string { 22 | // Named struct 23 | if (this.name !== undefined) { 24 | return `%${this.name}`; 25 | } 26 | 27 | return this.anonymousTypeString; 28 | } 29 | 30 | public get anonymousTypeString(): string { 31 | this.checkFinalized(); 32 | 33 | // Anonymous struct 34 | return '{ ' + this.fields.map((f) => f.ty.typeString).join(', ') + ' }'; 35 | } 36 | 37 | public get fields(): ReadonlyArray { return this.privFields; } 38 | 39 | public isEqual(to: Type): boolean { 40 | this.checkFinalized(); 41 | if (this === to) { 42 | return true; 43 | } 44 | 45 | if (!to.isStruct()) { 46 | return false; 47 | } 48 | 49 | const toStruct = to as Struct; 50 | toStruct.checkFinalized(); 51 | return toStruct.fields.length === this.fields.length && 52 | toStruct.fields.every((field, i) => field.ty.isEqual(this.fields[i].ty)); 53 | } 54 | 55 | public val(map: { [key: string]: values.constants.Constant }) 56 | : values.constants.Struct { 57 | this.checkFinalized(); 58 | 59 | const keys = Object.keys(map); 60 | assert.strictEqual(keys.length, this.fields.length, 61 | 'Invalid key count in `map` argument of `.val()`'); 62 | 63 | const fields = new Array(this.fields.length); 64 | keys.forEach((key) => { 65 | const field = this.lookupField(key); 66 | assert(field.ty.isEqual(map[key].ty), 67 | `Type mismatch for "${key}" field value`); 68 | 69 | fields[field.index] = map[key]; 70 | }); 71 | 72 | return new values.constants.Struct(this, fields); 73 | } 74 | 75 | public finalize(): void { 76 | assert(!this.finalized, 'Double finalization'); 77 | this.finalized = true; 78 | } 79 | 80 | public addField(ty: Type, name: string): Field { 81 | assert(validateName(name), 82 | `Invalid characters in struct field name: "${name}"`); 83 | 84 | assert(!this.finalized, 'Can\'t add fields after `.finalize()` call'); 85 | if (this.fieldMap.has(name)) { 86 | const existing = this.fieldMap.get(name)!; 87 | assert(existing.ty.isEqual(ty), 'Conflicting field types for: ' + name); 88 | return existing; 89 | } 90 | 91 | assert(!ty.isVoid(), 'Fields can\'t have void type'); 92 | assert(!ty.isSignature(), 93 | 'Fields can\'t have signature types, please use `sig.ptr()`'); 94 | 95 | const add = new Field(ty, name, this.privFields.length); 96 | this.privFields.push(add); 97 | this.fieldMap.set(name, add); 98 | return add; 99 | } 100 | 101 | public hasField(name: string): boolean { 102 | return this.fieldMap.has(name); 103 | } 104 | 105 | public lookupField(name: string): Field { 106 | assert(this.hasField(name), `Field "${name}" is unknown`); 107 | return this.fieldMap.get(name)!; 108 | } 109 | 110 | public getField(index: number): Field { 111 | assert(0 <= index && index < this.fields.length, 'Invalid field index'); 112 | return this.fields[index]; 113 | } 114 | 115 | protected checkFinalized(): void { 116 | assert(this.finalized, 117 | 'Please call `.finalize()` on the Struct instance first'); 118 | } 119 | } 120 | 121 | export { Field }; 122 | -------------------------------------------------------------------------------- /test/function-test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import { Builder } from '../src/bitcode'; 3 | 4 | describe('bitcode/function', () => { 5 | let b: Builder; 6 | beforeEach(() => { 7 | b = new Builder(); 8 | }); 9 | 10 | describe('Declaration', () => { 11 | it('should create a declaration', () => { 12 | const sig = b.signature(b.void(), [ b.i(32) ]); 13 | 14 | const decl = sig.declareFunction('some_func'); 15 | assert(decl.isDeclaration()); 16 | assert(decl.ty.isSignature()); 17 | assert(decl.ty.isEqual(sig)); 18 | assert.strictEqual(decl.name, 'some_func'); 19 | }); 20 | }); 21 | 22 | describe('Func', () => { 23 | it('should create a function', () => { 24 | const sig = b.signature(b.void(), [ b.i(32) ]); 25 | 26 | const fn = sig.defineFunction('some_func', [ 'p' ]); 27 | assert(fn.isDeclaration()); 28 | assert(fn.isFunction()); 29 | assert(fn.ty.isSignature()); 30 | assert(fn.ty.isEqual(sig)); 31 | assert.strictEqual(fn.name, 'some_func'); 32 | }); 33 | 34 | it('should allow populating body', () => { 35 | const sig = b.signature(b.void(), [ b.i(32) ]); 36 | 37 | const fn = sig.defineFunction('some_func', [ 'p' ]); 38 | 39 | const bb = fn.createBlock('bb'); 40 | 41 | fn.body.jmp(bb); 42 | bb.ret(); 43 | }); 44 | 45 | it('should add attributes', () => { 46 | const sig = b.signature(b.void(), [ b.i(32) ]); 47 | 48 | const fn = sig.defineFunction('some_func', [ 'p' ]); 49 | assert(fn.attrs.add('noreturn')); 50 | assert(!fn.attrs.add('noreturn')); 51 | assert(fn.attrs.delete('noreturn')); 52 | assert(!fn.attrs.delete('noreturn')); 53 | 54 | assert(fn.attrs.add({ key: 'align', value: 4 })); 55 | assert(!fn.attrs.add({ key: 'align', value: 4 })); 56 | assert.throws(() => { 57 | fn.attrs.add({ key: 'align', value: 2 }); 58 | }, /Conflicting attribute value for "align"/); 59 | assert(fn.attrs.delete({ key: 'align', value: 4 })); 60 | assert(!fn.attrs.delete({ key: 'align', value: 4 })); 61 | assert(!fn.attrs.delete({ key: 'align', value: 2 })); 62 | }); 63 | 64 | it('should set metadata', () => { 65 | const sig = b.signature(b.void(), [ b.i(32) ]); 66 | 67 | const fn = sig.defineFunction('some_func', [ 'p' ]); 68 | 69 | const m = b.metadata([ b.metadata('ohai') ]); 70 | fn.metadata.set('prof', b.metadata([ 71 | b.metadata('hello'), 72 | m, 73 | b.metadata(fn), 74 | ])); 75 | }); 76 | 77 | it('should iterate through the blocks/instructions', () => { 78 | const sig = b.signature(b.i(32), [ b.i(32) ]); 79 | 80 | const fn = sig.defineFunction('some_func', [ 'p' ]); 81 | fn.body.name = 'fn'; 82 | 83 | const loopStart = fn.createBlock('loop_start'); 84 | fn.body.jmp(loopStart); 85 | 86 | const phi = loopStart.phi({ 87 | fromBlock: fn.body, 88 | value: fn.getArgument('p'), 89 | }); 90 | 91 | const loop = fn.createBlock('loop'); 92 | loopStart.jmp(loop); 93 | 94 | const inc = loop.binop('add', phi, b.i(32).val(1)); 95 | phi.addEdge({ fromBlock: loop, value: inc }); 96 | 97 | const loopEnd = fn.createBlock('loop_end'); 98 | loop.branch(b.i(1).val(1), loopStart, loopEnd); 99 | 100 | loopEnd.ret(phi); 101 | 102 | const blocks = []; 103 | const instrs = []; 104 | for (const bb of fn) { 105 | blocks.push(bb.name); 106 | 107 | const subInstrs = []; 108 | for (const i of bb) { 109 | subInstrs.push(i.opcode); 110 | } 111 | instrs.push(subInstrs); 112 | } 113 | 114 | assert.deepStrictEqual(blocks, [ 115 | 'fn', 'loop_start', 'loop', 'loop_end', 116 | ]); 117 | 118 | assert.deepStrictEqual(instrs, [ 119 | [ 'jmp' ], 120 | [ 'phi', 'jmp' ], 121 | [ 'binop.add', 'branch' ], 122 | [ 'ret' ], 123 | ]); 124 | }); 125 | }); 126 | }); 127 | -------------------------------------------------------------------------------- /test/constants-test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import { Buffer} from 'buffer'; 3 | 4 | import { Builder } from '../src/bitcode'; 5 | 6 | describe('bitcode/constants', () => { 7 | let b: Builder; 8 | beforeEach(() => { 9 | b = new Builder(); 10 | }); 11 | 12 | describe('Int', () => { 13 | it('should create Int constant', () => { 14 | const c = b.i(32).val(123); 15 | 16 | assert(c.isInt()); 17 | assert.strictEqual(c.value, 123); 18 | assert.strictEqual(c.ty.typeString, 'i32'); 19 | }); 20 | 21 | it('should support `.isEqual()`', () => { 22 | const c = b.i(32).val(123); 23 | 24 | assert(c.isEqual(c)); 25 | 26 | const c1 = b.i(32).val(456); 27 | assert(!c.isEqual(c1)); 28 | }); 29 | }); 30 | 31 | describe('Array', () => { 32 | it('should create Array constant', () => { 33 | const i32 = b.i(32); 34 | const c = b.array(3, i32).val([ i32.val(1), i32.val(2), i32.val(3) ]); 35 | 36 | assert(c.isArray()); 37 | assert.strictEqual(c.ty.typeString, '[3 x i32]'); 38 | assert.strictEqual(c.elems.length, 3); 39 | 40 | assert.strictEqual(c.elems[0].ty.typeString, 'i32'); 41 | assert.strictEqual(c.elems[0].toInt().value, 1); 42 | 43 | assert.strictEqual(c.elems[1].ty.typeString, 'i32'); 44 | assert.strictEqual(c.elems[1].toInt().value, 2); 45 | 46 | assert.strictEqual(c.elems[2].ty.typeString, 'i32'); 47 | assert.strictEqual(c.elems[2].toInt().value, 3); 48 | }); 49 | 50 | it('should support `.isEqual()`', () => { 51 | const i32 = b.i(32); 52 | const c = b.array(3, i32).val([ i32.val(1), i32.val(2), i32.val(3) ]); 53 | assert(c.isEqual(c)); 54 | 55 | const c1 = b.array(1, i32).val([ i32.val(1) ]); 56 | assert(!c.isEqual(c1)); 57 | 58 | const c2 = b.array(3, i32).val([ i32.val(1), i32.val(2), i32.val(4) ]); 59 | assert(!c.isEqual(c2)); 60 | }); 61 | 62 | it('should create cstring', () => { 63 | const cstr = b.cstring('hello'); 64 | assert.strictEqual(cstr.ty.typeString, '[6 x i8]'); 65 | }); 66 | 67 | it('should create blob', () => { 68 | const cstr = b.blob(Buffer.from('hello')); 69 | assert.strictEqual(cstr.ty.typeString, '[5 x i8]'); 70 | }); 71 | }); 72 | 73 | describe('Struct', () => { 74 | it('should create Struct constant', () => { 75 | const i32 = b.i(32); 76 | const i8 = b.i(8); 77 | const s = b.struct(); 78 | s.addField(i32, 'a'); 79 | s.addField(i8, 'b'); 80 | s.finalize(); 81 | 82 | const sv = s.val({ a: i32.val(1), b: i8.val(2) }); 83 | 84 | assert(sv.isStruct()); 85 | assert.strictEqual(sv.ty.typeString, '{ i32, i8 }'); 86 | assert.strictEqual(sv.fields.length, 2); 87 | 88 | assert.strictEqual(sv.fields[0].ty.typeString, 'i32'); 89 | assert(sv.fields[0].isInt()); 90 | assert.strictEqual(sv.fields[0].toInt().value, 1); 91 | 92 | assert.strictEqual(sv.fields[1].ty.typeString, 'i8'); 93 | assert(sv.fields[1].isInt()); 94 | assert.strictEqual(sv.fields[1].toInt().value, 2); 95 | }); 96 | 97 | it('should support `.isEqual()`', () => { 98 | const i32 = b.i(32); 99 | const s = b.struct(); 100 | s.addField(i32, 'a'); 101 | s.finalize(); 102 | 103 | const sv = s.val({ a: i32.val(1) }); 104 | assert(sv.isEqual(sv)); 105 | 106 | const sv1 = s.val({ a: i32.val(2) }); 107 | assert(!sv.isEqual(sv1)); 108 | }); 109 | }); 110 | 111 | describe('Null', () => { 112 | it('should create Null constant', () => { 113 | const c = b.i(32).ptr().val(null); 114 | 115 | assert(c.isNull()); 116 | }); 117 | 118 | it('should support `.isEqual()`', () => { 119 | const c = b.i(32).ptr().val(null); 120 | 121 | assert(c.isEqual(c)); 122 | 123 | const c1 = b.i(8).ptr().val(null); 124 | assert(!c.isEqual(c1)); 125 | }); 126 | }); 127 | 128 | describe('Undef', () => { 129 | it('should create Undef constant', () => { 130 | const c = b.i(32).ptr().undef(); 131 | 132 | assert(c.isUndef()); 133 | }); 134 | 135 | it('should support `.isEqual()`', () => { 136 | const c = b.i(32).ptr().undef(); 137 | 138 | assert(c.isEqual(c)); 139 | 140 | const c1 = b.i(8).ptr().undef(); 141 | assert(!c.isEqual(c1)); 142 | }); 143 | }); 144 | }); 145 | -------------------------------------------------------------------------------- /test/instructions-test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import { Builder, values } from '../src/bitcode'; 3 | 4 | describe('bitcode/instructions', () => { 5 | const b = new Builder(); 6 | let fn: values.constants.Func; 7 | 8 | beforeEach(() => { 9 | const sig = b.signature(b.void(), 10 | [ b.i(32), b.i(32), b.i(8), b.i(8).ptr() ]); 11 | fn = sig.defineFunction('some_func', [ 'a', 'b', 'c', 'p' ]); 12 | }); 13 | 14 | describe('phi', () => { 15 | it('should be created with type', () => { 16 | const i = fn.body.phi(b.i(8)); 17 | 18 | assert.strictEqual(i.opcode, 'phi'); 19 | assert(i.ty.isEqual(b.i(8))); 20 | }); 21 | 22 | it('should be created with edge', () => { 23 | const bb = fn.createBlock('named'); 24 | 25 | const i = fn.body.phi({ fromBlock: bb, value: b.i(8).val(1) }); 26 | assert.strictEqual(i.opcode, 'phi'); 27 | assert(i.ty.isEqual(b.i(8))); 28 | }); 29 | 30 | it('should check type for new edges', () => { 31 | const bb = fn.createBlock('named'); 32 | 33 | const i = fn.body.phi({ fromBlock: bb, value: b.i(8).val(1) }); 34 | assert.strictEqual(i.opcode, 'phi'); 35 | assert(i.ty.isEqual(b.i(8))); 36 | 37 | const bb1 = fn.createBlock('named1'); 38 | assert.doesNotThrow(() => { 39 | i.addEdge({ fromBlock: bb1, value: b.i(8).val(2) }); 40 | }); 41 | 42 | const bb2 = fn.createBlock('named2'); 43 | assert.throws(() => { 44 | i.addEdge({ fromBlock: bb2, value: b.i(16).val(2) }); 45 | }, /Type mismatch for Phi edge/); 46 | }); 47 | }); 48 | 49 | describe('binop', () => { 50 | it('should be created', () => { 51 | const i = fn.body.binop('add', fn.getArgument('a'), fn.getArgument('b')); 52 | assert.strictEqual(i.opcode, 'binop.add'); 53 | assert.strictEqual(i.binopType, 'add'); 54 | assert(i.ty.isEqual(fn.getArgument('a').ty)); 55 | }); 56 | 57 | it('should check that operands are of Int type', () => { 58 | assert.throws(() => { 59 | fn.body.binop('add', fn.getArgument('a'), b.i(32).ptr().val(null)); 60 | }, /Only Int/); 61 | }); 62 | 63 | it('should check equality of types', () => { 64 | assert.throws(() => { 65 | fn.body.binop('add', fn.getArgument('a'), fn.getArgument('c')); 66 | }, /Left and right.*different types/); 67 | }); 68 | }); 69 | 70 | describe('cast', () => { 71 | it('should be created', () => { 72 | const i = fn.body.cast('zext', fn.getArgument('a'), b.i(64)); 73 | assert.strictEqual(i.opcode, 'cast.zext'); 74 | assert.strictEqual(i.castType, 'zext'); 75 | assert(i.ty.isEqual(b.i(64))); 76 | }); 77 | 78 | it('should check width for `trunc`', () => { 79 | assert.doesNotThrow(() => { 80 | fn.body.cast('trunc', fn.getArgument('a'), b.i(8)); 81 | }); 82 | assert.throws(() => { 83 | fn.body.cast('trunc', fn.getArgument('a'), b.i(64)); 84 | }, /should reduce bit width/); 85 | }); 86 | 87 | it('should check width for `zext`', () => { 88 | assert.doesNotThrow(() => { 89 | fn.body.cast('zext', fn.getArgument('a'), b.i(64)); 90 | }); 91 | assert.throws(() => { 92 | fn.body.cast('zext', fn.getArgument('a'), b.i(8)); 93 | }, /should extend bit width/); 94 | }); 95 | 96 | it('should check width for `sext`', () => { 97 | assert.doesNotThrow(() => { 98 | fn.body.cast('sext', fn.getArgument('a'), b.i(64)); 99 | }); 100 | assert.throws(() => { 101 | fn.body.cast('sext', fn.getArgument('a'), b.i(8)); 102 | }, /should extend bit width/); 103 | }); 104 | 105 | it('should check type for `ptrtoint`', () => { 106 | assert.doesNotThrow(() => { 107 | fn.body.cast('ptrtoint', fn.getArgument('p'), b.i(64)); 108 | }); 109 | assert.throws(() => { 110 | fn.body.cast('ptrtoint', fn.getArgument('a'), b.i(8)); 111 | }, /Invalid types/); 112 | assert.throws(() => { 113 | fn.body.cast('ptrtoint', fn.getArgument('p'), b.i(8).ptr()); 114 | }, /Invalid types/); 115 | }); 116 | 117 | it('should check type for `inttoptr`', () => { 118 | assert.doesNotThrow(() => { 119 | fn.body.cast('inttoptr', fn.getArgument('a'), b.i(8).ptr()); 120 | }); 121 | assert.throws(() => { 122 | fn.body.cast('inttoptr', fn.getArgument('a'), b.i(8)); 123 | }, /Invalid types/); 124 | assert.throws(() => { 125 | fn.body.cast('inttoptr', fn.getArgument('p'), b.i(8).ptr()); 126 | }, /Invalid types/); 127 | }); 128 | 129 | it('should check type for `bitcast`', () => { 130 | assert.doesNotThrow(() => { 131 | fn.body.cast('bitcast', fn.getArgument('p'), b.i(16).ptr()); 132 | }); 133 | assert.throws(() => { 134 | fn.body.cast('bitcast', fn.getArgument('a'), b.i(8).ptr()); 135 | }, /Invalid types/); 136 | assert.throws(() => { 137 | fn.body.cast('bitcast', fn.getArgument('p'), b.i(8)); 138 | }, /Invalid types/); 139 | }); 140 | }); 141 | 142 | describe('getelementptr', () => { 143 | it('should be created', () => { 144 | const i = fn.body.getelementptr(fn.getArgument('p'), b.i(32).val(1)); 145 | assert.strictEqual(i.opcode, 'getelementptr'); 146 | assert(i.ty.isEqual(b.i(8).ptr())); 147 | }); 148 | }); 149 | }); 150 | -------------------------------------------------------------------------------- /test/passes-verify-test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import { Builder, passes } from '../src/bitcode'; 3 | 4 | describe('bitcode/passes/verify', () => { 5 | let b: Builder; 6 | beforeEach(() => { 7 | b = new Builder(); 8 | }); 9 | 10 | it('should throw on undeclared functions', () => { 11 | const sig = b.signature(b.void(), []); 12 | 13 | const undeclared = sig.declareFunction('undeclared'); 14 | 15 | const user = sig.defineFunction('user', []); 16 | 17 | user.body.call(undeclared, []); 18 | user.body.ret(); 19 | 20 | const verify = new passes.Verify({ 21 | declarations: [], 22 | functions: [ user ], 23 | globals: [], 24 | }); 25 | 26 | assert.throws(() => { 27 | verify.run(); 28 | }, /Found reference to.*function.*undeclared/); 29 | }); 30 | 31 | it('should throw on undeclared globals', () => { 32 | const sig = b.signature(b.void(), []); 33 | 34 | const undeclared = b.global(b.i(32).ptr(), 'undeclared'); 35 | 36 | const user = sig.defineFunction('user', []); 37 | 38 | user.body.load(undeclared); 39 | user.body.ret(); 40 | 41 | const verify = new passes.Verify({ 42 | declarations: [], 43 | functions: [ user ], 44 | globals: [], 45 | }); 46 | 47 | assert.throws(() => { 48 | verify.run(); 49 | }, /Found reference to.*global.*undeclared/); 50 | }); 51 | 52 | it('should not throw on not conflicting globals', () => { 53 | const sig = b.signature(b.void(), []); 54 | 55 | const one = b.global(b.i(32).ptr(), 'one'); 56 | const sameOne = b.global(b.i(32).ptr(), 'one'); 57 | 58 | const user = sig.defineFunction('user', []); 59 | 60 | user.body.load(sameOne); 61 | user.body.ret(); 62 | 63 | const verify = new passes.Verify({ 64 | declarations: [], 65 | functions: [ user ], 66 | globals: [ one, sameOne ], 67 | }); 68 | 69 | assert.doesNotThrow(() => { 70 | verify.run(); 71 | }); 72 | }); 73 | 74 | it('should throw on operands not dominating uses', () => { 75 | const sig = b.signature(b.i(8), []); 76 | 77 | const fn = sig.defineFunction('fn', []); 78 | 79 | const onTrue = fn.createBlock('true'); 80 | const onFalse = fn.createBlock('false'); 81 | const join = fn.createBlock('join'); 82 | 83 | fn.body.branch(b.i(1).val(1), onTrue, onFalse); 84 | 85 | const sum = onTrue.binop('add', b.i(8).val(2), b.i(8).val(3)); 86 | onTrue.jmp(join); 87 | 88 | onFalse.jmp(join); 89 | 90 | join.ret(sum); 91 | 92 | const verify = new passes.Verify({ 93 | declarations: [], 94 | functions: [ fn ], 95 | globals: [], 96 | }); 97 | 98 | assert.throws(() => { 99 | verify.run(); 100 | }, /binop.add.*doesn't dominate.*ret/); 101 | }); 102 | 103 | it('should not throw on phis', () => { 104 | const sig = b.signature(b.i(8), []); 105 | 106 | const fn = sig.defineFunction('fn', []); 107 | 108 | const onTrue = fn.createBlock('true'); 109 | const onFalse = fn.createBlock('false'); 110 | const join = fn.createBlock('join'); 111 | 112 | fn.body.branch(b.i(1).val(1), onTrue, onFalse); 113 | 114 | const sum1 = onTrue.binop('add', b.i(8).val(2), b.i(8).val(3)); 115 | onTrue.jmp(join); 116 | 117 | const sum2 = onFalse.binop('sub', b.i(8).val(2), b.i(8).val(3)); 118 | onFalse.jmp(join); 119 | 120 | const phi = join.phi({ fromBlock: onTrue, value: sum1 }); 121 | phi.addEdge({ fromBlock: onFalse, value: sum2 }); 122 | join.ret(phi); 123 | 124 | const verify = new passes.Verify({ 125 | declarations: [], 126 | functions: [ fn ], 127 | globals: [], 128 | }); 129 | 130 | assert.doesNotThrow(() => { 131 | verify.run(); 132 | }); 133 | }); 134 | 135 | it('should not throw on phis of arguments', () => { 136 | const sig = b.signature(b.i(8), [ b.i(8) ]); 137 | 138 | const fn = sig.defineFunction('fn', [ 'p' ]); 139 | 140 | const onTrue = fn.createBlock('true'); 141 | const onFalse = fn.createBlock('false'); 142 | const join = fn.createBlock('join'); 143 | 144 | fn.body.branch(b.i(1).val(1), onTrue, onFalse); 145 | 146 | const sum1 = onTrue.binop('add', b.i(8).val(2), b.i(8).val(3)); 147 | onTrue.jmp(join); 148 | 149 | onFalse.jmp(join); 150 | 151 | const phi = join.phi({ fromBlock: onTrue, value: sum1 }); 152 | phi.addEdge({ fromBlock: onFalse, value: fn.getArgument('p') }); 153 | join.ret(phi); 154 | 155 | const verify = new passes.Verify({ 156 | declarations: [], 157 | functions: [ fn ], 158 | globals: [], 159 | }); 160 | 161 | assert.doesNotThrow(() => { 162 | verify.run(); 163 | }); 164 | }); 165 | 166 | it('should support loops', () => { 167 | const sig = b.signature(b.i(8), [ b.i(8) ]); 168 | 169 | const fn = sig.defineFunction('fn', [ 'p' ]); 170 | 171 | const start = fn.createBlock('start'); 172 | const loop = fn.createBlock('loop'); 173 | 174 | const sum = fn.body.binop('add', b.i(8).val(1), b.i(8).val(2)); 175 | 176 | fn.body.jmp(start); 177 | start.jmp(loop); 178 | 179 | loop.binop('sub', sum, b.i(8).val(1)); 180 | loop.jmp(start); 181 | 182 | const verify = new passes.Verify({ 183 | declarations: [], 184 | functions: [ fn ], 185 | globals: [], 186 | }); 187 | 188 | assert.doesNotThrow(() => { 189 | verify.run(); 190 | }); 191 | }); 192 | }); 193 | -------------------------------------------------------------------------------- /src/values/basic-block.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import { validateName } from '../utils'; 3 | 4 | import { CallingConv } from '../calling-conv'; 5 | import { Label, Type } from '../types'; 6 | import { Value } from './base'; 7 | import { Constant, Func } from './constants'; 8 | import { 9 | Binop, BinopType, Branch, Call, CallType, Cast, CastType, ExtractValue, 10 | GetElementPtr, ICmp, ICmpPredicate, InsertValue, Instruction, IPhiEdge, 11 | ISwitchCase, Jump, Load, Phi, Ret, Store, Switch, Unreachable, 12 | } from './instructions'; 13 | 14 | export class BasicBlock extends Value { 15 | protected readonly privPredecessors: BasicBlock[] = []; 16 | protected readonly privSuccessors: BasicBlock[] = []; 17 | private readonly instructions: Instruction[] = []; 18 | private readonly phis: Phi[] = []; 19 | private privTerminator: Instruction | undefined = undefined; 20 | private privName: string | undefined = undefined; 21 | 22 | constructor(public readonly parent: Func, name?: string) { 23 | super(new Label()); 24 | if (name !== undefined) { 25 | this.name = name; 26 | } 27 | } 28 | 29 | public toString(): string { 30 | if (this.name === undefined) { 31 | return '[anonymous block]'; 32 | } else { 33 | return `[block ${this.name}]`; 34 | } 35 | } 36 | 37 | public get name(): string | undefined { 38 | return this.privName; 39 | } 40 | 41 | public set name(value: string | undefined) { 42 | if (value !== undefined) { 43 | assert(validateName(value), 44 | `Invalid characters in Block name: "${value}"`); 45 | } 46 | this.privName = value; 47 | } 48 | 49 | public get terminator(): Instruction | undefined { 50 | return this.privTerminator; 51 | } 52 | 53 | public isTerminated(): boolean { return this.privTerminator !== undefined; } 54 | 55 | public get predecessors(): ReadonlyArray { 56 | return this.privPredecessors; 57 | } 58 | 59 | public get successors(): ReadonlyArray { 60 | return this.privSuccessors; 61 | } 62 | 63 | // Special instructions 64 | 65 | public phi(edgeOrTy: Type | IPhiEdge): Phi { 66 | assert.strictEqual(this.terminator, undefined, 67 | 'Can\'t push into terminated block'); 68 | const phi = new Phi(edgeOrTy); 69 | this.phis.push(phi); 70 | return phi; 71 | } 72 | 73 | // Regular instructions 74 | 75 | public binop(binopType: BinopType, left: Value, right: Value): Binop { 76 | return this.push(new Binop(binopType, left, right)); 77 | } 78 | 79 | public cast(castType: CastType, value: Value, targetType: Type): Cast { 80 | return this.push(new Cast(castType, value, targetType)); 81 | } 82 | 83 | public icmp(predicate: ICmpPredicate, left: Value, right: Value): ICmp { 84 | return this.push(new ICmp(predicate, left, right)); 85 | } 86 | 87 | public load(ptr: Value, alignment?: number, isVolatile: boolean = false) 88 | : Load { 89 | return this.push(new Load(ptr, alignment, isVolatile)); 90 | } 91 | 92 | public store(value: Value, ptr: Value, alignment?: number, 93 | isVolatile: boolean = false) 94 | : Store { 95 | return this.push(new Store(value, ptr, alignment, isVolatile)); 96 | } 97 | 98 | public getelementptr(ptr: Value, ptrIndex: Value, index?: Value, 99 | inbounds: boolean = false): GetElementPtr { 100 | return this.push( 101 | new GetElementPtr(ptr, ptrIndex, index, inbounds)); 102 | } 103 | 104 | public extractvalue(aggr: Value, index: number): ExtractValue { 105 | return this.push(new ExtractValue(aggr, index)); 106 | } 107 | 108 | public insertvalue(aggr: Value, elem: Value, index: number): InsertValue { 109 | return this.push(new InsertValue(aggr, elem, index)); 110 | } 111 | 112 | public call(callee: Value, args: ReadonlyArray, 113 | callType: CallType = 'normal', cconv?: CallingConv): Call { 114 | // Pick cconv from declaration 115 | if (!cconv && callee.isConstant()) { 116 | const decl = callee.toConstant().toDeclaration(); 117 | cconv = decl.cconv; 118 | } 119 | return this.push(new Call(callee, args, callType, cconv)); 120 | } 121 | 122 | // Terminators 123 | 124 | public ret(operand?: Value): Ret { 125 | const returnType = this.parent.ty.toSignature().returnType; 126 | if (operand === undefined) { 127 | assert(returnType.isVoid(), 'Void return from non-Void function'); 128 | } else { 129 | assert(returnType.isEqual(operand.ty), 'Return type mismatch'); 130 | } 131 | return this.terminate(new Ret(operand)); 132 | } 133 | 134 | public jmp(target: BasicBlock): Jump { 135 | this.addSuccessor(target); 136 | return this.terminate(new Jump(target)); 137 | } 138 | 139 | public branch(condition: Value, onTrue: BasicBlock, 140 | onFalse: BasicBlock): Branch { 141 | this.addSuccessor(onTrue); 142 | this.addSuccessor(onFalse); 143 | return this.terminate(new Branch(condition, onTrue, onFalse)); 144 | } 145 | 146 | public switch(condition: Value, otherwise: BasicBlock, 147 | cases: ISwitchCase[]): Switch { 148 | this.addSuccessor(otherwise); 149 | cases.forEach((c) => this.addSuccessor(c.block)); 150 | return this.terminate(new Switch(condition, otherwise, cases)); 151 | } 152 | 153 | public unreachable(): Unreachable { 154 | return this.terminate(new Unreachable()); 155 | } 156 | 157 | public *[Symbol.iterator](): Iterator { 158 | yield* this.phis; 159 | yield* this.instructions; 160 | } 161 | 162 | // Helpers 163 | 164 | private push(instr: T): T { 165 | assert.strictEqual(this.terminator, undefined, 166 | 'Can\'t push into terminated block'); 167 | this.instructions.push(instr); 168 | return instr; 169 | } 170 | 171 | private terminate(instr: T): T { 172 | assert.strictEqual(this.terminator, undefined, 'Block already terminated'); 173 | this.instructions.push(instr); 174 | this.privTerminator = instr; 175 | return instr; 176 | } 177 | 178 | private addSuccessor(block: BasicBlock) { 179 | this.privSuccessors.push(block); 180 | block.privPredecessors.push(this); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/passes/verify.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | import * as values from '../values'; 4 | import { Pass } from './base'; 5 | 6 | import constants = values.constants; 7 | import instructions = values.instructions; 8 | import BasicBlock = values.BasicBlock; 9 | import Func = constants.Func; 10 | 11 | type ReachableSet = Set; 12 | type ReachableMap = Map; 13 | 14 | type LiveSet = Set; 15 | type LiveMap = Map; 16 | 17 | // Verify that all values are declared before their use 18 | export class Verify extends Pass { 19 | private readonly globals: Map = new Map(); 20 | 21 | public run(): void { 22 | // Add all possible global references 23 | for (const decl of this.input.declarations) { 24 | if (this.globals.has(decl.name)) { 25 | assert(decl.ty.isEqual(this.globals.get(decl.name)!.ty), 26 | `Conflicting type for declaration with name: "${decl.name}"`); 27 | continue; 28 | } 29 | this.globals.set(decl.name, decl); 30 | } 31 | 32 | for (const global of this.input.globals) { 33 | if (this.globals.has(global.name)) { 34 | assert(global.ty.isEqual(this.globals.get(global.name)!.ty), 35 | `Conflicting type for global with name: "${global.name}"`); 36 | continue; 37 | } 38 | this.globals.set(global.name, global); 39 | } 40 | 41 | for (const fn of this.input.functions) { 42 | if (this.globals.has(fn.name)) { 43 | assert(fn.ty.isEqual(this.globals.get(fn.name)!.ty), 44 | `Conflicting type for function with name: "${fn.name}"`); 45 | continue; 46 | } 47 | this.globals.set(fn.name, fn); 48 | } 49 | 50 | // Calculate liveness map 51 | for (const fn of this.input.functions) { 52 | this.visitFunction(fn); 53 | } 54 | } 55 | 56 | private visitFunction(fn: Func): void { 57 | const reachable = this.computeReachable(fn); 58 | const live = this.computeLiveness(reachable, fn); 59 | 60 | // Now that we know which values are alive in each block 61 | // Walk through instructions and check that every of them 62 | for (const bb of fn) { 63 | this.checkBlock(live, bb); 64 | } 65 | } 66 | 67 | // Private API 68 | 69 | private computeReachable(fn: Func): ReachableMap { 70 | const reachable: ReachableMap = new Map(); 71 | for (const bb of fn) { 72 | reachable.set(bb, new Set([ bb ])); 73 | } 74 | 75 | let changed = true; 76 | while (changed) { 77 | changed = false; 78 | 79 | for (const bb of fn) { 80 | if (this.propagateReachable(reachable, bb)) { 81 | changed = true; 82 | } 83 | } 84 | } 85 | 86 | return reachable; 87 | } 88 | 89 | private propagateReachable(reachable: ReachableMap, bb: BasicBlock): boolean { 90 | let changed = false; 91 | 92 | const reachSet = reachable.get(bb)!; 93 | for (const pred of bb.predecessors) { 94 | for (const predReach of reachable.get(pred)!) { 95 | if (!reachSet.has(predReach)) { 96 | reachSet.add(predReach); 97 | changed = true; 98 | } 99 | } 100 | } 101 | return changed; 102 | } 103 | 104 | private computeLiveness(reachable: ReachableMap, fn: Func): LiveMap { 105 | // Create liveness sets 106 | const live: LiveMap = new Map(); 107 | for (const bb of fn) { 108 | const liveSet: LiveSet = new Set(); 109 | live.set(bb, liveSet); 110 | 111 | for (const i of bb) { 112 | if (!i.ty.isVoid()) { 113 | liveSet.add(i); 114 | } 115 | } 116 | } 117 | 118 | // Propagate liveness through successors until stabilized 119 | let changed = true; 120 | while (changed) { 121 | changed = false; 122 | 123 | for (const bb of fn) { 124 | if (this.propagateBlockLiveness(live, reachable, bb)) { 125 | changed = true; 126 | } 127 | } 128 | } 129 | 130 | return live; 131 | } 132 | 133 | private propagateBlockLiveness(live: LiveMap, reachable: ReachableMap, 134 | bb: BasicBlock): boolean { 135 | let changed = false; 136 | const liveSet = live.get(bb)!; 137 | for (const succ of bb.successors) { 138 | const liveSucc = live.get(succ)!; 139 | 140 | const dominated = succ.predecessors.every((pred) => { 141 | return reachable.get(pred)!.has(bb); 142 | }); 143 | 144 | for (const value of liveSet) { 145 | let missing = false; 146 | 147 | if (!dominated) { 148 | // Propagate the value only if all predecessors has it 149 | // (If it's definition dominates) 150 | for (const pred of succ.predecessors) { 151 | if (pred === bb) { 152 | continue; 153 | } 154 | if (!live.get(pred)!.has(value)) { 155 | missing = true; 156 | } 157 | } 158 | 159 | if (missing) { 160 | continue; 161 | } 162 | } 163 | 164 | if (!liveSucc.has(value)) { 165 | liveSucc.add(value); 166 | changed = true; 167 | } 168 | } 169 | } 170 | return changed; 171 | } 172 | 173 | private checkBlock(liveMap: LiveMap, bb: BasicBlock): void { 174 | assert(bb.isTerminated(), `Unterminated block "${bb}" in "${bb.parent}"`); 175 | const liveSet = liveMap.get(bb)!; 176 | for (const i of bb) { 177 | if (i instanceof instructions.Phi) { 178 | const preds = new Set(bb.predecessors); 179 | 180 | for (const edge of i.edges) { 181 | assert( 182 | preds.delete(edge.fromBlock), 183 | 'Duplicate or unknown `fromBlock`: ' + 184 | `"${edge.fromBlock}" of phi in block "${bb}"`); 185 | 186 | // We're interested only in instructions 187 | if (!(edge.value instanceof instructions.Instruction)) { 188 | continue; 189 | } 190 | 191 | const livePred = liveMap.get(edge.fromBlock)!; 192 | assert( 193 | livePred.has(edge.value), 194 | `Incoming value "${edge.value}" doesn't dominate phi "${i}"`); 195 | } 196 | 197 | assert.strictEqual(preds.size, 0, 198 | `phi "${i}" doesn't cover all predecessors of block "${bb}"`); 199 | continue; 200 | } 201 | 202 | for (const operand of i) { 203 | if (operand instanceof constants.Declaration) { 204 | assert(this.globals.has(operand.name), 205 | `Found reference to undeclared function: "${operand}"`); 206 | assert(this.globals.get(operand.name)!.ty.isEqual(operand.ty), 207 | `Found conflicting reference to function: "${operand}"`); 208 | 209 | } else if (operand instanceof values.Global) { 210 | assert(this.globals.has(operand.name), 211 | `Found reference to undeclared global: "${operand}"`); 212 | assert(this.globals.get(operand.name)!.ty.isEqual(operand.ty), 213 | `Found conflicting reference to global: "${operand}"`); 214 | 215 | } else if (operand instanceof instructions.Instruction) { 216 | assert( 217 | liveSet.has(operand), 218 | `Declaration of "${operand}" ` + 219 | `doesn't dominate it's use at "${i}"`); 220 | } 221 | } 222 | } 223 | } 224 | } 225 | --------------------------------------------------------------------------------