From f82773b69437004da8c2ccbf3fae5052da55472a Mon Sep 17 00:00:00 2001 From: Cai Date: Fri, 26 Apr 2024 04:14:02 +0800 Subject: [PATCH 1/4] add skipStrictAliasingCheck to config --- src/config.ts | 8 ++++++++ src/interpreter/runtime.ts | 6 +++++- src/memory/memory.ts | 9 ++++++++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/config.ts b/src/config.ts index b193c52..dc3b8da 100644 --- a/src/config.ts +++ b/src/config.ts @@ -12,9 +12,14 @@ export interface MemoryConfig { export type Endianness = "little" | "big"; +export interface UBConfig { + skipStrictAliasingCheck: boolean; +} + export interface RuntimeConfig { memory: MemoryConfig; endianness: Endianness; + UB: UBConfig; } export const DEFAULT_CONFIG: RuntimeConfig = { @@ -37,4 +42,7 @@ export const DEFAULT_CONFIG: RuntimeConfig = { }, }, endianness: "little", + UB: { + skipStrictAliasingCheck: false, + }, }; diff --git a/src/interpreter/runtime.ts b/src/interpreter/runtime.ts index dfa3a60..9d1e3ef 100644 --- a/src/interpreter/runtime.ts +++ b/src/interpreter/runtime.ts @@ -95,7 +95,11 @@ export class Runtime { this.symbolTable = new SymbolTable(); this.config = config; this.effectiveTypeTable = new EffectiveTypeTable(); - this.memory = new Memory(config.memory, this.effectiveTypeTable); + this.memory = new Memory( + config.memory, + this.effectiveTypeTable, + config.UB.skipStrictAliasingCheck, + ); this.stack = new RuntimeStack(config.memory.stack.baseAddress); this.functionCalls = new Stack(); this.heap = new Heap( diff --git a/src/memory/memory.ts b/src/memory/memory.ts index 9e20161..cf11e36 100644 --- a/src/memory/memory.ts +++ b/src/memory/memory.ts @@ -60,8 +60,13 @@ export class Memory { private readonly readonlyAddresses: Set; private readonly executableAddresses: Set; private readonly effectiveTypeTable: EffectiveTypeTable; + private readonly skipStrictAliasingCheck: boolean; - constructor(config: MemoryConfig, effectiveTypeTable: EffectiveTypeTable) { + constructor( + config: MemoryConfig, + effectiveTypeTable: EffectiveTypeTable, + skipStrictAliasingCheck: boolean = false, + ) { checkMemoryConfig(config); this.segments = { stack: new MemoryRegion(config.stack), @@ -72,6 +77,7 @@ export class Memory { this.readonlyAddresses = new Set(); this.executableAddresses = new Set(); this.effectiveTypeTable = effectiveTypeTable; + this.skipStrictAliasingCheck = skipStrictAliasingCheck; } private getMemoryRegion(address: number): MemoryRegion { @@ -181,6 +187,7 @@ export class Memory { throw new Error("no object allocated at " + decimalAddressToHex(address)); } + if (this.skipStrictAliasingCheck) return true; if (et === NO_EFFECTIVE_TYPE) return true; if (isChar(t) || isUnsignedChar(t) || isSignedChar(t)) return true; From 5857e551d34cca8b2a3b7193736d46e03a6945f9 Mon Sep 17 00:00:00 2001 From: Cai Date: Fri, 26 Apr 2024 05:33:59 +0800 Subject: [PATCH 2/4] add print function --- src/builtins.ts | 14 ++++++++++- src/interpreter/object.ts | 49 ++++++++++++++++++++++++++++++++++++-- src/interpreter/runtime.ts | 12 ++++++++++ src/typing/main.ts | 6 +++++ src/typing/types.ts | 18 +++++++++++++- 5 files changed, 95 insertions(+), 4 deletions(-) diff --git a/src/builtins.ts b/src/builtins.ts index 015ee28..32612e1 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -1,12 +1,13 @@ import { Identifier } from "./ast/types"; import { NO_EFFECTIVE_TYPE } from "./interpreter/effectiveTypeTable"; -import { TemporaryObject } from "./interpreter/object"; +import { TemporaryObject, stringify } 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, + _any, functionType, int, isArithmeticType, @@ -73,4 +74,15 @@ export const BUILTIN_FUNCTIONS: Record = { rt.effectiveTypeTable.remove(n, blockSize); }, }, + print: { + type: functionType(voidType(), [{ identifier: "obj", type: _any() }]), + body: (rt: Runtime, args: StashItem[]) => { + if (args.length !== 1) throw "expected 1 arg for print"; + const o = args[0]; + if (!isTemporaryObject(o)) throw "expected object for print"; + rt.appendToStdout( + stringify(o.bytes, o.typeInfo, rt.config.endianness) + "\n", + ); + }, + }, }; diff --git a/src/interpreter/object.ts b/src/interpreter/object.ts index a0f95e7..5118a45 100644 --- a/src/interpreter/object.ts +++ b/src/interpreter/object.ts @@ -1,8 +1,18 @@ -import { FunctionType } from "./../typing/types"; +import { + FunctionType, + isArray, + isChar, + isPointer, + isScalarType, + isSigned, + isStructure, +} from "./../typing/types"; import { Identifier } from "./../ast/types"; import { ObjectTypeInfo } from "../typing/types"; -import { checkValidByte } from "../utils"; +import { checkValidByte, decimalAddressToHex } from "../utils"; import { Memory } from "../memory/memory"; +import { Endianness } from "../config"; +import { bytesToBigint } from "../typing/representation"; interface Identifiable { identifier: Identifier; @@ -113,3 +123,38 @@ export class RuntimeObject this._identifier = i; } } + +export const stringify = ( + bytes: number[], + t: ObjectTypeInfo, + endianness: Endianness, +): string => { + if (bytes.length !== t.size) + throw new Error("number of bytes do not match type given"); + if (isScalarType(t)) { + const n = bytesToBigint(bytes, isSigned(t), endianness); + if (isChar(t)) return "'" + encodeURI(String.fromCharCode(Number(n))) + "'"; + if (isPointer(t)) { + if (n === BigInt(0)) return "NULL"; + return decimalAddressToHex(Number(n)); + } + return n.toString(); + } + if (isArray(t)) { + const et = t.elementType; + const res = []; + for (let i = 0; i < t.size; i += et.size) { + res.push(stringify(bytes.slice(i, i + et.size), et, endianness)); + } + return "[" + res.join(", ") + "]"; + } + if (isStructure(t)) { + const res = []; + for (const i of t.members) { + const b = bytes.slice(i.relativeAddress, i.relativeAddress + i.type.size); + res.push(stringify(b, i.type, endianness)); + } + return (t.tag || "") + "{" + res.join(", ") + "}"; + } + return "?"; +}; diff --git a/src/interpreter/runtime.ts b/src/interpreter/runtime.ts index 9d1e3ef..664bff1 100644 --- a/src/interpreter/runtime.ts +++ b/src/interpreter/runtime.ts @@ -47,6 +47,7 @@ export class RuntimeView { heapMemUsage: number; effectiveTypeTable: Record; initTable: InitializedTable; + stdout: string; constructor(rt: Runtime) { this.agenda = rt.agenda.getArr(); @@ -66,6 +67,7 @@ export class RuntimeView { this.heapMemUsage = rt.heap.memUsage; this.effectiveTypeTable = rt.effectiveTypeTable.getTable(); this.initTable = cloneDeep(rt.initTable); + this.stdout = rt.stdout; } } @@ -85,6 +87,7 @@ export class Runtime { private functions: [string, TypedCompoundStatement, FunctionType][]; private builtinFunctions: BuiltinFunction[]; private _exitCode: number | undefined; + private _stdout: string; private _dataPtr: number; private _textPtr: number; @@ -111,6 +114,7 @@ export class Runtime { this.functions = []; this.builtinFunctions = []; this._exitCode = undefined; + this._stdout = ""; this._dataPtr = config.memory.data.baseAddress; this._textPtr = config.memory.text.baseAddress; @@ -151,6 +155,14 @@ export class Runtime { return this._exitCode; } + public appendToStdout(s: string) { + this._stdout += s; + } + + public get stdout() { + return this._stdout; + } + public addFunction( identifier: string, body: TypedCompoundStatement, diff --git a/src/typing/main.ts b/src/typing/main.ts index 36375b5..2a8d96a 100644 --- a/src/typing/main.ts +++ b/src/typing/main.ts @@ -157,6 +157,7 @@ import { Array, Structure, unsignedInt, + Type, } from "./types"; import { checkSimpleAssignmentConstraint, @@ -1136,6 +1137,11 @@ const typePostfixExpressionNode = ( if (value.length !== ft.arity) throw "function call has wrong number of arguments"; ft.parameterTypes.forEach((p, i) => { + if (p.type.type === Type._Any) { + if (!isObjectTypeInfo(value[i].typeInfo)) + throw "expected parameter to have object type"; + return; + } if ( !checkSimpleAssignmentConstraint( p.type, diff --git a/src/typing/types.ts b/src/typing/types.ts index 214a895..c807458 100644 --- a/src/typing/types.ts +++ b/src/typing/types.ts @@ -45,6 +45,8 @@ export enum Type { Structure = "struct", Function = "fn", Pointer = "ptr", + + _Any = "any object", // hack for inbuilt print function } export const getTypeName = (i: TypeInfo): string => { @@ -73,7 +75,8 @@ export type ObjectType = | Type.UnsignedLongLongInt | Type.Array | Type.Structure - | Type.Pointer; + | Type.Pointer + | Type._Any; export type IncompleteType = Type.Void; @@ -175,6 +178,19 @@ export interface ObjectTypeInfo extends BaseTypeInfo { export const isObjectTypeInfo = (i: BaseTypeInfo): i is ObjectTypeInfo => !isIncompleteTypeInfo(i) && !isFunctionTypeInfo(i); +export interface _Any extends BaseTypeInfo { + type: Type._Any; + size: -1; + alignment: -1; +} + +export const _any = (): _Any => ({ + type: Type._Any, + size: -1, + alignment: -1, + isCompatible: () => false, +}); + export interface _Bool extends ObjectTypeInfo { type: Type._Bool; size: typeof CHAR_SIZE; From 2136dc17423c29c1894a62e353dbc01d7af944b1 Mon Sep 17 00:00:00 2001 From: Cai Date: Fri, 26 Apr 2024 13:50:29 +0800 Subject: [PATCH 3/4] implement more operators --- src/interpreter/evaluators.ts | 171 ++++++++++++++++++++++++++++++++-- 1 file changed, 161 insertions(+), 10 deletions(-) diff --git a/src/interpreter/evaluators.ts b/src/interpreter/evaluators.ts index 4758e71..a20eeb0 100644 --- a/src/interpreter/evaluators.ts +++ b/src/interpreter/evaluators.ts @@ -192,7 +192,6 @@ export const ASTNodeEvaluator: { rt: Runtime, { value: expr }: TypedJumpStatementReturn, ) => { - // TODO: conversion as if by assignment rt.agenda.push(returnInstruction()); if (expr) rt.agenda.push(expr); }, @@ -274,6 +273,9 @@ export const ASTNodeEvaluator: { evaluateAsLvalue: boolean, ) => { switch (op) { + case "!": + case "+": + case "~": case "-": { rt.agenda.push(unaryOpInstruction(op)); rt.agenda.push(expr); @@ -539,15 +541,16 @@ export const instructionEvaluator: { [InstructionType.UNARY_OP]: (rt: Runtime, { op }: UnaryOpInstruction) => { const v = rt.stash.pop(); switch (op) { + case "+": case "-": { if (!(isTemporaryObject(v) && isIntegerType(v.typeInfo))) - throw new Error("operand of - should be an integer value"); + throw new Error("operand of unary +/- should be an integer value"); let n = bytesToBigint( v.bytes, isSigned(v.typeInfo), rt.config.endianness, ); - n = -n; + if (op === "-") n = -n; rt.stash.pushWithoutConversions( new TemporaryObject( v.typeInfo, @@ -556,6 +559,26 @@ export const instructionEvaluator: { ); return; } + case "!": { + if (!(isTemporaryObject(v) && isScalarType(v.typeInfo))) + throw new Error("operand of ! should be of scalar type"); + let n = bytesToBigint( + v.bytes, + isSigned(v.typeInfo), + rt.config.endianness, + ); + n = n === BigInt(0) ? BigInt(1) : BigInt(0); + rt.stash.pushWithoutConversions( + new TemporaryObject( + v.typeInfo, + BIGINT_TO_BYTES[v.typeInfo.type](n, rt.config.endianness), + ), + ); + return; + } + case "~": { + throw new Error("not implemented"); + } case "*": { if (!(isTemporaryObject(v) && isPointer(v.typeInfo))) throw new Error("operand of * should have pointer type"); @@ -584,7 +607,7 @@ export const instructionEvaluator: { throw new Error("invalid dereference"); } case "&": { - throw new Error("not implemented"); + throw new Error("invariant broken"); } } throw new Error("not implemented"); @@ -655,9 +678,63 @@ export const instructionEvaluator: { rt.stash.pushWithoutConversions(res); return; } - case "-": - // apply usual arithmetic conversions - break; + case "-": { + let res: TemporaryObject | undefined = undefined; + + if (isArithmeticType(t0) && isArithmeticType(t1)) { + let l = bytesToBigint(lo.bytes, isSigned(t0), rt.config.endianness); + let r = bytesToBigint(ro.bytes, isSigned(t1), rt.config.endianness); + const ct = applyUsualArithmeticConversions(t0, t1); + l = convertValue(l, ct, rt.config.endianness); + r = convertValue(r, ct, rt.config.endianness); + res = new TemporaryObject( + ct, + 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 - (pointer - pointer not implemented yet)"); + rt.stash.pushWithoutConversions(res); + return; + } case "*": case "/": case "%": { @@ -690,8 +767,50 @@ export const instructionEvaluator: { } case "==": case "!=": { - // apply usual arithmetic conversions before comparing - break; + let isTruthy: boolean | undefined = undefined; + + if (isArithmeticType(t0) && isArithmeticType(t1)) { + let l = bytesToBigint(lo.bytes, isSigned(t0), rt.config.endianness); + let r = bytesToBigint(ro.bytes, isSigned(t1), rt.config.endianness); + const ct = applyUsualArithmeticConversions(t0, t1); + l = convertValue(l, ct, rt.config.endianness); + r = convertValue(r, ct, rt.config.endianness); + switch (op) { + case "==": { + isTruthy = l == r; + break; + } + case "!=": { + isTruthy = l != r; + break; + } + } + } + + if (isScalarType(t0) && isScalarType(t1)) { // TODO: improve type checking here for this (supposed to be ptrs) + const l = bytesToBigint(lo.bytes, isSigned(t0), rt.config.endianness); + const r = bytesToBigint(ro.bytes, isSigned(t1), rt.config.endianness); + switch (op) { + case "==": { + isTruthy = l == r; + break; + } + case "!=": { + isTruthy = l != r; + break; + } + } + } + + if (isTruthy === undefined) + throw new Error("invalid types for ==, !="); + const res = BIGINT_TO_BYTES[Type.Int]( + isTruthy ? BigInt(1) : BigInt(0), + rt.config.endianness, + ); + const t = new TemporaryObject(int(), res); + rt.stash.pushWithoutConversions(t); + return; } case "<": case ">": @@ -735,11 +854,43 @@ export const instructionEvaluator: { rt.stash.pushWithoutConversions(t); return; } + case "<<": + case ">>": case "^": case "&": case "|": { // apply usual arithmetic conversions - break; + throw new Error("bitwise binary operators not implemented"); + } + case "&&": + case "||": { + // TODO: implement short-circuit evaluation + let isTruthy: boolean | undefined = undefined; + + if (isScalarType(t0) && isScalarType(t1)) { + const l = bytesToBigint(lo.bytes, isSigned(t0), rt.config.endianness); + const r = bytesToBigint(ro.bytes, isSigned(t1), rt.config.endianness); + switch (op) { + case "&&": { + isTruthy = (l !== BigInt(0)) && (r !== BigInt(0)); + break; + } + case "||": { + isTruthy = (l !== BigInt(0)) || (r !== BigInt(0)); + break; + } + } + } + + if (isTruthy === undefined) + throw new Error("invalid types for &&, ||"); + const res = BIGINT_TO_BYTES[Type.Int]( + isTruthy ? BigInt(1) : BigInt(0), + rt.config.endianness, + ); + const t = new TemporaryObject(int(), res); + rt.stash.pushWithoutConversions(t); + return; } } throw new Error("unknown binary operator"); From f9cd520c5eca42223759addc338ed97a4f599f60 Mon Sep 17 00:00:00 2001 From: Cai Date: Fri, 26 Apr 2024 13:51:33 +0800 Subject: [PATCH 4/4] publish v0.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8fa261e..8ff47f3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "c-viz", - "version": "0.1.0", + "version": "0.1.1", "description": "", "nyc": { "extension": [