@tsplc/compiler 0.2.9 → 0.2.11

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/dist/codesys.js CHANGED
@@ -144,7 +144,56 @@ function direct(address) {
144
144
  return { kind: "direct", address };
145
145
  }
146
146
  // packages/compiler/backends/codesys/emit.ts
147
- import { defaultValue, instancedBlock as instancedBlock5, standardBlocks } from "@tsplc/compiler/ir";
147
+ import { defaultValue, instancedBlock as instancedBlock5, standardBlocks as standardBlocks2 } from "@tsplc/compiler/ir";
148
+
149
+ // packages/compiler/backends/codesys/global-names.ts
150
+ import { standardBlocks } from "@tsplc/compiler/ir";
151
+
152
+ // packages/compiler/backends/codesys/storage.ts
153
+ import { basename } from "node:path";
154
+ import { instancedBlock } from "@tsplc/compiler/ir";
155
+ var moduleName = (variable) => basename(variable.span.file).replace(/\.[^.]+$/, "");
156
+ function storageLayout(core) {
157
+ const types = new Map(core.types.map((type) => [type.id, type]));
158
+ const blocks = new Map(core.blocks.map((block) => [block.id, block]));
159
+ const retention = new Map;
160
+ const blockRetained = (id) => {
161
+ const cached = retention.get(id);
162
+ if (cached !== undefined)
163
+ return cached;
164
+ const block = blocks.get(id);
165
+ if (!block)
166
+ throw new Error("Missing verified block during retention planning.");
167
+ const retained = block.state.some((field) => {
168
+ const type = instancedBlock(field.type, types);
169
+ return field.retain || type !== undefined && blockRetained(type.block);
170
+ });
171
+ retention.set(id, retained);
172
+ return retained;
173
+ };
174
+ const retainedBlocks = new Set(core.blocks.filter((block) => blockRetained(block.id)).map((block) => block.id));
175
+ const retainedVariables = new Set([...core.variables, ...core.blocks.flatMap((block) => block.state)].filter((variable) => {
176
+ const type = instancedBlock(variable.type, types);
177
+ return variable.retain || type !== undefined && retainedBlocks.has(type.block);
178
+ }).map((variable) => variable.id));
179
+ const groups = new Map;
180
+ for (const variable of core.variables) {
181
+ if (variable.storage === "input" || variable.storage === "output")
182
+ continue;
183
+ const storage = variable.storage === "constant" ? "constants" : retainedVariables.has(variable.id) ? "retained" : "state";
184
+ const id = `${variable.span.file}:${storage}`;
185
+ let group = groups.get(id);
186
+ if (!group) {
187
+ group = { id, module: moduleName(variable), storage, variables: [] };
188
+ groups.set(id, group);
189
+ }
190
+ group.variables.push(variable);
191
+ }
192
+ return { retainedBlocks, retainedVariables, globalLists: [...groups.values()] };
193
+ }
194
+
195
+ // packages/compiler/backends/codesys/types.ts
196
+ import { instancedBlock as instancedBlock2 } from "@tsplc/compiler/ir";
148
197
 
149
198
  // packages/compiler/backends/codesys/numeric.ts
150
199
  import {
@@ -424,60 +473,212 @@ END_IF;
424
473
  }
425
474
  }
426
475
 
