@typesol/cli 0.22.0 → 0.23.0
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 +21 -0
- package/README.md +1 -1
- package/dist/compiler/analyze.js +93 -15
- package/dist/compiler/emit/base.d.ts +11 -1
- package/dist/compiler/emit/base.js +21 -0
- package/dist/compiler/emit/calls.d.ts +11 -1
- package/dist/compiler/emit/calls.js +68 -6
- package/dist/compiler/emit/cpi.js +42 -4
- package/dist/compiler/emit/expressions.js +9 -0
- package/dist/compiler/emit/function.js +5 -3
- package/dist/compiler/emit/program.js +9 -2
- package/dist/compiler/emit/shared.d.ts +32 -2
- package/dist/compiler/emit/shared.js +43 -0
- package/dist/compiler/emit/statements.d.ts +35 -0
- package/dist/compiler/emit/statements.js +237 -79
- package/dist/compiler/emit/storage.js +39 -13
- package/dist/compiler/model.d.ts +18 -1
- package/docs/language.md +9 -9
- package/lib/typesol.d.ts +8 -0
- package/package.json +1 -1
- package/runtime/typesol_rt.h +19 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
import { type AccountParam, type FieldType, type Helper, type IntName, type ParamKind, type ScalarType, type ValueType } from "../model.js";
|
|
2
|
+
import { type AccountParam, type FieldType, type Helper, type IntName, type ParamKind, type ScalarType, type StructDef, type ValueType } from "../model.js";
|
|
3
3
|
/** Maximum accounts the generated entrypoint deserializes. */
|
|
4
4
|
export declare const MAX_ACCOUNTS = 32;
|
|
5
5
|
export declare const MAX_SEEDS = 15;
|
|
@@ -46,16 +46,27 @@ export interface Ref {
|
|
|
46
46
|
vec?: {
|
|
47
47
|
len: string;
|
|
48
48
|
items: string;
|
|
49
|
+
/** The account's size without this Vec's elements (so with `n` of them: 8 + fixed + n × elemSize). */
|
|
49
50
|
fixed: string;
|
|
50
51
|
elemSize: number;
|
|
51
52
|
resizable: boolean;
|
|
52
53
|
cap?: number;
|
|
54
|
+
/** Growable Vecs stored after this one move when it grows or shrinks: where its elements end, and their paths. */
|
|
55
|
+
tail?: {
|
|
56
|
+
end: string;
|
|
57
|
+
dataEnd: string;
|
|
58
|
+
paths: string[];
|
|
59
|
+
};
|
|
53
60
|
};
|
|
54
61
|
}
|
|
55
62
|
export interface Local {
|
|
56
63
|
c: string;
|
|
64
|
+
/** A value, or a local FixedArray / FixedVec (`fixedArray<T, N>(…)`, `const ys = xs.map(…)`) held in place. */
|
|
57
65
|
type: ValueType | (FieldType & {
|
|
58
66
|
kind: "array";
|
|
67
|
+
}) | (FieldType & {
|
|
68
|
+
kind: "vec";
|
|
69
|
+
cap: number;
|
|
59
70
|
});
|
|
60
71
|
mutable: boolean;
|
|
61
72
|
/** Pubkey held as a `const SolPubkey *` (helper parameters) rather than a value. */
|
|
@@ -70,7 +81,8 @@ export interface Local {
|
|
|
70
81
|
}
|
|
71
82
|
/** A closure and the scopes it was declared in (its free names resolve there, wherever it is called). */
|
|
72
83
|
export interface Closure {
|
|
73
|
-
|
|
84
|
+
/** The function expanded: a closure, or a helper expanded where it is called because it was given one. */
|
|
85
|
+
node: ts.ArrowFunction | ts.FunctionExpression | ts.FunctionDeclaration;
|
|
74
86
|
name: string;
|
|
75
87
|
scopes: Map<string, Local>[];
|
|
76
88
|
}
|
|
@@ -102,6 +114,24 @@ export interface EmitOptions {
|
|
|
102
114
|
/** Log a marker and the remaining compute units before every statement (`typesol build --profile`). */
|
|
103
115
|
profile?: boolean;
|
|
104
116
|
}
|
|
117
|
+
/** C type of a growable Vec stored after the first one of struct `s`: `{ uint32_t len; T items[]; }`. */
|
|
118
|
+
export declare function laterVecC(s: string, field: string): string;
|
|
119
|
+
/** The account size of struct `s` with every growable Vec empty, as C (8-byte discriminator not included). */
|
|
120
|
+
export declare function emptySizeC(s: StructDef): string;
|
|
121
|
+
/**
|
|
122
|
+
* Where growable Vec `k` of a struct at `base` (a C lvalue of type TSS_<S>) starts, as C: its length and elements.
|
|
123
|
+
* The first is a member of the struct; each further one starts right after the previous one's elements.
|
|
124
|
+
*/
|
|
125
|
+
export declare function vecPlace(s: StructDef, base: string, k: number): {
|
|
126
|
+
len: string;
|
|
127
|
+
items: string;
|
|
128
|
+
end: string;
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* C lines checking that an account's size matches its growable Vecs' lengths (`d`: its TSS_<S> pointer, `a`: its
|
|
132
|
+
* TsAccount). One Vec: ts_check_vec_len; more: each length read only once the bytes before it are known to exist.
|
|
133
|
+
*/
|
|
134
|
+
export declare function vecLenCheck(s: StructDef, a: string, d: string, pad?: string): string[];
|
|
105
135
|
/** C typedef name of a FixedVec<T, N>: `{ uint32_t len; T items[N]; }`. */
|
|
106
136
|
export declare function fixedVecC(t: FieldType & {
|
|
107
137
|
kind: "vec";
|
|
@@ -202,6 +202,49 @@ export function helperSignature(h) {
|
|
|
202
202
|
params.push(h.returns.kind === "struct" ? `TSS_${h.returns.name} *ret` : h.returns.kind === "string" ? `TS_Str *ret` : `${scalarC(h.returns)} *ret`);
|
|
203
203
|
return `static uint64_t fn_${h.name}(${params.join(", ")})`;
|
|
204
204
|
}
|
|
205
|
+
/** C type of a growable Vec stored after the first one of struct `s`: `{ uint32_t len; T items[]; }`. */
|
|
206
|
+
export function laterVecC(s, field) {
|
|
207
|
+
return `TSW_${s}__${field}`;
|
|
208
|
+
}
|
|
209
|
+
/** The account size of struct `s` with every growable Vec empty, as C (8-byte discriminator not included). */
|
|
210
|
+
export function emptySizeC(s) {
|
|
211
|
+
const n = s.vecs?.length ?? 0;
|
|
212
|
+
return n > 1 ? `(sizeof(TSS_${s.name}) + ${4 * (n - 1)})` : `sizeof(TSS_${s.name})`;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Where growable Vec `k` of a struct at `base` (a C lvalue of type TSS_<S>) starts, as C: its length and elements.
|
|
216
|
+
* The first is a member of the struct; each further one starts right after the previous one's elements.
|
|
217
|
+
*/
|
|
218
|
+
export function vecPlace(s, base, k) {
|
|
219
|
+
const vs = s.vecs;
|
|
220
|
+
const first = vs[0];
|
|
221
|
+
let end = `((uint8_t *)${base}.${first.name} + (uint64_t)${base}.${first.name}__len * ${first.elemSize})`;
|
|
222
|
+
let place = { len: `${base}.${first.name}__len`, items: `${base}.${first.name}`, end };
|
|
223
|
+
for (let j = 1; j <= k; j++) {
|
|
224
|
+
const v = vs[j];
|
|
225
|
+
const h = `((${laterVecC(s.name, v.name)} *)${end})`;
|
|
226
|
+
end = `((uint8_t *)${h}->items + (uint64_t)${h}->len * ${v.elemSize})`;
|
|
227
|
+
place = { len: `${h}->len`, items: `${h}->items`, end };
|
|
228
|
+
}
|
|
229
|
+
return place;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* C lines checking that an account's size matches its growable Vecs' lengths (`d`: its TSS_<S> pointer, `a`: its
|
|
233
|
+
* TsAccount). One Vec: ts_check_vec_len; more: each length read only once the bytes before it are known to exist.
|
|
234
|
+
*/
|
|
235
|
+
export function vecLenCheck(s, a, d, pad = " ") {
|
|
236
|
+
const vs = s.vecs ?? [];
|
|
237
|
+
if (vs.length === 1)
|
|
238
|
+
return [`${pad}TS_TRY(ts_check_vec_len(${a}, sizeof(TSS_${s.name}), ${d}->${vs[0].name}__len, TS_ELEM_${s.name}));`];
|
|
239
|
+
const o = `${d.replace(/\W/g, "")}__vo`;
|
|
240
|
+
const lines = [`${pad}{`, `${pad} uint64_t ${o} = sizeof(TSS_${s.name}) + (uint64_t)${d}->${vs[0].name}__len * ${vs[0].elemSize};`];
|
|
241
|
+
for (const v of vs.slice(1)) {
|
|
242
|
+
lines.push(`${pad} if (${a}->data_len < 8 + ${o} + 4) TS_FAIL(TS_ERR_ACCOUNT_TOO_SMALL, "TypeSOL: account size does not match its Vec lengths");`);
|
|
243
|
+
lines.push(`${pad} ${o} += 4 + (uint64_t)((${laterVecC(s.name, v.name)} *)(${a}->data + 8 + ${o}))->len * ${v.elemSize};`);
|
|
244
|
+
}
|
|
245
|
+
lines.push(`${pad} if (${a}->data_len != 8 + ${o}) TS_FAIL(TS_ERR_ACCOUNT_TOO_SMALL, "TypeSOL: account size does not match its Vec lengths");`, `${pad}}`);
|
|
246
|
+
return lines;
|
|
247
|
+
}
|
|
205
248
|
/** C typedef name of a FixedVec<T, N>: `{ uint32_t len; T items[N]; }`. */
|
|
206
249
|
export function fixedVecC(t) {
|
|
207
250
|
return `TSF_${fixedVecName(t)}`;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
+
import { type FieldType } from "../model.js";
|
|
2
3
|
import { type Value, type Closure } from "./shared.js";
|
|
3
4
|
import { CpiEmitter } from "./cpi.js";
|
|
4
5
|
/**
|
|
@@ -17,6 +18,40 @@ export declare abstract class StatementEmitter extends CpiEmitter {
|
|
|
17
18
|
* As in JS, the length is read again before every step, so a body that pushes or pops sees the new length.
|
|
18
19
|
*/
|
|
19
20
|
protected forOf(stmt: ts.ForOfStatement): void;
|
|
21
|
+
/** `xs.map(…)` / `xs.filter(…)` (or a chain of them): a list built element by element, not stored anywhere yet. */
|
|
22
|
+
protected isPipeline(e: ts.Expression): e is ts.CallExpression;
|
|
23
|
+
/** The list under a chain of map / filter calls, and the calls in the order they run. */
|
|
24
|
+
protected pipelineStages(e: ts.Expression): {
|
|
25
|
+
base: ts.Expression;
|
|
26
|
+
stages: {
|
|
27
|
+
kind: "map" | "filter";
|
|
28
|
+
fn: ts.ArrowFunction | ts.FunctionExpression;
|
|
29
|
+
}[];
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Run `body` for each element of a list, or of a chain of `map` / `filter` calls on one (`xs.filter(f).map(g)`):
|
|
33
|
+
* one index loop over the list (its length read every step, as `for … of`), each callback expanded in it. A
|
|
34
|
+
* filter skips to the next element, a map binds its result, so a chain builds no list. `body` gets the element (as
|
|
35
|
+
* `list.get(i)` gives it, structs by reference, or the mapped value) and its index in the chain's output (after a
|
|
36
|
+
* filter, the count of elements it let through, as JS gives callbacks).
|
|
37
|
+
*/
|
|
38
|
+
protected eachElement(list: ts.Expression, at: ts.Node, names: {
|
|
39
|
+
index: string;
|
|
40
|
+
ci: string;
|
|
41
|
+
next?: string;
|
|
42
|
+
alwaysNext?: boolean;
|
|
43
|
+
}, body: (element: ts.Expression, index: ts.Expression) => void): void;
|
|
44
|
+
/**
|
|
45
|
+
* `const ys = xs.map(…)` / `xs.filter(…)`: the chain's elements in a local FixedVec, as big as the list it starts
|
|
46
|
+
* from (a FixedArray's length, a FixedVec's capacity) or as annotated (`const ys: FixedVec<u64, 16> = …`, needed
|
|
47
|
+
* when it starts from a Vec). It lives on the stack, so it holds at most 2 KiB; one more element than it holds aborts.
|
|
48
|
+
*/
|
|
49
|
+
protected localList(d: ts.VariableDeclaration, init: ts.CallExpression, c: string): FieldType & {
|
|
50
|
+
kind: "vec";
|
|
51
|
+
cap: number;
|
|
52
|
+
};
|
|
53
|
+
/** The element type of a Vec, FixedArray, FixedVec or Vec argument. */
|
|
54
|
+
protected listElem(list: ts.Expression): FieldType;
|
|
20
55
|
/** Whether `e` is the remaining accounts (or a const bound to them). */
|
|
21
56
|
protected isRemaining(e: ts.Expression): boolean;
|
|
22
57
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
import { MAX_RETURN_DATA, resolveValueType, structNameOf } from "../analyze.js";
|
|
2
|
+
import { MAX_RETURN_DATA, resolveScalarType, resolveValueType, structNameOf, typesolName } from "../analyze.js";
|
|
3
3
|
import { INT_INFO, isWritable, optionKey, scalarSize, typeName } from "../model.js";
|
|
4
|
-
import { U64, BOOL, cString, optC, scalarC, scalarStorageC, valueC } from "./shared.js";
|
|
4
|
+
import { U64, BOOL, cString, fixedVecC, optC, scalarC, scalarStorageC, valueC } from "./shared.js";
|
|
5
5
|
import { CpiEmitter } from "./cpi.js";
|
|
6
6
|
/**
|
|
7
7
|
* Statements: variables, assignment, control flow and return.
|
|
@@ -259,37 +259,196 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
259
259
|
n.parent = stmt;
|
|
260
260
|
return n;
|
|
261
261
|
};
|
|
262
|
-
|
|
263
|
-
const length = place(f.createPropertyAccessExpression(stmt.expression, "length"));
|
|
264
|
-
const get = place(f.createCallExpression(place(f.createPropertyAccessExpression(stmt.expression, "get")), undefined, [indexId]));
|
|
265
|
-
// The element's declaration: the source's own name (so aliasing checks find its uses) bound to list.get(i).
|
|
266
|
-
const decl = f.createVariableDeclaration(d.name, undefined, undefined, get);
|
|
267
|
-
ts.setTextRange(decl, d);
|
|
268
|
-
decl.parent = list;
|
|
269
|
-
const declList = { ...list, flags: ts.NodeFlags.Const, declarations: [decl] };
|
|
262
|
+
void place;
|
|
270
263
|
const ci = this.tmp("of_i");
|
|
271
264
|
const label = this.tmp("ts_continue_");
|
|
265
|
+
this.eachElement(stmt.expression, stmt, { index, ci, next: label, alwaysNext: true }, (element) => {
|
|
266
|
+
// The element's declaration: the source's own name (so aliasing checks find its uses) bound to the element.
|
|
267
|
+
const decl = f.createVariableDeclaration(d.name, undefined, undefined, element);
|
|
268
|
+
ts.setTextRange(decl, d);
|
|
269
|
+
decl.parent = list;
|
|
270
|
+
const declList = { ...list, flags: ts.NodeFlags.Const, declarations: [decl] };
|
|
271
|
+
this.variable(declList);
|
|
272
|
+
this.loops.push(label);
|
|
273
|
+
this.line(`{`);
|
|
274
|
+
this.block(() => this.body(stmt.statement));
|
|
275
|
+
this.line(`}`);
|
|
276
|
+
this.loops.pop();
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
/** `xs.map(…)` / `xs.filter(…)` (or a chain of them): a list built element by element, not stored anywhere yet. */
|
|
280
|
+
isPipeline(e) {
|
|
281
|
+
return ts.isCallExpression(e) && ts.isPropertyAccessExpression(e.expression) && (e.expression.name.text === "map" || e.expression.name.text === "filter");
|
|
282
|
+
}
|
|
283
|
+
/** The list under a chain of map / filter calls, and the calls in the order they run. */
|
|
284
|
+
pipelineStages(e) {
|
|
285
|
+
const stages = [];
|
|
286
|
+
let base = e;
|
|
287
|
+
while (this.isPipeline(base)) {
|
|
288
|
+
const access = base.expression;
|
|
289
|
+
const kind = access.name.text;
|
|
290
|
+
if (base.arguments.length !== 1)
|
|
291
|
+
this.fail(base, `${kind}() takes one argument: the callback`);
|
|
292
|
+
const fn = base.arguments[0];
|
|
293
|
+
if (!ts.isArrowFunction(fn) && !ts.isFunctionExpression(fn))
|
|
294
|
+
this.fail(fn, `pass ${kind} an arrow function written here: xs.${kind}((x) => …)`);
|
|
295
|
+
if (fn.parameters.length > 2)
|
|
296
|
+
this.fail(fn.parameters[2], `${kind}'s callback takes the element and its index`);
|
|
297
|
+
stages.unshift({ kind, fn });
|
|
298
|
+
base = access.expression;
|
|
299
|
+
}
|
|
300
|
+
if (stages.length) {
|
|
301
|
+
const l = ts.isIdentifier(base) ? this.lookup(base.text) : undefined;
|
|
302
|
+
if (!(l?.type.kind === "vecArg" || l?.type.kind === "array" || this.peekArray(base)))
|
|
303
|
+
this.fail(base, "map and filter work on a Vec, FixedArray or FixedVec");
|
|
304
|
+
}
|
|
305
|
+
return { base, stages };
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Run `body` for each element of a list, or of a chain of `map` / `filter` calls on one (`xs.filter(f).map(g)`):
|
|
309
|
+
* one index loop over the list (its length read every step, as `for … of`), each callback expanded in it. A
|
|
310
|
+
* filter skips to the next element, a map binds its result, so a chain builds no list. `body` gets the element (as
|
|
311
|
+
* `list.get(i)` gives it, structs by reference, or the mapped value) and its index in the chain's output (after a
|
|
312
|
+
* filter, the count of elements it let through, as JS gives callbacks).
|
|
313
|
+
*/
|
|
314
|
+
eachElement(list, at, names, body) {
|
|
315
|
+
const f = ts.factory;
|
|
316
|
+
const place = (n) => {
|
|
317
|
+
ts.setTextRange(n, at);
|
|
318
|
+
n.parent = at;
|
|
319
|
+
return n;
|
|
320
|
+
};
|
|
321
|
+
const { base, stages } = this.pipelineStages(list);
|
|
322
|
+
const { index, ci } = names;
|
|
323
|
+
const indexId = place(f.createIdentifier(index));
|
|
324
|
+
const length = place(f.createPropertyAccessExpression(base, "length"));
|
|
325
|
+
const get = place(f.createCallExpression(place(f.createPropertyAccessExpression(base, "get")), undefined, [indexId]));
|
|
326
|
+
const next = names.next ?? (stages.some((s) => s.kind === "filter") ? this.tmp("ts_next_") : undefined);
|
|
272
327
|
this.line(`{`);
|
|
273
328
|
this.block(() => {
|
|
274
329
|
this.line(`uint64_t ${ci} = 0;`);
|
|
275
330
|
this.scopes.at(-1).set(index, { c: ci, type: U64, mutable: false });
|
|
331
|
+
const counters = stages
|
|
332
|
+
.filter((st) => st.kind === "filter")
|
|
333
|
+
.map(() => {
|
|
334
|
+
const k = this.tmp("lm_k");
|
|
335
|
+
this.line(`uint64_t ${k} = 0;`);
|
|
336
|
+
return k;
|
|
337
|
+
});
|
|
276
338
|
this.line(`while (1) {`);
|
|
277
339
|
this.block(() => {
|
|
278
|
-
const n = this.expect(this.expr(length, U64), U64,
|
|
340
|
+
const n = this.expect(this.expr(length, U64), U64, base);
|
|
279
341
|
this.line(`if (!(${ci} < ${n.c})) break;`);
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
342
|
+
let element = get;
|
|
343
|
+
let idx = indexId;
|
|
344
|
+
let k = 0;
|
|
345
|
+
for (const st of stages) {
|
|
346
|
+
const closure = { node: st.fn, name: `${st.kind}'s callback`, scopes: [...this.scopes] };
|
|
347
|
+
const v = this.expandClosure(closure, [element, idx].slice(0, st.fn.parameters.length), st.fn);
|
|
348
|
+
if (st.kind === "filter") {
|
|
349
|
+
if (!v)
|
|
350
|
+
this.fail(st.fn, "filter's callback returns a boolean");
|
|
351
|
+
const b = this.expect(v, BOOL, st.fn);
|
|
352
|
+
this.line(`if (!(${b.c})) goto ${next};`);
|
|
353
|
+
const id = `__lk${this.temp++}`;
|
|
354
|
+
const ic = this.tmp("lm_ki");
|
|
355
|
+
this.line(`uint64_t ${ic} = ${counters[k++]}++;`);
|
|
356
|
+
this.scopes.at(-1).set(id, { c: ic, type: U64, mutable: false });
|
|
357
|
+
idx = place(f.createIdentifier(id));
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
if (!v)
|
|
361
|
+
this.fail(st.fn, "map's callback returns the new element");
|
|
362
|
+
const mv = v.literal !== undefined ? this.literal(v.literal, U64, st.fn) : v;
|
|
363
|
+
if (!["int", "bool", "pubkey", "enum"].includes(mv.type.kind))
|
|
364
|
+
this.fail(st.fn, `map's callback returns integers, booleans, Pubkeys or enums (the new list's elements), not ${typeName(mv.type)}`);
|
|
365
|
+
const id = `__lv${this.temp++}`;
|
|
366
|
+
const mc = this.tmp("lm_v");
|
|
367
|
+
this.line(mv.type.kind === "pubkey" ? `SolPubkey ${mc} = *(${mv.c});` : `${valueC(mv.type)} ${mc} = ${mv.c};`);
|
|
368
|
+
this.scopes.at(-1).set(id, { c: mc, type: mv.type, mutable: false });
|
|
369
|
+
element = place(f.createIdentifier(id));
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
body(element, idx);
|
|
373
|
+
if (next && (names.alwaysNext || counters.length))
|
|
374
|
+
this.line(`${next}:;`);
|
|
287
375
|
this.line(`${ci}++;`);
|
|
288
376
|
});
|
|
289
377
|
this.line(`}`);
|
|
290
378
|
});
|
|
291
379
|
this.line(`}`);
|
|
292
380
|
}
|
|
381
|
+
/**
|
|
382
|
+
* `const ys = xs.map(…)` / `xs.filter(…)`: the chain's elements in a local FixedVec, as big as the list it starts
|
|
383
|
+
* from (a FixedArray's length, a FixedVec's capacity) or as annotated (`const ys: FixedVec<u64, 16> = …`, needed
|
|
384
|
+
* when it starts from a Vec). It lives on the stack, so it holds at most 2 KiB; one more element than it holds aborts.
|
|
385
|
+
*/
|
|
386
|
+
localList(d, init, c) {
|
|
387
|
+
const { base, stages } = this.pipelineStages(init);
|
|
388
|
+
let cap;
|
|
389
|
+
let annotatedElem;
|
|
390
|
+
if (d.type) {
|
|
391
|
+
const t = d.type;
|
|
392
|
+
const args = ts.isTypeReferenceNode(t) ? (t.typeArguments ?? []) : [];
|
|
393
|
+
const n = args[1] && ts.isLiteralTypeNode(args[1]) && ts.isNumericLiteral(args[1].literal) ? Number(args[1].literal.text) : NaN;
|
|
394
|
+
const elemNode = args[0];
|
|
395
|
+
const sn = elemNode && structNameOf(elemNode, this.scope);
|
|
396
|
+
annotatedElem = elemNode
|
|
397
|
+
? (resolveScalarType(elemNode, this.scope) ?? (sn && this.model.structs.has(sn) ? { kind: "struct", name: sn } : undefined))
|
|
398
|
+
: undefined;
|
|
399
|
+
if (!ts.isTypeReferenceNode(t) || typesolName(t, this.scope.imports) !== "FixedVec" || !annotatedElem || !Number.isInteger(n) || n < 1)
|
|
400
|
+
this.fail(t, "annotate the new list as FixedVec<T, N> (N a literal)");
|
|
401
|
+
cap = n;
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
const r = this.reporter.guard(() => this.ref(base));
|
|
405
|
+
const l = ts.isIdentifier(base) ? this.lookup(base.text) : undefined;
|
|
406
|
+
if (r?.type.kind === "array")
|
|
407
|
+
cap = r.type.len;
|
|
408
|
+
else if (r?.type.kind === "vec" && r.type.cap !== undefined)
|
|
409
|
+
cap = r.type.cap;
|
|
410
|
+
if (cap === undefined || l?.type.kind === "vecArg")
|
|
411
|
+
this.fail(init, "this list has no fixed size: annotate the new one with its capacity, const ys: FixedVec<T, N> = …");
|
|
412
|
+
}
|
|
413
|
+
// The element type: the last map's result, else the list's own elements (known once the loop is emitted).
|
|
414
|
+
const declAt = this.lines.length;
|
|
415
|
+
this.line(`/* ${c} */`);
|
|
416
|
+
let elem = stages.some((st) => st.kind === "map") ? undefined : this.listElem(base);
|
|
417
|
+
const cap0 = cap;
|
|
418
|
+
this.eachElement(init, init, { index: `__ml${this.temp++}`, ci: this.tmp("ml_i") }, (element) => {
|
|
419
|
+
if (!elem) {
|
|
420
|
+
const v = this.expr(element);
|
|
421
|
+
elem = v.type;
|
|
422
|
+
}
|
|
423
|
+
const target = annotatedElem ?? elem;
|
|
424
|
+
const sub = { lv: `${c}.items[${c}.len]`, type: target, writable: true };
|
|
425
|
+
this.line(`if (${c}.len >= ${cap0}) TS_FAIL(TS_ERR_INDEX, "TypeSOL: the new list is full");`);
|
|
426
|
+
const vt = this.storageValueType(target);
|
|
427
|
+
if (vt)
|
|
428
|
+
this.line(this.writeStorage(sub, this.ownedString(this.expect(this.expr(element, vt), vt, element)).c) + ";");
|
|
429
|
+
else
|
|
430
|
+
this.writeAggregate(sub, element);
|
|
431
|
+
this.line(`${c}.len++;`);
|
|
432
|
+
});
|
|
433
|
+
const t = { kind: "vec", elem: annotatedElem ?? elem, cap: cap0 };
|
|
434
|
+
const bytes = 4 + this.fieldBytes(t.elem) * cap0;
|
|
435
|
+
if (bytes > 2048)
|
|
436
|
+
this.fail(init, `the new list takes ${bytes} bytes; locals live on the 4 KiB stack, so keep it under 2 KiB (a smaller FixedVec<T, N>)`);
|
|
437
|
+
this.model.localVecs.set(fixedVecC(t), t);
|
|
438
|
+
const pad = " ".repeat(this.indent);
|
|
439
|
+
this.lines[declAt] = `${pad}${fixedVecC(t)} ${c};\n${pad}ts_memzero(&${c}, sizeof(${c}));`;
|
|
440
|
+
return t;
|
|
441
|
+
}
|
|
442
|
+
/** The element type of a Vec, FixedArray, FixedVec or Vec argument. */
|
|
443
|
+
listElem(list) {
|
|
444
|
+
const l = ts.isIdentifier(list) ? this.lookup(list.text) : undefined;
|
|
445
|
+
if (l?.type.kind === "vecArg")
|
|
446
|
+
return l.type.elem;
|
|
447
|
+
const r = this.ref(list);
|
|
448
|
+
if (r && (r.type.kind === "array" || r.type.kind === "vec"))
|
|
449
|
+
return r.type.elem;
|
|
450
|
+
this.fail(list, "expected a Vec, FixedArray or FixedVec");
|
|
451
|
+
}
|
|
293
452
|
/** Whether `e` is the remaining accounts (or a const bound to them). */
|
|
294
453
|
isRemaining(e) {
|
|
295
454
|
if (ts.isCallExpression(e) && this.exportName(e.expression) === "remainingAccounts")
|
|
@@ -337,6 +496,11 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
337
496
|
this.fail(p, "closure parameters are plain names");
|
|
338
497
|
const arg = args[i];
|
|
339
498
|
const name = p.name.text;
|
|
499
|
+
// A function parameter (of a helper expanded here): the function given, expanded where the body calls it.
|
|
500
|
+
if (p.type && ts.isFunctionTypeNode(p.type)) {
|
|
501
|
+
params.set(name, { c: "", type: BOOL, mutable: false, closure: this.closureOf(arg) });
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
340
504
|
const c2 = `l_${name}_${this.temp++}`;
|
|
341
505
|
const annotated = p.type ? resolveValueType(p.type, this.scope) : undefined;
|
|
342
506
|
if (!annotated && this.looksLikeStructPlace(arg)) {
|
|
@@ -378,6 +542,8 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
378
542
|
this.line(`{`);
|
|
379
543
|
this.block(() => {
|
|
380
544
|
const body = fn.body;
|
|
545
|
+
if (!body)
|
|
546
|
+
this.fail(fn, `${c.name} has no body`);
|
|
381
547
|
if (ts.isBlock(body))
|
|
382
548
|
body.statements.forEach((s) => this.reporter.guard(() => this.statement(s)));
|
|
383
549
|
else {
|
|
@@ -434,9 +600,7 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
434
600
|
this.fail(cb, `pass ${method} an arrow function written here: xs.${method}((x) => …)`);
|
|
435
601
|
const fn = cb;
|
|
436
602
|
const index = `__lm${this.temp++}`;
|
|
437
|
-
|
|
438
|
-
const length = place(f.createPropertyAccessExpression(list, "length"));
|
|
439
|
-
const element = place(f.createCallExpression(place(f.createPropertyAccessExpression(list, "get")), undefined, [indexId]));
|
|
603
|
+
void place;
|
|
440
604
|
const I64 = { kind: "int", name: "i64" };
|
|
441
605
|
// The result, declared before the loop.
|
|
442
606
|
const res = this.tmp("lm");
|
|
@@ -482,69 +646,58 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
482
646
|
}
|
|
483
647
|
const findDecl = method === "find" ? this.lines.length - 1 : -1;
|
|
484
648
|
const ci = this.tmp("lm_i");
|
|
485
|
-
this.
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
this.line(`
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
this.
|
|
499
|
-
|
|
649
|
+
this.eachElement(list, node, { index, ci }, (element, indexId) => {
|
|
650
|
+
if (valueForm) {
|
|
651
|
+
const eq = place(f.createBinaryExpression(element, ts.SyntaxKind.EqualsEqualsEqualsToken, place(f.createIdentifier(needle))));
|
|
652
|
+
const hit = this.expect(this.expr(eq, BOOL), BOOL, node);
|
|
653
|
+
this.line(`if (${hit.c}) {`);
|
|
654
|
+
this.line(method === "includes" ? ` ${res} = true;` : ` ${res} = (int64_t)${this.expr(indexId, U64).c};`);
|
|
655
|
+
this.line(` break;`);
|
|
656
|
+
this.line(`}`);
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
const closure = { node: fn, name: `${method}'s callback`, scopes: [...this.scopes] };
|
|
660
|
+
const accId = method === "reduce" ? place(f.createIdentifier(`__acc${this.temp++}`)) : undefined;
|
|
661
|
+
if (accId)
|
|
662
|
+
this.scopes.at(-1).set(accId.text, { c: res, type: accType, mutable: true });
|
|
663
|
+
const all = method === "reduce" ? [accId, element, indexId] : [element, indexId];
|
|
664
|
+
const v = this.expandClosure(closure, all.slice(0, fn.parameters.length), cb);
|
|
665
|
+
if (method === "forEach") {
|
|
666
|
+
if (v)
|
|
667
|
+
this.fail(cb, "forEach's callback returns nothing");
|
|
668
|
+
}
|
|
669
|
+
else if (method === "reduce") {
|
|
670
|
+
if (!v)
|
|
671
|
+
this.fail(cb, "reduce's callback returns the new accumulator");
|
|
672
|
+
const nv = this.expect(v, accType, cb);
|
|
673
|
+
this.line(accType.kind === "pubkey" ? `${res} = *(${nv.c});` : `${res} = ${nv.c};`);
|
|
500
674
|
}
|
|
501
675
|
else {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
this.
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
this.
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
this.line(`if (${b.c}) { ${res} = true; break; }`);
|
|
524
|
-
if (method === "every")
|
|
525
|
-
this.line(`if (!(${b.c})) { ${res} = false; break; }`);
|
|
526
|
-
if (method === "findIndex")
|
|
527
|
-
this.line(`if (${b.c}) { ${res} = (int64_t)${ci}; break; }`);
|
|
528
|
-
if (method === "find") {
|
|
529
|
-
this.line(`if (${b.c}) {`);
|
|
530
|
-
this.block(() => {
|
|
531
|
-
const e = this.expr(element);
|
|
532
|
-
elemType = e.type;
|
|
533
|
-
if (!["int", "bool", "pubkey", "enum"].includes(e.type.kind))
|
|
534
|
-
this.fail(node, "find returns an Option of the element, so the elements must be integers, booleans, Pubkeys or enums; use findIndex");
|
|
535
|
-
this.line(`${res}.some = 1;`);
|
|
536
|
-
this.line(e.type.kind === "pubkey" ? `${res}.v = *(${e.c});` : `${res}.v = ${e.c};`);
|
|
537
|
-
this.line(`break;`);
|
|
538
|
-
});
|
|
539
|
-
this.line(`}`);
|
|
540
|
-
}
|
|
676
|
+
if (!v)
|
|
677
|
+
this.fail(cb, `${method}'s callback returns a boolean`);
|
|
678
|
+
const b = this.expect(v, BOOL, cb);
|
|
679
|
+
if (method === "some")
|
|
680
|
+
this.line(`if (${b.c}) { ${res} = true; break; }`);
|
|
681
|
+
if (method === "every")
|
|
682
|
+
this.line(`if (!(${b.c})) { ${res} = false; break; }`);
|
|
683
|
+
if (method === "findIndex")
|
|
684
|
+
this.line(`if (${b.c}) { ${res} = (int64_t)${this.expr(indexId, U64).c}; break; }`);
|
|
685
|
+
if (method === "find") {
|
|
686
|
+
this.line(`if (${b.c}) {`);
|
|
687
|
+
this.block(() => {
|
|
688
|
+
const e = this.expr(element);
|
|
689
|
+
elemType = e.type;
|
|
690
|
+
if (!["int", "bool", "pubkey", "enum"].includes(e.type.kind))
|
|
691
|
+
this.fail(node, "find returns an Option of the element, so the elements must be integers, booleans, Pubkeys or enums; use findIndex");
|
|
692
|
+
this.line(`${res}.some = 1;`);
|
|
693
|
+
this.line(e.type.kind === "pubkey" ? `${res}.v = *(${e.c});` : `${res}.v = ${e.c};`);
|
|
694
|
+
this.line(`break;`);
|
|
695
|
+
});
|
|
696
|
+
this.line(`}`);
|
|
541
697
|
}
|
|
542
698
|
}
|
|
543
|
-
|
|
544
|
-
});
|
|
545
|
-
this.line(`}`);
|
|
699
|
+
}
|
|
546
700
|
});
|
|
547
|
-
this.line(`}`);
|
|
548
701
|
if (method === "find") {
|
|
549
702
|
const inner = elemType;
|
|
550
703
|
this.model.options.set(optionKey(inner), inner);
|
|
@@ -650,6 +803,11 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
650
803
|
this.scopes.at(-1).set(name, { c, type: t, mutable });
|
|
651
804
|
continue;
|
|
652
805
|
}
|
|
806
|
+
if (this.isPipeline(init)) {
|
|
807
|
+
const t = this.localList(d, init, c);
|
|
808
|
+
this.scopes.at(-1).set(name, { c, type: t, mutable });
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
653
811
|
if (fn === "loadAccount" || fn === "loadAccountMut" || fn === "loadForeign") {
|
|
654
812
|
if (mutable)
|
|
655
813
|
this.fail(d, "bind loaded accounts with const");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
import { resolveScalarType } from "../analyze.js";
|
|
3
3
|
import { INT_INFO, isScalar, isWritable, sameType, scalarSize, typeName } from "../model.js";
|
|
4
|
-
import { U64, BOOL, PUBKEY, STRING, optC, fieldC, fixedVecC, storageDecl, vecCount } from "./shared.js";
|
|
4
|
+
import { U64, BOOL, PUBKEY, STRING, optC, fieldC, fixedVecC, storageDecl, vecCount, vecPlace, emptySizeC } from "./shared.js";
|
|
5
5
|
import { EmitterBase } from "./base.js";
|
|
6
6
|
/**
|
|
7
7
|
* Places in account data and locals: references, reads and writes, Vec/array indexing, aliasing rules and
|
|
@@ -258,6 +258,8 @@ export class StorageEmitter extends EmitterBase {
|
|
|
258
258
|
const l = this.lookup(node.text);
|
|
259
259
|
if (l && l.type.kind === "array")
|
|
260
260
|
return { lv: l.c, type: l.type, writable: !l.readonly };
|
|
261
|
+
if (l && l.type.kind === "vec")
|
|
262
|
+
return this.withFixedVec({ lv: l.c, type: l.type, writable: true });
|
|
261
263
|
// A struct loaded from a remaining account.
|
|
262
264
|
if (l && l.type.kind === "structRef")
|
|
263
265
|
return { lv: `(*${l.c})`, type: { kind: "struct", name: l.type.struct }, writable: l.type.writable };
|
|
@@ -278,15 +280,35 @@ export class StorageEmitter extends EmitterBase {
|
|
|
278
280
|
const r = { lv: `d_${a.name}->${f.name}`, type: f.type, writable: isWritable(a.kind), account: a, path: [f.name] };
|
|
279
281
|
if (f.type.kind === "vec" && f.type.cap !== undefined)
|
|
280
282
|
r.vec = this.fixedVec(r.lv, f.type.elem, f.type.cap);
|
|
281
|
-
else if (f.type.kind === "vec")
|
|
283
|
+
else if (f.type.kind === "vec") {
|
|
282
284
|
// Growing needs the account's length at instruction entry: instructions have it, helpers get it passed.
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
285
|
+
const s = a.kind.struct;
|
|
286
|
+
const k = s.vecs.findIndex((v) => v.name === f.name);
|
|
287
|
+
if (s.vecs.length === 1)
|
|
288
|
+
r.vec = {
|
|
289
|
+
len: `d_${a.name}->${f.name}__len`,
|
|
290
|
+
items: r.lv,
|
|
291
|
+
fixed: `sizeof(TSS_${s.name})`,
|
|
292
|
+
elemSize: s.vec.elemSize,
|
|
293
|
+
resizable: isWritable(a.kind),
|
|
294
|
+
};
|
|
295
|
+
else {
|
|
296
|
+
const base = `(*d_${a.name})`;
|
|
297
|
+
const at = vecPlace(s, base, k);
|
|
298
|
+
const others = s.vecs.map((v, j) => (j === k ? "" : ` + (uint64_t)${vecPlace(s, base, j).len} * ${v.elemSize}`)).join("");
|
|
299
|
+
r.lv = at.items;
|
|
300
|
+
r.vec = {
|
|
301
|
+
len: at.len,
|
|
302
|
+
items: at.items,
|
|
303
|
+
fixed: `(${emptySizeC(s)}${others})`,
|
|
304
|
+
elemSize: s.vecs[k].elemSize,
|
|
305
|
+
resizable: isWritable(a.kind),
|
|
306
|
+
tail: k < s.vecs.length - 1
|
|
307
|
+
? { end: at.end, dataEnd: `(a_${a.name}->data + a_${a.name}->data_len)`, paths: s.vecs.slice(k + 1).map((v) => `${a.name}.${v.name}[]`) }
|
|
308
|
+
: undefined,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
}
|
|
290
312
|
return r;
|
|
291
313
|
}
|
|
292
314
|
}
|
|
@@ -330,8 +352,12 @@ export class StorageEmitter extends EmitterBase {
|
|
|
330
352
|
this.fail(node.name, `${s.name} has no field "${field}"`);
|
|
331
353
|
const r = { ...base, lv: `${base.lv}.${f.name}`, type: f.type, path: base.path && [...base.path, f.name], vec: undefined };
|
|
332
354
|
// Vecs of loaded accounts can be read and updated in place but not resized (no entry length known).
|
|
333
|
-
if (f.type.kind === "vec" && f.type.cap === undefined)
|
|
334
|
-
|
|
355
|
+
if (f.type.kind === "vec" && f.type.cap === undefined) {
|
|
356
|
+
const k = s.vecs.findIndex((v) => v.name === f.name);
|
|
357
|
+
const at = s.vecs.length === 1 ? { len: `${base.lv}.${f.name}__len`, items: r.lv } : vecPlace(s, base.lv, k);
|
|
358
|
+
r.lv = at.items;
|
|
359
|
+
r.vec = { len: at.len, items: at.items, fixed: emptySizeC(s), elemSize: s.vecs[k].elemSize, resizable: false };
|
|
360
|
+
}
|
|
335
361
|
return this.withFixedVec(r);
|
|
336
362
|
}
|
|
337
363
|
return undefined;
|
|
@@ -671,8 +697,8 @@ export class StorageEmitter extends EmitterBase {
|
|
|
671
697
|
const l = this.lookup(node.text);
|
|
672
698
|
if (!l)
|
|
673
699
|
this.fail(node, `"${node.text}" is not a local variable`);
|
|
674
|
-
if (l.type.kind === "array")
|
|
675
|
-
this.fail(node, `use ${node.text}.set(i, v) to change
|
|
700
|
+
if (l.type.kind === "array" || l.type.kind === "vec")
|
|
701
|
+
this.fail(node, `use ${node.text}.set(i, v) to change list elements`);
|
|
676
702
|
if (!l.mutable)
|
|
677
703
|
this.fail(node, `"${node.text}" is const or an argument and cannot be reassigned`);
|
|
678
704
|
if (l.type.kind === "pubkey")
|