From ba7e44cd28918cddfe708c64045ed10a03d53c7f Mon Sep 17 00:00:00 2001 From: Cai Date: Tue, 9 Apr 2024 21:45:15 +0800 Subject: [PATCH] yay --- .gitignore | 1 + parsers/cparser.pegjs | 55 +- src/ast/types.ts | 193 ++++++- src/builtins.ts | 76 +++ src/constants.ts | 1 + src/index.ts | 7 +- src/interpreter/agenda.ts | 26 + src/interpreter/effectiveTypeTable.ts | 94 ++++ src/interpreter/evaluators.ts | 696 ++++++++++++++++++++++---- src/interpreter/heap.ts | 55 ++ src/interpreter/instructions.ts | 61 ++- src/interpreter/object.ts | 16 +- src/interpreter/runtime.ts | 119 ++++- src/interpreter/stack.ts | 40 +- src/interpreter/stash.ts | 44 +- src/interpreter/symbolTable.ts | 30 +- src/memory/errors.ts | 8 +- src/memory/memory.ts | 149 +++++- src/typing/env.ts | 29 +- src/typing/main.ts | 295 ++++++++++- src/typing/representation.ts | 5 +- src/typing/specifiers.ts | 64 ++- src/typing/types.ts | 48 +- src/typing/utils.ts | 56 ++- src/utils.ts | 4 + 25 files changed, 1894 insertions(+), 278 deletions(-) create mode 100644 src/builtins.ts create mode 100644 src/interpreter/effectiveTypeTable.ts create mode 100644 src/interpreter/heap.ts diff --git a/.gitignore b/.gitignore index c64c997..484a3a9 100644 --- a/.gitignore +++ b/.gitignore @@ -133,3 +133,4 @@ lib/ .tscache src/cparser.js .vscode/settings.json +c-viz.code-workspace diff --git a/parsers/cparser.pegjs b/parsers/cparser.pegjs index 8d5f65f..7f8df61 100644 --- a/parsers/cparser.pegjs +++ b/parsers/cparser.pegjs @@ -384,10 +384,11 @@ EnumerationConstant // (6.4.4.4) character-constant CharacterConstant - = [LuU]? "'" a:CCharSequence "'" + = a:[LuU]? "'" b:CCharSequence "'" { - throwNotImplemented("character constants"); - return a; + if (a) throwNotImplemented("char constant prefix"); + if (b.length > 1) throwNotImplemented("multi char constant") + return b[0]; } // (6.4.4.4) c-char-sequence @@ -659,13 +660,11 @@ UnaryExpression } / SIZEOF a:UnaryExpression { - throwNotImplemented("sizeof expr"); - return makeNode("UnaryExpressionSizeofExpr", a); + return makeNode("UnaryExpressionSizeof", { value: a }); } / SIZEOF LPAR a:TypeName RPAR { - throwNotImplemented("sizeof type"); - return makeNode("UnaryExpressionSizeofType", a); + return makeNode("UnaryExpressionSizeof", { value: a }); } / ALIGNOF LPAR a:TypeName RPAR { @@ -687,9 +686,8 @@ CastExpression = UnaryExpression / LPAR a:TypeName RPAR b:CastExpression { - throwNotImplemented("cast expressions"); return makeNode("CastExpression", { - type: a, + targetType: a, expr: b }); } @@ -811,6 +809,7 @@ Expression // (6.6) constant-expression ConstantExpression = ConditionalExpression + { throwNotImplemented("constant expressions"); } // ========== // A.2.2 Declarations @@ -879,7 +878,6 @@ InitDeclarator // (6.7.1) storage-class-specifier StorageClassSpecifier = TYPEDEF - { throwNotImplemented("typedef"); } / EXTERN { throwNotImplemented("extern storage class"); } / STATIC @@ -961,7 +959,6 @@ SpecifierQualifierList = (TypeQualifier / AlignmentSpecifier)* TypedefName (TypeQualifier / AlignmentSpecifier)* - { throwNotImplemented("typedef"); } / ( TypeSpecifier / TypeQualifier @@ -1043,8 +1040,10 @@ DirectDeclarator b:( LBRC c:IntegerConstant? RBRC { + if (c === null) throwNotImplemented("unknown size array"); return { partType: "array", length: c }; } + / LBRC c:ConstantExpression RBRC / LBRC c:TypeQualifierList? d:AssignmentExpression? RBRC { throwNotImplemented("variable length array"); @@ -1127,10 +1126,10 @@ IdentifierList TypeName = a:SpecifierQualifierList b:AbstractDeclarator? { - return makeNode("TypeName", { + return { specifierQualifierList: a, - abstractDeclarator: b - }); + abstractDeclarator: b || [] + }; } // (6.7.7) abstract-declarator @@ -1178,25 +1177,34 @@ Initializer = a:AssignmentExpression { return a; } / LCUR a:InitializerList COMMA? RCUR - { throwNotImplemented("initializer list"); } + { return a; } // (6.7.9) initializer-list InitializerList - = Designation? Initializer - (COMMA Designation? Initializer)* + = a:Designation? b:Initializer + c:(COMMA d:Designation? e:Initializer { return { designation: d || [], initializer: e }; })* + { + const i = { designation: a || [], initializer: b }; + return makeNode("InitializerList", [i].concat(c || [])); + } // (6.7.9) designation Designation - = DesignatorList EQ + = a:DesignatorList EQ + { return a; } // (6.7.9) designator-list DesignatorList - = Designator+ + = a:Designator+ + { return a; } // (6.7.9) designator Designator - = LBRC ConstantExpression RBRC - / DOT Identifier + = LBRC a:IntegerConstant RBRC + { return { type: "arrayDesignator", idx: a }; } + / LBRC ConstantExpression RBRC + / DOT a:Identifier + { return { type: "structDesignator", identifier: a }; } // (6.7.10) static_assert-declaration StaticAssertDeclaration @@ -1242,7 +1250,10 @@ BlockItem // (6.8.3) expression-statement ExpressionStatement = a:Expression? SEMI - { return a === null ? makeNode("EmptyExpressionStatement") : a; } + { + return makeNode("ExpressionStatement", + a === null ? makeNode("EmptyExpressionStatement") : a); + } // (6.8.4) selection-statement SelectionStatement diff --git a/src/ast/types.ts b/src/ast/types.ts index 9fbef24..ef423d0 100644 --- a/src/ast/types.ts +++ b/src/ast/types.ts @@ -1,8 +1,10 @@ +import { has, isObject } from "lodash"; import { FunctionType, ObjectTypeInfo, ScalarType, TypeInfo, + Void, } from "../typing/types"; export interface PositionInfo { @@ -27,11 +29,15 @@ export type ASTNode = | JumpStatementReturn | EmptyExpressionStatement | CommaOperator + | CastExpressionNode + | ExpressionStatement + | InitializerList | AssignmentExpressionNode | ConditionalExpressionNode | BinaryExpressionNode | UnaryExpressionIncr | UnaryExpressionDecr + | UnaryExpressionSizeof | UnaryExpressionNode | PostfixExpressionNode | ArraySubscriptingOp @@ -51,16 +57,21 @@ export type TypedASTNode = | TypedFunctionDefinition | TypedDeclaration | TypedInitDeclarator + | TypedefDeclaration | TypedCompoundStatement | TypedJumpStatementReturn | EmptyExpressionStatement | TypedCommaOperator + | TypedCastExpressionNode + | TypedExpressionStatement + | TypedInitializerList | TypedAssignmentExpressionNode | TypedConditionalExpressionNode | TypedBinaryExpressionNode | TypedUnaryExpressionIncr | TypedUnaryExpressionDecr | TypedUnaryExpressionNode + | TypedUnaryExpressionSizeof | TypedPostfixExpressionNode | TypedArraySubscriptingOp | TypedFunctionCallOp @@ -87,7 +98,8 @@ export type ExternalDeclaration = FunctionDefinition | Declaration; export type TypedExternalDeclaration = | TypedFunctionDefinition - | TypedDeclaration; + | TypedDeclaration + | TypedefDeclaration; export interface FunctionDefinition extends BaseNode { type: "FunctionDefinition"; @@ -120,7 +132,17 @@ export interface TypedDeclaration extends BaseNode { declaratorList: TypedInitDeclarator[]; } -export type DeclarationSpecifiers = TypeSpecifier[]; +export interface TypedefDeclaration extends BaseNode { + type: "TypedefDeclaration"; + declaratorList: Typedef[]; +} + +export const isTypedefDeclaration = (i: BaseNode): i is TypedefDeclaration => + i.type === "TypedefDeclaration"; + +export type DeclarationSpecifiers = DeclarationSpecifier[]; + +export type DeclarationSpecifier = TypeSpecifier | StorageClassSpecifier; export type TypeSpecifier = | "void" @@ -131,7 +153,19 @@ export type TypeSpecifier = | "signed" | "unsigned" | "_Bool" - | StructSpecifier; + | StructSpecifier + | TypedefName; + +export const isTypeSpecifier = (i: DeclarationSpecifier): i is TypeSpecifier => + !isStorageClassSpecifier(i); + +export type TypedefName = string; + +export type StorageClassSpecifier = "typedef"; + +export const isStorageClassSpecifier = ( + i: DeclarationSpecifier, +): i is StorageClassSpecifier => i === "typedef"; export interface StructSpecifier extends BaseNode { type: "StructSpecifier"; @@ -155,9 +189,77 @@ export interface TypedInitDeclarator extends BaseNode { initializer: TypedInitializer | null; } -export type Initializer = AssignmentExpression; +export interface Typedef extends BaseNode { + type: "Typedef"; + identifier: Identifier; + typeInfo: TypeInfo; +} + +export const isTypedef = (i: BaseNode): i is Typedef => i.type === "Typedef"; + +export type Initializer = AssignmentExpression | InitializerList; + +export type TypedInitializer = TypedAssignmentExpression | TypedInitializerList; + +export const isTypedInitializerList = ( + i: TypedInitializer, +): i is TypedInitializerList => i.type === "InitializerList"; + +export interface InitializerList extends BaseNode { + type: "InitializerList"; + value: DesignationAndInitializer[]; +} + +export const isInitializerList = (i: BaseNode): i is InitializerList => + i.type === "InitializerList"; + +export interface TypedInitializerList extends BaseNode { + type: "InitializerList"; + value: TypedDesignationAndInitializer[]; +} + +export interface DesignationAndInitializer { + designation: Designator[]; + initializer: Initializer; +} + +export interface TypedDesignationAndInitializer { + designation: TypedDesignator[]; + initializer: TypedInitializer; +} + +export type Designator = ArrayDesignator | StructDesignator; + +export type TypedDesignator = TypedArrayDesignator | TypedStructDesignator; + +export interface ArrayDesignator { + type: "arrayDesignator"; + idx: IntegerConstant; +} + +export const isArrayDesignator = (i: Designator): i is ArrayDesignator => + i.type === "arrayDesignator"; + +export interface TypedArrayDesignator { + type: "arrayDesignator"; + idx: TypedIntegerConstant; + typeInfo: ObjectTypeInfo; +} + +export const isTypedArrayDesignator = ( + i: TypedDesignator, +): i is TypedArrayDesignator => i.type === "arrayDesignator"; -export type TypedInitializer = TypedAssignmentExpression; +export interface StructDesignator { + type: "structDesignator"; + identifier: Identifier; +} + +export interface TypedStructDesignator { + type: "structDesignator"; + identifier: Identifier; + typeInfo: ObjectTypeInfo; +} export type Declarator = DeclaratorPart[]; @@ -238,7 +340,10 @@ export interface TypedCompoundStatement extends BaseNode { export type BlockItem = Statement | Declaration; -export type TypedBlockItem = TypedStatement | TypedDeclaration; +export type TypedBlockItem = + | TypedStatement + | TypedDeclaration + | TypedefDeclaration; export type Statement = CompoundStatement | ExpressionStatement | JumpStatement; @@ -267,11 +372,15 @@ export interface TypedJumpStatementReturn extends BaseNode { value: TypedExpression | null; } -export type ExpressionStatement = Expression | EmptyExpressionStatement; +export interface ExpressionStatement extends BaseNode { + type: "ExpressionStatement"; + value: Expression | EmptyExpressionStatement; +} -export type TypedExpressionStatement = - | TypedExpression - | EmptyExpressionStatement; +export interface TypedExpressionStatement extends BaseNode { + type: "ExpressionStatement"; + value: TypedExpression | EmptyExpressionStatement; +} export interface EmptyExpressionStatement extends BaseNode { type: "EmptyExpressionStatement"; @@ -416,21 +525,45 @@ export type BinaryOperator = | "/" | "%"; -export type CastExpression = UnaryExpression; +export type CastExpression = UnaryExpression | CastExpressionNode; + +export type TypedCastExpression = + | TypedUnaryExpression + | TypedCastExpressionNode; + +export interface CastExpressionNode extends BaseNode { + type: "CastExpression"; + targetType: TypeName; + expr: CastExpression; +} -export type TypedCastExpression = TypedUnaryExpression; +export const isCastExpressionNode = (i: BaseNode): i is CastExpressionNode => + i.type === "CastExpression"; + +export interface TypedCastExpressionNode extends TypedExpressionBaseNode { + type: "CastExpression"; + targetType: Void | ScalarType; + expr: TypedCastExpression; +} + +export interface TypeName { + specifierQualifierList: TypeSpecifier[]; + abstractDeclarator: AbstractDeclarator; +} export type UnaryExpression = | PostfixExpression | UnaryExpressionIncr | UnaryExpressionDecr - | UnaryExpressionNode; + | UnaryExpressionNode + | UnaryExpressionSizeof; export type TypedUnaryExpression = | TypedPostfixExpression | TypedUnaryExpressionIncr | TypedUnaryExpressionDecr - | TypedUnaryExpressionNode; + | TypedUnaryExpressionNode + | TypedUnaryExpressionSizeof; export interface UnaryExpressionIncr extends BaseNode { type: "UnaryExpressionIncr"; @@ -458,6 +591,23 @@ export interface TypedUnaryExpressionDecr extends TypedExpressionBaseNode { value: TypedUnaryExpression; } +export interface UnaryExpressionSizeof extends BaseNode { + type: "UnaryExpressionSizeof"; + value: UnaryExpression | TypeName; +} + +export const isTypeName = (i: UnaryExpression | TypeName): i is TypeName => + has(i, "specifierQualifierList"); + +export const isUnaryExpressionSizeof = ( + i: BaseNode, +): i is UnaryExpressionSizeof => i.type === "UnaryExpressionSizeof"; + +export interface TypedUnaryExpressionSizeof extends TypedExpressionBaseNode { + type: "UnaryExpressionSizeof"; + value: number; +} + export interface UnaryExpressionNode extends BaseNode { type: "UnaryExpression"; op: UnaryOperator; @@ -559,6 +709,10 @@ export interface TypedPointerMemberOp extends BaseNode { value: Identifier; } +export const isTypedPointerMemberOp = ( + i: TypedPostfixOp, +): i is TypedPointerMemberOp => i.type === "PointerMember"; + export interface StructMemberOp extends BaseNode { type: "StructMember"; value: Identifier; @@ -658,9 +812,9 @@ export interface TypedPrimaryExprParenthesis extends TypedExpressionBaseNode { export type Identifier = string; -export type Constant = IntegerConstant; +export type Constant = IntegerConstant | CharacterConstant; -export type TypedConstant = TypedIntegerConstant; +export type TypedConstant = TypedIntegerConstant | CharacterConstant; export interface IntegerConstant extends BaseNode { type: "IntegerConstant"; @@ -669,6 +823,9 @@ export interface IntegerConstant extends BaseNode { isDecimal: boolean; } +export const isIntegerConstant = (i: Constant): i is IntegerConstant => + isObject(i); + export interface TypedIntegerConstant extends TypedExpressionBaseNode { type: "IntegerConstant"; value: bigint; @@ -677,10 +834,12 @@ export interface TypedIntegerConstant extends TypedExpressionBaseNode { export const isTypedIntegerConstant = ( expr: TypedConstant, -): expr is TypedIntegerConstant => expr.type === "IntegerConstant"; +): expr is TypedIntegerConstant => isObject(expr); export interface IntegerConstantSuffix { unsigned?: true; long?: true; longLong?: true; } + +export type CharacterConstant = string; diff --git a/src/builtins.ts b/src/builtins.ts new file mode 100644 index 0000000..015ee28 --- /dev/null +++ b/src/builtins.ts @@ -0,0 +1,76 @@ +import { Identifier } from "./ast/types"; +import { NO_EFFECTIVE_TYPE } from "./interpreter/effectiveTypeTable"; +import { TemporaryObject } from "./interpreter/object"; +import { Runtime } from "./interpreter/runtime"; +import { StashItem, isTemporaryObject } from "./interpreter/stash"; +import { BIGINT_TO_BYTES, bytesToBigint } from "./typing/representation"; +import { + FunctionType, + Type, + functionType, + int, + isArithmeticType, + isFunction, + isPointer, + isSigned, + pointer, + voidType, +} from "./typing/types"; + +export interface BuiltinFunction { + type: FunctionType; + body: (rt: Runtime, args: StashItem[]) => void; +} + +export const BUILTIN_FUNCTIONS: Record = { + malloc: { + type: functionType(pointer(voidType()), [ + { identifier: "size", type: int() }, + ]), + body: (rt: Runtime, args: StashItem[]) => { + if (args.length !== 1) throw "expected 1 arg for malloc"; + const o = args[0]; + if (!(isTemporaryObject(o) && isArithmeticType(o.typeInfo))) + throw "expected integer arg for malloc"; + const n = Number( + bytesToBigint(o.bytes, isSigned(o.typeInfo), rt.config.endianness), + ); + + const res = n <= 0 ? 0 : rt.heap.allocate(n); + if (res) + for (let i = 0; i < n; i++) { + rt.effectiveTypeTable.add(res + i, NO_EFFECTIVE_TYPE); + } + + const t = new TemporaryObject( + pointer(voidType()), + BIGINT_TO_BYTES[Type.Pointer](BigInt(res), rt.config.endianness), + ); + rt.stash.push(rt, t); + }, + }, + free: { + type: functionType(voidType(), [ + { identifier: "ptr", type: pointer(voidType()) }, + ]), + body: (rt: Runtime, args: StashItem[]) => { + if (args.length !== 1) throw "expected 1 arg for free"; + const o = args[0]; + if ( + !( + isTemporaryObject(o) && + isPointer(o.typeInfo) && + !isFunction(o.typeInfo.referencedType) + ) + ) + throw "expected pointer to void arg for free"; + const n = Number( + bytesToBigint(o.bytes, isSigned(o.typeInfo), rt.config.endianness), + ); + + const blockSize = rt.heap.getBlockSize(n); + rt.heap.free(n); + rt.effectiveTypeTable.remove(n, blockSize); + }, + }, +}; diff --git a/src/constants.ts b/src/constants.ts index d4f5c27..ba88b58 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -50,3 +50,4 @@ export const LONG_ALIGN = 1 << 2; export const ULONG_ALIGN = LONG_ALIGN; export const LLONG_ALIGN = 1 << 3; // windows and linux differs export const ULLONG_ALIGN = LLONG_ALIGN; +export const MAX_ALIGN = LLONG_ALIGN; diff --git a/src/index.ts b/src/index.ts index a9725d2..a1bfa68 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ import * as cparser from "./cparser"; import { TranslationUnit, TypedTranslationUnit } from "./ast/types"; import { typeTranslationUnit } from "./typing/main"; import { Runtime } from "./interpreter/runtime"; -import { DEFAULT_CONFIG } from "./config"; +import { DEFAULT_CONFIG, RuntimeConfig } from "./config"; export default { parseProgram(source: string): TranslationUnit { @@ -20,9 +20,10 @@ export default { throw new Error("" + err); } }, - run(source: string): Runtime { + run(source: string, config: Partial = {}): Runtime { const program = this.parseProgram(source); const typedTranslationUnit = this.typeCheck(program); - return new Runtime(typedTranslationUnit, DEFAULT_CONFIG); + const c: RuntimeConfig = { ...DEFAULT_CONFIG, ...config }; + return new Runtime(typedTranslationUnit, c); }, }; diff --git a/src/interpreter/agenda.ts b/src/interpreter/agenda.ts index ea2a0b2..e85d96e 100644 --- a/src/interpreter/agenda.ts +++ b/src/interpreter/agenda.ts @@ -19,8 +19,11 @@ export function isInstruction(i: AgendaItem): i is Instruction { } export class Agenda extends Stack { + private lvalueFlagStack: Stack; + constructor(program: TypedTranslationUnit) { super(); + this.lvalueFlagStack = new Stack(); const mainFunction: TypedPrimaryExprIdentifier = { type: "PrimaryExprIdentifier", @@ -45,4 +48,27 @@ export class Agenda extends Stack { this.push(mainFunction); this.push(program); } + + override push(x: AgendaItem): void { + this.lvalueFlagStack.push(false); + super.push(x); + } + + public pushAsLvalue(x: AgendaItem): void { + this.lvalueFlagStack.push(true); + super.push(x); + } + + override pop(): AgendaItem { + this.lvalueFlagStack.pop(); + return super.pop(); + } + + public topIsLvalue(): boolean { + return this.lvalueFlagStack.peek(); + } + + public getLvalueFlags(): boolean[] { + return this.lvalueFlagStack.getArr(); + } } diff --git a/src/interpreter/effectiveTypeTable.ts b/src/interpreter/effectiveTypeTable.ts new file mode 100644 index 0000000..8c59a3b --- /dev/null +++ b/src/interpreter/effectiveTypeTable.ts @@ -0,0 +1,94 @@ +import { cloneDeep } from "lodash"; +import { + ObjectTypeInfo, + isArray, + isScalarType, + isStructure, +} from "../typing/types"; +import { decimalAddressToHex } from "../utils"; + +export const NO_EFFECTIVE_TYPE = "NO_EFFECTIVE_TYPE"; + +export type EffectiveTypeTableEntry = ObjectTypeInfo | typeof NO_EFFECTIVE_TYPE; + +export class EffectiveTypeTable { + private readonly table: Record = {}; + + add(address: number, t: EffectiveTypeTableEntry): void { + for (let i = 0; i < (t === NO_EFFECTIVE_TYPE ? 1 : t.size); i++) + this.checkNotInTable(address + i); + + this.table[address] = t; + + if (t === NO_EFFECTIVE_TYPE || isScalarType(t)) { + return; + } + + if (isArray(t)) { + for (let i = 1; i < t.length; i++) + this.add(address + i * t.elementType.size, t.elementType); + return; + } + + if (isStructure(t)) { + t.members + .slice(1) + .forEach((m) => this.add(address + m.relativeAddress, m.type)); + return; + } + + throw new Error("invalid object type"); + } + + get(address: number): EffectiveTypeTableEntry { + this.checkInTable(address); + return this.table[address]; + } + + remove(address: number, size: number | null = null): void { + let s: number; + if (size === null) { + this.checkInTable(address); + const t = this.table[address]; + if (t === NO_EFFECTIVE_TYPE) + throw new Error("invalid call to remove effective types"); + s = t.size; + } else s = size; + for (let i = 0; i < s; i++) { + if (address + i in this.table) delete this.table[address + i]; + } + } + + change(address: number, t: ObjectTypeInfo): void { + for (let i = 0; i < t.size; i++) { + if (this.get(address + i) !== NO_EFFECTIVE_TYPE) + throw new Error( + "can only change effective type of memory with no effective type", + ); + } + this.remove(address, t.size); + this.add(address, t); + } + + getTable() { + return cloneDeep(this.table); + } + + checkInTable(address: number): void { + if (!(address in this.table)) + throw new Error( + "address " + + decimalAddressToHex(address) + + " not in effective type table", + ); + } + + checkNotInTable(address: number): void { + if (address in this.table) + throw new Error( + "address " + + decimalAddressToHex(address) + + " already has effective type", + ); + } +} diff --git a/src/interpreter/evaluators.ts b/src/interpreter/evaluators.ts index 449e292..4db9198 100644 --- a/src/interpreter/evaluators.ts +++ b/src/interpreter/evaluators.ts @@ -1,12 +1,20 @@ import { + ObjectTypeInfo, ScalarType, + TypeInfo, int, isArithmeticType, + isArray, isIntegerType, + isObjectTypeInfo, isPointer, isScalarType, isSigned, + isStructure, + isVoid, + pointer, shortInt, + unsignedInt, } from "./../typing/types"; import { TypedCompoundStatement, @@ -25,41 +33,67 @@ import { TypedInitDeclarator, TypedUnaryExpressionNode, TypedPrimaryExprIdentifier, + TypedCastExpressionNode, + TypedExpressionStatement, + isTypedIntegerConstant, + isTypedInitializerList, + TypedInitializer, + isTypedArrayDesignator, + TypedAssignmentExpressionNode, + TypedStructMemberOp, + TypedPointerMemberOp, + TypedArraySubscriptingOp, + isTypedPointerMemberOp, + isTypedArraySubscriptingOp, + isTypedPostfixExpressionNode, + isTypedUnaryExpressionNode, + TypedUnaryExpressionSizeof, + isEmptyExpressionStatement, } from "../ast/types"; import { ArithmeticConversionInstruction, - AssignInstruction, + ArraySubscriptInstruction, BinaryOpInstruction, BranchInstruction, CallInstruction, + CastInstruction, Instruction, InstructionType, + PushInstruction, UnaryOpInstruction, arithmeticConversionInstruction, + arraySubscriptInstruction, assignInstruction, binaryOpInstruction, branchInstruction, callInstruction, + castInstruction, isMarkInstruction, markInstruction, popInstruction, + pushInstruction, returnInstruction, unaryOpInstruction, } from "./instructions"; import { Type, isFunction } from "../typing/types"; import { Runtime } from "./runtime"; import { FunctionDesignator, RuntimeObject, TemporaryObject } from "./object"; -import { SHRT_SIZE } from "../constants"; import { BIGINT_TO_BYTES, bytesToBigint } from "../typing/representation"; import { isTemporaryObject } from "./stash"; -import { applyUsualArithmeticConversions } from "../typing/conversions"; +import { + applyUsualArithmeticConversions, + applyImplicitConversions as applyImplicitConversionsToExpression, +} from "../typing/conversions"; import { Endianness } from "../config"; import { RuntimeStack } from "./stack"; +import { SHRT_SIZE } from "../constants"; +import { checkSimpleAssignmentConstraint, getMember } from "../typing/utils"; export const ASTNodeEvaluator: { [NodeType in TypedASTNode["type"]]: ( rt: Runtime, i: Extract, + evaluateAsLvalue: boolean, ) => void; } = { TranslationUnit: ( @@ -74,13 +108,16 @@ export const ASTNodeEvaluator: { rt: Runtime, { identifier, typeInfo, body }: TypedFunctionDefinitionAST, ) => { - const address = rt.allocateText(SHRT_SIZE); - const idx = rt.addFunction(body); + const address = rt.allocateText(shortInt()); + const idx = rt.addFunction(identifier, body, typeInfo); + rt.effectiveTypeTable.add(address, shortInt()); rt.memory.setScalar( address, BigInt(idx), - Type.ShortInt, + shortInt(), rt.config.endianness, + true, + true, ); const fd = new FunctionDesignator(typeInfo, address, identifier); rt.textAndData.push(fd); @@ -88,30 +125,31 @@ export const ASTNodeEvaluator: { }, Declaration: (rt: Runtime, { declaratorList }: TypedDeclarationAST) => { if (declaratorList.length == 1) { - return ASTNodeEvaluator["InitDeclarator"](rt, declaratorList[0]); + return ASTNodeEvaluator["InitDeclarator"](rt, declaratorList[0], false); } for (let i = declaratorList.length - 1; i >= 0; i--) { rt.agenda.push(declaratorList[i]); } }, + TypedefDeclaration: () => {}, InitDeclarator: ( rt: Runtime, { identifier, typeInfo, initializer }: TypedInitDeclarator, ) => { - if (rt.symbolTable.inFileScope) { + if (!rt.symbolTable.inFileScope) { throw new Error("declarations in functions not implemented"); } - const address = rt.allocateAndZeroData(typeInfo.size); + const address = rt.allocateAndZeroData(typeInfo); const o = new RuntimeObject(typeInfo, address, identifier, rt.memory); rt.textAndData.push(o); rt.symbolTable.addAddress(identifier, address); + rt.effectiveTypeTable.add(address, typeInfo); - if (initializer) { - rt.agenda.push(popInstruction()); - rt.agenda.push(assignInstruction(identifier, typeInfo)); - rt.agenda.push(initializer); - } + if (initializer) evaluateInitializer(initializer, address, typeInfo, rt); + }, + InitializerList: () => { + throw new Error("cannot evaluate initializer list on its own"); }, CompoundStatement: ( rt: Runtime, @@ -129,21 +167,47 @@ export const ASTNodeEvaluator: { rt.agenda.push(returnInstruction()); if (expr) rt.agenda.push(expr); }, + ExpressionStatement: (rt: Runtime, { value }: TypedExpressionStatement) => { + if (isEmptyExpressionStatement(value)) return; + if (!isVoid(value.typeInfo)) rt.agenda.push(popInstruction()); + rt.agenda.push(value); + }, EmptyExpressionStatement: () => {}, CommaOperator: (rt: Runtime, { value: exprs }: TypedCommaOperator) => { for (let i = exprs.length - 1; i >= 0; i--) { + if (i !== exprs.length - 1 && !isVoid(exprs[i].typeInfo)) + rt.agenda.push(popInstruction()); rt.agenda.push(exprs[i]); } }, - AssignmentExpression: () => { - throw new Error("not implemented"); + CastExpression: ( + rt: Runtime, + { expr, targetType }: TypedCastExpressionNode, + ) => { + rt.agenda.push(castInstruction(targetType)); + rt.agenda.push(expr); + }, + AssignmentExpression: ( + rt: Runtime, + { op, left, right }: TypedAssignmentExpressionNode, + ) => { + if (op !== "=") throw new Error("not implemented"); + if (!isObjectTypeInfo(left.typeInfo)) throw new Error("invalid LHS type"); + rt.agenda.push(assignInstruction()); + rt.agenda.push(right); + rt.agenda.pushAsLvalue(left); }, ConditionalExpression: ( rt: Runtime, { cond, exprIfTrue, exprIfFalse, typeInfo }: TypedConditionalExpressionNode, ) => { - if (isArithmeticType(typeInfo)) - rt.agenda.push(arithmeticConversionInstruction(typeInfo)); + if (isArithmeticType(typeInfo)) { + if ( + !typeInfo.isCompatible(exprIfTrue.typeInfo) || + !typeInfo.isCompatible(exprIfFalse.typeInfo) + ) + rt.agenda.push(arithmeticConversionInstruction(typeInfo)); + } rt.agenda.push(branchInstruction(exprIfTrue, exprIfFalse)); rt.agenda.push(cond); }, @@ -158,13 +222,50 @@ export const ASTNodeEvaluator: { UnaryExpressionDecr: () => { throw new Error("not implemented"); }, - UnaryExpression: (rt: Runtime, { expr, op }: TypedUnaryExpressionNode) => { + UnaryExpressionSizeof: ( + rt: Runtime, + { value }: TypedUnaryExpressionSizeof, + ) => { + rt.stash.pushWithoutConversions( + new TemporaryObject( + unsignedInt(), + BIGINT_TO_BYTES[Type.UnsignedInt](BigInt(value), rt.config.endianness), + ), + ); + }, + UnaryExpression: ( + rt: Runtime, + { expr, op }: TypedUnaryExpressionNode, + evaluateAsLvalue: boolean, + ) => { switch (op) { case "-": { rt.agenda.push(unaryOpInstruction(op)); rt.agenda.push(expr); break; } + case "*": { + if (evaluateAsLvalue) { + rt.agenda.push(applyImplicitConversionsToExpression(expr)); + } else { + rt.agenda.push(unaryOpInstruction(op)); + rt.agenda.push(expr); + } + break; + } + case "&": { + if ( + isTypedPostfixExpressionNode(expr) && + isTypedArraySubscriptingOp(expr.op) + ) { + rt.agenda.pushAsLvalue(expr); + } else if (isTypedUnaryExpressionNode(expr) && expr.op === "*") { + rt.agenda.push(expr.expr); + } else { + rt.agenda.pushAsLvalue(expr); + } + break; + } default: throw new Error("not implemented"); } @@ -172,12 +273,25 @@ export const ASTNodeEvaluator: { PostfixExpression: ( rt: Runtime, { expr, op }: TypedPostfixExpressionNode, + evaluateAsLvalue: boolean, ) => { - rt.agenda.push(op); - rt.agenda.push(expr); + if (evaluateAsLvalue) { + rt.agenda.pushAsLvalue(op); + if (isTypedArraySubscriptingOp(op) || isTypedPointerMemberOp(op)) + rt.agenda.push(expr); + else rt.agenda.pushAsLvalue(expr); + } else { + rt.agenda.push(op); + rt.agenda.push(expr); + } }, - ArraySubscripting: () => { - throw new Error("not implemented"); + ArraySubscripting: ( + rt: Runtime, + { value: expr }: TypedArraySubscriptingOp, + evaluateAsLvalue: boolean, + ) => { + rt.agenda.pushAsLvalue(arraySubscriptInstruction(evaluateAsLvalue)); + rt.agenda.push(expr); }, FunctionCall: (rt: Runtime, { value: args }: TypedFunctionCallOp) => { rt.agenda.push(callInstruction(args.length)); @@ -185,11 +299,97 @@ export const ASTNodeEvaluator: { rt.agenda.push(args[i]); } }, - PointerMember: () => { - throw new Error("not implemented"); + PointerMember: ( + rt: Runtime, + { value: identifier }: TypedPointerMemberOp, + evaluateAsLvalue: boolean, + ) => { + const o = rt.stash.pop(); + if ( + !( + isTemporaryObject(o) && + isPointer(o.typeInfo) && + isStructure(o.typeInfo.referencedType) + ) + ) + throw new Error("expected ptr to struct"); + + const m = getMember(o.typeInfo.referencedType, identifier); + const relAddr = m[1]; + const typeInfo = m[2]; + const addr = Number( + bytesToBigint(o.bytes, isSigned(o.typeInfo), rt.config.endianness), + ); + if (!evaluateAsLvalue) { + const bytes = rt.memory.getObjectBytes(addr + relAddr, typeInfo); + rt.stash.push( + rt, + new TemporaryObject(typeInfo, bytes, addr + relAddr), + addr + relAddr, + ); + } else { + rt.stash.pushWithoutConversions( + new TemporaryObject( + pointer(applyImplicitConversions(typeInfo)), + BIGINT_TO_BYTES[Type.Pointer]( + BigInt(addr + relAddr), + rt.config.endianness, + ), + ), + ); + } }, - StructMember: () => { - throw new Error("not implemented"); + StructMember: ( + rt: Runtime, + { value: identifier }: TypedStructMemberOp, + evaluateAsLvalue: boolean, + ) => { + const o = rt.stash.pop(); + if (!evaluateAsLvalue) { + if (!(isTemporaryObject(o) && isStructure(o.typeInfo))) + throw new Error("expected struct"); + const m = getMember(o.typeInfo, identifier); + const relAddr = m[1]; + const typeInfo = m[2]; + if (isArray(typeInfo) && o.address === null) + throw new Error("cannot take address of temporary object"); + rt.stash.push( + rt, + new TemporaryObject( + typeInfo, + o.bytes.slice(relAddr, relAddr + typeInfo.size), + o.address === null ? null : o.address + relAddr, + ), + o.address === null ? null : o.address + relAddr, + ); + return; + } + + if ( + !( + isTemporaryObject(o) && + isPointer(o.typeInfo) && + isStructure(o.typeInfo.referencedType) + ) + ) + throw new Error("expected ptr to struct"); + const addr = bytesToBigint( + o.bytes, + isSigned(o.typeInfo), + rt.config.endianness, + ); + const m = getMember(o.typeInfo.referencedType, identifier); + const relAddr = m[1]; + const typeInfo = m[2]; + rt.stash.pushWithoutConversions( + new TemporaryObject( + pointer(applyImplicitConversions(typeInfo)), + BIGINT_TO_BYTES[Type.Pointer]( + addr + BigInt(relAddr), + rt.config.endianness, + ), + ), + ); }, PostfixIncrement: () => { throw new Error("not implemented"); @@ -200,29 +400,49 @@ export const ASTNodeEvaluator: { PrimaryExprIdentifier: ( rt: Runtime, { value: identifier, typeInfo }: TypedPrimaryExprIdentifier, + evaluateAsLvalue: boolean, ) => { + const address = rt.symbolTable.getAddress(identifier); + if (evaluateAsLvalue) { + rt.stash.pushWithoutConversions( + new TemporaryObject( + pointer( + isFunction(typeInfo) + ? typeInfo + : applyImplicitConversions(typeInfo), + ), + BIGINT_TO_BYTES[Type.Pointer](BigInt(address), rt.config.endianness), + ), + ); + return; + } + + let t: TemporaryObject | FunctionDesignator; if (isFunction(typeInfo)) { - for (const i of rt.textAndData) { - if (i.identifier === identifier) { - rt.stash.push(i as FunctionDesignator); - return; - } - } - throw "function " + identifier + " not found"; + t = new FunctionDesignator(typeInfo, address, identifier); + } else { + const bytes = rt.memory.getObjectBytes(address, typeInfo); + t = new TemporaryObject(typeInfo, bytes, address); } - const address = rt.symbolTable.getAddress(identifier); - const bytes = rt.memory.getBytes(address, typeInfo.size); - const t = new TemporaryObject(typeInfo, bytes); - rt.stash.push(t); + rt.stash.push(rt, t, address); }, PrimaryExprConstant: ( rt: Runtime, - { value: integerConstant }: TypedPrimaryExprConstant, + { value: v }: TypedPrimaryExprConstant, ) => { - const { typeInfo, value } = integerConstant; + if (!isTypedIntegerConstant(v)) { + const bytes = BIGINT_TO_BYTES[Type.Int]( + BigInt(v.charCodeAt(0)), + rt.config.endianness, + ); + const t = new TemporaryObject(int(), bytes); + rt.stash.pushWithoutConversions(t); + return; + } + const { typeInfo, value } = v; const bytes = BIGINT_TO_BYTES[typeInfo.type](value, rt.config.endianness); const t = new TemporaryObject(typeInfo, bytes); - rt.stash.push(t); + rt.stash.pushWithoutConversions(t); }, PrimaryExprString: () => { throw new Error("not implemented"); @@ -230,8 +450,10 @@ export const ASTNodeEvaluator: { PrimaryExprParenthesis: ( rt: Runtime, { value: expr }: TypedPrimaryExprParenthesis, + evaluateAsLvalue: boolean, ) => { - rt.agenda.push(expr); + if (evaluateAsLvalue) rt.agenda.pushAsLvalue(expr); + else rt.agenda.push(expr); }, }; @@ -253,13 +475,43 @@ export const instructionEvaluator: { rt.config.endianness, ); n = -n; - rt.stash.push( + rt.stash.pushWithoutConversions( new TemporaryObject( v.typeInfo, BIGINT_TO_BYTES[v.typeInfo.type](n, rt.config.endianness), ), ); - break; + return; + } + case "*": { + if (!(isTemporaryObject(v) && isPointer(v.typeInfo))) + throw new Error("operand of * should have pointer type"); + const addr = Number( + bytesToBigint(v.bytes, isSigned(v.typeInfo), rt.config.endianness), + ); + const pt = v.typeInfo.referencedType; + if (isObjectTypeInfo(pt)) { + return rt.stash.push( + rt, + new TemporaryObject(pt, rt.memory.getObjectBytes(addr, pt), addr), + addr, + ); + } + if (isFunction(pt)) { + return rt.stash.push( + rt, + new FunctionDesignator( + pt, + addr, + rt.symbolTable.getIdentifier(addr), + ), + addr, + ); + } + throw new Error("invalid dereference"); + } + case "&": { + throw new Error("not implemented"); } } throw new Error("not implemented"); @@ -287,9 +539,47 @@ export const instructionEvaluator: { BIGINT_TO_BYTES[ct.type](l + r, rt.config.endianness), ); } + if ( + isPointer(t0) && + isObjectTypeInfo(t0.referencedType) && + isIntegerType(t1) + ) { + const iv = Number( + bytesToBigint(ro.bytes, isSigned(t1), rt.config.endianness), + ); + const pv = Number( + bytesToBigint(lo.bytes, isSigned(t0), rt.config.endianness), + ); + res = new TemporaryObject( + t0, + BIGINT_TO_BYTES[t0.type]( + BigInt(pv + iv * t0.referencedType.size), + rt.config.endianness, + ), + ); + } + if ( + isPointer(t1) && + isObjectTypeInfo(t1.referencedType) && + isIntegerType(t0) + ) { + const pv = Number( + bytesToBigint(ro.bytes, isSigned(t1), rt.config.endianness), + ); + const iv = Number( + bytesToBigint(lo.bytes, isSigned(t0), rt.config.endianness), + ); + res = new TemporaryObject( + t1, + BIGINT_TO_BYTES[t1.type]( + BigInt(pv + iv * t1.referencedType.size), + rt.config.endianness, + ), + ); + } if (res === undefined) throw new Error("invalid types for +"); - rt.stash.push(res); + rt.stash.pushWithoutConversions(res); return; } case "-": @@ -322,7 +612,7 @@ export const instructionEvaluator: { } const bytes = BIGINT_TO_BYTES[ct.type](res, rt.config.endianness); const t = new TemporaryObject(ct, bytes); - rt.stash.push(t); + rt.stash.pushWithoutConversions(t); return; } case "==": @@ -369,7 +659,7 @@ export const instructionEvaluator: { rt.config.endianness, ); const t = new TemporaryObject(int(), res); - rt.stash.push(t); + rt.stash.pushWithoutConversions(t); return; } case "^": @@ -384,30 +674,76 @@ export const instructionEvaluator: { [InstructionType.POP]: (rt: Runtime) => { rt.stash.pop(); }, - [InstructionType.ASSIGN]: ( - rt: Runtime, - { identifier, typeInfo }: AssignInstruction, - ) => { - const address = rt.symbolTable.getAddress(identifier); + [InstructionType.PUSH]: (rt: Runtime, { item }: PushInstruction) => { + rt.stash.pushWithoutConversions(item); + }, + [InstructionType.ASSIGN]: (rt: Runtime) => { const o = rt.stash.pop(); if (!isTemporaryObject(o)) throw new Error("expected object for assign"); - if (isArithmeticType(typeInfo) && isArithmeticType(o.typeInfo)) { - const n = bytesToBigint( - o.bytes, - isSigned(o.typeInfo), - rt.config.endianness, - ); - rt.memory.setBytes( - address, - BIGINT_TO_BYTES[typeInfo.type](n, rt.config.endianness), - ); - return; + + const ptr = rt.stash.pop(); + if ( + !( + isTemporaryObject(ptr) && + isPointer(ptr.typeInfo) && + isObjectTypeInfo(ptr.typeInfo.referencedType) + ) + ) + throw new Error("expected ptr to object for assign"); + + const address = Number( + bytesToBigint(ptr.bytes, isSigned(ptr.typeInfo), rt.config.endianness), + ); + const typeInfo = ptr.typeInfo.referencedType; + + if ( + checkSimpleAssignmentConstraint( + typeInfo, + o.typeInfo, + isIntegerType(o.typeInfo) && + bytesToBigint(o.bytes, isSigned(o.typeInfo), rt.config.endianness) === + BigInt(0), + ) + ) { + if (isScalarType(typeInfo) && isScalarType(o.typeInfo)) { + const n = bytesToBigint( + o.bytes, + isSigned(o.typeInfo), + rt.config.endianness, + ); + rt.memory.setScalar(address, n, typeInfo, rt.config.endianness); + rt.stash.pushWithoutConversions( + new TemporaryObject( + typeInfo, + BIGINT_TO_BYTES[typeInfo.type](n, rt.config.endianness), + ), + ); + return; + } + if (isStructure(typeInfo) && isStructure(o.typeInfo)) { + rt.memory.setObjectBytes(address, o.bytes, typeInfo); + rt.stash.pushWithoutConversions(o); + } } - // TODO: implement for other types + throw new Error("unexpected types for assign"); }, - [InstructionType.MARK]: () => { - throw new Error("mark encountered without return"); + [InstructionType.MARK]: (rt: Runtime) => { + const idx = rt.functionCalls.peek(); + if (rt.getFunctions()[idx][0] !== "main") { + throw new Error("mark encountered without return"); + } + // main implicitly returns 0 if no return statement + rt.stash.pushWithoutConversions( + new TemporaryObject( + int(), + BIGINT_TO_BYTES[Type.Int](BigInt(0), rt.config.endianness), + ), + ); + const block = rt.symbolTable.exitBlock(); + Object.values(block).forEach((addr) => rt.effectiveTypeTable.remove(addr)); + rt.stack.pop(); + rt.functionCalls.pop(); }, [InstructionType.EXIT]: (rt: Runtime) => { const o = rt.stash.pop(); @@ -442,23 +778,25 @@ export const instructionEvaluator: { isSigned(o.typeInfo), rt.config.endianness, ); - const fnIdxBytes = rt.memory.getBytes( - Number(fnAddr), - o.typeInfo.size, - true, - ); + const fnIdxBytes = rt.memory.getBytes(Number(fnAddr), SHRT_SIZE, true); const fnIdx = bytesToBigint( fnIdxBytes, isSigned(shortInt()), rt.config.endianness, ); - const fnBody = rt.getFunction(Number(fnIdx)); + if (fnIdx < 0) { + const builtinFn = rt.getBuiltinFunction(-Number(fnIdx) - 1); + builtinFn.body(rt, args); + return; + } + + const [fnBody, fnType] = rt.getFunction(Number(fnIdx)); + rt.functionCalls.push(Number(fnIdx)); rt.agenda.push(markInstruction()); if (fnBody.value.length === 1) rt.agenda.push(fnBody.value[0]); else rt.agenda.push(fnBody); - const fnType = o.typeInfo.referencedType; const frame = RuntimeStack.calculateStackFrame( fnType.parameterTypes, fnBody, @@ -473,32 +811,31 @@ export const instructionEvaluator: { const address = rt.stack.rbp + frame[identifier].address; rt.symbolTable.addAddress(identifier, address); + rt.effectiveTypeTable.add(address, typeInfo); - const o = args[i]; - if ( - !( - isTemporaryObject(o) && - isArithmeticType(o.typeInfo) && - isArithmeticType(typeInfo) - ) - ) - throw new Error("not implemented"); - const n = bytesToBigint( - o.bytes, - isSigned(o.typeInfo), - rt.config.endianness, - ); - rt.memory.setBytes( - address, - BIGINT_TO_BYTES[typeInfo.type](n, rt.config.endianness), + rt.agenda.push(popInstruction()); + rt.agenda.push(assignInstruction()); + rt.agenda.push(pushInstruction(args[i])); + rt.agenda.push( + pushInstruction( + new TemporaryObject( + pointer(typeInfo), + BIGINT_TO_BYTES[Type.Pointer]( + BigInt(address), + rt.config.endianness, + ), + ), + ), ); } }, [InstructionType.RETURN]: (rt: Runtime) => { while (!isMarkInstruction(rt.agenda.peek())) rt.agenda.pop(); rt.agenda.pop(); - rt.symbolTable.exitBlock(); + const block = rt.symbolTable.exitBlock(); + Object.values(block).forEach((addr) => rt.effectiveTypeTable.remove(addr)); rt.stack.pop(); + rt.functionCalls.pop(); }, [InstructionType.BRANCH]: ( rt: Runtime, @@ -532,7 +869,96 @@ export const instructionEvaluator: { ); const res = BIGINT_TO_BYTES[typeInfo.type](n, rt.config.endianness); const t = new TemporaryObject(typeInfo, res); - rt.stash.push(t); + rt.stash.pushWithoutConversions(t); + }, + [InstructionType.CAST]: (rt: Runtime, { targetType }: CastInstruction) => { + const o = rt.stash.pop(); + if (isVoid(targetType)) return; + if (!(isTemporaryObject(o) && isScalarType(o.typeInfo))) + throw new Error("expected scalar type"); + if (o.typeInfo.isCompatible(targetType)) return; + const val = bytesToBigint( + o.bytes, + isSigned(o.typeInfo), + rt.config.endianness, + ); + + let res: bigint | null = null; + if ( + checkSimpleAssignmentConstraint( + targetType, + o.typeInfo, + isIntegerType(o.typeInfo) && val === BigInt(0), + ) + ) + res = val; + if (isIntegerType(o.typeInfo) && isPointer(targetType)) res = val; + if (isPointer(o.typeInfo) && isIntegerType(targetType)) res = val; + if ( + isPointer(o.typeInfo) && + isObjectTypeInfo(o.typeInfo.referencedType) && + isPointer(targetType) && + isObjectTypeInfo(targetType.referencedType) + ) + res = val; + if ( + isPointer(o.typeInfo) && + isFunction(o.typeInfo.referencedType) && + isPointer(targetType) && + isFunction(targetType.referencedType) + ) + res = val; + + if (res === null) throw new Error("invalid cast"); + const bytes = BIGINT_TO_BYTES[targetType.type](res, rt.config.endianness); + const t = new TemporaryObject(targetType, bytes); + rt.stash.pushWithoutConversions(t); + }, + [InstructionType.ARRAY_SUBSCRIPT]: ( + rt: Runtime, + { evaluateAsLvalue }: ArraySubscriptInstruction, + ) => { + let r = rt.stash.pop(); + let l = rt.stash.pop(); + if (!(isTemporaryObject(l) && isTemporaryObject(r))) + throw new Error("expected 2 objects for array subscript"); + + if (isPointer(r.typeInfo)) [l, r] = [r, l]; + if ( + !( + isPointer(l.typeInfo) && + isObjectTypeInfo(l.typeInfo.referencedType) && + isIntegerType(r.typeInfo) + ) + ) + throw new Error( + "expected ptr to object and integer type for array subscript", + ); + const offset = Number( + bytesToBigint(r.bytes, isSigned(r.typeInfo), rt.config.endianness), + ); + const addr = Number( + bytesToBigint(l.bytes, isSigned(l.typeInfo), rt.config.endianness), + ); + const newAddr = addr + offset * l.typeInfo.referencedType.size; + if (!evaluateAsLvalue) { + rt.stash.push( + rt, + new TemporaryObject( + l.typeInfo.referencedType, + rt.memory.getObjectBytes(newAddr, l.typeInfo.referencedType), + newAddr, + ), + newAddr, + ); + } else { + rt.stash.pushWithoutConversions( + new TemporaryObject( + pointer(applyImplicitConversions(l.typeInfo.referencedType)), + BIGINT_TO_BYTES[Type.Pointer](BigInt(newAddr), rt.config.endianness), + ), + ); + } }, }; @@ -544,3 +970,89 @@ const convertValue = ( const bytes = BIGINT_TO_BYTES[t.type](i, e); return bytesToBigint(bytes, isSigned(t), e); }; + +const evaluateInitializer = ( + t: TypedInitializer, + address: number, + tt: ObjectTypeInfo, + rt: Runtime, +): void => { + if (!isTypedInitializerList(t)) { + rt.agenda.push(popInstruction()); + rt.agenda.push(assignInstruction()); + rt.agenda.push(t); + rt.agenda.push( + pushInstruction( + new TemporaryObject( + pointer(tt), + BIGINT_TO_BYTES[Type.Pointer](BigInt(address), rt.config.endianness), + ), + ), + ); + return; + } + if (isScalarType(tt)) { + rt.agenda.push(popInstruction()); + rt.agenda.push(assignInstruction()); + rt.agenda.push(t.value[0].initializer); + rt.agenda.push( + pushInstruction( + new TemporaryObject( + pointer(tt), + BIGINT_TO_BYTES[Type.Pointer](BigInt(address), rt.config.endianness), + ), + ), + ); + return; + } + if (!(isStructure(tt) || isArray(tt))) + throw new Error("invalid initialization"); + + let i = 0; + t.value.forEach(({ designation, initializer }) => { + let currType: ObjectTypeInfo = tt; + let currAddress = isArray(tt) + ? address + i * tt.elementType.size + : address + tt.members[i].relativeAddress; + + if (designation.length) { + let first = true; + for (const d of designation) { + if (isTypedArrayDesignator(d)) { + if (!isArray(currType)) + throw "array designator when current object is not array"; + const idxVal = Number(d.idx.value); + if (first) i = idxVal; + currAddress += i * currType.elementType.size; + currType = currType.elementType; + } else { + if (!isStructure(currType)) + throw "struct designator when current object is not struct"; + const [idx, relativeAddress, typeInfo] = getMember( + currType, + d.identifier, + ); + if (first) i = idx; + currAddress += relativeAddress; + currType = typeInfo; + } + first = false; + } + } else { + currType = isArray(tt) ? tt.elementType : tt.members[i].type; + } + + i++; + evaluateInitializer(initializer, currAddress, currType, rt); + }); +}; + +const applyImplicitConversions = (t: TypeInfo): TypeInfo => { + if (isArray(t)) { + return pointer(t.elementType); + } + if (isFunction(t)) { + return pointer(t); + } + return t; +}; diff --git a/src/interpreter/heap.ts b/src/interpreter/heap.ts new file mode 100644 index 0000000..4425cdf --- /dev/null +++ b/src/interpreter/heap.ts @@ -0,0 +1,55 @@ +import { cloneDeep } from "lodash"; +import { roundUpM } from "../utils"; +import { MAX_ALIGN } from "../constants"; + +export type HeapEntry = { size: number }; + +export class Heap { + private readonly allocations: Record; + + private heapStart: number; + private heapEnd: number; + + constructor(heapStart: number, heapEnd: number) { + this.allocations = {}; + this.heapStart = heapStart; + this.heapEnd = heapEnd; + } + + public allocate(size: number): number { + let curr = this.heapStart; + let ok = false; + Object.keys(this.allocations) + .map(Number) + .sort() + .forEach((i) => { + if (ok || i - roundUpM(curr, MAX_ALIGN) >= size) { + ok = true; + return; + } + curr = i + this.allocations[i].size; + }); + if (!ok && this.heapEnd - roundUpM(curr, MAX_ALIGN) >= size) ok = true; + if (!ok) return 0; + curr = roundUpM(curr, MAX_ALIGN); + this.allocations[curr] = { size }; + return curr; + } + + public free(addr: number): void { + if (!(addr in this.allocations)) + throw new Error( + "undefined behaviour: free on address not returned by malloc", + ); + delete this.allocations[addr]; + } + + public getBlockSize(addr: number): number { + if (!(addr in this.allocations)) return 0; + return this.allocations[addr].size; + } + + public getAllocations(): Record { + return cloneDeep(this.allocations); + } +} diff --git a/src/interpreter/instructions.ts b/src/interpreter/instructions.ts index 9b92762..d3737fe 100644 --- a/src/interpreter/instructions.ts +++ b/src/interpreter/instructions.ts @@ -1,16 +1,13 @@ -import { - BinaryOperator, - TypedExpression, - Identifier, - UnaryOperator, -} from "../ast/types"; -import { ArithmeticType, ObjectTypeInfo } from "../typing/types"; +import { BinaryOperator, TypedExpression, UnaryOperator } from "../ast/types"; +import { ArithmeticType, ScalarType, Void } from "../typing/types"; import { AgendaItem, isInstruction } from "./agenda"; +import { StashItem } from "./stash"; export enum InstructionType { UNARY_OP = "UnaryOp", BINARY_OP = "BinaryOp", POP = "Pop", + PUSH = "Push", ASSIGN = "Assign", MARK = "Mark", EXIT = "Exit", @@ -18,6 +15,8 @@ export enum InstructionType { RETURN = "Return", BRANCH = "Branch", ARITHMETIC_CONVERSION = "ArithmeticConversion", + CAST = "Cast", + ARRAY_SUBSCRIPT = "ArraySubscript", } export interface BaseInstruction { @@ -54,19 +53,22 @@ export const popInstruction = (): PopInstruction => ({ type: InstructionType.POP, }); +export interface PushInstruction extends BaseInstruction { + type: InstructionType.PUSH; + item: StashItem; +} + +export const pushInstruction = (item: StashItem): PushInstruction => ({ + type: InstructionType.PUSH, + item, +}); + export interface AssignInstruction extends BaseInstruction { type: InstructionType.ASSIGN; - identifier: Identifier; - typeInfo: ObjectTypeInfo; } -export const assignInstruction = ( - identifier: Identifier, - typeInfo: ObjectTypeInfo, -): AssignInstruction => ({ +export const assignInstruction = (): AssignInstruction => ({ type: InstructionType.ASSIGN, - identifier, - typeInfo, }); export interface MarkInstruction extends BaseInstruction { @@ -110,6 +112,9 @@ export const returnInstruction = (): ReturnInstruction => ({ type: InstructionType.RETURN, }); +export const isReturnInstruction = (i: AgendaItem): i is ReturnInstruction => + isInstruction(i) && i.type === InstructionType.RETURN; + export interface BranchInstruction extends BaseInstruction { type: InstructionType.BRANCH; exprIfTrue: TypedExpression; @@ -137,14 +142,38 @@ export const arithmeticConversionInstruction = ( typeInfo, }); +export interface CastInstruction extends BaseInstruction { + type: InstructionType.CAST; + targetType: Void | ScalarType; +} + +export const castInstruction = ( + targetType: Void | ScalarType, +): CastInstruction => ({ type: InstructionType.CAST, targetType }); + +export interface ArraySubscriptInstruction extends BaseInstruction { + type: InstructionType.ARRAY_SUBSCRIPT; + evaluateAsLvalue: boolean; +} + +export const arraySubscriptInstruction = ( + evaluateAsLvalue: boolean = false, +): ArraySubscriptInstruction => ({ + type: InstructionType.ARRAY_SUBSCRIPT, + evaluateAsLvalue, +}); + export type Instruction = | UnaryOpInstruction | BinaryOpInstruction | PopInstruction + | PushInstruction | MarkInstruction | ExitInstruction | CallInstruction | ReturnInstruction | BranchInstruction | AssignInstruction - | ArithmeticConversionInstruction; + | ArithmeticConversionInstruction + | CastInstruction + | ArraySubscriptInstruction; diff --git a/src/interpreter/object.ts b/src/interpreter/object.ts index 0324577..2998e2d 100644 --- a/src/interpreter/object.ts +++ b/src/interpreter/object.ts @@ -33,9 +33,11 @@ export class FunctionDesignator implements IdentifiableAndAddressable { // https://en.cppreference.com/w/cpp/language/object abstract class CvizObject { public readonly typeInfo: ObjectTypeInfo; + public readonly size: number; public constructor(typeInfo: ObjectTypeInfo) { this.typeInfo = typeInfo; + this.size = typeInfo.size; } public abstract get bytes(): number[]; @@ -43,8 +45,13 @@ abstract class CvizObject { export class TemporaryObject extends CvizObject { private readonly _bytes: number[]; + public readonly address: number | null; - public constructor(typeInfo: ObjectTypeInfo, bytes: number[]) { + public constructor( + typeInfo: ObjectTypeInfo, + bytes: number[], + address: number | null = null, + ) { super(typeInfo); if (typeInfo.size !== bytes.length) { throw ( @@ -56,6 +63,7 @@ export class TemporaryObject extends CvizObject { } bytes.forEach(checkValidByte); this._bytes = bytes; + this.address = address; } public get bytes() { @@ -92,10 +100,6 @@ export class RuntimeObject } public get bytes() { - const res = []; - for (let i = 0; i < this.typeInfo.size; i++) { - res.push(this.memory.getByte(this.address + i)); - } - return res; + return this.memory.getObjectBytes(this.address, this.typeInfo); } } diff --git a/src/interpreter/runtime.ts b/src/interpreter/runtime.ts index 7504333..f09cbf0 100644 --- a/src/interpreter/runtime.ts +++ b/src/interpreter/runtime.ts @@ -1,4 +1,8 @@ -import { TypedCompoundStatement, TypedTranslationUnit } from "../ast/types"; +import { + Identifier, + TypedCompoundStatement, + TypedTranslationUnit, +} from "../ast/types"; import { Memory } from "../memory/memory"; import { Agenda, AgendaItem, isInstruction } from "./agenda"; import { Stash, StashItem } from "./stash"; @@ -8,33 +12,70 @@ import { FunctionDesignator, RuntimeObject } from "./object"; import { SymbolTable } from "./symbolTable"; import { cloneDeep } from "lodash"; import { RuntimeStack, StackFrame } from "./stack"; +import { Stack, roundUpM } from "../utils"; +import { FunctionType, ObjectTypeInfo, shortInt } from "../typing/types"; +import { BUILTIN_FUNCTIONS, BuiltinFunction } from "../builtins"; +import { Heap, HeapEntry } from "./heap"; +import { + EffectiveTypeTable, + EffectiveTypeTableEntry, +} from "./effectiveTypeTable"; -export type TextandData = (FunctionDesignator | RuntimeObject)[]; +export type TextAndDataEntry = FunctionDesignator | RuntimeObject; + +export type TextAndData = TextAndDataEntry[]; + +export const isFunctionDesignator = ( + i: TextAndDataEntry, +): i is FunctionDesignator => i instanceof FunctionDesignator; export class RuntimeView { agenda: AgendaItem[]; + lvalueFlags: boolean[]; stash: StashItem[]; - textAndData: TextandData; + textAndData: TextAndData; stack: StackFrame[]; + blockScopes: Record[]; + fileScope: Record; + memory: Memory; + functions: [string, TypedCompoundStatement, FunctionType][]; + functionCalls: number[]; + rbpArr: number[]; + heap: Record; + effectiveTypeTable: Record; constructor(rt: Runtime) { this.agenda = rt.agenda.getArr(); + this.lvalueFlags = rt.agenda.getLvalueFlags(); this.stash = rt.stash.getArr(); this.textAndData = cloneDeep(rt.textAndData); this.stack = rt.stack.getArr(); + this.blockScopes = rt.symbolTable.getBlockScopes(); + this.fileScope = rt.symbolTable.getFileScope(); + this.memory = cloneDeep(rt.memory); + this.functions = rt.getFunctions(); + this.functionCalls = rt.functionCalls.getArr(); + this.rbpArr = rt.stack.getRbpArr(); + this.rbpArr.push(rt.stack.rbp); + this.heap = rt.heap.getAllocations(); + this.effectiveTypeTable = rt.effectiveTypeTable.getTable(); } } export class Runtime { public readonly agenda: Agenda; public readonly stash: Stash; - public readonly textAndData: TextandData; + public readonly textAndData: TextAndData; public readonly symbolTable: SymbolTable; public readonly config: RuntimeConfig; + public readonly effectiveTypeTable: EffectiveTypeTable; public readonly memory: Memory; public readonly stack: RuntimeStack; + public readonly functionCalls: Stack; + public readonly heap: Heap; - private functions: TypedCompoundStatement[]; + private functions: [string, TypedCompoundStatement, FunctionType][]; + private builtinFunctions: BuiltinFunction[]; private _exitCode: number | undefined; private _dataPtr: number; private _textPtr: number; @@ -45,24 +86,47 @@ export class Runtime { this.textAndData = []; this.symbolTable = new SymbolTable(); this.config = config; - this.memory = new Memory(config.memory); + this.effectiveTypeTable = new EffectiveTypeTable(); + this.memory = new Memory(config.memory, this.effectiveTypeTable); this.stack = new RuntimeStack(config.memory.stack.baseAddress); + this.functionCalls = new Stack(); + this.heap = new Heap( + config.memory.heap.baseAddress, + config.memory.heap.baseAddress + config.memory.heap.size, + ); this.functions = []; + this.builtinFunctions = []; this._exitCode = undefined; this._dataPtr = config.memory.data.baseAddress; this._textPtr = config.memory.text.baseAddress; + + for (const [identifier, f] of Object.entries(BUILTIN_FUNCTIONS)) { + const address = this.allocateText(shortInt()); + const idx = this.addBuiltinFunction(f); + this.effectiveTypeTable.add(address, shortInt()) + this.memory.setScalar( + address, + BigInt(-idx - 1), + shortInt(), + this.config.endianness, + true, + true, + ); + this.symbolTable.addAddress(identifier, address); + } } public next(): void { if (this.agenda.isEmpty()) throw new RangeError("agenda is empty"); + const isLvalue = this.agenda.topIsLvalue(); const next = this.agenda.pop(); if (isInstruction(next)) { // eslint-disable-next-line @typescript-eslint/no-explicit-any instructionEvaluator[next.type](this, next as any); } else { // eslint-disable-next-line @typescript-eslint/no-explicit-any - ASTNodeEvaluator[next.type](this, next as any); + ASTNodeEvaluator[next.type](this, next as any, isLvalue); } } @@ -74,28 +138,51 @@ export class Runtime { return this._exitCode; } - public addFunction(body: TypedCompoundStatement): number { - return this.functions.push(body) - 1; + public addFunction( + identifier: string, + body: TypedCompoundStatement, + typeInfo: FunctionType, + ): number { + return this.functions.push([identifier, body, typeInfo]) - 1; } - public getFunction(idx: number): TypedCompoundStatement { + public addBuiltinFunction(i: BuiltinFunction): number { + return this.builtinFunctions.push(i) - 1; + } + + public getFunction(idx: number): [TypedCompoundStatement, FunctionType] { try { - return this.functions[idx]; + const i = this.functions[idx]; + return [i[1], i[2]]; } catch (err) { throw "invalid function index " + idx + " provided"; } } - public allocateAndZeroData(size: number): number { - this.memory.setRepeatedByte(this._dataPtr, size, 0); + public getBuiltinFunction(idx: number): BuiltinFunction { + try { + return this.builtinFunctions[idx]; + } catch (err) { + throw "invalid builtin function index " + idx + " provided"; + } + } + + public getFunctions(): [string, TypedCompoundStatement, FunctionType][] { + return cloneDeep(this.functions); + } + + public allocateAndZeroData(t: ObjectTypeInfo): number { + this._dataPtr = roundUpM(this._dataPtr, t.alignment); + this.memory.setRepeatedByte(this._dataPtr, t.size, 0); const address = this._dataPtr; - this._dataPtr += size; + this._dataPtr += t.size; return address; } - public allocateText(size: number): number { + public allocateText(t: ObjectTypeInfo): number { + this._textPtr = roundUpM(this._textPtr, t.alignment); const address = this._textPtr; - this._textPtr += size; + this._textPtr += t.size; return address; } } diff --git a/src/interpreter/stack.ts b/src/interpreter/stack.ts index 12c07a2..0a478e4 100644 --- a/src/interpreter/stack.ts +++ b/src/interpreter/stack.ts @@ -1,6 +1,6 @@ import { Identifier, TypedCompoundStatement } from "../ast/types"; import { ObjectTypeInfo, ParameterTypeAndIdentifier } from "../typing/types"; -import { Stack } from "../utils"; +import { Stack, roundUpM } from "../utils"; export type StackFrame = Record< Identifier, @@ -13,26 +13,35 @@ export type StackFrame = Record< export class RuntimeStack extends Stack { private _rbp: number; private _rsp: number; + private _rbpStack: Stack; + private _rspStack: Stack; constructor(stackBaseAddress: number) { super(); this._rbp = stackBaseAddress; this._rsp = stackBaseAddress; + this._rbpStack = new Stack(); + this._rspStack = new Stack(); + } + + getRbpArr(): number[] { + return this._rbpStack.getArr(); } override push(x: StackFrame): void { + this._rbpStack.push(this._rbp); + this._rspStack.push(this._rsp); super.push(x); - this._rbp = this._rsp + 1; - this._rsp += RuntimeStack.getStackFrameSize(x); + + const [size, alignment] = RuntimeStack.getStackFrameSizeAndAlignment(x); + this._rbp = roundUpM(this._rsp + 1, alignment); + this._rsp = this._rbp + size - 1; } override pop(): StackFrame { - const res = super.pop(); - this._rsp = this._rbp - 1; - if (super.isEmpty()) this._rbp = this._rsp; - else - this._rbp = this._rsp - RuntimeStack.getStackFrameSize(super.peek()) + 1; - return res; + this._rsp = this._rspStack.pop(); + this._rbp = this._rbpStack.pop(); + return super.pop(); } get rbp(): number { @@ -52,6 +61,7 @@ export class RuntimeStack extends Stack { let ptr = 0; for (const p of params) { if (!p.identifier) throw new Error("param missing identifier"); + ptr = roundUpM(ptr, p.type.alignment); res[p.identifier] = { address: ptr, typeInfo: p.type, @@ -61,7 +71,15 @@ export class RuntimeStack extends Stack { return res; } - static getStackFrameSize(f: StackFrame): number { - return Object.values(f).reduce((A, i) => A + i.typeInfo.size, 0); + static getStackFrameSizeAndAlignment(f: StackFrame): [number, number] { + const size = Math.max( + 1, + ...Object.values(f).map((i) => i.address + i.typeInfo.size), + ); + const alignment = Math.max( + 1, + ...Object.values(f).map((i) => i.typeInfo.alignment), + ); + return [size, alignment]; } } diff --git a/src/interpreter/stash.ts b/src/interpreter/stash.ts index a6ff1b6..d83dbf0 100644 --- a/src/interpreter/stash.ts +++ b/src/interpreter/stash.ts @@ -1,5 +1,8 @@ +import { BIGINT_TO_BYTES } from "../typing/representation"; +import { Type, isArray, pointer } from "../typing/types"; import { Stack } from "../utils"; import { FunctionDesignator, TemporaryObject } from "./object"; +import { Runtime } from "./runtime"; export type StashItem = TemporaryObject | FunctionDesignator; @@ -9,4 +12,43 @@ export const isTemporaryObject = (i: StashItem): i is TemporaryObject => export const isFunctionDesignator = (i: StashItem): i is FunctionDesignator => i instanceof FunctionDesignator; -export class Stash extends Stack {} +export class Stash { + private st: Stack; + + constructor() { + this.st = new Stack(); + } + + pop(): StashItem { + return this.st.pop(); + } + + push(rt: Runtime, x: StashItem, addr: number | null = null): void { + let res: TemporaryObject; + if (isFunctionDesignator(x)) { + const addr = BigInt(rt.symbolTable.getAddress(x.identifier)); + res = new TemporaryObject( + pointer(x.typeInfo), + BIGINT_TO_BYTES[Type.Pointer](addr, rt.config.endianness), + ); + } else if (isArray(x.typeInfo)) { + if (addr === null) + throw new Error( + "address should be not null when calling stash push with array", + ); + res = new TemporaryObject( + pointer(x.typeInfo.elementType), + BIGINT_TO_BYTES[Type.Pointer](BigInt(addr), rt.config.endianness), + ); + } else res = x; + this.st.push(res); + } + + pushWithoutConversions(x: StashItem): void { + this.st.push(x); + } + + getArr(): StashItem[] { + return this.st.getArr(); + } +} diff --git a/src/interpreter/symbolTable.ts b/src/interpreter/symbolTable.ts index c25bfa1..530622f 100644 --- a/src/interpreter/symbolTable.ts +++ b/src/interpreter/symbolTable.ts @@ -1,3 +1,4 @@ +import { cloneDeep } from "lodash"; import { Identifier } from "../ast/types"; export class SymbolTable { @@ -8,9 +9,10 @@ export class SymbolTable { this.blockScopes.push({}); } - exitBlock(): void { - if (this.blockScopes.length === 1) throw new RangeError("no block to exit"); - this.blockScopes.pop(); + exitBlock(): Record { + const res = this.blockScopes.pop(); + if (res === undefined) throw new RangeError("no block to exit"); + return res; } getAddress(id: Identifier): number { @@ -25,6 +27,20 @@ export class SymbolTable { throw "identifier " + id + " not declared"; } + getIdentifier(addr: number): string { + for (let i = this.blockScopes.length - 1; i >= 0; i--) { + const res = Object.keys(this.blockScopes[i]).find( + (k) => this.blockScopes[i][k] === addr, + ); + if (res !== undefined) return res; + } + const res = Object.keys(this.fileScope).find( + (k) => this.fileScope[k] === addr, + ); + if (res !== undefined) return res; + throw "address " + addr + " not found"; + } + addAddress(id: Identifier, address: number): void { if (this.inFileScope) { if (id in this.fileScope) @@ -41,4 +57,12 @@ export class SymbolTable { get inFileScope(): boolean { return this.blockScopes.length === 0; } + + getFileScope() { + return cloneDeep(this.fileScope); + } + + getBlockScopes() { + return cloneDeep(this.blockScopes); + } } diff --git a/src/memory/errors.ts b/src/memory/errors.ts index 157905b..0792f43 100644 --- a/src/memory/errors.ts +++ b/src/memory/errors.ts @@ -1,3 +1,5 @@ +import { decimalAddressToHex } from "../utils"; + export class MemoryError extends Error { constructor(msg: string) { super(msg); @@ -13,18 +15,18 @@ export class SegmentationFault extends MemoryError { export class ReadSegmentationFault extends SegmentationFault { constructor(address: number) { - super("read " + address); + super("read " + decimalAddressToHex(address)); } } export class WriteSegmentationFault extends SegmentationFault { constructor(address: number) { - super("write to " + address); + super("write to " + decimalAddressToHex(address)); } } export class ExecuteSegmentationFault extends SegmentationFault { constructor(address: number) { - super("execute " + address); + super("execute " + decimalAddressToHex(address)); } } diff --git a/src/memory/memory.ts b/src/memory/memory.ts index 05774c7..f98e204 100644 --- a/src/memory/memory.ts +++ b/src/memory/memory.ts @@ -1,6 +1,25 @@ import { Endianness, MemoryConfig, MemoryRegionConfig } from "../config"; +import { + EffectiveTypeTable, + EffectiveTypeTableEntry, + NO_EFFECTIVE_TYPE, +} from "../interpreter/effectiveTypeTable"; import { BIGINT_TO_BYTES } from "../typing/representation"; -import { ScalarType } from "../typing/types"; +import { + ObjectTypeInfo, + ScalarType, + getSignedVersion, + getUnsignedVersion, + isChar, + isSignedChar, + isSignedIntegerType, + isUnsignedChar, + isUnsignedIntegerType, + isArray, + isStructure, + getTypeName, +} from "../typing/types"; +import { decimalAddressToHex } from "../utils"; import { ExecuteSegmentationFault, SegmentationFault, @@ -38,8 +57,11 @@ const checkMemoryConfig = (config: MemoryConfig) => { export class Memory { private readonly segments: Segments; + private readonly readonlyAddresses: Set; + private readonly executableAddresses: Set; + private readonly effectiveTypeTable: EffectiveTypeTable; - constructor(config: MemoryConfig) { + constructor(config: MemoryConfig, effectiveTypeTable: EffectiveTypeTable) { checkMemoryConfig(config); this.segments = { stack: new MemoryRegion(config.stack), @@ -47,18 +69,21 @@ export class Memory { data: new MemoryRegion(config.data), text: new MemoryRegion(config.text), }; + this.readonlyAddresses = new Set(); + this.executableAddresses = new Set(); + this.effectiveTypeTable = effectiveTypeTable; } - private getMemoryRegion(address: number): [keyof Segments, MemoryRegion] { - for (const [name, region] of Object.entries(this.segments)) { - if (region.containsAddress(address)) return [name, region]; + private getMemoryRegion(address: number): MemoryRegion { + for (const region of Object.values(this.segments)) { + if (region.containsAddress(address)) return region; } - throw new SegmentationFault("access " + address); + throw new SegmentationFault("access " + decimalAddressToHex(address)); } - public getByte(address: number, toExecute: boolean = false): number { - const [name, region] = this.getMemoryRegion(address); - if (toExecute && name !== "text") + private getByte(address: number, toExecute: boolean = false): number { + const region = this.getMemoryRegion(address); + if (toExecute && !this.executableAddresses.has(address)) throw new ExecuteSegmentationFault(address); return region.getByte(address - region.baseAddress); } @@ -75,14 +100,59 @@ export class Memory { return res; } - public setByte(address: number, byte: number): void { - const [name, region] = this.getMemoryRegion(address); - if (name === "text") throw new WriteSegmentationFault(address); + public getObjectBytes( + address: number, + t: ObjectTypeInfo, + toExecute: boolean = false, + ): number[] { + this.checkValidObjectAccess(address, t); + return this.getBytes(address, t.size, toExecute); + } + + private setByte( + address: number, + byte: number, + readonly: boolean = false, + executable: boolean = false, + ): void { + const region = this.getMemoryRegion(address); + if (this.readonlyAddresses.has(address)) + throw new WriteSegmentationFault(address); region.setByte(address - region.baseAddress, byte); + if (readonly) this.readonlyAddresses.add(address); + if (executable) this.executableAddresses.add(address); } - public setBytes(address: number, bytes: number[]): void { - for (let i = 0; i < bytes.length; i++) this.setByte(address + i, bytes[i]); + public setBytes( + address: number, + bytes: number[], + readonly: boolean = false, + executable: boolean = false, + ): void { + for (let i = 0; i < bytes.length; i++) + this.setByte(address + i, bytes[i], readonly, executable); + } + + public setObjectBytes( + address: number, + bytes: number[], + t: ObjectTypeInfo, + readonly: boolean = false, + executable: boolean = false, + ): void { + if (bytes.length !== t.size) + throw new Error("object size and bytes provided don't match"); + + this.checkValidObjectAccess(address, t); + if (this.effectiveTypeTable.get(address) === NO_EFFECTIVE_TYPE) { + try { + this.effectiveTypeTable.change(address, t); + } catch (e) { + throw new Error("object to be written overlaps with existing object"); + } + } + + this.setBytes(address, bytes, readonly, executable); } public setRepeatedByte(address: number, size: number, byte: number): void { @@ -92,10 +162,55 @@ export class Memory { public setScalar( address: number, i: bigint, - t: ScalarType["type"], + t: ScalarType, e: Endianness = "little", + readonly: boolean = false, + executable: boolean = false, ): void { - const bytes = BIGINT_TO_BYTES[t](i, e); - this.setBytes(address, bytes); + const bytes = BIGINT_TO_BYTES[t.type](i, e); + this.setObjectBytes(address, bytes, t, readonly, executable); + } + + private checkStrictAliasing(address: number, t: ObjectTypeInfo): boolean { + if (isChar(t) || isUnsignedChar(t) || isSignedChar(t)) return true; + + let et: EffectiveTypeTableEntry; + try { + et = this.effectiveTypeTable.get(address); + } catch (e) { + throw new Error("no object allocated at " + decimalAddressToHex(address)); + } + + if (et === NO_EFFECTIVE_TYPE) return true; + + if (t.isCompatible(et)) return true; + if (isSignedIntegerType(t) && getUnsignedVersion(t).isCompatible(et)) + return true; + if (isUnsignedIntegerType(t) && getSignedVersion(t).isCompatible(et)) + return true; + + const ot = et; + if (isArray(et)) et = et.elementType; + else if (isStructure(et)) et = et.members[0].type; + + if (t.isCompatible(et)) return true; + if (isSignedIntegerType(t) && getUnsignedVersion(t).isCompatible(et)) + return true; + if (isUnsignedIntegerType(t) && getSignedVersion(t).isCompatible(et)) + return true; + + let err = getTypeName(ot); + if (ot !== et) err += " or " + getTypeName(et); + throw new Error( + "undefined behaviour: strict aliasing violated, effective type " + + err + + " but access of type " + + getTypeName(t), + ); + } + + private checkValidObjectAccess(address: number, t: ObjectTypeInfo): boolean { + if (address % t.alignment) throw new Error("misaligned access"); + return this.checkStrictAliasing(address, t); } } diff --git a/src/typing/env.ts b/src/typing/env.ts index 74a495d..c9ead07 100644 --- a/src/typing/env.ts +++ b/src/typing/env.ts @@ -1,8 +1,16 @@ import { Identifier } from "../ast/types"; +import { BUILTIN_FUNCTIONS } from "../builtins"; import { Structure, TypeInfo, isStructure } from "./types"; export class TypeEnv { - private env: Record[] = [{}]; + private env: Record[]; + + constructor() { + this.env = [{}]; + for (const [identifier, f] of Object.entries(BUILTIN_FUNCTIONS)) { + this.addIdentifierTypeInfo(identifier, f.type); + } + } enterBlock(): void { this.env.push({}); @@ -13,10 +21,13 @@ export class TypeEnv { this.env.pop(); } - getIdentifierTypeInfo(id: Identifier): TypeInfo { + getIdentifierTypeInfo(id: Identifier, isTypedef: boolean = false): TypeInfo { for (let i = this.env.length - 1; i >= 0; i--) { if (id in this.env[i]) { - return this.env[i][id]; + const [t, b] = this.env[i][id]; + if (isTypedef && !b) throw "identifier " + id + " does not name a type"; + if (!isTypedef && b) throw "identifier " + id + " is a typedef"; + return t; } } throw "identifier " + id + " not declared"; @@ -27,7 +38,7 @@ export class TypeEnv { const id = "tag::" + tag; for (let i = this.env.length - 1; i >= 0; i--) { if (id in this.env[i]) { - const t = this.env[i][id]; + const t = this.env[i][id][0]; if (!isStructure(t)) { throw "tag " + tag + " does not refer to a structure"; } @@ -37,16 +48,20 @@ export class TypeEnv { throw "tag " + tag + " not declared"; } - addIdentifierTypeInfo(id: Identifier, t: TypeInfo): void { + addIdentifierTypeInfo( + id: Identifier, + t: TypeInfo, + isTypedef: boolean = false, + ): void { const currBlock = this.env[this.env.length - 1]; if (id in currBlock) throw "redeclaration of identifier " + id; - currBlock[id] = t; + currBlock[id] = [t, isTypedef]; } addTagTypeInfo(tag: Identifier, t: Structure): void { const currBlock = this.env[this.env.length - 1]; const id = "tag::" + tag; if (id in currBlock) throw "redeclaration of tag " + tag; - currBlock[id] = t; + currBlock[id] = [t, false]; } } diff --git a/src/typing/main.ts b/src/typing/main.ts index 22a7085..d50954f 100644 --- a/src/typing/main.ts +++ b/src/typing/main.ts @@ -1,8 +1,27 @@ import { BaseNode, + CastExpressionNode, + InitializerList, + TypeSpecifier, + TypedCastExpressionNode, + TypedDesignator, + TypedInitializerList, TypedUnaryExpressionDecr, + TypedUnaryExpressionSizeof, + Typedef, + TypedefDeclaration, UnaryExpressionDecr, + UnaryExpressionSizeof, + isArrayDesignator, + isCastExpressionNode, + isInitializerList, + isIntegerConstant, + isStorageClassSpecifier, + isTypeName, + isTypeSpecifier, isTypedArraySubscriptingOp, + isTypedIntegerConstant, + isUnaryExpressionSizeof, } from "./../ast/types"; import { PrimaryExprParenthesis, @@ -16,7 +35,6 @@ import { CompoundStatement, ConditionalExpression, Declaration, - DeclarationSpecifiers, Expression, ExpressionStatement, ExternalDeclaration, @@ -119,11 +137,15 @@ import { ScalarType, FunctionType, ObjectTypeInfo, + Array, + Structure, + unsignedInt, } from "./types"; import { checkSimpleAssignmentConstraint, constructType, getMemberTypeInfo, + getMember, isLvalue, isModifiableLvalue, isNullPtrConst, @@ -182,9 +204,15 @@ const typeFunctionDefinition = ( env: TypeEnv, ): TypedFunctionDefinition => typeCheck(t, () => { + const storageClassSpecifiers = t.specifiers.filter(isStorageClassSpecifier); + if (storageClassSpecifiers.length > 0) + throw "function definition cannot include storage class specifiers"; + + const typeSpecifiers = t.specifiers.filter(isTypeSpecifier); const { identifier, type: typeInfo } = constructType( - t.specifiers, + typeSpecifiers, t.declarator, + env, ); if (!identifier) throw "function definition must contain an identifier"; @@ -212,49 +240,184 @@ const typeFunctionDefinition = ( return { ...t, identifier, typeInfo, body }; }); -const typeDeclaration = (t: Declaration, env: TypeEnv): TypedDeclaration => +const typeDeclaration = ( + t: Declaration, + env: TypeEnv, +): TypedDeclaration | TypedefDeclaration => typeCheck(t, () => { + const storageClassSpecifiers = t.specifiers.filter(isStorageClassSpecifier); + const typeSpecifiers = t.specifiers.filter(isTypeSpecifier); + // this is evaluated for side effects (declaring struct tags) + getTypeInfoFromSpecifiers(typeSpecifiers, env); + if (storageClassSpecifiers.length > 0) { + if (storageClassSpecifiers.length > 1) + throw "multiple typedef specifiers"; + return { + ...t, + type: "TypedefDeclaration", + declaratorList: t.declaratorList.map((i) => + typeTypedef(i, env, typeSpecifiers), + ), + }; + } return { ...t, declaratorList: t.declaratorList.map((i) => - typeInitDeclarator(i, env, t.specifiers), + typeInitDeclarator(i, env, typeSpecifiers), ), }; }); +const typeTypedef = ( + t: InitDeclarator, + env: TypeEnv, + specifiers: TypeSpecifier[], +): Typedef => + typeCheck(t, () => { + if (t.initializer) throw "cannot initialize typedef"; + const { identifier, type: typeInfo } = constructType( + specifiers, + t.declarator, + env, + ); + if (!identifier) throw "declarator must declare one identifier"; + env.addIdentifierTypeInfo(identifier, typeInfo, true); + return { + ...t, + type: "Typedef", + identifier, + typeInfo, + }; + }); + const typeInitDeclarator = ( t: InitDeclarator, env: TypeEnv, - specifiers: DeclarationSpecifiers, + specifiers: TypeSpecifier[], ): TypedInitDeclarator => typeCheck(t, () => { const { identifier, type: typeInfo } = constructType( specifiers, t.declarator, + env, ); if (!identifier) throw "declarator must declare one identifier"; if (isFunction(typeInfo)) throw "cannot declare function (forward declarations are not supported)"; if (isVoid(typeInfo)) throw "cannot declare a variable of type void"; + + let initializer: TypedInitializer | null = null; if (t.initializer) { if (!isObjectTypeInfo(typeInfo)) throw "cannot initialize non object type"; // TODO: if we are in file scope, need check that initializer is a constant expression // see (6.6) Constant expressions and (6.7.8) Initialization - // TODO: type check this + initializer = typeInitializer(t.initializer, env, typeInfo); } + const res = { ...t, identifier, typeInfo: typeInfo as ObjectTypeInfo, - initializer: t.initializer ? typeInitializer(t.initializer, env) : null, + initializer, }; env.addIdentifierTypeInfo(identifier, typeInfo); return res; }); -const typeInitializer = (t: Initializer, env: TypeEnv): TypedInitializer => - typeAssignmentExpression(t, env); +const typeInitializer = ( + t: Initializer, + env: TypeEnv, + targetType: ObjectTypeInfo, +): TypedInitializer => + typeCheck(t, () => { + if (isScalarType(targetType)) { + let e: AssignmentExpression; + if (isInitializerList(t)) { + const xs = t.value; + if (xs.length !== 1) throw "invalid initializer type for scalar"; + if (xs[0].designation.length) + throw "invalid initializer type for scalar"; + if (isInitializerList(xs[0].initializer)) + throw "invalid initializer type for scalar"; + e = xs[0].initializer; + } else { + e = t; + } + const res = typeAssignmentExpression(e, env); + if ( + !checkSimpleAssignmentConstraint( + targetType, + res.typeInfo, + isNullPtrConst(res), + ) + ) + throw "invalid initializer type for scalar"; + return res; + } + + if (isArray(targetType) || isStructure(targetType)) { + if (!isInitializerList(t)) { + const res = typeAssignmentExpression(t, env); + if (!res.typeInfo.isCompatible(targetType)) + throw "invalid initializer type for aggregate type"; + return res; + } + return typeInitializerList(t, env, targetType); + } + + throw "invalid initializer"; + }); + +const typeInitializerList = ( + t: InitializerList, + env: TypeEnv, + tt: Array | Structure, +): TypedInitializerList => + typeCheck(t, () => { + let i = 0; + const value = t.value.map(({ designation, initializer }) => { + if (i >= (isArray(tt) ? tt.length : tt.members.length)) + throw "excess initializers in initializer list"; + let curr: ObjectTypeInfo = tt; + const typedDesignators: TypedDesignator[] = []; + + if (designation.length) { + let first = true; + for (const d of designation) { + if (isArrayDesignator(d)) { + if (!isArray(curr)) + throw "array designator when current object is not array"; + const idx = typeIntegerConstant(d.idx); + const idxVal = Number(idx.value); + if (!(0 <= idxVal && idxVal < curr.length)) + throw "index for array designator out of bounds"; + if (first) i = idxVal; + curr = curr.elementType; + typedDesignators.push({ ...d, idx, typeInfo: curr }); + } else { + if (!isStructure(curr)) + throw "struct designator when current object is not struct"; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [idx, _, typeInfo] = getMember(curr, d.identifier); + if (first) i = idx; + curr = typeInfo; + typedDesignators.push({ ...d, typeInfo }); + } + first = false; + } + } else { + curr = isArray(tt) ? tt.elementType : tt.members[i].type; + } + + i++; + return { + designation: typedDesignators, + initializer: typeInitializer(initializer, env, curr), + }; + }); + return { ...t, value }; + }); const typeCompoundStatement = ( t: CompoundStatement, @@ -312,7 +475,13 @@ const typeJumpStatementReturn = ( throw "empty return statement in function declared with non-void return type"; const expr = typeExpression(t.value, env); - if (!checkSimpleAssignmentConstraint(expectedReturnType, expr)) + if ( + !checkSimpleAssignmentConstraint( + expectedReturnType, + expr.typeInfo, + isNullPtrConst(expr), + ) + ) throw "wrong return type"; return { ...t, value: expr }; @@ -323,8 +492,10 @@ const typeExpressionStatement = ( env: TypeEnv, ): TypedExpressionStatement => typeCheck(t, () => { - if (isEmptyExpressionStatement(t)) return t; - return typeExpression(t, env); + let value; + if (isEmptyExpressionStatement(t.value)) value = t.value; + else value = typeExpression(t.value, env); + return { ...t, value }; }); const typeExpression = (t: Expression, env: TypeEnv): TypedExpression => @@ -359,14 +530,21 @@ const typeAssignmentExpression = ( const left = typeUnaryExpression(t.left, env); if (!isModifiableLvalue(left)) throw "require a modifiable lvalue as left operand in assignment"; - const leftType = left.typeInfo; + const leftType = applyImplicitConversions(left).typeInfo; const right = typeAssignmentExpression(t.right, env); - const rightType = right.typeInfo; + const rightType = applyImplicitConversions(right).typeInfo; let ok = false; switch (t.op) { case "=": { - if (checkSimpleAssignmentConstraint(leftType, right)) ok = true; + if ( + checkSimpleAssignmentConstraint( + leftType, + rightType, + isNullPtrConst(right), + ) + ) + ok = true; break; } case "*=": @@ -478,8 +656,8 @@ const typeBinaryExpression = ( const left = typeBinaryExpression(t.left, env); const right = typeBinaryExpression(t.right, env); - let t0 = left.typeInfo; - let t1 = right.typeInfo; + let t0 = applyImplicitConversions(left).typeInfo; + let t1 = applyImplicitConversions(right).typeInfo; let typeInfo: TypeInfo; switch (t.op) { @@ -628,7 +806,37 @@ const typeBinaryExpression = ( const typeCastExpression = ( t: CastExpression, env: TypeEnv, -): TypedCastExpression => typeUnaryExpression(t, env); +): TypedCastExpression => + typeCheck(t, () => { + if (isCastExpressionNode(t)) return typeCastExpressionNode(t, env); + return typeUnaryExpression(t, env); + }); + +const typeCastExpressionNode = ( + t: CastExpressionNode, + env: TypeEnv, +): TypedCastExpressionNode => + typeCheck(t, () => { + const typeSpecifiers = t.targetType.specifierQualifierList; + const declarator = t.targetType.abstractDeclarator; + const { type: targetType } = constructType(typeSpecifiers, declarator, env); + const expr = typeCastExpression(t.expr, env); + if ( + !( + isVoid(targetType) || + (isScalarType(targetType) && isScalarType(expr.typeInfo)) + ) + ) { + throw "only cast to void or scalar type cast to scalar type allowed"; + } + return { + ...t, + targetType, + expr, + typeInfo: targetType, + lvalue: false, + }; + }); const typeUnaryExpression = ( t: UnaryExpression, @@ -638,9 +846,31 @@ const typeUnaryExpression = ( if (isUnaryExpressionIncr(t)) return typeUnaryExpressionIncr(t, env); if (isUnaryExpressionDecr(t)) return typeUnaryExpressionDecr(t, env); if (isUnaryExpressionNode(t)) return typeUnaryExpressionNode(t, env); + if (isUnaryExpressionSizeof(t)) return typeUnaryExpressionSizeof(t, env); return typePostfixExpression(t, env); }); +const typeUnaryExpressionSizeof = ( + t: UnaryExpressionSizeof, + env: TypeEnv, +): TypedUnaryExpressionSizeof => + typeCheck(t, () => { + let value: number; + if (isTypeName(t.value)) { + const typeSpecifiers = t.value.specifierQualifierList; + const declarator = t.value.abstractDeclarator; + const { type } = constructType(typeSpecifiers, declarator, env); + if (!isObjectTypeInfo(type)) throw "sizeof operator requires object type"; + value = type.size; + } else { + const tt = typeUnaryExpression(t.value, env); + if (!isObjectTypeInfo(tt.typeInfo)) + throw "sizeof operator requires object type"; + value = tt.typeInfo.size; + } + return { ...t, value, typeInfo: unsignedInt(), lvalue: false }; + }); + const typeUnaryExpressionIncr = ( t: UnaryExpressionIncr, env: TypeEnv, @@ -704,8 +934,9 @@ const typeUnaryExpressionNode = ( break; } case "*": { - if (!isPointer(t0)) throw "operand of * must be of pointer type"; - const rt = t0.referencedType; + const p0 = applyImplicitConversions(expr).typeInfo; + if (!isPointer(p0)) throw "operand of * must be of pointer type"; + const rt = p0.referencedType; exprType = { typeInfo: rt, lvalue: isObjectTypeInfo(rt) }; break; } @@ -789,7 +1020,13 @@ const typePostfixExpressionNode = ( if (value.length !== ft.arity) throw "function call has wrong number of arguments"; ft.parameterTypes.forEach((p, i) => { - if (!checkSimpleAssignmentConstraint(p.type, value[i])) + if ( + !checkSimpleAssignmentConstraint( + p.type, + value[i].typeInfo, + isNullPtrConst(value[i]), + ) + ) throw "mismatch in argument type"; }); @@ -871,7 +1108,12 @@ const typePrimaryExprConstant = ( ): TypedPrimaryExprConstant => typeCheck(t, () => { const value = typeConstant(t.value); - return { ...t, value, typeInfo: value.typeInfo, lvalue: value.lvalue }; + return { + ...t, + value, + typeInfo: isTypedIntegerConstant(value) ? value.typeInfo : int(), + lvalue: isTypedIntegerConstant(value) ? value.lvalue : false, + }; }); const typePrimaryExprString = (t: PrimaryExprString): TypedPrimaryExprString => @@ -895,7 +1137,10 @@ const typePrimaryExprParenthesis = ( }; }); -const typeConstant = (t: Constant): TypedConstant => typeIntegerConstant(t); +const typeConstant = (t: Constant): TypedConstant => { + if (isIntegerConstant(t)) return typeIntegerConstant(t); + return t; +}; const typeIntegerConstant = (t: IntegerConstant): TypedIntegerConstant => typeCheck(t, () => { @@ -975,9 +1220,7 @@ const typeIntegerConstant = (t: IntegerConstant): TypedIntegerConstant => for (const ti of typesToTry) { const [type_min, type_max] = getNumericalLimitFromSpecifiers(ti); if (type_min <= t.value && t.value <= type_max) { - typeInfo = getTypeInfoFromSpecifiers( - ti.split(" ") as DeclarationSpecifiers, - ); + typeInfo = getTypeInfoFromSpecifiers(ti.split(" ") as TypeSpecifier[]); break; } } diff --git a/src/typing/representation.ts b/src/typing/representation.ts index 3aa4594..c447fe1 100644 --- a/src/typing/representation.ts +++ b/src/typing/representation.ts @@ -19,7 +19,9 @@ import { ScalarType, Type } from "./types"; const checkInputInRange = (i: bigint, s: string): void => { const [min, max] = getNumericalLimitFromSpecifiers(s); if (min <= i && i <= max) return; - throw i.toString() + " out of range for type " + s; + throw new Error( + "undefined behaviour: " + i.toString() + " out of range for type " + s, + ); }; const clamp = (i: bigint, s: string): bigint => { @@ -47,6 +49,7 @@ export const bytesToBigint = ( signed: boolean, e: Endianness = "little", ): bigint => { + bytes = structuredClone(bytes); bytes.forEach(checkValidByte); if (e === "little") bytes.reverse(); const isNegative = signed && bytes[0] & 0x80; diff --git a/src/typing/specifiers.ts b/src/typing/specifiers.ts index 88f7d43..d60f154 100644 --- a/src/typing/specifiers.ts +++ b/src/typing/specifiers.ts @@ -1,7 +1,9 @@ import { - DeclarationSpecifiers, StructSpecifier, + TypeSpecifier, + isStorageClassSpecifier, isStructSpecifier, + isTypeSpecifier, } from "../ast/types"; import { CHAR_MAX, @@ -22,10 +24,10 @@ import { ULONG_MAX, USHRT_MAX, } from "../constants"; +import { TypeEnv } from "./env"; import { IntegerType, Structure, - StructureMember, TypeInfo, unsignedInt, Void, @@ -138,7 +140,8 @@ const unorderedCompare = (s1: string, s2: string): boolean => s1.split(" ").sort().join(" ") === s2.split(" ").sort().join(" "); export const getTypeInfoFromSpecifiers = ( - ls: DeclarationSpecifiers, + ls: TypeSpecifier[], + env: TypeEnv | null = null, ): TypeInfo => { if (ls.length === 0) throw "at least one type specifier must be given"; @@ -146,7 +149,7 @@ export const getTypeInfoFromSpecifiers = ( if (n > 1) throw "more than 1 struct specifier"; if (n === 1) { if (ls.length !== 1) throw "struct specifier should be the only specifier"; - return constructStructFromSpecifier(ls[0] as StructSpecifier); + return constructStructFromSpecifier(ls[0] as StructSpecifier, env); } const s = ls.join(" "); @@ -156,26 +159,31 @@ export const getTypeInfoFromSpecifiers = ( } } - throw ( - "unknown type specifier " + - JSON.stringify(ls) + - ", expected one of " + - JSON.stringify(Object.keys(TYPE_SPECIFIER_TO_TYPE_INFO)) - ); + if (!env) throw "unknown type specifiers"; + if (ls.length > 1) + throw "more than 1 typedef specified or unknown type specifiers"; + return env.getIdentifierTypeInfo(ls[0] as string, true); }; -const constructStructFromSpecifier = (s: StructSpecifier): Structure => { - if (s.declarationList.length === 0) - throw "struct specifier cannot be empty (forward declarations are not supported)"; - +const constructStructFromSpecifier = ( + s: StructSpecifier, + env: TypeEnv | null, +): Structure => { const tag = s.identifier || undefined; - const members: StructureMember[] = []; + const members = []; for (const d of s.declarationList) { for (const id of d.declaratorList) { + const storageClassSpecifiers = d.specifiers.filter( + isStorageClassSpecifier, + ); + if (storageClassSpecifiers.length > 0) + throw "typedef in struct definition"; + const typeSpecifiers = d.specifiers.filter(isTypeSpecifier); const { identifier: name, type } = constructType( - d.specifiers, + typeSpecifiers, id.declarator, + env, ); if (!isObjectTypeInfo(type)) throw "non object type declared in struct"; if (name) members.push({ name, type }); @@ -183,7 +191,27 @@ const constructStructFromSpecifier = (s: StructSpecifier): Structure => { } } - return structure(members, tag); + const res = structure(members, tag); + if (tag) { + let other: Structure | undefined; + try { + other = env?.getTagTypeInfo(tag); + } catch (e) { + other = undefined; + } + if (s.declarationList.length === 0) { + if (!other) throw "empty struct specifier"; + return other; + } + if (other) { + if (!res.isCompatible(other)) throw "redefinition of struct " + tag; + } else { + env?.addTagTypeInfo(tag, res); + } + } else { + if (s.declarationList.length === 0) throw "empty struct specifier"; + } + return res; }; export const getNumericalLimitFromSpecifiers = ( @@ -207,5 +235,5 @@ export const typeInfoToSpecifier = (typeInfo: IntegerType | Void): string => { for (const [specifier, t] of Object.entries(TYPE_SPECIFIER_TO_TYPE_INFO)) { if (t.type === typeInfo.type) return specifier; } - throw "type " + getTypeName(typeInfo.type) + " has no specifier"; + throw "type " + getTypeName(typeInfo) + " has no specifier"; }; diff --git a/src/typing/types.ts b/src/typing/types.ts index c1863fc..5aae041 100644 --- a/src/typing/types.ts +++ b/src/typing/types.ts @@ -41,13 +41,19 @@ export enum Type { Void = "void", - Array = "array", + Array = "arr", Structure = "struct", - Function = "function", + Function = "fn", Pointer = "ptr", } -export const getTypeName = (i: Type): string => i; +export const getTypeName = (i: TypeInfo): string => { + if (isArray(i)) return i.type + " of " + getTypeName(i.elementType); + if (isFunction(i)) return i.type + " returning " + getTypeName(i.returnType); + if (isPointer(i)) return i.type + " to " + getTypeName(i.referencedType); + if (isStructure(i)) return i.type + (i.tag ? " " + i.tag : ""); + return i.type; +}; // types are partitioned into object types (fully describes object), // function types (describes function), and incomplete types (lack info to determine sizes) @@ -85,6 +91,17 @@ export const isSignedIntegerType = (t: TypeInfo): t is SignedIntegerType => isLongInt(t) || isLongLongInt(t); +export const getUnsignedVersion = ( + t: SignedIntegerType, +): UnsignedIntegerType => { + if (isSignedChar(t)) return unsignedChar(); + if (isShortInt(t)) return unsignedShortInt(); + if (isInt(t)) return unsignedInt(); + if (isLongInt(t)) return unsignedLongInt(); + if (isLongLongInt(t)) return unsignedLongLongInt(); + throw new Error("unrecognized signed integer type"); +}; + export type UnsignedIntegerType = | _Bool | UnsignedChar @@ -101,6 +118,15 @@ export const isUnsignedIntegerType = (t: TypeInfo): t is UnsignedIntegerType => isUnsignedLongInt(t) || isUnsignedLongLongInt(t); +export const getSignedVersion = (t: UnsignedIntegerType): SignedIntegerType => { + if (isUnsignedChar(t)) return signedChar(); + if (isUnsignedShortInt(t)) return shortInt(); + if (isUnsignedInt(t)) return int(); + if (isUnsignedLongInt(t)) return longInt(); + if (isUnsignedLongLongInt(t)) return longLongInt(); + throw new Error("unrecognized unsigned integer type"); +}; + export type BasicType = Char | SignedIntegerType | UnsignedIntegerType; export type CharacterType = Char | SignedChar | UnsignedChar; @@ -381,6 +407,7 @@ export const isArray = (t: TypeInfo): t is Array => t.type === Type.Array; export interface StructureMember { name?: string; type: ObjectTypeInfo; + relativeAddress: number; } export interface Structure extends ObjectTypeInfo { @@ -390,16 +417,21 @@ export interface Structure extends ObjectTypeInfo { } export const structure = ( - members: StructureMember[], + m: { + name?: string; + type: ObjectTypeInfo; + }[], tag?: string, ): Structure => { // the alignment of a structure must be at least as strict as the alignment of its strictest member - const strictestAlignment = Math.max(...members.map((m) => m.type.alignment)); + const strictestAlignment = Math.max(...m.map((i) => i.type.alignment)); + const members: StructureMember[] = []; let currAddr = 0; // next free addr - for (const m of members) { - currAddr = roundUpM(currAddr, m.type.alignment); - currAddr += m.type.size; + for (const i of m) { + currAddr = roundUpM(currAddr, i.type.alignment); + members.push({ ...i, relativeAddress: currAddr }); + currAddr += i.type.size; } // add padding at end for array of struct currAddr = roundUpM(currAddr, strictestAlignment); diff --git a/src/typing/utils.ts b/src/typing/utils.ts index 010ebc9..83d1bff 100644 --- a/src/typing/utils.ts +++ b/src/typing/utils.ts @@ -1,6 +1,11 @@ -import { ExpressionTypeInfo, isTypedIntegerConstant } from "./../ast/types"; import { - DeclarationSpecifiers, + ExpressionTypeInfo, + TypeSpecifier, + isStorageClassSpecifier, + isTypeSpecifier, + isTypedIntegerConstant, +} from "./../ast/types"; +import { Declarator, DeclaratorPart, Identifier, @@ -27,6 +32,7 @@ import { isVoid, pointer, } from "./types"; +import { TypeEnv } from "./env"; export const getIdentifierFromDeclarator = ( d: Declarator, @@ -49,6 +55,7 @@ type DeclaratorWithoutIdentifier = Exclude< export const constructDerivedTypes = ( ls: DeclaratorWithoutIdentifier, baseType: TypeInfo, + env: TypeEnv | null, ): TypeInfo => { const p = ls.pop(); if (!p) return baseType; @@ -70,9 +77,15 @@ export const constructDerivedTypes = ( if (isFunctionTypeInfo(baseType)) throw "cannot return function type from function"; if (isArray(baseType)) throw "cannot return array type from function"; - const paramTypeInfo = p.argTypes.map((i) => - constructType(i.specifiers, i.declarator), - ); + const paramTypeInfo = p.argTypes.map((i) => { + const storageClassSpecifiers = i.specifiers.filter( + isStorageClassSpecifier, + ); + if (storageClassSpecifiers.length > 0) + throw "typedef in parameter declaration"; + const typeSpecifiers = i.specifiers.filter(isTypeSpecifier); + return constructType(typeSpecifiers, i.declarator, env); + }); t = functionType(baseType, paramTypeInfo); break; } @@ -82,20 +95,22 @@ export const constructDerivedTypes = ( } } - return constructDerivedTypes(ls, t); + return constructDerivedTypes(ls, t, env); }; export const constructType = ( - specifiers: DeclarationSpecifiers, + specifiers: TypeSpecifier[], declarator: Declarator, + env: TypeEnv | null, ): { identifier: Identifier | null; type: TypeInfo } => { - const specifiedType = getTypeInfoFromSpecifiers(specifiers); + const specifiedType = getTypeInfoFromSpecifiers(specifiers, env); const identifier = getIdentifierFromDeclarator(declarator); const type = constructDerivedTypes( declarator.filter( (i) => !isIdentifierDeclaratorPart(i), ) as DeclaratorWithoutIdentifier, specifiedType, + env, ); return { identifier, type }; }; @@ -126,9 +141,9 @@ export const isNullPtrConst = (expr: TypedExpression): boolean => { export const checkSimpleAssignmentConstraint = ( leftType: TypeInfo, - right: TypedExpression, + rightType: TypeInfo, + rightIsNullPtrConstant: boolean, ): boolean => { - const rightType = right.typeInfo; if (isArithmeticType(leftType) && isArithmeticType(rightType)) return true; if (isStructure(leftType) && leftType.isCompatible(rightType)) return true; if ( @@ -137,7 +152,7 @@ export const checkSimpleAssignmentConstraint = ( leftType.referencedType.isCompatible(rightType.referencedType) ) return true; - if (isPointer(leftType) && isNullPtrConst(right)) return true; + if (isPointer(leftType) && rightIsNullPtrConstant) return true; if (isBool(leftType) && isPointer(rightType)) return true; if ( checkPtrToVoidAssignment(leftType, rightType) || @@ -174,3 +189,22 @@ export const getMemberTypeInfo = ( (t.tag ? " " + t.tag : "") ); }; + +export const getMember = ( + t: Structure, + identifier: Identifier, +): [number, number, ObjectTypeInfo] => { + let i = 0; + for (const m of t.members) { + if (m.name === identifier) { + return [i, m.relativeAddress, m.type]; + } + i++; + } + throw ( + "member " + + identifier + + " does not exist on struct" + + (t.tag ? " " + t.tag : "") + ); +}; diff --git a/src/utils.ts b/src/utils.ts index 6d83721..97d58fc 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -76,3 +76,7 @@ export function checkValidByte(i: number) { export function mod(a: bigint, b: bigint): bigint { return ((a % b) + b) % b; } + +export function decimalAddressToHex(addr: number) { + return "0x" + ("00000000" + addr.toString(16).toUpperCase()).slice(-8); +}