476
+ // packages/compiler/backends/codesys/types.ts
477
+ class CodesysTypes {
478
+ types;
479
+ objects = [];
480
+ names = new Map;
481
+ fields = new Map;
482
+ constructor(types, names, blockNames) {
483
+ this.types = types;
484
+ for (const type of types.values()) {
485
+ if (type.kind === "block") {
486
+ const name = blockNames.get(type.block);
487
+ if (!name)
488
+ throw new Error("Missing verified block name");
489
+ this.names.set(type.id, name);
490
+ }
491
+ if (["record", "array", "enum", "string"].includes(type.kind)) {
492
+ const label = type.id.split(":").at(-1);
493
+ if (type.kind === "record" || type.kind === "enum") {
494
+ const prefix = type.kind === "record" ? "ST" : "E";
495
+ this.names.set(type.id, names.allocateObject(prefix, label));
496
+ } else
497
+ this.names.set(type.id, names.allocate(`T_${label}`));
498
+ }
499
+ if (type.kind === "record" || type.kind === "enum") {
500
+ const names = new Names, fields = new Map;
501
+ for (const member of type.kind === "record" ? type.fields : type.members) {
502
+ fields.set(member.name, names.allocate(member.name, true));
503
+ }
504
+ this.fields.set(type.id, fields);
505
+ }
506
+ }
507
+ for (const type of types.values())
508
+ if (type.kind === "array" && instancedBlock2(type.id, types))
509
+ this.names.set(type.id, `ARRAY[${type.lowerBound}..${type.lowerBound + type.length - 1}] OF ${this.name(type.element)}`);
510
+ const emitted = new Set;
511
+ const emit = (type) => {
512
+ if (instancedBlock2(type.id, types) || !this.names.has(type.id) || emitted.has(type.id))
513
+ return;
514
+ if (type.kind === "array")
515
+ emit(this.type(type.element));
516
+ if (type.kind === "record") {
517
+ for (const field of type.fields)
518
+ emit(this.type(field.type));
519
+ }
520
+ emitted.add(type.id);
521
+ const name = this.name(type.id);
522
+ let declaration;
523
+ if (type.kind === "record") {
524
+ const fields = type.fields.map((field) => ` ${this.field(type.id, field.name)} : ${this.name(field.type)};`).join(`
525
+ `);
526
+ declaration = `TYPE ${name} :
527
+ STRUCT
528
+ ${fields}
529
+ END_STRUCT
530
+ END_TYPE
531
+ `;
532
+ } else if (type.kind === "array") {
533
+ declaration = `TYPE ${name} : ARRAY[${type.lowerBound}..${type.lowerBound + type.length - 1}] OF ${this.name(type.element)};
534
+ END_TYPE
535
+ `;
536
+ } else if (type.kind === "string") {
537
+ declaration = `TYPE ${name} : ${type.encoding === "utf8" ? "STRING" : "WSTRING"}(${type.capacity});
538
+ END_TYPE
539
+ `;
540
+ } else if (type.kind === "enum") {
541
+ const members = type.members.map((member) => ` ${this.field(type.id, member.name)} := ${member.value}`).join(`,
542
+ `);
543
+ declaration = `{attribute 'qualified_only'}
544
+ {attribute 'strict'}
545
+ TYPE ${name} : (
546
+ ${members}
547
+ ) DINT;
548
+ END_TYPE
549
+ `;
550
+ } else
551
+ throw new Error("Unsupported verified data type");
552
+ this.objects.push({ id: type.id, kind: "dataType", name, declaration });
553
+ };
554
+ for (const type of types.values())
555
+ emit(type);
556
+ }
557
+ type(id) {
558
+ const type = this.types.get(id);
559
+ if (!type)
560
+ throw new Error("Missing verified type");
561
+ return type;
562
+ }
563
+ name(id) {
564
+ return this.names.get(id) ?? id;
565
+ }
566
+ field(id, name) {
567
+ const field = this.fields.get(id)?.get(name);
568
+ if (!field)
569
+ throw new Error("Missing verified field");
570
+ return field;
571
+ }
572
+ declarationInitializer(value, id) {
573
+ const type = this.type(id);
574
+ if ((type.kind === "record" || type.kind === "array") && this.isZero(value))
575
+ return "";
576
+ return ` := ${this.literal(value, id)}`;
577
+ }
578
+ isZero(value) {
579
+ if (value.kind === "bool")
580
+ return !value.value;
581
+ if (value.kind === "integer")
582
+ return value.value === "0";
583
+ if (value.kind === "float")
584
+ return Object.is(value.value, 0);
585
+ if (value.kind === "string")
586
+ return value.value === "";
587
+ if (value.kind === "array")
588
+ return value.items.every((item) => this.isZero(item));
589
+ if (value.kind === "record")
590
+ return Object.values(value.fields).every((item) => this.isZero(item));
591
+ return false;
592
+ }
593
+ literal(value, id) {
594
+ const type = this.type(id);
595
+ if (type.kind === "enum" && value.kind === "integer") {
596
+ const member = type.members.find((member) => member.value === value.value);
597
+ if (!member)
598
+ throw new Error("Missing verified enum member");
599
+ return `${this.name(id)}.${this.field(id, member.name)}`;
600
+ }
601
+ if (type.kind === "string" && value.kind === "string") {
602
+ const wide = type.encoding === "utf16", quote = wide ? '"' : "'";
603
+ let text = "";
604
+ for (const char of value.value) {
605
+ const code = char.charCodeAt(0);
606
+ if (char === "$")
607
+ text += "$$";
608
+ else if (char === quote)
609
+ text += `$${char}`;
610
+ else if (code < 32 || code === 127)
611
+ text += `$${code.toString(16).toUpperCase().padStart(wide ? 4 : 2, "0")}`;
612
+ else
613
+ text += char;
614
+ }
615
+ return `${quote}${text}${quote}`;
616
+ }
617
+ if (type.kind === "array" && value.kind === "array") {
618
+ return `[${value.items.map((item) => this.literal(item, type.element)).join(", ")}]`;
619
+ }
620
+ if (type.kind === "record" && value.kind === "record") {
621
+ const fields = type.fields.map((field) => {
622
+ const initial = value.fields[field.name];
623
+ if (!initial)
624
+ throw new Error("Missing verified record initializer");
625
+ return `${this.field(id, field.name)} := ${this.literal(initial, field.type)}`;
626
+ });
627
+ return `(${fields.join(", ")})`;
628
+ }
629
+ if (type.kind === "duration" && value.kind === "duration") {
630
+ return `T#${BigInt(value.nanoseconds) / 1000000n}ms`;
631
+ }
632
+ if (type.kind === "calendar" && value.kind === "calendar") {
633
+ if (type.domain === "date")
634
+ return `D#${value.value}`;
635
+ if (type.domain === "timeOfDay")
636
+ return `TOD#${value.value}`;
637
+ return `DT#${value.value.replace("T", "-")}`;
638
+ }
639
+ return scalarLiteral(value, id);
640
+ }
641
+ }
642
+
643
+ // packages/compiler/backends/codesys/global-names.ts
644
+ var planGlobalNames = (core, programNames = {}) => {
645
+ const names = new Names;
646
+ for (const name of Object.values(programNames))
647
+ names.reserve(name);
648
+ const typeMap = new Map(core.types.map((type) => [type.id, type]));
649
+ const blockNames = new Map(core.blocks.map((block) => [
650
+ block.id,
651
+ block.standard ? standardBlocks[block.standard].native : names.allocateObject("FB", block.name)
652
+ ]));
653
+ for (const name of blockNames.values())
654
+ names.reserve(name);
655
+ const targetTypes = new CodesysTypes(typeMap, names, blockNames);
656
+ const { globalLists, retainedVariables } = storageLayout(core);
657
+ const hasExports = (group) => group.variables.some((variable) => variable.exported && !variable.exposure);
658
+ const globals = globalLists.map((group) => {
659
+ const splitByStorage = globalLists.some((other) => other.module === group.module && other.storage !== group.storage && hasExports(other));
660
+ const name = hasExports(group) ? names.allocate(pascalCase(splitByStorage ? `${group.module}-${group.storage}` : group.module)) : names.allocateObject("GVL", `${group.module}_${group.storage}`);
661
+ const fields = new Names;
662
+ for (const name of blockNames.values())
663
+ fields.reserve(name);
664
+ return {
665
+ ...group,
666
+ name,
667
+ fields: new Map(group.variables.map((item) => [item.id, fields.allocate(item.name)]))
668
+ };
669
+ });
670
+ return { names, typeMap, blockNames, targetTypes, globals, retainedVariables };
671
+ };
672
+
427
673
  // packages/compiler/backends/codesys/plan.ts
