Skip to content

Commit e4e4631

Browse files
author
Kyriel Abad
authored
Streamline CSE machine (#1711)
* Remove RESUME_CONT instruction from active CSE machine instructions * Remove GENERATE_CONT instruction from active CSE machine instructions * Remove all trace of GENERATE_CONT and RESUME_CONT * Remove unnecessary types and imports * Make the implementation of continuations cleaner * Change representation of call/cc * Add test file for call/cc * Improve testing for continuations
1 parent f4adb13 commit e4e4631

File tree

6 files changed

+119
-140
lines changed

6 files changed

+119
-140
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Call_cc, Continuation, isCallWithCurrentContinuation } from '../continuations'
2+
import { Control, Stash } from '../interpreter'
3+
4+
test('call/cc is a singleton', () => {
5+
expect(Call_cc.get()).toBe(Call_cc.get())
6+
})
7+
8+
test('call/cc toString', () => {
9+
expect(Call_cc.get().toString()).toBe('call/cc')
10+
})
11+
12+
test('isCallWithCurrentContinuation works on call/cc only', () => {
13+
expect(isCallWithCurrentContinuation(Call_cc.get())).toBe(true)
14+
expect(isCallWithCurrentContinuation(1)).toBe(false)
15+
})
16+
17+
test('Continuation toString', () => {
18+
const cont = new Continuation(new Control(), new Stash(), [])
19+
expect(cont.toString()).toBe('continuation')
20+
})

src/cse-machine/continuations.ts

