Skip to content

Commit

Permalink
support self-refrential types
Browse files Browse the repository at this point in the history
  • Loading branch information
caipng committed Apr 23, 2024
1 parent 807eb9d commit 40ce8dc
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 44 deletions.
32 changes: 29 additions & 3 deletions src/typing/env.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import { Identifier } from "../ast/types";
import { BUILTIN_FUNCTIONS } from "../builtins";
import { Structure, TypeInfo, isStructure } from "./types";
import {
AggregateType,
Structure,
TypeInfo,
isAggregateType,
isStructure,
} from "./types";

const TAG_PREFIX = "tag::";

export class TypeEnv {
private env: Record<Identifier, [TypeInfo, boolean]>[];
public readonly aggTypes: AggregateType[];

constructor() {
this.env = [{}];
this.aggTypes = [];
for (const [identifier, f] of Object.entries(BUILTIN_FUNCTIONS)) {
this.addIdentifierTypeInfo(identifier, f.type);
}
Expand Down Expand Up @@ -35,7 +45,7 @@ export class TypeEnv {

// see (6.2.3) Name spaces of identifiers
getTagTypeInfo(tag: Identifier): Structure {
const id = "tag::" + tag;
const id = TAG_PREFIX + tag;
for (let i = this.env.length - 1; i >= 0; i--) {
if (id in this.env[i]) {
const t = this.env[i][id][0];
Expand All @@ -48,20 +58,36 @@ export class TypeEnv {
throw "tag " + tag + " not declared";
}

getAllTagsInCurrentScope() {
const currScope = this.env[this.env.length - 1];
const res: { tag: Identifier; struct: Structure }[] = [];
Object.entries(currScope).forEach(([k, v]) => {
if (k.startsWith(TAG_PREFIX)) {
const tag = k.replace(TAG_PREFIX, "");
if (!isStructure(v[0]))
throw "tag " + tag + " does not refer to a structure";
res.push({ tag, struct: v[0] });
}
});
return res;
}

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;
if (isAggregateType(t)) this.aggTypes.push(t);
currBlock[id] = [t, isTypedef];
}

addTagTypeInfo(tag: Identifier, t: Structure): void {
const currBlock = this.env[this.env.length - 1];
const id = "tag::" + tag;
const id = TAG_PREFIX + tag;
if (id in currBlock) throw "redeclaration of tag " + tag;
if (isAggregateType(t)) this.aggTypes.push(t);
currBlock[id] = [t, false];
}
}
6 changes: 4 additions & 2 deletions src/typing/errors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { BaseNode } from "../ast/types";

export class TypeCheckingError extends Error {
constructor(msg: string) {
super(msg);
constructor(t: BaseNode, msg: string) {
super("line " + t.start.line + " col " + t.start.column + ": " + msg);
Object.setPrototypeOf(this, TypeCheckingError.prototype);
}
}
27 changes: 15 additions & 12 deletions src/typing/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,24 +149,18 @@ import {
isLvalue,
isModifiableLvalue,
isNullPtrConst,
fillInForwardDeclarations,
} from "./utils";
import { getErrorMessage } from "../utils";
import { TypeCheckingError } from "./errors";

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function typeCheck<T>(t: BaseNode, f: () => T) {
function typeCheck<T>(t: BaseNode, f: () => T): T {
try {
return f();
} catch (err) {
console.error(err);
if (err instanceof TypeCheckingError) throw err;
throw new TypeCheckingError(
"line " +
t.start.line +
" col " +
t.start.column +
": " +
getErrorMessage(err),
);
throw new TypeCheckingError(t, getErrorMessage(err));
}
}

Expand All @@ -177,9 +171,18 @@ export const typeTranslationUnit = (t: TranslationUnit): TypedTranslationUnit =>
value: [],
};
const env = new TypeEnv();

// pre typechecking
fillInForwardDeclarations(t, env);

// typechecking
for (const i of t.value) {
res.value.push(typeExternalDeclaration(i, env));
}

// post typechecking
env.aggTypes.forEach((i) => i.recalculateSizeAndAlignment());

try {
env.getIdentifierTypeInfo("main");
} catch (err) {
Expand Down Expand Up @@ -377,8 +380,6 @@ const typeInitializerList = (
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[] = [];

Expand Down Expand Up @@ -407,6 +408,8 @@ const typeInitializerList = (
first = false;
}
} else {
if (i >= (isArray(tt) ? tt.length : tt.members.length))
throw "excess initializers in initializer list";
curr = isArray(tt) ? tt.elementType : tt.members[i].type;
}

Expand Down
38 changes: 32 additions & 6 deletions src/typing/specifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,14 +142,19 @@ const unorderedCompare = (s1: string, s2: string): boolean =>
export const getTypeInfoFromSpecifiers = (
ls: TypeSpecifier[],
env: TypeEnv | null = null,
allowEmptyStructSpecifier: boolean = false,
): TypeInfo => {
if (ls.length === 0) throw "at least one type specifier must be given";

const n = ls.reduce((A, i) => (isStructSpecifier(i) ? A + 1 : A), 0);
if (n > 1) throw "more than 1 struct specifier";
if (n > 1) throw "more than 1 struct specifier in specifier list";
if (n === 1) {
if (ls.length !== 1) throw "struct specifier should be the only specifier";
return constructStructFromSpecifier(ls[0] as StructSpecifier, env);
return constructStructFromSpecifier(
ls[0] as StructSpecifier,
env,
allowEmptyStructSpecifier,
);
}

const s = ls.join(" ");
Expand All @@ -162,12 +167,15 @@ export const getTypeInfoFromSpecifiers = (
if (!env) throw "unknown type specifiers";
if (ls.length > 1)
throw "more than 1 typedef specified or unknown type specifiers";
// pre typecheck, don't care about typedefs
if (allowEmptyStructSpecifier) return int();
return env.getIdentifierTypeInfo(ls[0] as string, true);
};

const constructStructFromSpecifier = (
export const constructStructFromSpecifier = (
s: StructSpecifier,
env: TypeEnv | null,
allowEmptyStructSpecifier: boolean,
): Structure => {
const tag = s.identifier || undefined;
const members = [];
Expand All @@ -184,6 +192,7 @@ const constructStructFromSpecifier = (
typeSpecifiers,
id.declarator,
env,
allowEmptyStructSpecifier,
);
if (!isObjectTypeInfo(type)) throw "non object type declared in struct";
if (name) members.push({ name, type });
Expand All @@ -200,16 +209,33 @@ const constructStructFromSpecifier = (
other = undefined;
}
if (s.declarationList.length === 0) {
if (!other) throw "empty struct specifier";
if (!other) {
if (allowEmptyStructSpecifier) return res;
throw "empty struct specifier";
}
return other;
}
if (other) {
if (!res.isCompatible(other)) throw "redefinition of struct " + tag;
if (!res.isCompatible(other)) {
if (allowEmptyStructSpecifier) {
// first scan
throw "redefinition of struct " + tag;
} else {
// second scan: other must be forward declaration
for (let i = 0; i < members.length; i++) {
other.members[i] = res.members[i];
}
return other;
}
}
} else {
env?.addTagTypeInfo(tag, res);
}
} else {
if (s.declarationList.length === 0) throw "empty struct specifier";
if (s.declarationList.length === 0) {
if (allowEmptyStructSpecifier) return res;
throw "empty struct specifier";
}
}
return res;
};
Expand Down
80 changes: 63 additions & 17 deletions src/typing/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,17 @@ export const isSigned = (t: ScalarType): boolean =>

export type AggregateType = Array | Structure;

export const isAggregateType = (t: TypeInfo): t is AggregateType =>
isArray(t) || isStructure(t);

export type DerviedDeclaratorType = Array | FunctionType | Pointer;

// maximum recursion depth for isCompatible checks to deal with cyclic types
export const COMPATIBLE_CHECK_MAX_DEPTH = 32;

export interface BaseTypeInfo {
type: Type;
isCompatible: (other: TypeInfo) => boolean;
isCompatible: (other: TypeInfo, depth?: number) => boolean;
}

export type TypeInfo = ObjectTypeInfo | IncompleteTypeInfo | FunctionTypeInfo;
Expand Down Expand Up @@ -385,6 +391,7 @@ export interface Array extends ObjectTypeInfo {
type: Type.Array;
elementType: ObjectTypeInfo;
length: number;
recalculateSizeAndAlignment: (depth?: number) => void;
}

export const array = (elementType: ObjectTypeInfo, length: number): Array => ({
Expand All @@ -393,13 +400,20 @@ export const array = (elementType: ObjectTypeInfo, length: number): Array => ({
alignment: elementType.alignment,
elementType,
length,
isCompatible: (other: TypeInfo) => {
isCompatible(other: TypeInfo, depth: number = 0) {
if (depth > COMPATIBLE_CHECK_MAX_DEPTH) return true;
return (
isArray(other) &&
length === other.length &&
elementType.isCompatible(other.elementType)
this.length === other.length &&
this.elementType.isCompatible(other.elementType, depth + 1)
);
},
recalculateSizeAndAlignment(depth: number = 0) {
if (!isAggregateType(this.elementType)) return;
this.elementType.recalculateSizeAndAlignment(depth + 1);
this.size = this.elementType.size * length;
this.alignment = this.elementType.alignment;
},
});

export const isArray = (t: TypeInfo): t is Array => t.type === Type.Array;
Expand All @@ -414,6 +428,7 @@ export interface Structure extends ObjectTypeInfo {
type: Type.Structure;
tag?: string;
members: StructureMember[];
recalculateSizeAndAlignment: (depth?: number) => void;
}

export const structure = (
Expand All @@ -424,7 +439,7 @@ export const structure = (
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(...m.map((i) => i.type.alignment));
const strictestAlignment = Math.max(1, ...m.map((i) => i.type.alignment));
const members: StructureMember[] = [];

let currAddr = 0; // next free addr
Expand All @@ -438,19 +453,46 @@ export const structure = (

const res: Structure = {
type: Type.Structure,
size: currAddr,
size: Math.max(1, currAddr),
alignment: strictestAlignment,
members,
isCompatible: (other: TypeInfo) => {
// TODO: memoize this, currently exponential in number of nested structs
recalculateSizeAndAlignment(depth: number = 0) {
if (depth > COMPATIBLE_CHECK_MAX_DEPTH)
throw new Error(
"struct " +
this.tag +
" cannot contain itself (do you mean to use a pointer instead?)",
);
this.members.forEach((i) => {
if (isAggregateType(i.type))
i.type.recalculateSizeAndAlignment(depth + 1);
});
const strictestAlignment = Math.max(
1,
...this.members.map((i) => i.type.alignment),
);
let currAddr = 0;
for (const i of this.members) {
currAddr = roundUpM(currAddr, i.type.alignment);
currAddr += i.type.size;
}
currAddr = roundUpM(currAddr, strictestAlignment);

this.size = Math.max(1, currAddr);
this.alignment = strictestAlignment;
},
isCompatible(other: TypeInfo, depth: number = 0) {
if (depth > COMPATIBLE_CHECK_MAX_DEPTH) return true;
return (
isStructure(other) &&
other.tag === tag &&
other.members.length === members.length &&
other.tag === this.tag &&
other.members.length === this.members.length &&
other.members.reduce(
(A, m, i) =>
A &&
m.name === members[i].name &&
m.type.isCompatible(members[i].type),
m.name === this.members[i].name &&
m.type.isCompatible(this.members[i].type, depth + 1),
true,
)
);
Expand All @@ -476,9 +518,11 @@ export const pointer = (referencedType: TypeInfo): Pointer => ({
size: INT_SIZE,
alignment: INT_ALIGN,
referencedType,
isCompatible: (other: TypeInfo) => {
isCompatible(other: TypeInfo, depth: number = 0) {
if (depth > COMPATIBLE_CHECK_MAX_DEPTH) return true;
return (
isPointer(other) && other.referencedType.isCompatible(referencedType)
isPointer(other) &&
other.referencedType.isCompatible(this.referencedType, depth + 1)
);
},
});
Expand Down Expand Up @@ -574,13 +618,15 @@ export const functionType = (

return {
...res,
isCompatible: (other: TypeInfo): boolean => {
isCompatible(other: TypeInfo, depth: number = 0) {
if (depth > COMPATIBLE_CHECK_MAX_DEPTH) return true;
return (
isFunction(other) &&
other.returnType.isCompatible(returnType) &&
other.arity === res.arity &&
other.returnType.isCompatible(this.returnType, depth + 1) &&
other.arity === this.arity &&
other.parameterTypes.reduce(
(A, p, i) => A && p.type.isCompatible(res.parameterTypes[i].type),
(A, p, i) =>
A && p.type.isCompatible(this.parameterTypes[i].type, depth + 1),
true,
)
);
Expand Down
Loading

0 comments on commit 40ce8dc

Please sign in to comment.