@chenglou/freerange 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +237 -67
- package/dist/fr.js +7821 -0
- package/fr.ts +0 -2
- package/package.json +6 -3
- package/src/audit.ts +2 -1
- package/src/domain/value-identity.ts +56 -0
- package/src/engine/analyze.ts +19 -12
- package/src/engine/state.ts +23 -11
- package/src/engine/transfer.ts +78 -29
- package/src/ir/instructions.ts +7 -1
- package/src/ir/program.ts +7 -0
- package/src/lower/accept.ts +6 -2
- package/src/lower/context.ts +11 -5
- package/src/lower/expression.ts +61 -33
- package/src/lower/function-unit.ts +64 -0
- package/src/lower/module.ts +20 -4
- package/src/lower/platform.ts +3 -0
- package/src/lower/program.ts +62 -34
- package/src/lower/statements.ts +3 -8
- package/src/lower/static-intrinsics.ts +9 -8
- package/src/project.ts +2 -2
- package/src/report/index.ts +2 -0
- package/src/requirements/infer.ts +76 -25
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import type {SiteID, ValueID} from '../ir/ids.ts'
|
|
2
2
|
import type {InstructionIR} from '../ir/instructions.ts'
|
|
3
3
|
import type {FunctionIR} from '../ir/program.ts'
|
|
4
|
+
import {
|
|
5
|
+
createValueIdentityOwner,
|
|
6
|
+
sameValueIdentity,
|
|
7
|
+
type ValueIdentity,
|
|
8
|
+
type ValueIdentityOwner,
|
|
9
|
+
} from '../domain/value-identity.ts'
|
|
4
10
|
import type {InferredPrecondition, NumericExpression} from './model.ts'
|
|
5
11
|
|
|
6
12
|
export type ExpressionContext = {
|
|
7
13
|
parameterExpressions: Array<NumericExpression | null>
|
|
8
|
-
// Calls pass the caller's
|
|
9
|
-
// in a callee refer to the same
|
|
10
|
-
//
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
// Calls pass the caller's identities directly, so duplicate arguments and facts created
|
|
15
|
+
// in a callee refer to the same stored value. Local identities use the evaluation's
|
|
16
|
+
// owner token, which distinguishes separate calls without encoding the call path.
|
|
17
|
+
parameterIdentities: ValueIdentity[]
|
|
18
|
+
identityOwner: ValueIdentityOwner
|
|
19
|
+
identityByValue: Array<ValueIdentity | undefined>
|
|
13
20
|
parameterIndexByValue: Array<number | undefined>
|
|
14
21
|
instructionByValue: Array<InstructionIR | undefined>
|
|
15
22
|
instructionCount: number
|
|
@@ -18,21 +25,27 @@ export type ExpressionContext = {
|
|
|
18
25
|
export function createExpressionContext(
|
|
19
26
|
fn: FunctionIR,
|
|
20
27
|
parameterExpressions: Array<NumericExpression | null>,
|
|
21
|
-
|
|
22
|
-
|
|
28
|
+
parameterIdentities?: ValueIdentity[],
|
|
29
|
+
identityOwner = createValueIdentityOwner(),
|
|
23
30
|
): ExpressionContext {
|
|
24
|
-
const identityKeys = parameterIdentityKeys ?? fn.parameters.map((_, index) => `p${index}`)
|
|
25
|
-
if (identityKeys.length !== fn.parameters.length) {
|
|
26
|
-
throw new Error(`Expected ${fn.parameters.length} parameter identity keys for ${fn.name}`)
|
|
27
|
-
}
|
|
28
31
|
const context: ExpressionContext = {
|
|
29
32
|
parameterExpressions,
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
parameterIdentities: [],
|
|
34
|
+
identityOwner,
|
|
35
|
+
identityByValue: [],
|
|
32
36
|
parameterIndexByValue: [],
|
|
33
37
|
instructionByValue: [],
|
|
34
38
|
instructionCount: 0,
|
|
35
39
|
}
|
|
40
|
+
context.parameterIdentities = parameterIdentities
|
|
41
|
+
?? fn.parameters.map(parameter => ({
|
|
42
|
+
kind: 'local',
|
|
43
|
+
owner: identityOwner,
|
|
44
|
+
value: parameter.value,
|
|
45
|
+
}))
|
|
46
|
+
if (context.parameterIdentities.length !== fn.parameters.length) {
|
|
47
|
+
throw new Error(`Expected ${fn.parameters.length} parameter identities for ${fn.name}`)
|
|
48
|
+
}
|
|
36
49
|
for (let index = 0; index < fn.parameters.length; index++) {
|
|
37
50
|
context.parameterIndexByValue[fn.parameters[index]!.value] = index
|
|
38
51
|
}
|
|
@@ -159,29 +172,67 @@ export function staticRequirement(
|
|
|
159
172
|
return null
|
|
160
173
|
}
|
|
161
174
|
|
|
162
|
-
// A stable
|
|
175
|
+
// A stable identity for the runtime value an IR value holds. Forward value facts and exact
|
|
163
176
|
// same-value operations share this rule instead of maintaining separate notions of
|
|
164
177
|
// identity. Property and array reads are stable under the accepted subset's immutability
|
|
165
178
|
// rules; module and platform reads stay value-keyed because they may change between reads.
|
|
166
|
-
export function
|
|
179
|
+
export function canonicalValueIdentity(
|
|
180
|
+
value: ValueID,
|
|
181
|
+
context: ExpressionContext,
|
|
182
|
+
): ValueIdentity {
|
|
183
|
+
const cached = context.identityByValue[value]
|
|
184
|
+
if (cached != null) return cached
|
|
167
185
|
const stored = resolveStoredValue(value, context)
|
|
168
|
-
if (stored !== value)
|
|
186
|
+
if (stored !== value) {
|
|
187
|
+
const identity = canonicalValueIdentity(stored, context)
|
|
188
|
+
context.identityByValue[value] = identity
|
|
189
|
+
return identity
|
|
190
|
+
}
|
|
169
191
|
const parameterIndex = context.parameterIndexByValue[value]
|
|
170
|
-
if (parameterIndex != null)
|
|
192
|
+
if (parameterIndex != null) {
|
|
193
|
+
const identity = context.parameterIdentities[parameterIndex]
|
|
194
|
+
if (identity == null) throw new Error(`Missing identity for parameter ${parameterIndex}`)
|
|
195
|
+
context.identityByValue[value] = identity
|
|
196
|
+
return identity
|
|
197
|
+
}
|
|
171
198
|
const producer = context.instructionByValue[value]
|
|
199
|
+
let identity: ValueIdentity
|
|
172
200
|
if (producer?.kind === 'property') {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
201
|
+
identity = {
|
|
202
|
+
kind: 'property',
|
|
203
|
+
object: canonicalValueIdentity(producer.object, context),
|
|
204
|
+
property: producer.property,
|
|
205
|
+
}
|
|
206
|
+
} else if (producer?.kind === 'arrayLength') {
|
|
207
|
+
identity = {
|
|
208
|
+
kind: 'property',
|
|
209
|
+
object: canonicalValueIdentity(producer.array, context),
|
|
210
|
+
property: 'length',
|
|
211
|
+
}
|
|
212
|
+
} else if (producer?.kind === 'stringLength') {
|
|
213
|
+
identity = {
|
|
214
|
+
kind: 'property',
|
|
215
|
+
object: canonicalValueIdentity(producer.value, context),
|
|
216
|
+
property: 'length',
|
|
217
|
+
}
|
|
218
|
+
} else if (producer?.kind === 'arrayIndex') {
|
|
219
|
+
identity = {
|
|
220
|
+
kind: 'arrayIndex',
|
|
221
|
+
array: canonicalValueIdentity(producer.array, context),
|
|
222
|
+
index: canonicalValueIdentity(producer.index, context),
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
identity = {kind: 'local', owner: context.identityOwner, value}
|
|
179
226
|
}
|
|
180
|
-
|
|
227
|
+
context.identityByValue[value] = identity
|
|
228
|
+
return identity
|
|
181
229
|
}
|
|
182
230
|
|
|
183
231
|
export function sameRuntimeValue(left: ValueID, right: ValueID, context: ExpressionContext): boolean {
|
|
184
|
-
return left === right ||
|
|
232
|
+
return left === right || sameValueIdentity(
|
|
233
|
+
canonicalValueIdentity(left, context),
|
|
234
|
+
canonicalValueIdentity(right, context),
|
|
235
|
+
)
|
|
185
236
|
}
|
|
186
237
|
|
|
187
238
|
export function addPrecondition(preconditions: InferredPrecondition[], candidate: InferredPrecondition): void {
|