From 400867d04e56ea1997b2e64a7eb024be95dbacb5 Mon Sep 17 00:00:00 2001 From: Shen Yi Hong Date: Sun, 31 Mar 2024 14:06:19 +0800 Subject: [PATCH 01/13] refactor: abstract `AstMap` as a class object This is mainly so that we are able to maintain a state to keep track of what is the next `uid` for a AST node. We need to produce more uid since we may do runtime transpilation (e.g. `ForClause` -> `ForCondition`) that introduces more AST nodes. A more straightforward example would be the inject of `main()` call expression to the AST before the execution, where the `CallExpression` needs a uid. --- src/go-slang/ece.ts | 14 ++++++++------ src/go-slang/lib/astMap.ts | 25 +++++++++++++++++++++++++ src/go-slang/lib/env.ts | 6 ------ 3 files changed, 33 insertions(+), 12 deletions(-) create mode 100644 src/go-slang/lib/astMap.ts diff --git a/src/go-slang/ece.ts b/src/go-slang/ece.ts index d7146952e..2932af7be 100644 --- a/src/go-slang/ece.ts +++ b/src/go-slang/ece.ts @@ -3,10 +3,11 @@ import { Stack } from '../cse-machine/utils' import { RuntimeSourceError } from '../errors/runtimeSourceError' import { Value } from '../types' import { FuncArityError, UndefinedError, UnknownInstructionError } from './error' +import { AstMap } from './lib/astMap' import { evaluateBinaryOp } from './lib/binaryOp' -import { Environment, createGlobalEnvironment } from './lib/env' +import { Environment } from './lib/env' import { Heap, HeapAddress } from './lib/heap' -import { PREDECLARED_FUNCTIONS } from './lib/predeclared' +import { PREDECLARED_FUNCTIONS, PREDECLARED_IDENTIFIERS } from './lib/predeclared' import { zip, isAny } from './lib/utils' import { ApplyBuiltinOp, @@ -52,7 +53,6 @@ import { type Control = Stack type Stash = Stack type Builtins = Map any> -type AstMap = Map interface Context { C: Control @@ -72,9 +72,11 @@ const CALL_MAIN: CallExpression = { export function evaluate(program: SourceFile, slangContext: SlangContext): Value { const C = new Stack() const S = new Stack() - const E = createGlobalEnvironment() - const H = new Heap() - const A = new Map() + const E = new Environment({ ...PREDECLARED_IDENTIFIERS }) + + // `SourceFile` is the root node of the AST which has latest (monotonically increasing) uid of all AST nodes + // Therefore, the next uid to be used to track AST nodes is the uid of SourceFile + 1 + const A = new AstMap((program.uid as number) + 1) // inject predeclared functions into the global environment const B = new Map any>() diff --git a/src/go-slang/lib/astMap.ts b/src/go-slang/lib/astMap.ts new file mode 100644 index 000000000..6d56db944 --- /dev/null +++ b/src/go-slang/lib/astMap.ts @@ -0,0 +1,25 @@ +import { Node } from '../types' + +export class AstMap extends Map { + private nextAstId: number + + constructor(nextAstId: number) { + super() + this.nextAstId = nextAstId + } + + /** + * Track an AST node + * + * If the node already has a unique identifier, it will be returned as is. + * Otherwise, a new unique identifier will be assigned to the node (in-place). + * + * @param node AST node to track + * @returns AST node with a unique identifier + */ + public track(node: Node): Node { + node.uid = node.uid ?? this.nextAstId++ + this.set(node.uid, node) + return node + } +} diff --git a/src/go-slang/lib/env.ts b/src/go-slang/lib/env.ts index bac0adba0..55b664cee 100644 --- a/src/go-slang/lib/env.ts +++ b/src/go-slang/lib/env.ts @@ -1,5 +1,3 @@ -import { PREDECLARED_IDENTIFIERS } from './predeclared' - type Maybe = T | null interface Frame { @@ -65,7 +63,3 @@ export class Environment { return this } } - -export function createGlobalEnvironment(): Environment { - return new Environment({ ...PREDECLARED_IDENTIFIERS }) -} From 43f9432debffa15415986fc14cbad9d35fdb704b Mon Sep 17 00:00:00 2001 From: Shen Yi Hong Date: Sun, 31 Mar 2024 14:12:15 +0800 Subject: [PATCH 02/13] =?UTF-8?q?chore:=20remove=20`husky`=20for=20better?= =?UTF-8?q?=20DX=20=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `pre-commit` and `pre-push` hooks are super slow and we don't have any tests right now so I decided to remove it temporarily (we can always revert it later). --- .husky/.gitignore | 1 - .husky/pre-commit | 4 ---- .husky/pre-push | 4 ---- 3 files changed, 9 deletions(-) delete mode 100644 .husky/.gitignore delete mode 100755 .husky/pre-commit delete mode 100755 .husky/pre-push diff --git a/.husky/.gitignore b/.husky/.gitignore deleted file mode 100644 index c9cdc63b0..000000000 --- a/.husky/.gitignore +++ /dev/null @@ -1 +0,0 @@ -_ \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100755 index f67d43ffd..000000000 --- a/.husky/pre-commit +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -yarn eslint --fix && yarn format && yarn eslint \ No newline at end of file diff --git a/.husky/pre-push b/.husky/pre-push deleted file mode 100755 index 9e5ab0cee..000000000 --- a/.husky/pre-push +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -yarn jsdoc && yarn autocomplete && yarn format:ci && yarn eslint && yarn test From e8636afeab7de13be662cd757c1139c521511d96 Mon Sep 17 00:00:00 2001 From: Shen Yi Hong Date: Sun, 31 Mar 2024 14:18:12 +0800 Subject: [PATCH 03/13] feat: implement `AstNode` and `CallOp` support in the heap Added helper functions in the heap to allocate/resolve multiple values. This would prevent having to `H.(alloc/resolve).bind(H)` multiple times in the ECE --- src/go-slang/lib/heap/index.ts | 56 ++++++++++++++++++++++++++++++++-- src/go-slang/lib/heap/tags.ts | 2 ++ src/go-slang/types.ts | 6 +++- 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/go-slang/lib/heap/index.ts b/src/go-slang/lib/heap/index.ts index 0a597006c..c6f0fbcb7 100644 --- a/src/go-slang/lib/heap/index.ts +++ b/src/go-slang/lib/heap/index.ts @@ -1,4 +1,5 @@ -import { BuiltinOp, ClosureOp, CommandType, isCommand } from '../../types' +import { BuiltinOp, CallOp, ClosureOp, CommandType, Node, isCommand, isNode } from '../../types' +import { AstMap } from '../astMap' import { DEFAULT_HEAP_SIZE, WORD_SIZE } from './config' import { PointerTag } from './tags' @@ -8,7 +9,11 @@ export class Heap { private memory: DataView private free: number = 0 // n_words to the next free block - constructor(n_words?: number) { + // we need to keep track of the AstMap to be able to resolve AST nodes stored in the heap + private astMap: AstMap + + constructor(astMap: AstMap, n_words?: number) { + this.astMap = astMap this.memory = new DataView(new ArrayBuffer((n_words ?? DEFAULT_HEAP_SIZE) * WORD_SIZE)) // Allocate special values in the heap to avoid re-allocating them @@ -36,9 +41,17 @@ export class Heap { return this.allocateNumber(value) } + // AST nodes + if (isNode(value)) { + // we need to track the AST node to be able to resolve it later + return this.allocateAstNode(this.astMap.track(value)) + } + // ECE operations if (isCommand(value)) { switch (value.type) { + case CommandType.CallOp: + return this.allocateCallOp(value) case CommandType.BuiltinOp: return this.allocateBuiltinOp(value) case CommandType.ClosureOp: @@ -49,6 +62,10 @@ export class Heap { return value } + public allocM(values: any[]): HeapAddress[] { + return values.map(this.alloc.bind(this)) + } + /** * Resolve a heap address to its underlying data/value * @@ -69,6 +86,14 @@ export class Heap { return true case PointerTag.Number: return this.get(heap_addr + 1) + case PointerTag.AstNode: + return this.astMap.get(this.memory.getInt16(mem_addr + 1)) + case PointerTag.CallOp: + return { + type: CommandType.CallOp, + calleeNodeId: this.memory.getInt16(mem_addr + 1), + arity: this.memory.getInt16(mem_addr + 3) + } as CallOp case PointerTag.BuiltInOp: return { type: CommandType.BuiltinOp, @@ -84,6 +109,10 @@ export class Heap { } } + public resolveM(heap_addrs: any[]): any[] { + return heap_addrs.map(this.resolve.bind(this)) + } + private allocateBoolean(value: boolean): HeapAddress { // booleans are represented as tagged pointers with no underlying data return this.allocateTaggedPtr(value ? PointerTag.True : PointerTag.False, 0) @@ -99,6 +128,29 @@ export class Heap { return ptr_heap_addr } + /* Memory Layout of an AST Node: [0:tag, 1-2:astId, 3-4:_unused_, 5-6:size, 7:_unused_] (1 word) */ + private allocateAstNode({ uid }: Node): HeapAddress { + const ptr_heap_addr = this.allocateTaggedPtr(PointerTag.AstNode) + + const ptr_mem_addr = ptr_heap_addr * WORD_SIZE + this.memory.setInt16(ptr_mem_addr + 1, uid as number) + + return ptr_heap_addr + } + + /* Memory Layout of a CallOp: [0:tag, 1-2:calleeNodeId, 3-4:arity, 5-6:size, 7:_unused_] (1 word) */ + private allocateCallOp({ calleeNodeId, arity }: CallOp): HeapAddress { + const ptr_heap_addr = this.allocateTaggedPtr(PointerTag.CallOp) + + const ptr_mem_addr = ptr_heap_addr * WORD_SIZE + // NOTE: assume there will be no more than 2^16 AST nodes + this.memory.setInt16(ptr_mem_addr + 1, calleeNodeId) + // NOTE: assume there will be no more than 2^16 arguments + this.memory.setInt16(ptr_mem_addr + 3, arity) + + return ptr_heap_addr + } + /* Memory Layout of a BuiltinOp: [0:tag, 1-2: arity, 3-4:id, 5-6:size, 7:_unused_] (1 word) */ private allocateBuiltinOp({ arity, id }: BuiltinOp): HeapAddress { const ptr_heap_addr = this.allocateTaggedPtr(PointerTag.BuiltInOp) diff --git a/src/go-slang/lib/heap/tags.ts b/src/go-slang/lib/heap/tags.ts index c18f26db6..dbdecf902 100644 --- a/src/go-slang/lib/heap/tags.ts +++ b/src/go-slang/lib/heap/tags.ts @@ -2,6 +2,8 @@ export enum PointerTag { False = 0, True, Number, + AstNode, + CallOp, BuiltInOp, ClosureOp } diff --git a/src/go-slang/types.ts b/src/go-slang/types.ts index e22b14e37..5f97638d3 100644 --- a/src/go-slang/types.ts +++ b/src/go-slang/types.ts @@ -54,6 +54,10 @@ export interface Node { loc?: NodeLocation } +export function isNode(v: any): boolean { + return v && v.type && NodeType[v.type] +} + export interface SourceFile extends Node { type: NodeType.SourceFile topLevelDecls: TopLevelDeclaration[] @@ -241,7 +245,7 @@ export interface ClosureOp extends Command { export interface CallOp extends Command { type: CommandType.CallOp arity: number - calleeName: string + calleeNodeId: number } export interface BranchOp extends Command { From 9f8e24948331488225514f3142f76cdc02321f6e Mon Sep 17 00:00:00 2001 From: Shen Yi Hong Date: Sun, 31 Mar 2024 14:29:43 +0800 Subject: [PATCH 04/13] feat: integrate AST nodes and `CallOp` with heap Bunch of minor refactors to make code more readable --- src/go-slang/ece.ts | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/go-slang/ece.ts b/src/go-slang/ece.ts index 2932af7be..25bcbc0c6 100644 --- a/src/go-slang/ece.ts +++ b/src/go-slang/ece.ts @@ -35,7 +35,6 @@ import { IfStatement, Instruction, Literal, - Node, NodeType, PopS, PopTillM, @@ -50,7 +49,7 @@ import { VariableDeclaration } from './types' -type Control = Stack +type Control = Stack type Stash = Stack type Builtins = Map any> @@ -70,13 +69,14 @@ const CALL_MAIN: CallExpression = { } export function evaluate(program: SourceFile, slangContext: SlangContext): Value { - const C = new Stack() + const C = new Stack() const S = new Stack() const E = new Environment({ ...PREDECLARED_IDENTIFIERS }) // `SourceFile` is the root node of the AST which has latest (monotonically increasing) uid of all AST nodes // Therefore, the next uid to be used to track AST nodes is the uid of SourceFile + 1 const A = new AstMap((program.uid as number) + 1) + const H = new Heap(A) // inject predeclared functions into the global environment const B = new Map any>() @@ -95,10 +95,10 @@ export function evaluate(program: SourceFile, slangContext: SlangContext): Value const Context = { C, S, E, B, H, A } // start the program by calling `main` - C.pushR(program, CALL_MAIN) + C.pushR(H.alloc(program), H.alloc(CALL_MAIN)) while (!C.isEmpty()) { - const inst = C.pop() as Instruction + const inst = H.resolve(C.pop()) as Instruction if (!interpreter.hasOwnProperty(inst.type)) { slangContext.errors.push(new UnknownInstructionError(inst.type)) @@ -112,13 +112,16 @@ export function evaluate(program: SourceFile, slangContext: SlangContext): Value } } + // TEMP: for debugging purposes + console.log('Total memory allocated: ', H._debug_num_allocations() * 8, 'bytes') + return 'Program exited' } const interpreter: { [key: string]: (inst: Instruction, context: Context) => RuntimeSourceError | void } = { - SourceFile: ({ topLevelDecls }: SourceFile, { C }) => C.pushR(...topLevelDecls), + SourceFile: ({ topLevelDecls }: SourceFile, { C, H }) => C.pushR(...H.allocM(topLevelDecls)), FunctionDeclaration: (funcDeclNode: FunctionDeclaration, { E, A }) => { const { id, uid: funcDeclNodeUid } = funcDeclNode @@ -195,12 +198,12 @@ const interpreter: { BinaryExpression: ({ left, right, operator }: BinaryExpression, { C }) => C.pushR(left, right, { type: CommandType.BinaryOp, operator: operator }), - CallExpression: ({ callee, args }: CallExpression, { C }) => - C.pushR(callee, ...args, { - type: CommandType.CallOp, - calleeName: callee.name, - arity: args.length - }), + CallExpression: ({ callee, args }: CallExpression, { C, H, A }) => + C.pushR( + H.alloc(callee), + ...H.allocM(args), + H.alloc({ type: CommandType.CallOp, calleeNodeId: A.track(callee).uid, arity: args.length }) + ), ExpressionStatement: ({ expression }: ExpressionStatement, { C }) => C.pushR(expression, PopS), @@ -216,12 +219,12 @@ const interpreter: { }, BinaryOp: ({ operator }: BinaryOp, { S, H }) => { - const [left, right] = S.popNR(2).map(H.resolve.bind(H)) + const [left, right] = H.resolveM(S.popNR(2)) S.push(H.alloc(evaluateBinaryOp(operator, left, right))) }, - CallOp: ({ calleeName, arity }: CallOp, { C, S, E, H, A }) => { - const values = S.popNR(arity).map(H.resolve.bind(H)) + CallOp: ({ calleeNodeId, arity }: CallOp, { C, S, E, H, A }) => { + const values = H.resolveM(S.popNR(arity)) const op = H.resolve(S.pop()) as ClosureOp | BuiltinOp if (op.type === CommandType.BuiltinOp) { @@ -234,7 +237,8 @@ const interpreter: { const paramNames = params.map(({ name }) => name) if (paramNames.length !== values.length) { - return new FuncArityError(calleeName, values.length, params.length) + const calleeId = A.get(calleeNodeId) as Identifier + return new FuncArityError(calleeId.name, values.length, params.length) } C.pushR(body, RetMarker, { type: CommandType.EnvOp, envId: E.id() }) From e0caae70960228e0d986e35d338d0303bb0b493e Mon Sep 17 00:00:00 2001 From: Shen Yi Hong Date: Sun, 31 Mar 2024 15:02:00 +0800 Subject: [PATCH 05/13] fix: update uid of `CALL_MAIN` for each evaluate Currently, we are doing a in-place uid assign to `CALL_MAIN` and we only assign a uid if there no uid in the node. However, since `CALL_MAIN` in the outer scope of `evaluate`, we do not assign the right uid on each run of `evaluate`, since the `SourceFile` uid changes. --- src/go-slang/ece.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/go-slang/ece.ts b/src/go-slang/ece.ts index 25bcbc0c6..f7bd06fb8 100644 --- a/src/go-slang/ece.ts +++ b/src/go-slang/ece.ts @@ -62,12 +62,6 @@ interface Context { A: AstMap } -const CALL_MAIN: CallExpression = { - type: NodeType.CallExpression, - callee: { type: NodeType.Identifier, name: 'main' }, - args: [] -} - export function evaluate(program: SourceFile, slangContext: SlangContext): Value { const C = new Stack() const S = new Stack() @@ -95,6 +89,11 @@ export function evaluate(program: SourceFile, slangContext: SlangContext): Value const Context = { C, S, E, B, H, A } // start the program by calling `main` + const CALL_MAIN: CallExpression = { + type: NodeType.CallExpression, + callee: { type: NodeType.Identifier, name: 'main' }, + args: [] + } C.pushR(H.alloc(program), H.alloc(CALL_MAIN)) while (!C.isEmpty()) { From c48377637a0efdf78bd058757fe5910281f872de Mon Sep 17 00:00:00 2001 From: Shen Yi Hong Date: Sun, 31 Mar 2024 15:40:32 +0800 Subject: [PATCH 06/13] feat: implement support for `EnvOp` in heap --- src/go-slang/ece.ts | 9 +++------ src/go-slang/lib/heap/index.ts | 29 ++++++++++++++++++++++++++++- src/go-slang/lib/heap/tags.ts | 3 ++- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/go-slang/ece.ts b/src/go-slang/ece.ts index f7bd06fb8..5199d039c 100644 --- a/src/go-slang/ece.ts +++ b/src/go-slang/ece.ts @@ -111,9 +111,6 @@ export function evaluate(program: SourceFile, slangContext: SlangContext): Value } } - // TEMP: for debugging purposes - console.log('Total memory allocated: ', H._debug_num_allocations() * 8, 'bytes') - return 'Program exited' } @@ -128,8 +125,8 @@ const interpreter: { E.declare(id.name, { type: CommandType.ClosureOp, funcDeclNodeUid, envId: E.id() } as ClosureOp) }, - Block: ({ statements }: Block, { C, E }) => { - C.pushR(...statements, { type: CommandType.EnvOp, envId: E.id() }) + Block: ({ statements }: Block, { C, E, H }) => { + C.pushR(...statements, H.alloc({ type: CommandType.EnvOp, envId: E.id() })) E.extend({}) }, @@ -240,7 +237,7 @@ const interpreter: { return new FuncArityError(calleeId.name, values.length, params.length) } - C.pushR(body, RetMarker, { type: CommandType.EnvOp, envId: E.id() }) + C.pushR(body, RetMarker, H.alloc({ type: CommandType.EnvOp, envId: E.id() })) // set the environment to the closure's environment E.setId(envId).extend(Object.fromEntries(zip(paramNames, values))) }, diff --git a/src/go-slang/lib/heap/index.ts b/src/go-slang/lib/heap/index.ts index c6f0fbcb7..f6fa76c20 100644 --- a/src/go-slang/lib/heap/index.ts +++ b/src/go-slang/lib/heap/index.ts @@ -1,4 +1,13 @@ -import { BuiltinOp, CallOp, ClosureOp, CommandType, Node, isCommand, isNode } from '../../types' +import { + BuiltinOp, + CallOp, + ClosureOp, + CommandType, + EnvOp, + Node, + isCommand, + isNode +} from '../../types' import { AstMap } from '../astMap' import { DEFAULT_HEAP_SIZE, WORD_SIZE } from './config' import { PointerTag } from './tags' @@ -56,6 +65,8 @@ export class Heap { return this.allocateBuiltinOp(value) case CommandType.ClosureOp: return this.allocateClosureOp(value) + case CommandType.EnvOp: + return this.allocateEnvOp(value) } } @@ -106,6 +117,11 @@ export class Heap { funcDeclNodeUid: this.memory.getInt16(mem_addr + 1), envId: this.memory.getInt16(mem_addr + 3) } as ClosureOp + case PointerTag.EnvOp: + return { + type: CommandType.EnvOp, + envId: this.memory.getInt16(mem_addr + 1) + } as EnvOp } } @@ -177,6 +193,17 @@ export class Heap { return ptr_heap_addr } + /* Memory Layout of an EnvOp: [0:tag, 1-2:envId, 3-4:_unused_, 5-6:size, 7:_unused_] (1 word) */ + private allocateEnvOp({ envId }: EnvOp): HeapAddress { + const ptr_heap_addr = this.allocateTaggedPtr(PointerTag.EnvOp) + + const ptr_mem_addr = ptr_heap_addr * WORD_SIZE + // NOTE: assume there will be no more than 2^16 envs + this.memory.setInt16(ptr_mem_addr + 1, envId) + + return ptr_heap_addr + } + /** * Allocate a tagged pointer in the heap * diff --git a/src/go-slang/lib/heap/tags.ts b/src/go-slang/lib/heap/tags.ts index dbdecf902..a0c745aac 100644 --- a/src/go-slang/lib/heap/tags.ts +++ b/src/go-slang/lib/heap/tags.ts @@ -5,5 +5,6 @@ export enum PointerTag { AstNode, CallOp, BuiltInOp, - ClosureOp + ClosureOp, + EnvOp } From 009a6d45632f7747c7a385f653fbb25bbfd705b9 Mon Sep 17 00:00:00 2001 From: Shen Yi Hong Date: Sun, 31 Mar 2024 15:41:17 +0800 Subject: [PATCH 07/13] feat: pass all `Control` values through heap --- src/go-slang/ece.ts | 63 ++++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/src/go-slang/ece.ts b/src/go-slang/ece.ts index 5199d039c..fdaf0647b 100644 --- a/src/go-slang/ece.ts +++ b/src/go-slang/ece.ts @@ -126,23 +126,31 @@ const interpreter: { }, Block: ({ statements }: Block, { C, E, H }) => { - C.pushR(...statements, H.alloc({ type: CommandType.EnvOp, envId: E.id() })) + C.pushR(...H.allocM([...statements, { type: CommandType.EnvOp, envId: E.id() }])) E.extend({}) }, - ReturnStatement: ({ expression }: ReturnStatement, { C }) => - C.pushR(expression, PopTillM(RetMarker)), + ReturnStatement: ({ expression }: ReturnStatement, { C, H }) => + C.pushR(H.alloc(expression), H.alloc(PopTillM(RetMarker))), - IfStatement: ({ stmt, cond, cons, alt }: IfStatement, { C }) => { + IfStatement: ({ stmt, cond, cons, alt }: IfStatement, { C, H }) => { const branchOp: BranchOp = { type: CommandType.BranchOp, cons, alt } - stmt ? C.pushR(stmt, cond, branchOp) : C.pushR(cond, branchOp) + stmt ? C.pushR(...H.allocM([stmt, cond, branchOp])) : C.pushR(...H.allocM([cond, branchOp])) }, - ForStatement: (inst: ForStatement, { C }) => { + ForStatement: (inst: ForStatement, { C, H }) => { const { form, block: forBlock } = inst if (form === null || form.type === ForFormType.ForCondition) { const branch = { type: CommandType.BranchOp, cons: forBlock, alt: PopTillM(ForEndMarker) } - C.pushR(form ? form.expression : True, branch as BranchOp, ForStartMarker, inst, ForEndMarker) + C.pushR( + ...H.allocM([ + form ? form.expression : True, + branch as BranchOp, + ForStartMarker, + inst, + ForEndMarker + ]) + ) } else if (form.type === ForFormType.ForClause) { const { init, cond, post } = form const forCond = { @@ -156,27 +164,27 @@ const interpreter: { ] } } as ForStatement - C.push({ type: NodeType.Block, statements: [init ?? EmptyStmt, forCond] }) + C.push(H.alloc({ type: NodeType.Block, statements: [init ?? EmptyStmt, forCond] })) } }, - BreakStatement: (_inst, { C }) => C.push(PopTillM(ForEndMarker)), + BreakStatement: (_inst, { C, H }) => C.push(H.alloc(PopTillM(ForEndMarker))), - ContinueStatement: (_inst, { C }) => C.push(PopTillM(ForPostMarker, ForStartMarker)), + ContinueStatement: (_inst, { C, H }) => C.push(H.alloc(PopTillM(ForPostMarker, ForStartMarker))), - VariableDeclaration: ({ left, right }: VariableDeclaration, { C }) => { + VariableDeclaration: ({ left, right }: VariableDeclaration, { C, H }) => { const decls = left.map(({ name }) => ({ type: CommandType.VarDeclOp, name })) as Instruction[] return right.length === 0 ? // if there is no right side, we push zero value for each declaration - C.pushR(...decls.map(decl => ({ ...decl, zeroValue: true }))) + C.pushR(...H.allocM(decls.map(decl => ({ ...decl, zeroValue: true })))) : // assume: left.length === right.length - C.pushR(...right, ...decls.reverse()) + C.pushR(...H.allocM(right), ...H.allocM(decls.reverse())) }, - Assignment: ({ left, right }: Assignment, { C }) => { + Assignment: ({ left, right }: Assignment, { C, H }) => { const ids = left as Identifier[] // assume: left is always an array of identifiers const asgmts = ids.map(({ name }) => ({ type: CommandType.AssignOp, name })) as Instruction[] - C.pushR(...right, ...asgmts.reverse()) + C.pushR(...H.allocM(right), ...H.allocM(asgmts.reverse())) }, EmptyStatement: () => void {}, @@ -188,20 +196,23 @@ const interpreter: { return value === null ? new UndefinedError(name) : S.push(H.alloc(value)) }, - UnaryExpression: ({ argument, operator }: UnaryExpression, { C }) => - C.pushR(argument, { type: CommandType.UnaryOp, operator }), + UnaryExpression: ({ argument, operator }: UnaryExpression, { C, H }) => + C.pushR(H.alloc(argument), H.alloc({ type: CommandType.UnaryOp, operator })), - BinaryExpression: ({ left, right, operator }: BinaryExpression, { C }) => - C.pushR(left, right, { type: CommandType.BinaryOp, operator: operator }), + BinaryExpression: ({ left, right, operator }: BinaryExpression, { C, H }) => + C.pushR(...H.allocM([left, right, { type: CommandType.BinaryOp, operator }])), CallExpression: ({ callee, args }: CallExpression, { C, H, A }) => C.pushR( - H.alloc(callee), - ...H.allocM(args), - H.alloc({ type: CommandType.CallOp, calleeNodeId: A.track(callee).uid, arity: args.length }) + ...H.allocM([ + callee, + ...args, + { type: CommandType.CallOp, calleeNodeId: A.track(callee).uid, arity: args.length } + ]) ), - ExpressionStatement: ({ expression }: ExpressionStatement, { C }) => C.pushR(expression, PopS), + ExpressionStatement: ({ expression }: ExpressionStatement, { C, H }) => + C.pushR(...H.allocM([expression, PopS])), VarDeclOp: ({ name, zeroValue }: VarDeclOp, { S, E, H }) => zeroValue ? E.declareZeroValue(name) : E.declare(name, H.resolve(S.pop())), @@ -224,7 +235,7 @@ const interpreter: { const op = H.resolve(S.pop()) as ClosureOp | BuiltinOp if (op.type === CommandType.BuiltinOp) { - return C.pushR({ type: CommandType.ApplyBuiltinOp, builtinOp: op, values }) + return C.pushR(H.alloc({ type: CommandType.ApplyBuiltinOp, builtinOp: op, values })) } // handle ClosureOp @@ -237,13 +248,13 @@ const interpreter: { return new FuncArityError(calleeId.name, values.length, params.length) } - C.pushR(body, RetMarker, H.alloc({ type: CommandType.EnvOp, envId: E.id() })) + C.pushR(...H.allocM([body, RetMarker, { type: CommandType.EnvOp, envId: E.id() }])) // set the environment to the closure's environment E.setId(envId).extend(Object.fromEntries(zip(paramNames, values))) }, BranchOp: ({ cons, alt }: BranchOp, { S, C, H }) => - void (H.resolve(S.pop()) ? C.pushR(cons) : alt && C.pushR(alt)), + void (H.resolve(S.pop()) ? C.pushR(H.alloc(cons)) : alt && C.pushR(H.alloc(alt))), ApplyBuiltinOp: ({ builtinOp: { id }, values }: ApplyBuiltinOp, { S, B, H }) => void S.push(H.alloc(B.get(id)!(...values))), From f4ca71aeaf814feb0305aa1432f118dc79ba4f27 Mon Sep 17 00:00:00 2001 From: Shen Yi Hong Date: Sun, 31 Mar 2024 16:02:14 +0800 Subject: [PATCH 08/13] feat: implement support for `VarDeclOp` in heap Added some helper functions to `AstMap` --- src/go-slang/ece.ts | 14 ++++++++------ src/go-slang/lib/astMap.ts | 8 ++++++++ src/go-slang/lib/heap/index.ts | 20 ++++++++++++++++++++ src/go-slang/lib/heap/tags.ts | 1 + src/go-slang/types.ts | 2 +- 5 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/go-slang/ece.ts b/src/go-slang/ece.ts index fdaf0647b..c426d124a 100644 --- a/src/go-slang/ece.ts +++ b/src/go-slang/ece.ts @@ -172,8 +172,8 @@ const interpreter: { ContinueStatement: (_inst, { C, H }) => C.push(H.alloc(PopTillM(ForPostMarker, ForStartMarker))), - VariableDeclaration: ({ left, right }: VariableDeclaration, { C, H }) => { - const decls = left.map(({ name }) => ({ type: CommandType.VarDeclOp, name })) as Instruction[] + VariableDeclaration: ({ left, right }: VariableDeclaration, { C, H, A }) => { + const decls = A.trackM(left).map(({ uid }) => ({ type: CommandType.VarDeclOp, idNodeUid: uid })) return right.length === 0 ? // if there is no right side, we push zero value for each declaration C.pushR(...H.allocM(decls.map(decl => ({ ...decl, zeroValue: true })))) @@ -214,8 +214,10 @@ const interpreter: { ExpressionStatement: ({ expression }: ExpressionStatement, { C, H }) => C.pushR(...H.allocM([expression, PopS])), - VarDeclOp: ({ name, zeroValue }: VarDeclOp, { S, E, H }) => - zeroValue ? E.declareZeroValue(name) : E.declare(name, H.resolve(S.pop())), + VarDeclOp: ({ idNodeUid, zeroValue }: VarDeclOp, { S, E, H, A }) => { + const name = A.get(idNodeUid).name + zeroValue ? E.declareZeroValue(name) : E.declare(name, H.resolve(S.pop())) + }, AssignOp: ({ name }: AssignOp, { S, E, H }) => !E.assign(name, H.resolve(S.pop())) ? new UndefinedError(name) : void {}, @@ -240,11 +242,11 @@ const interpreter: { // handle ClosureOp const { funcDeclNodeUid, envId } = op - const { params, body } = A.get(funcDeclNodeUid) as FunctionDeclaration + const { params, body } = A.get(funcDeclNodeUid) const paramNames = params.map(({ name }) => name) if (paramNames.length !== values.length) { - const calleeId = A.get(calleeNodeId) as Identifier + const calleeId = A.get(calleeNodeId) return new FuncArityError(calleeId.name, values.length, params.length) } diff --git a/src/go-slang/lib/astMap.ts b/src/go-slang/lib/astMap.ts index 6d56db944..93151a724 100644 --- a/src/go-slang/lib/astMap.ts +++ b/src/go-slang/lib/astMap.ts @@ -22,4 +22,12 @@ export class AstMap extends Map { this.set(node.uid, node) return node } + + public trackM(nodes: Node[]): Node[] { + return nodes.map(this.track.bind(this)) + } + + public get(uid: number): T { + return super.get(uid) as T + } } diff --git a/src/go-slang/lib/heap/index.ts b/src/go-slang/lib/heap/index.ts index f6fa76c20..1ecd0bd10 100644 --- a/src/go-slang/lib/heap/index.ts +++ b/src/go-slang/lib/heap/index.ts @@ -5,6 +5,7 @@ import { CommandType, EnvOp, Node, + VarDeclOp, isCommand, isNode } from '../../types' @@ -59,6 +60,8 @@ export class Heap { // ECE operations if (isCommand(value)) { switch (value.type) { + case CommandType.VarDeclOp: + return this.allocateVarDeclOp(value) case CommandType.CallOp: return this.allocateCallOp(value) case CommandType.BuiltinOp: @@ -99,6 +102,12 @@ export class Heap { return this.get(heap_addr + 1) case PointerTag.AstNode: return this.astMap.get(this.memory.getInt16(mem_addr + 1)) + case PointerTag.VarDeclOp: + return { + type: CommandType.VarDeclOp, + idNodeUid: this.memory.getInt16(mem_addr + 1), + zeroValue: this.memory.getInt8(mem_addr + 7) === 1 + } as VarDeclOp case PointerTag.CallOp: return { type: CommandType.CallOp, @@ -154,6 +163,17 @@ export class Heap { return ptr_heap_addr } + /* Memory Layout of a VarDeclOp: [0:tag, 1-2:idNodeUid, 3-4:_unused_, 5-6:size, 7:isZeroValue] (1 word) */ + private allocateVarDeclOp({ zeroValue, idNodeUid }: VarDeclOp): HeapAddress { + const ptr_heap_addr = this.allocateTaggedPtr(PointerTag.VarDeclOp) + + const ptr_mem_addr = ptr_heap_addr * WORD_SIZE + this.memory.setInt16(ptr_mem_addr + 1, idNodeUid) + this.memory.setInt8(ptr_mem_addr + 7, zeroValue ? 1 : 0) + + return ptr_heap_addr + } + /* Memory Layout of a CallOp: [0:tag, 1-2:calleeNodeId, 3-4:arity, 5-6:size, 7:_unused_] (1 word) */ private allocateCallOp({ calleeNodeId, arity }: CallOp): HeapAddress { const ptr_heap_addr = this.allocateTaggedPtr(PointerTag.CallOp) diff --git a/src/go-slang/lib/heap/tags.ts b/src/go-slang/lib/heap/tags.ts index a0c745aac..29b2f6065 100644 --- a/src/go-slang/lib/heap/tags.ts +++ b/src/go-slang/lib/heap/tags.ts @@ -3,6 +3,7 @@ export enum PointerTag { True, Number, AstNode, + VarDeclOp, CallOp, BuiltInOp, ClosureOp, diff --git a/src/go-slang/types.ts b/src/go-slang/types.ts index 5f97638d3..7e5825636 100644 --- a/src/go-slang/types.ts +++ b/src/go-slang/types.ts @@ -218,7 +218,7 @@ export function isCommand(v: any): boolean { export interface VarDeclOp extends Command { type: CommandType.VarDeclOp zeroValue: boolean - name: string + idNodeUid: number } export interface AssignOp extends Command { From da9da11ad9ba79473d536448c5521fcd8a86e803 Mon Sep 17 00:00:00 2001 From: Shen Yi Hong Date: Sun, 31 Mar 2024 17:08:05 +0800 Subject: [PATCH 09/13] feat: implement `UnaryOp` and `BinaryOp` support in heap --- src/go-slang/ece.ts | 17 +++++++++-------- src/go-slang/lib/heap/index.ts | 27 +++++++++++++++++++++++++++ src/go-slang/lib/heap/tags.ts | 2 ++ src/go-slang/parser/go.js | 12 ++++++++---- src/go-slang/parser/go.pegjs | 12 ++++++++---- src/go-slang/types.ts | 24 +++++++++++++++--------- 6 files changed, 69 insertions(+), 25 deletions(-) diff --git a/src/go-slang/ece.ts b/src/go-slang/ece.ts index c426d124a..19bc53d90 100644 --- a/src/go-slang/ece.ts +++ b/src/go-slang/ece.ts @@ -36,6 +36,7 @@ import { Instruction, Literal, NodeType, + Operator, PopS, PopTillM, PopTillMOp, @@ -196,11 +197,11 @@ const interpreter: { return value === null ? new UndefinedError(name) : S.push(H.alloc(value)) }, - UnaryExpression: ({ argument, operator }: UnaryExpression, { C, H }) => - C.pushR(H.alloc(argument), H.alloc({ type: CommandType.UnaryOp, operator })), + UnaryExpression: ({ argument, operator: op }: UnaryExpression, { C, H, A }) => + C.pushR(...H.allocM([argument, { type: CommandType.UnaryOp, opNodeId: A.track(op).uid }])), - BinaryExpression: ({ left, right, operator }: BinaryExpression, { C, H }) => - C.pushR(...H.allocM([left, right, { type: CommandType.BinaryOp, operator }])), + BinaryExpression: ({ left, right, operator: op }: BinaryExpression, { C, H, A }) => + C.pushR(...H.allocM([left, right, { type: CommandType.BinaryOp, opNodeId: A.track(op).uid }])), CallExpression: ({ callee, args }: CallExpression, { C, H, A }) => C.pushR( @@ -222,14 +223,14 @@ const interpreter: { AssignOp: ({ name }: AssignOp, { S, E, H }) => !E.assign(name, H.resolve(S.pop())) ? new UndefinedError(name) : void {}, - UnaryOp: ({ operator }: UnaryOp, { S, H }) => { + UnaryOp: ({ opNodeId }: UnaryOp, { S, H, A }) => { const operand = H.resolve(S.pop()) - S.push(H.alloc(operator === '-' ? -operand : operand)) + S.push(H.alloc(A.get(opNodeId).op === '-' ? -operand : operand)) }, - BinaryOp: ({ operator }: BinaryOp, { S, H }) => { + BinaryOp: ({ opNodeId }: BinaryOp, { S, H, A }) => { const [left, right] = H.resolveM(S.popNR(2)) - S.push(H.alloc(evaluateBinaryOp(operator, left, right))) + S.push(H.alloc(evaluateBinaryOp(A.get(opNodeId).op, left, right))) }, CallOp: ({ calleeNodeId, arity }: CallOp, { C, S, E, H, A }) => { diff --git a/src/go-slang/lib/heap/index.ts b/src/go-slang/lib/heap/index.ts index 1ecd0bd10..592b9316c 100644 --- a/src/go-slang/lib/heap/index.ts +++ b/src/go-slang/lib/heap/index.ts @@ -1,10 +1,12 @@ import { + BinaryOp, BuiltinOp, CallOp, ClosureOp, CommandType, EnvOp, Node, + UnaryOp, VarDeclOp, isCommand, isNode @@ -62,6 +64,9 @@ export class Heap { switch (value.type) { case CommandType.VarDeclOp: return this.allocateVarDeclOp(value) + case CommandType.UnaryOp: + case CommandType.BinaryOp: + return this.allocateUnaryBinaryOp(value) case CommandType.CallOp: return this.allocateCallOp(value) case CommandType.BuiltinOp: @@ -108,6 +113,16 @@ export class Heap { idNodeUid: this.memory.getInt16(mem_addr + 1), zeroValue: this.memory.getInt8(mem_addr + 7) === 1 } as VarDeclOp + case PointerTag.UnaryOp: + return { + type: CommandType.UnaryOp, + opNodeId: this.memory.getInt16(mem_addr + 1) + } as UnaryOp + case PointerTag.BinaryOp: + return { + type: CommandType.BinaryOp, + opNodeId: this.memory.getInt16(mem_addr + 1) + } as BinaryOp case PointerTag.CallOp: return { type: CommandType.CallOp, @@ -163,6 +178,18 @@ export class Heap { return ptr_heap_addr } + /* Memory Layout of a Unary/Binary Op: [0:tag, 1-2:opNodeId, 3-4:_unused_, 5-6:size, 7:_unused_] (1 word) */ + private allocateUnaryBinaryOp({ type, opNodeId }: UnaryOp | BinaryOp): HeapAddress { + const ptr_heap_addr = this.allocateTaggedPtr( + type === CommandType.UnaryOp ? PointerTag.UnaryOp : PointerTag.BinaryOp + ) + + const ptr_mem_addr = ptr_heap_addr * WORD_SIZE + this.memory.setInt16(ptr_mem_addr + 1, opNodeId) + + return ptr_heap_addr + } + /* Memory Layout of a VarDeclOp: [0:tag, 1-2:idNodeUid, 3-4:_unused_, 5-6:size, 7:isZeroValue] (1 word) */ private allocateVarDeclOp({ zeroValue, idNodeUid }: VarDeclOp): HeapAddress { const ptr_heap_addr = this.allocateTaggedPtr(PointerTag.VarDeclOp) diff --git a/src/go-slang/lib/heap/tags.ts b/src/go-slang/lib/heap/tags.ts index 29b2f6065..9ac246dd7 100644 --- a/src/go-slang/lib/heap/tags.ts +++ b/src/go-slang/lib/heap/tags.ts @@ -4,6 +4,8 @@ export enum PointerTag { Number, AstNode, VarDeclOp, + UnaryOp, + BinaryOp, CallOp, BuiltInOp, ClosureOp, diff --git a/src/go-slang/parser/go.js b/src/go-slang/parser/go.js index 84346db9d..c99d8e87f 100644 --- a/src/go-slang/parser/go.js +++ b/src/go-slang/parser/go.js @@ -373,8 +373,8 @@ function peg$parse(input, options) { var peg$f8 = function(digits) { return buildLiteral(buildInteger(digits, 16)) }; - var peg$f9 = function(operator, argument) { - return makeNode({type: "UnaryExpression", operator, argument}) + var peg$f9 = function(op, argument) { + return makeNode({type: "UnaryExpression", operator: makeOperator(op), argument}) }; var peg$f10 = function(head, tail) { return buildBinaryExpression(head, tail); }; var peg$f11 = function(head, tail) { return buildBinaryExpression(head, tail); }; @@ -5117,16 +5117,20 @@ function peg$parse(input, options) { function makeNode(node) { return { ...node, uid: uid++, loc: location() }; } + + function makeOperator(op) { + return makeNode({ type: "Operator", op }); + } function buildLiteral(value) { return makeNode({ type: "Literal", value: value}); } - + function buildBinaryExpression(head, tail) { return tail.reduce(function(result, element) { return makeNode({ type: "BinaryExpression", - operator: element[1], + operator: makeOperator(element[1]), left: result, right: element[3] }); diff --git a/src/go-slang/parser/go.pegjs b/src/go-slang/parser/go.pegjs index 773cbc9e6..b19370b4a 100644 --- a/src/go-slang/parser/go.pegjs +++ b/src/go-slang/parser/go.pegjs @@ -20,16 +20,20 @@ function makeNode(node) { return { ...node, uid: uid++, loc: location() }; } + + function makeOperator(op) { + return makeNode({ type: "Operator", op }); + } function buildLiteral(value) { return makeNode({ type: "Literal", value: value}); } - + function buildBinaryExpression(head, tail) { return tail.reduce(function(result, element) { return makeNode({ type: "BinaryExpression", - operator: element[1], + operator: makeOperator(element[1]), left: result, right: element[3] }); @@ -141,8 +145,8 @@ HexDigit UnaryExpression = CallExpression / PrimaryExpression - / operator:UnaryOperator argument:UnaryExpression { - return makeNode({type: "UnaryExpression", operator, argument}) + / op:UnaryOperator argument:UnaryExpression { + return makeNode({type: "UnaryExpression", operator: makeOperator(op), argument}) } UnaryOperator diff --git a/src/go-slang/types.ts b/src/go-slang/types.ts index 7e5825636..20a7ae184 100644 --- a/src/go-slang/types.ts +++ b/src/go-slang/types.ts @@ -11,6 +11,7 @@ export enum NodeType { ExpressionStatement = 'ExpressionStatement', EmptyStatement = 'EmptyStatement', Assignment = 'Assignment', + Operator = 'Operator', UnaryExpression = 'UnaryExpression', BinaryExpression = 'BinaryExpression', Identifier = 'Identifier', @@ -158,12 +159,6 @@ export const True: Literal = { type: NodeType.Literal, value: true } export type UnaryOperator = '+' | '-' -export interface UnaryExpression extends Node { - type: NodeType.UnaryExpression - operator: UnaryOperator - argument: Expression -} - export type BinaryOperator = | '+' | '-' @@ -179,9 +174,20 @@ export type BinaryOperator = | '>' | '>=' +export interface Operator extends Node { + type: NodeType.Operator + op: UnaryOperator | BinaryOperator +} + +export interface UnaryExpression extends Node { + type: NodeType.UnaryExpression + operator: Operator + argument: Expression +} + export interface BinaryExpression extends Node { type: NodeType.BinaryExpression - operator: BinaryOperator + operator: Operator left: Expression right: Expression } @@ -228,12 +234,12 @@ export interface AssignOp extends Command { export interface UnaryOp extends Command { type: CommandType.UnaryOp - operator: UnaryOperator + opNodeId: number } export interface BinaryOp extends Command { type: CommandType.BinaryOp - operator: BinaryOperator + opNodeId: number } export interface ClosureOp extends Command { From 8c1c11e5ecd07fb6f27f9eee80380de556fd09b8 Mon Sep 17 00:00:00 2001 From: Shen Yi Hong Date: Sun, 31 Mar 2024 17:21:28 +0800 Subject: [PATCH 10/13] feat: implement `AssignOp` support in heap --- src/go-slang/ece.ts | 10 ++++++---- src/go-slang/lib/heap/index.ts | 18 ++++++++++++++++++ src/go-slang/lib/heap/tags.ts | 1 + src/go-slang/types.ts | 2 +- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/go-slang/ece.ts b/src/go-slang/ece.ts index 19bc53d90..a84cf2260 100644 --- a/src/go-slang/ece.ts +++ b/src/go-slang/ece.ts @@ -182,9 +182,9 @@ const interpreter: { C.pushR(...H.allocM(right), ...H.allocM(decls.reverse())) }, - Assignment: ({ left, right }: Assignment, { C, H }) => { + Assignment: ({ left, right }: Assignment, { C, H, A }) => { const ids = left as Identifier[] // assume: left is always an array of identifiers - const asgmts = ids.map(({ name }) => ({ type: CommandType.AssignOp, name })) as Instruction[] + const asgmts = ids.map(id => ({ type: CommandType.AssignOp, idNodeUid: A.track(id).uid })) C.pushR(...H.allocM(right), ...H.allocM(asgmts.reverse())) }, @@ -220,8 +220,10 @@ const interpreter: { zeroValue ? E.declareZeroValue(name) : E.declare(name, H.resolve(S.pop())) }, - AssignOp: ({ name }: AssignOp, { S, E, H }) => - !E.assign(name, H.resolve(S.pop())) ? new UndefinedError(name) : void {}, + AssignOp: ({ idNodeUid }: AssignOp, { S, E, H, A }) => { + const name = A.get(idNodeUid).name + !E.assign(name, H.resolve(S.pop())) ? new UndefinedError(name) : void {} + }, UnaryOp: ({ opNodeId }: UnaryOp, { S, H, A }) => { const operand = H.resolve(S.pop()) diff --git a/src/go-slang/lib/heap/index.ts b/src/go-slang/lib/heap/index.ts index 592b9316c..fe86672d7 100644 --- a/src/go-slang/lib/heap/index.ts +++ b/src/go-slang/lib/heap/index.ts @@ -1,4 +1,5 @@ import { + AssignOp, BinaryOp, BuiltinOp, CallOp, @@ -64,6 +65,8 @@ export class Heap { switch (value.type) { case CommandType.VarDeclOp: return this.allocateVarDeclOp(value) + case CommandType.AssignOp: + return this.allocateAssignOp(value) case CommandType.UnaryOp: case CommandType.BinaryOp: return this.allocateUnaryBinaryOp(value) @@ -113,6 +116,11 @@ export class Heap { idNodeUid: this.memory.getInt16(mem_addr + 1), zeroValue: this.memory.getInt8(mem_addr + 7) === 1 } as VarDeclOp + case PointerTag.AssignOp: + return { + type: CommandType.AssignOp, + idNodeUid: this.memory.getInt16(mem_addr + 1) + } as AssignOp case PointerTag.UnaryOp: return { type: CommandType.UnaryOp, @@ -201,6 +209,16 @@ export class Heap { return ptr_heap_addr } + /* Memory Layout of an AssignOp: [0:tag, 1-2:idNodeUid, 3-4:_unused_, 5-6:size, 7:_unused_] (1 word) */ + private allocateAssignOp({ idNodeUid }: AssignOp): HeapAddress { + const ptr_heap_addr = this.allocateTaggedPtr(PointerTag.AssignOp) + + const ptr_mem_addr = ptr_heap_addr * WORD_SIZE + this.memory.setInt16(ptr_mem_addr + 1, idNodeUid) + + return ptr_heap_addr + } + /* Memory Layout of a CallOp: [0:tag, 1-2:calleeNodeId, 3-4:arity, 5-6:size, 7:_unused_] (1 word) */ private allocateCallOp({ calleeNodeId, arity }: CallOp): HeapAddress { const ptr_heap_addr = this.allocateTaggedPtr(PointerTag.CallOp) diff --git a/src/go-slang/lib/heap/tags.ts b/src/go-slang/lib/heap/tags.ts index 9ac246dd7..919bbb87d 100644 --- a/src/go-slang/lib/heap/tags.ts +++ b/src/go-slang/lib/heap/tags.ts @@ -4,6 +4,7 @@ export enum PointerTag { Number, AstNode, VarDeclOp, + AssignOp, UnaryOp, BinaryOp, CallOp, diff --git a/src/go-slang/types.ts b/src/go-slang/types.ts index 20a7ae184..f7719b86f 100644 --- a/src/go-slang/types.ts +++ b/src/go-slang/types.ts @@ -229,7 +229,7 @@ export interface VarDeclOp extends Command { export interface AssignOp extends Command { type: CommandType.AssignOp - name: string + idNodeUid: number } export interface UnaryOp extends Command { From 26f81d01b6616660cec4843580ab42d4cfb93c4a Mon Sep 17 00:00:00 2001 From: Shen Yi Hong Date: Sun, 31 Mar 2024 17:26:13 +0800 Subject: [PATCH 11/13] feat: implement `PopSOp` support in heap --- src/go-slang/lib/heap/index.ts | 5 +++++ src/go-slang/lib/heap/tags.ts | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/go-slang/lib/heap/index.ts b/src/go-slang/lib/heap/index.ts index fe86672d7..a01e74730 100644 --- a/src/go-slang/lib/heap/index.ts +++ b/src/go-slang/lib/heap/index.ts @@ -7,6 +7,7 @@ import { CommandType, EnvOp, Node, + PopS, UnaryOp, VarDeclOp, isCommand, @@ -78,6 +79,8 @@ export class Heap { return this.allocateClosureOp(value) case CommandType.EnvOp: return this.allocateEnvOp(value) + case CommandType.PopSOp: + return this.allocateTaggedPtr(PointerTag.PopSOp) } } @@ -154,6 +157,8 @@ export class Heap { type: CommandType.EnvOp, envId: this.memory.getInt16(mem_addr + 1) } as EnvOp + case PointerTag.PopSOp: + return PopS } } diff --git a/src/go-slang/lib/heap/tags.ts b/src/go-slang/lib/heap/tags.ts index 919bbb87d..4f6c26f26 100644 --- a/src/go-slang/lib/heap/tags.ts +++ b/src/go-slang/lib/heap/tags.ts @@ -10,5 +10,6 @@ export enum PointerTag { CallOp, BuiltInOp, ClosureOp, - EnvOp + EnvOp, + PopSOp } From b5a51d79685dc4de7a91c93649e693dcbdb8f871 Mon Sep 17 00:00:00 2001 From: Shen Yi Hong Date: Mon, 1 Apr 2024 14:48:41 +0800 Subject: [PATCH 12/13] refactor: remove redundant indirection for `BuiltinOp` Instead of having a extra `ApplyBuiltinOp`, we execute the op during the handling of `CallOp` --- src/go-slang/ece.ts | 9 +++------ src/go-slang/types.ts | 10 +--------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/go-slang/ece.ts b/src/go-slang/ece.ts index a84cf2260..32269a75e 100644 --- a/src/go-slang/ece.ts +++ b/src/go-slang/ece.ts @@ -10,7 +10,6 @@ import { Heap, HeapAddress } from './lib/heap' import { PREDECLARED_FUNCTIONS, PREDECLARED_IDENTIFIERS } from './lib/predeclared' import { zip, isAny } from './lib/utils' import { - ApplyBuiltinOp, AssignOp, Assignment, BinaryExpression, @@ -235,12 +234,13 @@ const interpreter: { S.push(H.alloc(evaluateBinaryOp(A.get(opNodeId).op, left, right))) }, - CallOp: ({ calleeNodeId, arity }: CallOp, { C, S, E, H, A }) => { + CallOp: ({ calleeNodeId, arity }: CallOp, { C, S, E, B, H, A }) => { const values = H.resolveM(S.popNR(arity)) const op = H.resolve(S.pop()) as ClosureOp | BuiltinOp + // handle BuiltinOp if (op.type === CommandType.BuiltinOp) { - return C.pushR(H.alloc({ type: CommandType.ApplyBuiltinOp, builtinOp: op, values })) + return S.push(H.alloc(B.get(op.id)!(...values))) } // handle ClosureOp @@ -261,9 +261,6 @@ const interpreter: { BranchOp: ({ cons, alt }: BranchOp, { S, C, H }) => void (H.resolve(S.pop()) ? C.pushR(H.alloc(cons)) : alt && C.pushR(H.alloc(alt))), - ApplyBuiltinOp: ({ builtinOp: { id }, values }: ApplyBuiltinOp, { S, B, H }) => - void S.push(H.alloc(B.get(id)!(...values))), - EnvOp: ({ envId }: EnvOp, { E }) => void E.setId(envId), PopSOp: (_inst, { S }) => void S.pop(), diff --git a/src/go-slang/types.ts b/src/go-slang/types.ts index f7719b86f..8816d1767 100644 --- a/src/go-slang/types.ts +++ b/src/go-slang/types.ts @@ -209,8 +209,7 @@ export enum CommandType { EnvOp = 'EnvOp', PopSOp = 'PopSOp', PopTillMOp = 'PopTillMOp', - BuiltinOp = 'BuiltinOp', - ApplyBuiltinOp = 'ApplyBuiltinOp' + BuiltinOp = 'BuiltinOp' } export interface Command { @@ -288,12 +287,6 @@ export interface BuiltinOp extends Command { arity?: number } -export interface ApplyBuiltinOp extends Command { - type: CommandType.ApplyBuiltinOp - builtinOp: BuiltinOp - values: any[] -} - export enum MarkerType { RetMarker = 'RetMarker', ForStartMarker = 'ForStartMarker', @@ -338,5 +331,4 @@ export type Instruction = | PopSOp | PopTillMOp | BuiltinOp - | ApplyBuiltinOp | Marker From 02c33cfc9a0808175be9f3c71fb5382533533f69 Mon Sep 17 00:00:00 2001 From: Shen Yi Hong Date: Mon, 1 Apr 2024 14:58:30 +0800 Subject: [PATCH 13/13] refactor: add type assertion for clarity --- src/go-slang/ece.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/go-slang/ece.ts b/src/go-slang/ece.ts index 32269a75e..8d936a8be 100644 --- a/src/go-slang/ece.ts +++ b/src/go-slang/ece.ts @@ -75,7 +75,7 @@ export function evaluate(program: SourceFile, slangContext: SlangContext): Value // inject predeclared functions into the global environment const B = new Map any>() PREDECLARED_FUNCTIONS.forEach(({ name, func, op }, id) => { - E.declare(name, { ...op, id }) + E.declare(name, { ...op, id } as BuiltinOp) if (name === 'println') { // println is special case where we need to the `rawDisplay` slang builtin for // console capture, therefore we handle it differently from other predeclared functions