428
674
  import {
429
- instancedBlock as instancedBlock3,
675
+ instancedBlock as instancedBlock4,
430
676
  isVerifiedCore
431
677
  } from "@tsplc/compiler/ir";
432
678
  import { ioLayout as ioLayout2 } from "@tsplc/typegen/hardware";
433
679
 
434
680
  // packages/compiler/backends/codesys/io-paths.ts
435
681
  import { ioLayout } from "@tsplc/typegen/hardware";
436
-
437
- // packages/compiler/backends/codesys/storage.ts
438
- import { basename } from "node:path";
439
- import { instancedBlock } from "@tsplc/compiler/ir";
440
- var moduleName = (variable) => basename(variable.span.file).replace(/\.[^.]+$/, "");
441
- function storageLayout(core) {
442
- const types = new Map(core.types.map((type) => [type.id, type]));
443
- const blocks = new Map(core.blocks.map((block) => [block.id, block]));
444
- const retention = new Map;
445
- const blockRetained = (id) => {
446
- const cached = retention.get(id);
447
- if (cached !== undefined)
448
- return cached;
449
- const block = blocks.get(id);
450
- if (!block)
451
- throw new Error("Missing verified block during retention planning.");
452
- const retained = block.state.some((field) => {
453
- const type = instancedBlock(field.type, types);
454
- return field.retain || type !== undefined && blockRetained(type.block);
455
- });
456
- retention.set(id, retained);
457
- return retained;
458
- };
459
- const retainedBlocks = new Set(core.blocks.filter((block) => blockRetained(block.id)).map((block) => block.id));
460
- const retainedVariables = new Set([...core.variables, ...core.blocks.flatMap((block) => block.state)].filter((variable) => {
461
- const type = instancedBlock(variable.type, types);
462
- return variable.retain || type !== undefined && retainedBlocks.has(type.block);
463
- }).map((variable) => variable.id));
464
- const groups = new Map;
465
- for (const variable of core.variables) {
466
- if (variable.storage === "input" || variable.storage === "output")
467
- continue;
468
- const storage = variable.storage === "constant" ? "constants" : retainedVariables.has(variable.id) ? "retained" : "state";
469
- const id = `${variable.span.file}:${storage}`;
470
- let group = groups.get(id);
471
- if (!group) {
472
- group = { id, module: moduleName(variable), storage, variables: [] };
473
- groups.set(id, group);
474
- }
475
- group.variables.push(variable);
476
- }
477
- return { retainedBlocks, retainedVariables, globalLists: [...groups.values()] };
478
- }
479
-
480
- // packages/compiler/backends/codesys/io-paths.ts
481
682
  var pathPattern = /^([\w$-]+)\.([A-Za-z_$][\w$]*)((?:\.[A-Za-z_$][\w$]*|\[-?\d+\])*)$/;
482
683
  var isStoragePath = (key) => key.includes(".") || key.includes("[");
483
684
  function resolveStoragePath(core, key) {
@@ -1006,7 +1207,54 @@ IF digits=0 THEN malformed:=TRUE;END_IF;IF negative THEN numberValue:=-numberVal
1006
1207
  };
1007
1208
 
1008
1209
  // packages/compiler/backends/codesys/task-access.ts