Lines changed: 42 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,26 @@ import { Control, Stash } from './interpreter'
88
* If the interpreter sees this specific function, a continuation at the current
99
* point of evaluation is executed instead of a regular function call.
1010
*/
11+
export class Call_cc extends Function {
12+
private static instance: Call_cc = new Call_cc()
1113

12-
export function call_with_current_continuation(f: any): any {
13-
return f()
14+
private constructor() {
15+
super()
16+
}
17+
18+
public static get(): Call_cc {
19+
return Call_cc.instance
20+
}
21+
22+
public toString(): string {
23+
return 'call/cc'
24+
}
1425
}
1526

16-
/**
17-
* Checks if the function refers to the designated function object call/cc.
18-
*/
19-
export function isCallWithCurrentContinuation(f: Function): boolean {
20-
return f === call_with_current_continuation
27+
export const call_with_current_continuation = Call_cc.get()
28+
29+
export function isCallWithCurrentContinuation(value: any): boolean {
30+
return value === call_with_current_continuation
2131
}
2232

2333
/**
@@ -28,54 +38,41 @@ export function isCallWithCurrentContinuation(f: Function): boolean {
2838
* Continuations and functions are treated as the same by
2939
* the typechecker so that they can be first-class values.
3040
*/
31-
export interface Continuation extends Function {
32-
control: Control
33-
stash: Stash
34-
env: Environment[]
35-
}
36-
37-
// As the continuation needs to be immutable (we can call it several times)
38-
// we need to copy its elements whenever we access them
39-
export function getContinuationControl(cn: Continuation): Control {
40-
return cn.control.copy()
41-
}
41+
export class Continuation extends Function {
42+
private control: Control
43+
private stash: Stash
44+
private env: Environment[]
4245

43-
export function getContinuationStash(cn: Continuation): Stash {
44-
return cn.stash.copy()
45-
}
46-
47-
export function getContinuationEnv(cn: Continuation): Environment[] {
48-
return [...cn.env]
49-
}
46+
constructor(control: Control, stash: Stash, env: Environment[]) {
47+
super()
48+
this.control = control.copy()
49+
this.stash = stash.copy()
50+
this.env = [...env]
51+
}
5052

51-
export function makeContinuation(control: Control, stash: Stash, env: Environment[]): Function {
52-
// Cast a function into a continuation
53-
// a continuation may take any amount of arguments
54-
const fn: Function = (...x: any[]) => x
55-
const cn: Continuation = fn as Continuation
53+
// As the continuation needs to be immutable (we can call it several times)
54+
// we need to copy its elements whenever we access them
55+
public getControl(): Control {
56+
return this.control.copy()
57+
}
5658

57-
// Set the control, stash and environment
58-
// as shallow copies of the given program equivalents
59-
cn.control = control.copy()
60-
cn.stash = stash.copy()
61-
cn.env = [...env]
59+
public getStash(): Stash {
60+
return this.stash.copy()
61+
}
6262

63-
// Return the continuation as a function so that
64-
// the type checker allows it to be called
65-
return cn as Function
66-
}
63+
public getEnv(): Environment[] {
64+
return [...this.env]
65+
}
6766

68-
/**
69-
* Checks whether a given function is actually a continuation.
70-
*/
71-
export function isContinuation(f: Function): f is Continuation {
72-
return 'control' in f && 'stash' in f && 'env' in f
67+
public toString(): string {
68+
return 'continuation'
69+
}
7370
}
7471

7572
/**
7673
* Provides an adequate representation of what calling
7774
* call/cc or continuations looks like, to give to the
78-
* GENERATE_CONT and RESUME_CONT instructions.
75+
* APPLICATION instruction.
7976
*/
8077
export function makeDummyContCallExpression(callee: string, argument: string): es.CallExpression {
8178
return {

src/cse-machine/instrCreator.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,8 @@ import {
1313
BranchInstr,
1414
EnvInstr,
1515
ForInstr,
16-
GenContInstr,
1716
Instr,
1817
InstrType,
19-
ResumeContInstr,
2018
UnOpInstr,
2119
WhileInstr
2220
} from './types'
@@ -138,14 +136,3 @@ export const breakMarkerInstr = (srcNode: Node): Instr => ({
138136
instrType: InstrType.BREAK_MARKER,
139137
srcNode
140138
})
141-
142-
export const genContInstr = (srcNode: Node): GenContInstr => ({
143-
instrType: InstrType.GENERATE_CONT,
144-
srcNode
145-
})
146-
147-
export const resumeContInstr = (numOfArgs: number, srcNode: es.Node): ResumeContInstr => ({
148-
numOfArgs: numOfArgs,
149-
instrType: InstrType.RESUME_CONT,
150-
srcNode
151-
})

src/cse-machine/interpreter.ts

Lines changed: 44 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,7 @@ import { checkProgramForUndefinedVariables } from '../validator/validator'
2424
import Closure from './closure'
2525
import {
2626
Continuation,
27-
getContinuationControl,
28-
getContinuationEnv,
29-
getContinuationStash,
3027
isCallWithCurrentContinuation,
31-
isContinuation,
32-
makeContinuation,
3328
makeDummyContCallExpression
3429
} from './continuations'
3530
import * as instr from './instrCreator'
@@ -45,10 +40,8 @@ import {
4540
CseError,
4641
EnvInstr,
4742
ForInstr,
48-
GenContInstr,
4943
Instr,
5044
InstrType,
51-
ResumeContInstr,
5245
UnOpInstr,
5346
WhileInstr
5447
} from './types'
@@ -937,33 +930,65 @@ const cmdEvaluators: { [type: string]: CmdEvaluator } = {
937930
// Check for number of arguments mismatch error
938931
checkNumberOfArguments(context, func, args, command.srcNode)
939932

933+
// generate a continuation here
934+
const contControl = control.copy()
935+
const contStash = stash.copy()
936+
const contEnv = context.runtime.environments.slice()
937+
938+
// at this point, the extra CALL instruction
939+
// has been removed from the control stack.
940+
// additionally, the single closure argument has been
941+
// removed (as the parameter of call/cc) from the stash
942+
// and additionally, call/cc itself has been removed from the stash.
943+
944+
// as such, there is no further need to modify the
945+
// copied C, S and E!
946+
947+
const continuation = new Continuation(contControl, contStash, contEnv)
948+
940949
// Get the callee
941950
const cont_callee: Value = args[0]
942951

943952
const dummyFCallExpression = makeDummyContCallExpression('f', 'cont')
944953

945954
// Prepare a function call for the continuation-consuming function
946-
// along with a newly generated continuation
947955
control.push(instr.appInstr(command.numOfArgs, dummyFCallExpression))
948-
control.push(instr.genContInstr(dummyFCallExpression.arguments[0]))
956+
957+
// push the argument (the continuation caller) back onto the stash
949958
stash.push(cont_callee)
959+
960+
// finally, push the continuation onto the stash
961+
stash.push(continuation)
950962
return
951963
}
952964

953-
if (isContinuation(func)) {
965+
if (func instanceof Continuation) {
954966
// Check for number of arguments mismatch error
955967
checkNumberOfArguments(context, func, args, command.srcNode)
956968

957-
const dummyContCallExpression = makeDummyContCallExpression('f', 'cont')
969+
// const dummyContCallExpression = makeDummyContCallExpression('f', 'cont')
970+
971+
// // Restore the state of the stash,
972+
// // but replace the function application instruction with
973+
// // a resume continuation instruction
974+
// stash.push(func)
975+
// // we need to push the arguments back onto the stash
976+
// // as well
977+
// stash.push(...args)
978+
// control.push(instr.resumeContInstr(command.numOfArgs, dummyContCallExpression))
979+
980+
// get the C, S, E from the continuation
981+
const contControl = func.getControl()
982+
const contStash = func.getStash()
983+
const contEnv = func.getEnv()
958984

959-
// Restore the state of the stash,
960-
// but replace the function application instruction with
961-
// a resume continuation instruction
962-
stash.push(func)
963-
// we need to push the arguments back onto the stash
964-
// as well
985+
// update the C, S, E of the current context
986+
control.setTo(contControl)
987+
stash.setTo(contStash)
988+
context.runtime.environments = contEnv
989+
990+
// push the arguments back onto the stash
965991
stash.push(...args)
966-
control.push(instr.resumeContInstr(command.numOfArgs, dummyContCallExpression))
967992
return
968993
}
969994

@@ -1163,54 +1188,5 @@ const cmdEvaluators: { [type: string]: CmdEvaluator } = {
11631188
}
11641189
},
11651190

1166-
[InstrType.BREAK_MARKER]: function () {},
1167-
1168-
[InstrType.GENERATE_CONT]: function (
1169-
_command: GenContInstr,
1170-
context: Context,
1171-
control: Control,
1172-
stash: Stash
1173-
) {
1174-
const contControl = control.copy()
1175-
const contStash = stash.copy()
1176-
const contEnv = context.runtime.environments
1177-
1178-
// Remove all data related to the continuation-consuming function
1179-
contControl.pop()
1180-
contStash.pop()
1181-
1182-
// Now this will accurately represent the slice of the
1183-
// program execution at the time of the call/cc call
1184-
const continuation = makeContinuation(contControl, contStash, contEnv)
1185-
1186-
stash.push(continuation)
1187-
},
1188-
1189-
[InstrType.RESUME_CONT]: function (
1190-
command: ResumeContInstr,
1191-
context: Context,
1192-
control: Control,
1193-
stash: Stash
1194-
) {
1195-
// pop the arguments
1196-
const args: Value[] = []
1197-
for (let i = 0; i < command.numOfArgs; i++) {
1198-
args.unshift(stash.pop())
1199-
}
1200-
const cn: Continuation = stash.pop() as Continuation
1201-
1202-
const contControl = getContinuationControl(cn)
1203-
const contStash = getContinuationStash(cn)
1204-
const contEnv = getContinuationEnv(cn)
1205-
1206-
// Set the control and stash to the continuation's control and stash
1207-
control.setTo(contControl)
1208-
stash.setTo(contStash)
1209-
1210-
// Push the arguments given to the continuation back onto the stash
1211-
stash.push(...args)
1212-
1213-
// Restore the environment pointer to that of the continuation's environment
1214-
context.runtime.environments = contEnv
1215-
}
1191+
[InstrType.BREAK_MARKER]: function () {}
12161192
}

src/cse-machine/types.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@ export enum InstrType {
2222
CONTINUE = 'Continue',
2323
CONTINUE_MARKER = 'ContinueMarker',
2424
BREAK = 'Break',
25-
BREAK_MARKER = 'BreakMarker',
26-
GENERATE_CONT = 'GenerateContinuation',
27-
RESUME_CONT = 'ResumeContinuation'
25+
BREAK_MARKER = 'BreakMarker'
2826
}
2927

3028
interface BaseInstr {
@@ -76,12 +74,6 @@ export interface ArrLitInstr extends BaseInstr {
7674
arity: number
7775
}
7876

79-
export type GenContInstr = BaseInstr
80-
81-
export interface ResumeContInstr extends BaseInstr {
82-
numOfArgs: number
83-
}
84-
8577
export type Instr =
8678
| BaseInstr
8779
| WhileInstr
@@ -90,8 +82,6 @@ export type Instr =
9082
| BranchInstr
9183
| EnvInstr
9284
| ArrLitInstr
93-
| GenContInstr
94-
| ResumeContInstr
9585

9686
export type ControlItem = (Node | Instr) & {
9787
isEnvDependent?: boolean

src/cse-machine/utils.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ import * as errors from '../errors/errors'
66
import { RuntimeSourceError } from '../errors/runtimeSourceError'
77
import type { Environment, Node, StatementSequence, Value } from '../types'
88
import * as ast from '../utils/ast/astCreator'
9-
import { isContinuation } from './continuations'
109
import Heap from './heap'
1110
import * as instr from './instrCreator'
1211
import { Control } from './interpreter'
1312
import { AppInstr, EnvArray, ControlItem, Instr, InstrType } from './types'
1413
import Closure from './closure'
14+
import { Continuation, isCallWithCurrentContinuation } from './continuations'
1515

1616
/**
1717
* Typeguard for Instr to distinguish between program statements and instructions.
@@ -505,10 +505,19 @@ export const checkNumberOfArguments = (
505505
)
506506
)
507507
}
508-
} else if (isContinuation(callee)) {
508+
} else if (isCallWithCurrentContinuation(callee)) {
509+
// call/cc should have a single argument
510+
if (args.length !== 1) {
511+
return handleRuntimeError(
512+
context,
513+
new errors.InvalidNumberOfArguments(exp, 1, args.length, false)
514+
)
515+
}
516+
return undefined
517+
} else if (callee instanceof Continuation) {
509518
// Continuations have variadic arguments,
510519
// and so we can let it pass
511-
// in future, if we can somehow check the number of arguments
520+
// TODO: in future, if we can somehow check the number of arguments
512521
// expected by the continuation, we can add a check here.
513522
return undefined
514523
} else {

0 commit comments

Comments
 (0)