opencrbasic 1.0.0__py3-none-any.whl
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.
- opencrbasic/__init__.py +3 -0
- opencrbasic/__main__.py +5 -0
- opencrbasic/analysis.py +184 -0
- opencrbasic/bindings.py +1034 -0
- opencrbasic/catalog.py +173 -0
- opencrbasic/checkconfig.py +144 -0
- opencrbasic/checker/__init__.py +977 -0
- opencrbasic/checker/cr3xx.py +3118 -0
- opencrbasic/checker/ess.py +81 -0
- opencrbasic/checker/expressions.py +3581 -0
- opencrbasic/checker/instr_data.py +1802 -0
- opencrbasic/checker/instr_measure.py +1547 -0
- opencrbasic/checker/instr_rules.py +3540 -0
- opencrbasic/checker/instructions.py +17 -0
- opencrbasic/checker/intervals.py +678 -0
- opencrbasic/checker/longconv.py +133 -0
- opencrbasic/checker/scan_time.py +580 -0
- opencrbasic/checker/storage.py +456 -0
- opencrbasic/checker/structs.py +240 -0
- opencrbasic/checker/structure.py +6213 -0
- opencrbasic/checker/symbols.py +609 -0
- opencrbasic/checker/terminals.py +249 -0
- opencrbasic/checker/unused.py +186 -0
- opencrbasic/checker/var_types.py +564 -0
- opencrbasic/checker/warnings.py +661 -0
- opencrbasic/checks.py +491 -0
- opencrbasic/cli.py +391 -0
- opencrbasic/commands.py +128 -0
- opencrbasic/compiler.py +320 -0
- opencrbasic/consteval.py +327 -0
- opencrbasic/data/background_calibration.json +4 -0
- opencrbasic/data/before_beginprog.json +99 -0
- opencrbasic/data/cr1000x/background_calibration.json +4 -0
- opencrbasic/data/cr1000x/before_beginprog.json +138 -0
- opencrbasic/data/cr1000x/ess_variables.json +1087 -0
- opencrbasic/data/cr1000x/fnotch.json +29 -0
- opencrbasic/data/cr1000x/functions.json +1337 -0
- opencrbasic/data/cr1000x/instructions.json +4836 -0
- opencrbasic/data/cr1000x/measure.json +121 -0
- opencrbasic/data/cr1000x/mode.json +111 -0
- opencrbasic/data/cr1000x/pipeline_tasks.json +88 -0
- opencrbasic/data/cr1000x/settings_fields.json +321 -0
- opencrbasic/data/cr1000x/status_members.json +318 -0
- opencrbasic/data/cr1000x/symbols.json +872 -0
- opencrbasic/data/cr1000x/terminals.json +225 -0
- opencrbasic/data/cr300/before_beginprog.json +42 -0
- opencrbasic/data/cr300/ess_variables.json +1087 -0
- opencrbasic/data/cr300/fnotch.json +22 -0
- opencrbasic/data/cr300/functions.json +1261 -0
- opencrbasic/data/cr300/instructions.json +2837 -0
- opencrbasic/data/cr300/measure.json +33 -0
- opencrbasic/data/cr300/mode.json +121 -0
- opencrbasic/data/cr300/settings_fields.json +259 -0
- opencrbasic/data/cr300/status_members.json +303 -0
- opencrbasic/data/cr300/symbols.json +584 -0
- opencrbasic/data/cr300/terminals.json +193 -0
- opencrbasic/data/cr350/before_beginprog.json +44 -0
- opencrbasic/data/cr350/ess_variables.json +1087 -0
- opencrbasic/data/cr350/fnotch.json +22 -0
- opencrbasic/data/cr350/functions.json +1262 -0
- opencrbasic/data/cr350/instructions.json +2848 -0
- opencrbasic/data/cr350/measure.json +35 -0
- opencrbasic/data/cr350/mode.json +121 -0
- opencrbasic/data/cr350/settings_fields.json +292 -0
- opencrbasic/data/cr350/status_members.json +303 -0
- opencrbasic/data/cr350/symbols.json +572 -0
- opencrbasic/data/cr350/terminals.json +190 -0
- opencrbasic/data/ess_variables.json +1087 -0
- opencrbasic/data/fnotch.json +34 -0
- opencrbasic/data/functions.json +1337 -0
- opencrbasic/data/instructions.json +4965 -0
- opencrbasic/data/measure.json +123 -0
- opencrbasic/data/mode.json +121 -0
- opencrbasic/data/notices_cr6.json +890 -0
- opencrbasic/data/pipeline_tasks.json +84 -0
- opencrbasic/data/settings_fields.json +322 -0
- opencrbasic/data/status_members.json +319 -0
- opencrbasic/data/status_table.json +684 -0
- opencrbasic/data/symbols.json +902 -0
- opencrbasic/data/terminals.json +195 -0
- opencrbasic/data/type_codes.json +249 -0
- opencrbasic/diagnostics.py +196 -0
- opencrbasic/editing.py +827 -0
- opencrbasic/formatting.py +310 -0
- opencrbasic/helpdocs.py +677 -0
- opencrbasic/hover.py +667 -0
- opencrbasic/inlay.py +169 -0
- opencrbasic/lexer.py +183 -0
- opencrbasic/lsp/__init__.py +5 -0
- opencrbasic/lsp/__main__.py +7 -0
- opencrbasic/lsp/config.py +202 -0
- opencrbasic/lsp/features.py +602 -0
- opencrbasic/lsp/server.py +741 -0
- opencrbasic/lsp/session.py +1189 -0
- opencrbasic/lsp/worker.py +128 -0
- opencrbasic/mode.py +143 -0
- opencrbasic/navigation.py +401 -0
- opencrbasic/parser.py +3264 -0
- opencrbasic/positions.py +274 -0
- opencrbasic/preprocess.py +269 -0
- opencrbasic/profiles.py +266 -0
- opencrbasic/provenance.py +17 -0
- opencrbasic/quickfix.py +331 -0
- opencrbasic/rename.py +662 -0
- opencrbasic/signature.py +33 -0
- opencrbasic/source.py +600 -0
- opencrbasic/sources.py +125 -0
- opencrbasic/syntax.py +337 -0
- opencrbasic/tables.py +2173 -0
- opencrbasic/tables_hook.py +14 -0
- opencrbasic/tdf.py +142 -0
- opencrbasic/timing.py +565 -0
- opencrbasic-1.0.0.dist-info/METADATA +223 -0
- opencrbasic-1.0.0.dist-info/RECORD +117 -0
- opencrbasic-1.0.0.dist-info/WHEEL +4 -0
- opencrbasic-1.0.0.dist-info/entry_points.txt +3 -0
- opencrbasic-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
"""CR300/CR350 data-table allocator (`docs/tdf-rules.md` "Storage limit" and "Auto-sized tables").
|
|
2
|
+
|
|
3
|
+
After a clean parse the real compiler sizes every table in one pool (`cr300comp.exe` /
|
|
4
|
+
`cr350comp.exe`): up to 26 passes, then `Unable to allocate enough data table space to manage
|
|
5
|
+
flash write cycle limits.` and, when the last pass left the pool negative, `Not enough storage
|
|
6
|
+
area for DataTables.` `allocate` is a port of that allocator (decompiled in rounds 15 and 16,
|
|
7
|
+
confirmed with oracle threshold sweeps on both loggers), 32-bit wraparound and float32 arithmetic
|
|
8
|
+
included. It does three things at once:
|
|
9
|
+
|
|
10
|
+
- charges every called table of a fixed size `roundup4096(a + int(R * (N / k)) + 0x2000)`, raised
|
|
11
|
+
to the flash-wear minimum record count, against the pool less 16 KB per declared table
|
|
12
|
+
(`pool_bytes`);
|
|
13
|
+
- sizes the auto (`-1`) tables from what is left (`tables.build_tables` takes the TDF record
|
|
14
|
+
counts from here), and a table called only from a `Sub`/`AcceptDataRecords` is charged its auto
|
|
15
|
+
share instead of its own `N`;
|
|
16
|
+
- fails (both messages) when no pass fits, which also covers a size literal beyond +-2^31
|
|
17
|
+
(`INT_MIN`, whose wrapped charge sinks the pool whenever another table is auto-sized).
|
|
18
|
+
|
|
19
|
+
`PreserveVariables` / `PreserveOneVariable` take their store off the pool first
|
|
20
|
+
(`preserve_charge`); other unknown consumers could only shrink the real pool further, so the model
|
|
21
|
+
can miss a rejection or size an auto table too large but not invent a rejection.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import math
|
|
25
|
+
import struct
|
|
26
|
+
|
|
27
|
+
from .. import syntax as S
|
|
28
|
+
|
|
29
|
+
_POOL_PAGES = {"cr300": 2560, "cr350": 12800} # 0xA00000 / 0x3200000 bytes, in 4 KiB pages
|
|
30
|
+
_PAGE = 4096
|
|
31
|
+
_BLOCK_BYTES = 496 # a DataInterval table's block: 496 // bytes per record
|
|
32
|
+
_INT_MIN = -(1 << 31)
|
|
33
|
+
_BASE = 0x600000 # start of the table area
|
|
34
|
+
|
|
35
|
+
MSG_ALLOC = "Unable to allocate enough data table space to manage flash write cycle limits."
|
|
36
|
+
MSG_STORAGE = "Not enough storage area for DataTables."
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _f32(x: float) -> float:
|
|
40
|
+
try:
|
|
41
|
+
return struct.unpack("f", struct.pack("f", x))[0]
|
|
42
|
+
except OverflowError:
|
|
43
|
+
return math.copysign(math.inf, x)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _cvt_int(x: float) -> int:
|
|
47
|
+
"""`cvttss2si`: truncation, with the out-of-range / NaN result `INT_MIN`."""
|
|
48
|
+
if x != x or not (-2147483648.0 <= x < 2147483648.0):
|
|
49
|
+
return _INT_MIN
|
|
50
|
+
return int(x)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _wrap32(v: int) -> int:
|
|
54
|
+
v &= 0xFFFFFFFF
|
|
55
|
+
return v - (1 << 32) if v & 0x80000000 else v
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
_I32 = (-(1 << 31), (1 << 31) - 1)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _i32(v: int) -> int:
|
|
62
|
+
return _wrap32(v)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _min_recs(sp: dict, k: int, cc: int) -> int:
|
|
66
|
+
"""Flash-wear minimum record count of pass `cc`, `FUN_005b50c0` (also the `W == -1` and
|
|
67
|
+
`W == 0` branches). `sp` carries `flagged`, `day`, `own_ms`, `W` (call interval in seconds, `None`
|
|
68
|
+
when the call interval is unknown) and `B`'s block `k`."""
|
|
69
|
+
if sp["flagged"]:
|
|
70
|
+
period = 86400000.0
|
|
71
|
+
elif sp["day"]:
|
|
72
|
+
period = 2592000000.0
|
|
73
|
+
elif sp["own_ms"]:
|
|
74
|
+
period = _f32(float(sp["own_ms"]))
|
|
75
|
+
elif sp["W"] is None:
|
|
76
|
+
if cc == 0:
|
|
77
|
+
period = 50.0
|
|
78
|
+
elif cc < 5:
|
|
79
|
+
period = float(cc * 100)
|
|
80
|
+
elif cc < 10:
|
|
81
|
+
period = float(cc * 1000)
|
|
82
|
+
else:
|
|
83
|
+
period = float(cc * 5000)
|
|
84
|
+
elif sp["W"] == 0:
|
|
85
|
+
period = 50.0
|
|
86
|
+
for lim, val in (
|
|
87
|
+
(2, 2000.0),
|
|
88
|
+
(4, 5000.0),
|
|
89
|
+
(8, 10000.0),
|
|
90
|
+
(12, 20000.0),
|
|
91
|
+
(16, 30000.0),
|
|
92
|
+
(20, 60000.0),
|
|
93
|
+
(23, 120000.0),
|
|
94
|
+
):
|
|
95
|
+
if cc > lim:
|
|
96
|
+
period = val
|
|
97
|
+
else:
|
|
98
|
+
period = _f32(_f32(sp["W"]) * 1000.0)
|
|
99
|
+
den = _f32(max(float(10 - cc), 1.0))
|
|
100
|
+
try:
|
|
101
|
+
wear = _f32(
|
|
102
|
+
_f32(_f32(_f32(3600000.0 / period) * 24.0) / _f32(float(k))) / _f32(_f32(100000.0) / _f32(den * 365.0))
|
|
103
|
+
)
|
|
104
|
+
except ZeroDivisionError:
|
|
105
|
+
return _INT_MIN
|
|
106
|
+
return _cvt_int(wear)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def preserve_charge(a: int, d: int, q_arg: int, min_tick: int, tick: int, public_bytes: int) -> int:
|
|
110
|
+
"""Pool bytes taken by the variable-preservation store, `cr300comp` `FUN_005b4dd0(A, D, q_arg,
|
|
111
|
+
minimum)` (float32 at each step). `tick` is the smallest scan interval in 100 us units."""
|
|
112
|
+
t = max(tick, min_tick)
|
|
113
|
+
q = max(public_bytes // d, 1) if q_arg == 0 else q_arg # q = 0 charges as q = 1 (oracle, T = 10 s)
|
|
114
|
+
wear = _f32(_f32(_f32(3600000.0 / _f32(t / 10.0)) * 24.0) / 27.0)
|
|
115
|
+
recs = _f32(_f32(_f32(_f32(q * 0.6) * wear) + q) + 2.0)
|
|
116
|
+
n = _cvt_int(max(recs, 5.0))
|
|
117
|
+
return _round_page(max(_wrap32(n * a), 0x2000))
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _round_page(v: int) -> int:
|
|
121
|
+
"""`FUN_005d4660`: round an unsigned 32-bit address up to 4096."""
|
|
122
|
+
return _wrap32((-(-(v & 0xFFFFFFFF) // _PAGE)) * _PAGE)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def allocate(specs: list[dict], pool: int) -> dict:
|
|
126
|
+
"""Port of the table allocator (`cr300comp` `FUN_005ae6e0`, 26 passes; docs/tdf-rules.md
|
|
127
|
+
"Storage limit" and "Auto-sized tables"). `specs` are the user tables in declaration order:
|
|
128
|
+
`called`, `n` (record count argument; -1 auto, INT_MIN for a size beyond 32 bits), `flagged`,
|
|
129
|
+
`B` (bytes per record), `lapses`, `card`, `own_ms` (DataInterval in ms, 0 none), `W` (call
|
|
130
|
+
interval in seconds or None), `fillstop`, `kone`, `day`. Returns `{"sizes": [...], "msg1": bool,
|
|
131
|
+
"msg2": bool}` (sizes are the final record counts of the last pass)."""
|
|
132
|
+
st = []
|
|
133
|
+
for sp in specs:
|
|
134
|
+
st.append(
|
|
135
|
+
{
|
|
136
|
+
"N": sp["n"],
|
|
137
|
+
"k": 0,
|
|
138
|
+
"R": 0,
|
|
139
|
+
"lapses": sp["lapses"],
|
|
140
|
+
**{x: sp[x] for x in ("called", "flagged", "B", "card", "own_ms", "W", "fillstop", "kone", "day")},
|
|
141
|
+
}
|
|
142
|
+
)
|
|
143
|
+
e4 = pool
|
|
144
|
+
ok_exit = False
|
|
145
|
+
for cc in range(26):
|
|
146
|
+
e4 = pool
|
|
147
|
+
reserve = 0
|
|
148
|
+
rate = 0.0
|
|
149
|
+
d8 = False
|
|
150
|
+
ptr = _BASE
|
|
151
|
+
for t in st:
|
|
152
|
+
if not t["called"]:
|
|
153
|
+
t["N"] = 1
|
|
154
|
+
reserve = _i32(reserve + t["B"])
|
|
155
|
+
continue
|
|
156
|
+
if (t["N"] < 0 or t["W"] is None) and not t["flagged"]:
|
|
157
|
+
_blockfix(t, 512)
|
|
158
|
+
R, k = t["R"], t["k"]
|
|
159
|
+
c1 = (1 if R < 512 else 3) + (1024 // R) + (1 if k == 1 else 0)
|
|
160
|
+
c1 = max(c1 * R, 0x3000)
|
|
161
|
+
reserve = _i32(reserve + c1)
|
|
162
|
+
if not t["card"]:
|
|
163
|
+
reserve = _i32(reserve + _lap(t) * t["B"])
|
|
164
|
+
w = 1.0 if t["W"] is None else _f32(t["W"])
|
|
165
|
+
rate = _f32(_f32(float(R) / _f32(float(k) * w)) + rate)
|
|
166
|
+
else:
|
|
167
|
+
a1 = _lap(t)
|
|
168
|
+
_blockfix(t, _i32(a1 + t["N"]))
|
|
169
|
+
R, k = t["R"], t["k"]
|
|
170
|
+
a2 = _lap(t)
|
|
171
|
+
u = _i32(a2 + _cvt_int(_f32(_f32(float(R)) * _f32(_f32(float(t["N"])) / _f32(float(k))))) + 0x2000)
|
|
172
|
+
if not t["fillstop"]:
|
|
173
|
+
lo = _min_recs(t, k, cc)
|
|
174
|
+
if _cvt_int(_f32(_f32(float(u)) / _f32(float(R)))) < lo:
|
|
175
|
+
u = _i32(lo * R)
|
|
176
|
+
ptr, used = _alloc(ptr, u)
|
|
177
|
+
e4 = _i32(e4 - used)
|
|
178
|
+
dur = 0.0
|
|
179
|
+
if rate != 0.0:
|
|
180
|
+
dur = _f32(float(_i32(_i32(e4 - reserve) - 0x1000))) / rate if rate else 0.0
|
|
181
|
+
dur = _f32(dur)
|
|
182
|
+
if dur <= 0.0:
|
|
183
|
+
d8 = True
|
|
184
|
+
for t in st:
|
|
185
|
+
if t["N"] < 0 or (t["W"] is None and not t["flagged"]):
|
|
186
|
+
if t["called"] and t["B"]:
|
|
187
|
+
R, k = t["R"], t["k"]
|
|
188
|
+
w = 1.0 if t["W"] is None else _f32(t["W"])
|
|
189
|
+
n = _cvt_int(_f32(dur / w)) if w else _INT_MIN
|
|
190
|
+
u = _cvt_int(_f32(_f32(float(R)) * _f32(_f32(float(n)) / _f32(float(k)))))
|
|
191
|
+
u = _i32(u + _lap(t) + 0x2000)
|
|
192
|
+
if u < 0x3000:
|
|
193
|
+
u = 0x3000
|
|
194
|
+
if not t["fillstop"]:
|
|
195
|
+
lo = _min_recs(t, k, cc)
|
|
196
|
+
if _tdiv(u, R) < lo:
|
|
197
|
+
d8 = True
|
|
198
|
+
ptr, used = _alloc(ptr, u)
|
|
199
|
+
b4 = _tdiv(used, R)
|
|
200
|
+
if t["N"] < 0:
|
|
201
|
+
t["N"] = _i32((b4 - (4096 // R + 1)) * k)
|
|
202
|
+
e4 = _i32(e4 - used)
|
|
203
|
+
if t["N"] < 1:
|
|
204
|
+
t["N"] = 1
|
|
205
|
+
if t["N"] < t["k"]:
|
|
206
|
+
t["N"] = t["k"]
|
|
207
|
+
if e4 < 0:
|
|
208
|
+
d8 = True
|
|
209
|
+
if not d8:
|
|
210
|
+
ok_exit = True
|
|
211
|
+
break
|
|
212
|
+
return {"sizes": [t["N"] for t in st], "msg1": not ok_exit, "msg2": e4 < 0}
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _lap(t: dict) -> int:
|
|
216
|
+
"""Records kept for the Lapses count: `Lapses * 16` wraps to 32 bits before the division."""
|
|
217
|
+
return _tdiv(_i32(t["lapses"] * 16), t["B"]) + 1 if t["lapses"] else 0
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _tdiv(a: int, b: int) -> int:
|
|
221
|
+
q = abs(a) // abs(b)
|
|
222
|
+
return q if (a >= 0) == (b >= 0) else -q
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _alloc(ptr: int, u: int) -> tuple[int, int]:
|
|
226
|
+
"""One table's span from the running pointer: start and end rounded up to a page."""
|
|
227
|
+
start = _round_page(ptr)
|
|
228
|
+
end = _round_page(_i32(start + u))
|
|
229
|
+
return end, _i32(end - start)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _blockfix(t: dict, arg: int) -> None:
|
|
233
|
+
"""`FUN_005b7d00`: the record block (`k` records of `R = k * B + 16` bytes), fixed the first time."""
|
|
234
|
+
if t["R"]:
|
|
235
|
+
return
|
|
236
|
+
if t["lapses"] == 0:
|
|
237
|
+
t["k"] = 1
|
|
238
|
+
else:
|
|
239
|
+
t["k"] = min(_BLOCK_BYTES // t["B"], arg)
|
|
240
|
+
if t["k"] < 2:
|
|
241
|
+
t["k"], t["lapses"] = 1, 0
|
|
242
|
+
if t["kone"]:
|
|
243
|
+
t["k"], t["lapses"] = 1, 0
|
|
244
|
+
t["R"] = t["k"] * t["B"] + 16
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def pool_bytes(profile_name: str, n_declared: int, has_public: bool, n_named_const: int) -> int:
|
|
248
|
+
"""The pool left for the user's tables: 16 KB per table beyond the first (`DataTableInfo`, a
|
|
249
|
+
`Public` table and a named `ConstTable` are tables too)."""
|
|
250
|
+
extra = (1 if has_public else 0) + n_named_const
|
|
251
|
+
return (_POOL_PAGES[profile_name] - 4 * (n_declared + extra)) * _PAGE
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def spec_of(m: dict) -> dict:
|
|
255
|
+
"""The allocator input of one data table from its `tables._build_data_table` meta."""
|
|
256
|
+
from .. import tables as T
|
|
257
|
+
|
|
258
|
+
cls = T.size_class(m["size_raw"])
|
|
259
|
+
flagged = False
|
|
260
|
+
if cls is None:
|
|
261
|
+
n = 1
|
|
262
|
+
elif cls[0] == "auto":
|
|
263
|
+
n = -1
|
|
264
|
+
elif cls[0] in ("edge", "inf"):
|
|
265
|
+
n = 2147483647
|
|
266
|
+
flagged = cls[0] == "edge" and cls[1] < 0
|
|
267
|
+
elif cls[0] == "toxic":
|
|
268
|
+
n, flagged = _INT_MIN, True
|
|
269
|
+
else:
|
|
270
|
+
n, flagged = cls[1], cls[2]
|
|
271
|
+
di = bool(m["interval_seconds"]) or m["zero_interval"]
|
|
272
|
+
return {
|
|
273
|
+
"called": m["called"],
|
|
274
|
+
"n": n,
|
|
275
|
+
"flagged": flagged,
|
|
276
|
+
"B": m["bytes_per_record"],
|
|
277
|
+
"lapses": (m["lapses"] if m["lapses"] < 1 << 31 else _INT_MIN) if di else 0,
|
|
278
|
+
"card": False, # TableFile does not skip the Lapses term here
|
|
279
|
+
"own_ms": int(round(m["interval_seconds"] * 1000)) if m["interval_seconds"] else 0,
|
|
280
|
+
"W": m["call_w"],
|
|
281
|
+
"fillstop": m.get("fillstop", False),
|
|
282
|
+
"kone": False,
|
|
283
|
+
"day": False,
|
|
284
|
+
"zero_interval": m["zero_interval"],
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def run(metas: list[dict], pool: int) -> dict | None:
|
|
289
|
+
"""Runs the allocator on the data tables' metas (`None` when a table has no record bytes)."""
|
|
290
|
+
specs = [spec_of(m) for m in metas]
|
|
291
|
+
for sp in specs:
|
|
292
|
+
if sp["zero_interval"] and sp["W"]:
|
|
293
|
+
sp["own_ms"] = int(round(sp["W"] * 1000))
|
|
294
|
+
if any(not sp["B"] for sp in specs):
|
|
295
|
+
return None
|
|
296
|
+
return allocate(specs, pool)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
_MAX_ALLOC = 0x8000000 # CR6/CR1000X: largest single allocation request, 128 MiB
|
|
300
|
+
MSG_OOM = "Out of memory."
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
MSG_ALLOC_TABLE = "Out of Memory; Unable to allocate table {}."
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def cr6_table_oom(m: dict, auto_n: int | None = None, name: str = "") -> str | None:
|
|
307
|
+
"""CR6/CR1000X: a table's single block request exceeds `_MAX_ALLOC` (docs/tdf-rules.md "CR6/
|
|
308
|
+
CR1000X Out of memory"): returns `MSG_OOM`, or for an auto-sized table whose request is zero
|
|
309
|
+
`MSG_ALLOC_TABLE`, else `None`. `auto_n` is the resolved record count of an auto (or negative)
|
|
310
|
+
size, only modelled without CardOut/TableFile; a beyond-range size is left alone."""
|
|
311
|
+
from .. import tables as T
|
|
312
|
+
|
|
313
|
+
cls = T.size_class(m["size_raw"])
|
|
314
|
+
auto = False
|
|
315
|
+
if cls is None:
|
|
316
|
+
n = 1
|
|
317
|
+
elif cls[0] == "fixed" and not cls[2]:
|
|
318
|
+
n = cls[1]
|
|
319
|
+
elif cls[0] == "auto" or (cls[0] == "fixed" and cls[2]): # CR6/CR1000X size every negative size as auto
|
|
320
|
+
if auto_n is None or not (-1 << 31) < auto_n < 1 << 31 or auto_n in (0, 1) or m.get("has_card"):
|
|
321
|
+
return None # a CardOut/TableFile table is not modelled
|
|
322
|
+
n = auto_n
|
|
323
|
+
auto = True
|
|
324
|
+
else:
|
|
325
|
+
return None
|
|
326
|
+
b = m["bytes_per_record"]
|
|
327
|
+
if not b or not m["called"] or b > 2147483647:
|
|
328
|
+
return None # (a String field past INT_MAX compiles; oracle, 2026-10-25, round 35)
|
|
329
|
+
lap = (m["lapses"] if m["lapses"] < 1 << 31 else _INT_MIN) if m["display_interval"] else 0
|
|
330
|
+
a = _tdiv(_wrap32(abs(lap) * 16 if lap != _INT_MIN else 0), b) + 1 if lap else 0
|
|
331
|
+
mm = _wrap32(n + a)
|
|
332
|
+
k = 1
|
|
333
|
+
if lap:
|
|
334
|
+
k = 1008 // b if auto else min(1008 // b, mm) # an auto table's block is fixed before its count is known
|
|
335
|
+
if k < 2:
|
|
336
|
+
k = 1
|
|
337
|
+
r = k * b + 16
|
|
338
|
+
if n == 1:
|
|
339
|
+
c = r
|
|
340
|
+
else:
|
|
341
|
+
c = _wrap32((2048 // r + _tdiv(mm, k) + (1 if r < 1024 else 3)) * r)
|
|
342
|
+
if c > _MAX_ALLOC or c < 0: # a wrapped negative request fails the host allocation
|
|
343
|
+
return MSG_OOM
|
|
344
|
+
return MSG_ALLOC_TABLE.format(name) if auto and c == 0 else None
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _var_words(program: S.Program, env, kinds: tuple) -> list[tuple[int, int]]:
|
|
348
|
+
"""(elements, 4-byte words per element) of every `kinds` declaration (a Float/Long/Boolean
|
|
349
|
+
is one word, a Double two, a String its padded length / 4; any other type is not counted, so
|
|
350
|
+
the total can only be too small)."""
|
|
351
|
+
from .. import tables as T
|
|
352
|
+
|
|
353
|
+
out = []
|
|
354
|
+
for st in program.statements:
|
|
355
|
+
if not isinstance(st, S.Declaration) or st.kind not in kinds:
|
|
356
|
+
continue
|
|
357
|
+
for v in st.vars:
|
|
358
|
+
t = (v.type or "FLOAT").upper()
|
|
359
|
+
if t not in ("FLOAT", "LONG", "BOOLEAN", "DOUBLE", "STRING"):
|
|
360
|
+
continue
|
|
361
|
+
dims = [T._as_int(T._fold(d, env), default=1) for d in v.dims]
|
|
362
|
+
if len(dims) > 3 or any(not 1 <= d <= 2147483647 for d in dims):
|
|
363
|
+
continue # `Illegally dimension`: never allocated
|
|
364
|
+
info = T._make_var_info(v, env)
|
|
365
|
+
stored = info.stored_len or 0 # (set for every String)
|
|
366
|
+
if t == "STRING" and stored > 2147483647:
|
|
367
|
+
continue # the length wraps negative: compiles (oracle, 2026-10-25, round 35)
|
|
368
|
+
n = 1
|
|
369
|
+
for d in dims:
|
|
370
|
+
n *= d
|
|
371
|
+
out.append((n, 2 if t == "DOUBLE" else stored // 4 if t == "STRING" else 1))
|
|
372
|
+
return out
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def _segment_oom(words: list[tuple[int, int]]) -> bool:
|
|
376
|
+
"""One variable segment (`Dim` or `Public`) starts at 4 words and is requested as
|
|
377
|
+
`wrap32(words * 4)` bytes; the request fails above 128 MiB or when it wraps negative (zero
|
|
378
|
+
is a null request that crashes the compiler: not modelled)."""
|
|
379
|
+
total = 4
|
|
380
|
+
for n, w in words:
|
|
381
|
+
total = _wrap32(total + _wrap32(_wrap32(n) * w))
|
|
382
|
+
req = _wrap32(total * 4)
|
|
383
|
+
return req > _MAX_ALLOC or req < 0
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def variables_oom(program: S.Program, profile, env) -> bool:
|
|
387
|
+
"""CR6/CR1000X end of pass 1: a `Dim` or a `Public` segment too large to allocate (reported at
|
|
388
|
+
the program's last statement, ends the compile before pass 2)."""
|
|
389
|
+
return profile.name in ("cr6", "cr1000x") and any(
|
|
390
|
+
_segment_oom(_var_words(program, env, (k,))) for k in ("DIM", "PUBLIC")
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def string_length_oom_line(program: S.Program, diags) -> int | None:
|
|
395
|
+
"""All four loggers: a String length reported as `Variable illegal in Parameter` (a variable,
|
|
396
|
+
or on CR300/CR350 also an undefined name; `structure._report_decl_expr` tags the message with
|
|
397
|
+
the variable) leaves a length the end of pass 1 cannot allocate: `Out of memory.` at the
|
|
398
|
+
program's last statement, or at the declaration when it has an initializer (round 31)."""
|
|
399
|
+
tagged = {d.stmt for d in diags.errors if d.stmt and d.message.startswith("Variable illegal in Parameter:")}
|
|
400
|
+
if not tagged:
|
|
401
|
+
return None
|
|
402
|
+
for st in program.statements:
|
|
403
|
+
for v in st.vars if isinstance(st, S.Declaration) else st.params if isinstance(st, S.SubDef) else []:
|
|
404
|
+
if id(v) in tagged:
|
|
405
|
+
# (the parser keeps a String's `= init` inside `string_size`)
|
|
406
|
+
size = getattr(v, "string_size", None)
|
|
407
|
+
init = getattr(v, "init", None) or (isinstance(size, S.Binary) and size.op == "=")
|
|
408
|
+
return st.line if init else program.last_line
|
|
409
|
+
return None
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def check_pass1(program: S.Program, profile, diags, env) -> None:
|
|
413
|
+
line = string_length_oom_line(program, diags)
|
|
414
|
+
if line is None and variables_oom(program, profile, env):
|
|
415
|
+
line = program.last_line
|
|
416
|
+
if line is not None:
|
|
417
|
+
diags.error(line, MSG_OOM)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def public_table_oom(program: S.Program, env) -> bool:
|
|
421
|
+
"""The `Public` table is one record of the `Public` variables' bytes; its block request is
|
|
422
|
+
`2 * bytes + 32`, so it fails above 67,108,848 bytes."""
|
|
423
|
+
words = _var_words(program, env, ("PUBLIC",))
|
|
424
|
+
if any(n >= 1 << 31 for n, _ in words):
|
|
425
|
+
return False
|
|
426
|
+
return sum(n * w for n, w in words) * 4 > (_MAX_ALLOC - 32) // 2
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def check(program: S.Program, profile, result, diags) -> None:
|
|
430
|
+
"""Reports the storage-limit pair of messages (pass 2, only on an otherwise clean program)."""
|
|
431
|
+
if profile.name in ("cr6", "cr1000x") and not diags.errors:
|
|
432
|
+
from .. import consteval
|
|
433
|
+
|
|
434
|
+
if public_table_oom(program, consteval.Env(profile, dict(program.consts))):
|
|
435
|
+
diags.error(0, MSG_OOM)
|
|
436
|
+
else:
|
|
437
|
+
msgs = [
|
|
438
|
+
cr6_table_oom(t._storage, t._storage.get("_n_raw"), t.name)
|
|
439
|
+
for t in result.tables or []
|
|
440
|
+
if hasattr(t, "_storage")
|
|
441
|
+
]
|
|
442
|
+
if MSG_OOM in msgs: # one message; a plain `Out of memory.` wins over a table's zero request
|
|
443
|
+
diags.error(0, MSG_OOM)
|
|
444
|
+
elif any(msgs):
|
|
445
|
+
diags.error(0, next(m for m in msgs if m))
|
|
446
|
+
return
|
|
447
|
+
if profile.name not in _POOL_PAGES or diags.errors:
|
|
448
|
+
return
|
|
449
|
+
declared = [t for t in (result.tables or []) if hasattr(t, "_storage")]
|
|
450
|
+
if not declared:
|
|
451
|
+
return
|
|
452
|
+
alloc = declared[0]._storage.get("alloc")
|
|
453
|
+
if alloc and alloc["msg1"]:
|
|
454
|
+
diags.error(0, MSG_ALLOC)
|
|
455
|
+
if alloc["msg2"]:
|
|
456
|
+
diags.error(0, MSG_STORAGE)
|