1009
- import { instancedBlock as instancedBlock2 } from "@tsplc/compiler/ir";
1210
+ import { instancedBlock as instancedBlock3 } from "@tsplc/compiler/ir";
1211
+ function schedulePrograms(core, tasks, report) {
1212
+ const background = new Map;
1213
+ for (const program of core.programs) {
1214
+ if (!program.background)
1215
+ continue;
1216
+ const { priority, intervalNanoseconds } = program.background;
1217
+ const name = `TsplcBackground_${priority}_${BigInt(intervalNanoseconds) / 1000000n}`;
1218
+ const existing = background.get(name);
1219
+ if (existing) {
1220
+ existing.programs.push(program.name);
1221
+ continue;
1222
+ }
1223
+ background.set(name, {
1224
+ name,
1225
+ priority,
1226
+ intervalNanoseconds,
1227
+ programs: [program.name]
1228
+ });
1229
+ }
1230
+ return appendContinuations(core, [...tasks, ...background.values()], report);
1231
+ }
1232
+ function taskStorageOwnership(core, report) {
1233
+ const owners = new Map;
1234
+ const globals = new Map(core.variables.map((item) => [item.id, item]));
1235
+ const functions = new Map(core.functions.map((item) => [item.id, item]));
1236
+ const blocks = new Map(core.blocks.map((block) => [block.id, block.runtime?.kind]));
1237
+ const runtimes = new Map(core.types.flatMap((type) => type.kind === "block" ? [[type.id, blocks.get(type.block)]] : []));
1238
+ return (task, program) => {
1239
+ const fn = functions.get(program.entry);
1240
+ if (!fn)
1241
+ throw new Error("Verified core has a missing entry.");
1242
+ const worker = program.background?.worker;
1243
+ const host = worker && core.variables.find((item) => item.name === worker && item.span.file === fn.span.file && runtimes.get(item.type) === "backgroundHost");
1244
+ for (const id of accessedStorage(fn, core, host ? host.id : undefined)) {
1245
+ const item = globals.get(id);
1246
+ if (!item || item.storage === "constant" || runtimes.get(item.type) === "mailbox")
1247
+ continue;
1248
+ const ownsWorker = worker === item.name && item.span.file === fn.span.file;
1249
+ if (program.background && (!ownsWorker || host && item.id === host.id))
1250
+ report(`Background task ${program.name} accesses scan-owned storage ${item.name}; pass value snapshots through the mailbox.`, item.span);
1251
+ const owner = owners.get(id);
1252
+ if (owner && owner !== task)
1253
+ report(`Storage ${item.name} is shared across cyclic tasks ${owner} and ${task}.`, item.span);
1254
+ owners.set(id, task);
1255
+ }
1256
+ };
1257
+ }
1010
1258
  function accessedStorage(fn, core, scheduledHost) {
1011
1259
  const scheduledCall = fn.body.find((item) => item.kind === "invoke" && item.instance === scheduledHost);
1012
1260
  const result = new Set, seen = new Set;
@@ -1095,7 +1343,7 @@ function accessedStorage(fn, core, scheduledHost) {
1095
1343
  const variable = variables.get(item.instance);
1096
1344
  if (!variable)
1097
1345
  throw new Error("Missing verified instance during task planning.");
1098
- const type = instancedBlock2(variable.type, types);
1346
+ const type = instancedBlock3(variable.type, types);
1099
1347
  const block = type && blocks.get(type.block);
1100
1348
  if (block && !seen.has(block.id)) {
1101
1349
  seen.add(block.id);
@@ -1108,6 +1356,44 @@ function accessedStorage(fn, core, scheduledHost) {
1108
1356
  body(fn.body);
1109
1357
  return result;
1110
1358
  }
1359
+ function appendContinuations(core, tasks, report) {
1360
+ const continuations = core.programs.flatMap((program) => program.continuation ? [{ name: program.name, scope: program.continuation }] : []);
1361
+ if (!continuations.length)
1362
+ return tasks;
1363
+ const programs = new Map(core.programs.map((program) => [program.name, program]));
1364
+ const functions = new Map(core.functions.map((routine) => [routine.id, routine]));
1365
+ const placements = tasks.map((task) => {
1366
+ const roots = new Set;
1367
+ for (const name of task.programs) {
1368
+ const program = programs.get(name);
1369
+ if (!program || program.background)
1370
+ continue;
1371
+ const entry = functions.get(program.entry);
1372
+ if (entry)
1373
+ for (const id of accessedStorage(entry, core))
1374
+ roots.add(id);
1375
+ }
1376
+ return { task, roots, additions: [] };
1377
+ });
1378
+ for (const { name, scope } of continuations) {
1379
+ const root = core.variables.find((variable) => variable.name === scope.instance && variable.span.file === scope.file);
1380
+ if (!root) {
1381
+ report(`Missing continuation owner ${scope.instance}.`);
1382
+ continue;
1383
+ }
1384
+ const owners = placements.filter(({ roots }) => roots.has(root.id));
1385
+ const [owner] = owners;
1386
+ if (!owner || owners.length !== 1) {
1387
+ report(`Continuation owner ${scope.instance} must belong to exactly one caller task.`, root.span);
1388
+ continue;
1389
+ }
1390
+ owner.additions.push(name);
1391
+ }
1392
+ return placements.map(({ task, additions }) => ({
1393
+ ...task,
1394
+ programs: task.programs.concat(additions)
1395
+ }));
1396
+ }
1111
1397
 
1112
1398
  // packages/compiler/backends/codesys/publication.ts
1113
1399
  var globals = (core) => core.variables.filter((variable) => variable.storage === "module" && variable.exported);
@@ -1168,49 +1454,12 @@ function planCodesys(core, deployment, value) {
1168
1454
  return {
1169
1455
  diagnostics: parsed.error.issues.map((issue) => diagnostic(`${issue.path.join(".")}: ${issue.message}`))
1170
1456
  };
1171
- const backgroundTasks = new Map;
1172
- for (const program of core.programs) {
1173
- if (!program.background)
1174
- continue;
1175
- const { priority, intervalNanoseconds } = program.background;
1176
- const name = `TsplcBackground_${priority}_${BigInt(intervalNanoseconds) / 1000000n}`;
1177
- const existing = backgroundTasks.get(name);
1178
- backgroundTasks.set(name, {
1179
- name,
1180
- priority,
1181
- intervalNanoseconds,
1182
- programs: [...existing?.programs ?? [], program.name]
1183
- });
1184
- }
1185
- deployment = { tasks: [...deployment.tasks, ...backgroundTasks.values()] };
1186
1457
  const options = parsed.data;
1187
1458
  const diagnostics = [];
1188
1459
  const report = (message, span) => {
1189
1460
  diagnostics.push(diagnostic(message, span));
1190
1461
  };
1191
- const callerTasks = deployment.tasks;
1192
- for (const continuation of core.programs.filter((program) => program.continuation)) {
1193
- const scope = continuation.continuation;
1194
- const root = core.variables.find((variable) => variable.name === scope.instance && variable.span.file === scope.file);
1195
- if (!root) {
1196
- report(`Missing continuation owner ${scope.instance}.`);
1197
- continue;
1198
- }
1199
- const owners = callerTasks.filter((task) => task.programs.some((name) => {
1200
- const program = core.programs.find((program) => program.name === name);
1201
- if (!program || program.background)
1202
- return false;
1203
- const fn = core.functions.find((fn) => fn.id === program.entry);
1204
- return fn && accessedStorage(fn, core).has(root.id);
1205
- }));
1206
- if (owners.length !== 1) {
1207
- report(`Continuation owner ${scope.instance} must belong to exactly one caller task.`, root.span);
1208
- continue;
1209
- }
1210
- deployment = {
1211
- tasks: deployment.tasks.map((task) => task.name === owners[0].name ? { ...task, programs: [...task.programs, continuation.name] } : task)
1212
- };
1213
- }
1462
+ deployment = { tasks: schedulePrograms(core, deployment.tasks, report) };
1214
1463
  const http = new Map;
1215
1464
  for (const block of core.blocks)
1216
1465
  if (block.runtime?.kind === "http" || block.runtime?.kind === "rpc") {
@@ -1367,10 +1616,7 @@ function validateTasks(core, deployment, report) {
1367
1616
  const programs = new Map(core.programs.map((program) => [program.name, program]));
1368
1617
  const assignment = new Map;
1369
1618
  const taskNames = new Set;
1370
- const stateOwners = new Map;
1371
- const globals = new Map(core.variables.map((item) => [item.id, item]));
1372
- const functions = new Map(core.functions.map((item) => [item.id, item]));
1373
- const blockRuntimes = new Map(core.types.flatMap((type) => type.kind === "block" ? [[type.id, core.blocks.find((block) => block.id === type.block)?.runtime?.kind]] : []));
1619
+ const checkOwnership = taskStorageOwnership(core, report);
1374
1620
  for (const task of deployment.tasks) {
1375
1621
  if (!isIecIdentifier(task.name))
1376
1622
  report(`Invalid task name ${task.name}.`);
@@ -1397,24 +1643,7 @@ function validateTasks(core, deployment, report) {
1397
1643
  if (assignment.has(name))
1398
1644
  report(`Program ${name} must belong to exactly one task.`);
1399
1645
  assignment.set(name, task.name);
1400
- const fn = functions.get(program.entry);
1401
- if (!fn)
1402
- throw new Error("Verified core has a missing entry.");
1403
- const host = program.background && core.variables.find((item) => item.name === program.background?.worker && item.span.file === fn.span.file && blockRuntimes.get(item.type) === "backgroundHost");
1404
- for (const id of accessedStorage(fn, core, host ? host.id : undefined)) {
1405
- const item = globals.get(id);
1406
- if (!item || item.storage === "constant")
1407
- continue;
1408
- if (blockRuntimes.get(item.type) === "mailbox")
1409
- continue;
1410
- const ownsWorker = program.background?.worker === item.name && item.span.file === fn.span.file;
1411
- if (program.background && (!ownsWorker || item.id === host?.id))
1412
- report(`Background task ${program.name} accesses scan-owned storage ${item.name}; pass value snapshots through the mailbox.`, item.span);
1413
- const owner = stateOwners.get(id);
1414
- if (owner && owner !== task.name)
1415
- report(`Storage ${item.name} is shared across cyclic tasks ${owner} and ${task.name}.`, item.span);
1416
- stateOwners.set(id, task.name);
1417
- }
1646
+ checkOwnership(task.name, program);
1418
1647
  }
1419
1648
  }
1420
1649
  for (const name of programs.keys())
@@ -1442,7 +1671,7 @@ function validateStorage(core, options, report) {
1442
1671
  const blockHasInOut = (id) => {
1443
1672
  const block = blocks.get(id);
1444
1673
  return block.inputs.some((input) => input.mode === "inOut") || block.state.some((field) => {
1445
- const type = instancedBlock3(field.type, types);
1674
+ const type = instancedBlock4(field.type, types);
1446
1675
  return type !== undefined && blockHasInOut(type.block);
1447
1676
  });
1448
1677
  };
@@ -1451,7 +1680,7 @@ function validateStorage(core, options, report) {
1451
1680
  if (layout.retainedBlocks.has(block.id) && blockHasInOut(block.id))
1452
1681
  report("Retained function blocks cannot contain direct or nested InOut references.", block.span);
1453
1682
  for (const variable of core.variables) {
1454
- const type = instancedBlock3(variable.type, types);
1683
+ const type = instancedBlock4(variable.type, types);
1455
1684
  if (variable.retain && type && blockHasInOut(type.block))
1456
1685
  report("Retained function blocks cannot contain direct or nested InOut references.", variable.span);
1457
1686
  }
@@ -1543,174 +1772,6 @@ function validateConstants(core, report) {
1543
1772
  });
1544
1773
  }
1545
1774
 
1546
- // packages/compiler/backends/codesys/types.ts
1547
- import { instancedBlock as instancedBlock4 } from "@tsplc/compiler/ir";
1548
- class CodesysTypes {
1549
- types;
1550
- objects = [];
1551
- names = new Map;
1552
- fields = new Map;
1553
- constructor(types, names, blockNames) {
1554
- this.types = types;
1555
- for (const type of types.values()) {
1556
- if (type.kind === "block") {
1557
- const name = blockNames.get(type.block);
1558
- if (!name)
1559
- throw new Error("Missing verified block name");
1560
- this.names.set(type.id, name);
1561
- }
1562
- if (["record", "array", "enum", "string"].includes(type.kind)) {
1563
- const label = type.id.split(":").at(-1);
1564
- if (type.kind === "record" || type.kind === "enum") {
1565
- const prefix = type.kind === "record" ? "ST" : "E";
1566
- this.names.set(type.id, names.allocateObject(prefix, label));
1567
- } else
1568
- this.names.set(type.id, names.allocate(`T_${label}`));
1569
- }
1570
- if (type.kind === "record" || type.kind === "enum") {
1571
- const names = new Names, fields = new Map;
1572
- for (const member of type.kind === "record" ? type.fields : type.members) {
1573
- fields.set(member.name, names.allocate(member.name, true));
1574
- }
1575
- this.fields.set(type.id, fields);
1576
- }
1577
- }
1578
- for (const type of types.values())
1579
- if (type.kind === "array" && instancedBlock4(type.id, types))
1580
- this.names.set(type.id, `ARRAY[${type.lowerBound}..${type.lowerBound + type.length - 1}] OF ${this.name(type.element)}`);
1581
- const emitted = new Set;
1582
- const emit = (type) => {
1583
- if (instancedBlock4(type.id, types) || !this.names.has(type.id) || emitted.has(type.id))
1584
- return;
1585
- if (type.kind === "array")
1586
- emit(this.type(type.element));
1587
- if (type.kind === "record") {
1588
- for (const field of type.fields)
1589
- emit(this.type(field.type));
1590
- }
1591
- emitted.add(type.id);
1592
- const name = this.name(type.id);
1593
- let declaration;
1594
- if (type.kind === "record") {
1595
- const fields = type.fields.map((field) => ` ${this.field(type.id, field.name)} : ${this.name(field.type)};`).join(`
1596
- `);
1597
- declaration = `TYPE ${name} :
1598
- STRUCT
1599
- ${fields}
1600
- END_STRUCT
1601
- END_TYPE
1602
- `;
1603
- } else if (type.kind === "array") {
1604
- declaration = `TYPE ${name} : ARRAY[${type.lowerBound}..${type.lowerBound + type.length - 1}] OF ${this.name(type.element)};
1605
- END_TYPE
1606
- `;
1607
- } else if (type.kind === "string") {
1608
- declaration = `TYPE ${name} : ${type.encoding === "utf8" ? "STRING" : "WSTRING"}(${type.capacity});
1609
- END_TYPE
1610
- `;
1611
- } else if (type.kind === "enum") {
1612
- const members = type.members.map((member) => ` ${this.field(type.id, member.name)} := ${member.value}`).join(`,
1613
- `);
1614
- declaration = `{attribute 'qualified_only'}
1615
- {attribute 'strict'}
1616
- TYPE ${name} : (
1617
- ${members}
1618
- ) DINT;
1619
- END_TYPE
1620
- `;
1621
- } else
1622
- throw new Error("Unsupported verified data type");
1623
- this.objects.push({ id: type.id, kind: "dataType", name, declaration });
1624
- };
1625
- for (const type of types.values())
1626
- emit(type);
1627
- }
1628
- type(id) {
1629
- const type = this.types.get(id);
1630
- if (!type)
1631
- throw new Error("Missing verified type");
1632
- return type;
1633
- }
1634
- name(id) {
1635
- return this.names.get(id) ?? id;
1636
- }
1637
- field(id, name) {
1638
- const field = this.fields.get(id)?.get(name);
1639
- if (!field)
1640
- throw new Error("Missing verified field");
1641
- return field;
1642
- }
1643
- declarationInitializer(value, id) {
1644
- const type = this.type(id);
1645
- if ((type.kind === "record" || type.kind === "array") && this.isZero(value))
1646
- return "";
1647
- return ` := ${this.literal(value, id)}`;
1648
- }
1649
- isZero(value) {
1650
- if (value.kind === "bool")
1651
- return !value.value;
1652
- if (value.kind === "integer")
1653
- return value.value === "0";
1654
- if (value.kind === "float")
1655
- return Object.is(value.value, 0);
1656
- if (value.kind === "string")
1657
- return value.value === "";
1658
- if (value.kind === "array")
1659
- return value.items.every((item) => this.isZero(item));
1660
- if (value.kind === "record")
1661
- return Object.values(value.fields).every((item) => this.isZero(item));
1662
- return false;
1663
- }
1664
- literal(value, id) {
1665
- const type = this.type(id);
1666
- if (type.kind === "enum" && value.kind === "integer") {
1667
- const member = type.members.find((member) => member.value === value.value);
1668
- if (!member)
1669
- throw new Error("Missing verified enum member");
1670
- return `${this.name(id)}.${this.field(id, member.name)}`;
1671
- }
1672
- if (type.kind === "string" && value.kind === "string") {
1673
- const wide = type.encoding === "utf16", quote = wide ? '"' : "'";
1674
- let text = "";
1675
- for (const char of value.value) {
1676
- const code = char.charCodeAt(0);
1677
- if (char === "$")
1678
- text += "$$";
1679
- else if (char === quote)
1680
- text += `$${char}`;
1681
- else if (code < 32 || code === 127)
1682
- text += `$${code.toString(16).toUpperCase().padStart(wide ? 4 : 2, "0")}`;
1683
- else
1684
- text += char;
1685
- }
1686
- return `${quote}${text}${quote}`;
1687
- }
1688
- if (type.kind === "array" && value.kind === "array") {
1689
- return `[${value.items.map((item) => this.literal(item, type.element)).join(", ")}]`;
1690
- }
1691
- if (type.kind === "record" && value.kind === "record") {
1692
- const fields = type.fields.map((field) => {
1693
- const initial = value.fields[field.name];
1694
- if (!initial)
1695
- throw new Error("Missing verified record initializer");
1696
- return `${this.field(id, field.name)} := ${this.literal(initial, field.type)}`;
1697
- });
1698
- return `(${fields.join(", ")})`;
1699
- }
1700
- if (type.kind === "duration" && value.kind === "duration") {
1701
- return `T#${BigInt(value.nanoseconds) / 1000000n}ms`;
1702
- }
1703
- if (type.kind === "calendar" && value.kind === "calendar") {
1704
- if (type.domain === "date")
1705
- return `D#${value.value}`;
1706
- if (type.domain === "timeOfDay")
1707
- return `TOD#${value.value}`;
1708
- return `DT#${value.value.replace("T", "-")}`;
1709
- }
1710
- return scalarLiteral(value, id);
1711
- }
1712
- }
1713
-
1714
1775
  // packages/compiler/backends/codesys/emit.ts
1715
1776
  function required(value) {
1716
1777
  if (value === undefined)
@@ -1734,7 +1795,7 @@ function emitCodesys(plan) {
1734
1795
 
1735
1796
  class CodesysEmitter {
1736
1797
  plan;
1737
- names = new Names;
1798
+ names;
1738
1799
  networkName;
1739
1800
  rpcChannels = [];
1740
1801
  references = new Map;
@@ -1753,7 +1814,8 @@ class CodesysEmitter {
1753
1814
  routineScopes = new Map;
1754
1815
  targetTypes;
1755
1816
  numeric;
1756
- layout;
1817
+ globals;
1818
+ retainedVariables;
1757
1819
  constructor(plan) {
1758
1820
  this.plan = plan;
1759
1821
  const { core, options } = plan;
@@ -1762,19 +1824,15 @@ class CodesysEmitter {
1762
1824
  if (!fn.owner && [...accessedStorage(fn, core)].some((id) => persistent.has(id)))
1763
1825
  this.programRoutines.add(fn.id);
1764
1826
  }
1765
- for (const name of Object.values(options.programNames ?? {}))
1766
- this.names.reserve(name);
1767
- this.typeMap = new Map(core.types.map((type) => [type.id, type]));
1827
+ const { names, typeMap, blockNames, targetTypes, globals, retainedVariables } = planGlobalNames(core, options.programNames);
1828
+ this.names = names;
1829
+ this.typeMap = typeMap;
1830
+ this.blockNames = blockNames;
1831
+ this.targetTypes = targetTypes;
1832
+ this.globals = globals;
1833
+ this.retainedVariables = retainedVariables;
1768
1834
  this.blocks = new Map(core.blocks.map((block) => [block.id, block]));
1769
- this.blockNames = new Map(core.blocks.map((block) => [
1770
- block.id,
1771
- block.standard ? standardBlocks[block.standard].native : this.names.allocateObject("FB", block.name)
1772
- ]));
1773
- for (const name of this.blockNames.values())
1774
- this.names.reserve(name);
1775
- this.targetTypes = new CodesysTypes(this.typeMap, this.names, this.blockNames);
1776
1835
  this.numeric = new NumericLowering(this.typeMap, (label) => this.names.allocate(label));
1777
- this.layout = storageLayout(core);
1778
1836
  this.objects.push(...this.targetTypes.objects);
1779
1837
  }
1780
1838
  emit() {
@@ -1899,7 +1957,7 @@ export const serviceChannels=${JSON.stringify(this.rpcChannels, null, 2)} as con
1899
1957
  const program = core.programs.find((item) => item.id === object.id);
1900
1958
  if (program)
1901
1959
  source = program.entry.split(":")[0] ?? "";
1902
- const group = this.layout.globalLists.find((item) => item.id === object.id);
1960
+ const group = this.globals.find((item) => item.id === object.id);
1903
1961
  if (group) {
1904
1962
  source = group.variables[0]?.id.split(":")[0] ?? "";
1905
1963
  }
@@ -1915,23 +1973,18 @@ export const serviceChannels=${JSON.stringify(this.rpcChannels, null, 2)} as con
1915
1973
  }
1916
1974
  emitGlobals() {
1917
1975
  const { options } = this.plan;
1918
- const { layout, names, references, mappings, targetTypes, typeMap, objects } = this;
1919
- const hasExports = (group) => group.variables.some((variable) => variable.exported && !variable.exposure);
1920
- for (const group of layout.globalLists) {
1921
- const splitByStorage = layout.globalLists.some((other) => other.module === group.module && other.storage !== group.storage && hasExports(other));
1922
- const name = hasExports(group) ? names.allocate(pascalCase(splitByStorage ? `${group.module}-${group.storage}` : group.module)) : names.allocateObject("GVL", `${group.module}_${group.storage}`);
1976
+ const { globals, references, mappings, targetTypes, typeMap, objects } = this;
1977
+ for (const group of globals) {
1978
+ const { name, fields } = group;
1923
1979
  const published = new Set(this.plan.publication[options.buildTarget]);
1924
1980
  const publish = group.variables.some((variable) => published.has(variable.id));
1925
- const fields = new Names;
1926
- for (const name of this.blockNames.values())
1927
- fields.reserve(name);
1928
1981
  const lines = [
1929
1982
  ...publish ? ["{attribute 'linkalways'}"] : [],
1930
1983
  "{attribute 'qualified_only'}",
1931
1984
  group.storage === "constants" ? "VAR_GLOBAL CONSTANT" : group.storage === "retained" ? "VAR_GLOBAL RETAIN" : "VAR_GLOBAL"
1932
1985
  ];
1933
1986
  for (const item of group.variables) {
1934
- const field = fields.allocate(item.name);
1987
+ const field = required(fields.get(item.id));
1935
1988
  references.set(item.id, `${name}.${field}`);
1936
1989
  if (published.has(item.id))
1937
1990
  lines.push(` {attribute 'symbol' := '${item.exposure ? "read" : "readwrite"}'}`);
@@ -2076,7 +2129,7 @@ export const serviceChannels=${JSON.stringify(this.rpcChannels, null, 2)} as con
2076
2129
  fields.reserve(objectName);
2077
2130
  for (const method of core.functions.filter((fn) => fn.owner?.block === block.id))
2078
2131
  functionNames.set(method.id, fields.allocate(method.name));
2079
- const native = block.standard && standardBlocks[block.standard];
2132
+ const native = block.standard && standardBlocks2[block.standard];
2080
2133
  for (const item of [...block.inputs, ...block.outputs, ...block.state])
2081
2134
  mapped.set(item.id, native ? required([...native.inputs, ...native.outputs].find(([name]) => name === item.name))[1] : fields.allocate(item.name, true));
2082
2135
  if (block.runtime?.kind === "mailbox")
@@ -2171,7 +2224,7 @@ END_VAR`
2171
2224
  blocks,
2172
2225
  blockFields,
2173
2226
  numeric,
2174
- layout
2227
+ retainedVariables
2175
2228
  } = this;
2176
2229
  const block = "inputs" in fn ? fn : undefined;
2177
2230
  const method = "owner" in fn ? fn.owner : undefined;
@@ -2450,8 +2503,8 @@ END_VAR`
2450
2503
  if (block.runtime?.kind === "mailbox")
2451
2504
  declaration.push("VAR", ` ${required(this.mailboxLocks.get(block.id))} : __XWORD;`, "END_VAR");
2452
2505
  section("VAR_OUTPUT", block.outputs);
2453
- section("VAR", block.state.filter((item) => !layout.retainedVariables.has(item.id)));
2454
- section("VAR RETAIN", block.state.filter((item) => layout.retainedVariables.has(item.id)));
2506
+ section("VAR", block.state.filter((item) => !retainedVariables.has(item.id)));
2507
+ section("VAR RETAIN", block.state.filter((item) => retainedVariables.has(item.id)));
2455
2508
  }
2456
2509
  if (declarations.length) {
2457
2510
  declaration.push(block || program ? "VAR_TEMP" : "VAR");
@@ -2582,5 +2635,5 @@ export {
2582
2635
  publicationSelectors
2583
2636
  };
2584
2637
 
2585
- //# debugId=ADC5FCAD73E0D40364756E2164756E21
2638
+ //# debugId=C98587762BFC22D464756E2164756E21
2586
2639
  //# sourceMappingURL=codesys.js.map