nomen-lang 0.0.16 → 0.0.17
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/core/System/Buffer.nm +6 -2
- package/core/System/LinkedList.nm +16 -0
- package/core/System/List.nm +20 -0
- package/core/System/Map.nm +38 -9
- package/core/System/Set.nm +35 -8
- package/core/System/String.nm +18 -0
- package/dist/index.mjs +456 -16
- package/package.json +1 -1
package/core/System/Buffer.nm
CHANGED
|
@@ -356,7 +356,11 @@ pub struct Buffer<T> {
|
|
|
356
356
|
```
|
|
357
357
|
}
|
|
358
358
|
|
|
359
|
-
|
|
359
|
+
// `inline` splices the (width-folded at monomorphization) raw body at
|
|
360
|
+
// call sites — the load/store pair behind every container `.at`/`set` is
|
|
361
|
+
// the hottest path in data-heavy code, and the naked-inline path keeps it
|
|
362
|
+
// to a handful of instructions with no call overhead.
|
|
363
|
+
inline func load_T = (self, int i: i >= 0 && i < self.cap, out T) {
|
|
360
364
|
```
|
|
361
365
|
#arch: c
|
|
362
366
|
return ((T*)(unsigned long long)self->data)[i];
|
|
@@ -399,7 +403,7 @@ pub struct Buffer<T> {
|
|
|
399
403
|
```
|
|
400
404
|
}
|
|
401
405
|
|
|
402
|
-
func store_T = (ref self, int i: i >= 0 && i < self.cap, T val) {
|
|
406
|
+
inline func store_T = (ref self, int i: i >= 0 && i < self.cap, T val) {
|
|
403
407
|
```
|
|
404
408
|
#arch: c
|
|
405
409
|
((T*)(unsigned long long)self->data)[i] = val;
|
|
@@ -57,6 +57,22 @@ pub struct LinkedList<T>: Enumerable {
|
|
|
57
57
|
return self.values.load_T(idx)
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// Bounds-checked access: `fallback` when idx is outside [0, self.count).
|
|
61
|
+
pub func at_or = (self, int idx, T fallback, out T) {
|
|
62
|
+
if idx >= 0 && idx < self.count {
|
|
63
|
+
return self.at(idx)
|
|
64
|
+
}
|
|
65
|
+
return fallback
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Bounds-checked access that traps when idx is outside [0, self.count).
|
|
69
|
+
pub func at_or_panic = (self, int idx, out T) {
|
|
70
|
+
if idx >= 0 && idx < self.count {
|
|
71
|
+
return self.at(idx)
|
|
72
|
+
}
|
|
73
|
+
panic("index out of range")
|
|
74
|
+
}
|
|
75
|
+
|
|
60
76
|
pub func length = (self, out int) {
|
|
61
77
|
return self.count
|
|
62
78
|
}
|
package/core/System/List.nm
CHANGED
|
@@ -31,6 +31,26 @@ pub struct List<T>: Viewable {
|
|
|
31
31
|
return self.items.load_T(i)
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// Bounds-checked access: `fallback` when i is outside [0, self.length) —
|
|
35
|
+
// for indices that can't be proven at compile time and out-of-range is an
|
|
36
|
+
// expected case.
|
|
37
|
+
pub func at_or = (self, int i, T fallback, out T) {
|
|
38
|
+
if i >= 0 && i < self.length {
|
|
39
|
+
return self.at(i)
|
|
40
|
+
}
|
|
41
|
+
return fallback
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Bounds-checked access that traps when i is outside [0, self.length) —
|
|
45
|
+
// for indices that can't be proven at compile time and out-of-range is a
|
|
46
|
+
// bug, not a case.
|
|
47
|
+
pub func at_or_panic = (self, int i, out T) {
|
|
48
|
+
if i >= 0 && i < self.length {
|
|
49
|
+
return self.at(i)
|
|
50
|
+
}
|
|
51
|
+
panic("index out of range")
|
|
52
|
+
}
|
|
53
|
+
|
|
34
54
|
// A non-owning `view T` over [start, end). Delegates to the backing Buffer's
|
|
35
55
|
// #arch slice primitive — no #arch code here. Borrows from self.
|
|
36
56
|
pub func slice = (self, int start: start >= 0, int end: end >= start, out view T) {
|
package/core/System/Map.nm
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* An ordered key-value map with `set`/`get`/`has`/`remove`. Keys must be
|
|
2
|
+
* An ordered key-value map with `set`/`get`/`get_or`/`has`/`remove`. Keys must be
|
|
3
3
|
* `Hashable` (hashed to a `uint` via `key.hash()`) and `Equatable` (compared
|
|
4
4
|
* with `==`); values may be any type.
|
|
5
|
+
*
|
|
6
|
+
* The capacity is always a power of two (initial 8, doubling on rehash), so
|
|
7
|
+
* bucket indexing is a mask (`hash & (cap - 1)`) instead of a division — and
|
|
8
|
+
* it is correct for negative hashes too: two's-complement `&` already lands
|
|
9
|
+
* in `[0, cap)`, which is why no negative adjustment is needed.
|
|
5
10
|
**/
|
|
6
11
|
pub struct Map<TK: Hashable + Equatable, TV> {
|
|
7
12
|
var length = 0
|
|
@@ -53,6 +58,34 @@ pub struct Map<TK: Hashable + Equatable, TV> {
|
|
|
53
58
|
return self.values.load_T(idx)
|
|
54
59
|
}
|
|
55
60
|
|
|
61
|
+
// Fused lookup-with-fallback: one probe instead of the `has` + `get`
|
|
62
|
+
// pair (which probes twice) for the common "default when missing" shape.
|
|
63
|
+
pub func get_or = (self, TK key, TV fallback, out TV) {
|
|
64
|
+
var int cap = self.keys.cap
|
|
65
|
+
if cap == 0 {
|
|
66
|
+
return fallback
|
|
67
|
+
}
|
|
68
|
+
var int idx = self.find_slot(key, cap)
|
|
69
|
+
if self.used.load(idx) == 0 {
|
|
70
|
+
return fallback
|
|
71
|
+
}
|
|
72
|
+
return self.values.load_T(idx)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Lookup that traps when the key is absent — for when a missing key is
|
|
76
|
+
// a bug, not a case (and `get`'s 0-for-missing would be ambiguous).
|
|
77
|
+
pub func get_or_panic = (self, TK key, out TV) {
|
|
78
|
+
var int cap = self.keys.cap
|
|
79
|
+
if cap == 0 {
|
|
80
|
+
panic("key not found")
|
|
81
|
+
}
|
|
82
|
+
var int idx = self.find_slot(key, cap)
|
|
83
|
+
if self.used.load(idx) == 0 {
|
|
84
|
+
panic("key not found")
|
|
85
|
+
}
|
|
86
|
+
return self.values.load_T(idx)
|
|
87
|
+
}
|
|
88
|
+
|
|
56
89
|
pub func has = (self, TK key, out bool) {
|
|
57
90
|
var int cap = self.keys.cap
|
|
58
91
|
if cap == 0 {
|
|
@@ -91,10 +124,7 @@ pub struct Map<TK: Hashable + Equatable, TV> {
|
|
|
91
124
|
if self.used.load(k) == 0 {
|
|
92
125
|
break
|
|
93
126
|
}
|
|
94
|
-
var int home = (self.keys.load_T(k).hash() as int)
|
|
95
|
-
if home < 0 {
|
|
96
|
-
home = home + cap
|
|
97
|
-
}
|
|
127
|
+
var int home = (self.keys.load_T(k).hash() as int) & (cap - 1)
|
|
98
128
|
var int dgap = gap - home
|
|
99
129
|
if dgap < 0 {
|
|
100
130
|
dgap = dgap + cap
|
|
@@ -115,10 +145,9 @@ pub struct Map<TK: Hashable + Equatable, TV> {
|
|
|
115
145
|
}
|
|
116
146
|
|
|
117
147
|
func find_slot = (self, TK key, int cap, out int: out >= 0 && out < cap) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
148
|
+
// cap is a power of two, so this is `hash mod cap` via a mask —
|
|
149
|
+
// non-negative for any hash (two's complement), no adjustment.
|
|
150
|
+
var int idx = (key.hash() as int) & (cap - 1)
|
|
122
151
|
while self.used.load(idx) != 0 {
|
|
123
152
|
if self.keys.load_T(idx) == key {
|
|
124
153
|
return idx
|
package/core/System/Set.nm
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* An ordered set of unique values (`add`/`has`/`get`/`remove`). Elements must
|
|
3
3
|
* be `Hashable` (hashed to a `uint` via `value.hash()`) and `Equatable`
|
|
4
4
|
* (compared with `==`).
|
|
5
|
+
*
|
|
6
|
+
* The capacity is always a power of two (initial 8, doubling on rehash), so
|
|
7
|
+
* slot indexing is a mask (`hash & (cap - 1)`) — correct for negative hashes
|
|
8
|
+
* too (two's-complement `&` already lands in `[0, cap)`).
|
|
5
9
|
**/
|
|
6
10
|
pub struct Set<T: Hashable + Equatable> {
|
|
7
11
|
var length = 0
|
|
@@ -47,6 +51,33 @@ pub struct Set<T: Hashable + Equatable> {
|
|
|
47
51
|
return self.slots.load_T(idx)
|
|
48
52
|
}
|
|
49
53
|
|
|
54
|
+
// Membership lookup with a fallback value: `fallback` when the value is
|
|
55
|
+
// not in the set (one probe instead of the `has` + `get` pair).
|
|
56
|
+
pub func get_or = (self, T key, T fallback, out T) {
|
|
57
|
+
var int cap = self.slots.cap
|
|
58
|
+
if cap == 0 {
|
|
59
|
+
return fallback
|
|
60
|
+
}
|
|
61
|
+
var int idx = self.find_slot(key, cap)
|
|
62
|
+
if self.used.load(idx) == 0 {
|
|
63
|
+
return fallback
|
|
64
|
+
}
|
|
65
|
+
return self.slots.load_T(idx)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Membership lookup that traps when the value is not in the set.
|
|
69
|
+
pub func get_or_panic = (self, T key, out T) {
|
|
70
|
+
var int cap = self.slots.cap
|
|
71
|
+
if cap == 0 {
|
|
72
|
+
panic("value not found in set")
|
|
73
|
+
}
|
|
74
|
+
var int idx = self.find_slot(key, cap)
|
|
75
|
+
if self.used.load(idx) == 0 {
|
|
76
|
+
panic("value not found in set")
|
|
77
|
+
}
|
|
78
|
+
return self.slots.load_T(idx)
|
|
79
|
+
}
|
|
80
|
+
|
|
50
81
|
pub func remove = (ref self, T value) {
|
|
51
82
|
var int cap = self.slots.cap
|
|
52
83
|
if cap == 0 {
|
|
@@ -76,10 +107,7 @@ pub struct Set<T: Hashable + Equatable> {
|
|
|
76
107
|
if self.used.load(k) == 0 {
|
|
77
108
|
break
|
|
78
109
|
}
|
|
79
|
-
var int home = (self.slots.load_T(k).hash() as int)
|
|
80
|
-
if home < 0 {
|
|
81
|
-
home = home + cap
|
|
82
|
-
}
|
|
110
|
+
var int home = (self.slots.load_T(k).hash() as int) & (cap - 1)
|
|
83
111
|
var int dgap = gap - home
|
|
84
112
|
if dgap < 0 {
|
|
85
113
|
dgap = dgap + cap
|
|
@@ -99,10 +127,9 @@ pub struct Set<T: Hashable + Equatable> {
|
|
|
99
127
|
}
|
|
100
128
|
|
|
101
129
|
func find_slot = (self, T value, int cap, out int: out >= 0 && out < cap) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
130
|
+
// cap is a power of two: `hash mod cap` via a mask, non-negative
|
|
131
|
+
// for any hash — no adjustment needed.
|
|
132
|
+
var int idx = (value.hash() as int) & (cap - 1)
|
|
106
133
|
while self.used.load(idx) != 0 {
|
|
107
134
|
if self.slots.load_T(idx) == value {
|
|
108
135
|
return idx
|
package/core/System/String.nm
CHANGED
|
@@ -51,6 +51,24 @@ pub struct string: Stringable, Hashable, Equatable {
|
|
|
51
51
|
```
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
// Bounds-checked access: `fallback` when index is outside
|
|
55
|
+
// [0, self.length).
|
|
56
|
+
pub func at_or = (self, int index, char fallback, out char) {
|
|
57
|
+
if index >= 0 && index < self.length {
|
|
58
|
+
return self.at(index)
|
|
59
|
+
}
|
|
60
|
+
return fallback
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Bounds-checked access that traps when index is outside
|
|
64
|
+
// [0, self.length) — out-of-range is a bug, not a case.
|
|
65
|
+
pub func at_or_panic = (self, int index, out char) {
|
|
66
|
+
if index >= 0 && index < self.length {
|
|
67
|
+
return self.at(index)
|
|
68
|
+
}
|
|
69
|
+
panic("index out of range")
|
|
70
|
+
}
|
|
71
|
+
|
|
54
72
|
// A non-owning slice [start, end) into self's buffer. Returns a `view
|
|
55
73
|
// string` (ptr, len) that borrows from self: it may not outlive self's
|
|
56
74
|
// scope and is invalidated if self is reassigned. Use .to_string() to
|
package/dist/index.mjs
CHANGED
|
@@ -11779,7 +11779,7 @@ function extract_aarch64_asm(func, platform) {
|
|
|
11779
11779
|
return asm;
|
|
11780
11780
|
}
|
|
11781
11781
|
function count_x19_reads(asm) {
|
|
11782
|
-
const matches = asm.match(/\bx19\b/g);
|
|
11782
|
+
const matches = asm.split("\n").map((l) => l.replace(/\/\/.*$/, "")).join("\n").match(/\bx19\b/g);
|
|
11783
11783
|
return matches ? matches.length : 0;
|
|
11784
11784
|
}
|
|
11785
11785
|
function build_naked_inline(struct_node, func, status) {
|
|
@@ -11787,6 +11787,8 @@ function build_naked_inline(struct_node, func, status) {
|
|
|
11787
11787
|
const standalone_return_label = `.return_${struct_node.name}_${func.name.replace(/#/g, "")}`;
|
|
11788
11788
|
asm = asm.replaceAll(`b ${standalone_return_label}`, "");
|
|
11789
11789
|
if (count_x19_reads(asm) === 1) asm = asm.replace(/\bx19\b/g, "x0");
|
|
11790
|
+
const site = inline_counter++;
|
|
11791
|
+
asm = asm.replace(/(\.L[A-Za-z0-9_]+)/g, `$1_${site}`);
|
|
11790
11792
|
status.code += asm + "\n";
|
|
11791
11793
|
}
|
|
11792
11794
|
function build_inline_method(struct_node, func, status) {
|
|
@@ -11811,9 +11813,11 @@ function build_inline_method(struct_node, func, status) {
|
|
|
11811
11813
|
const old_buffer_data_cache = status.buffer_data_cache;
|
|
11812
11814
|
const old_heap_cleanup_stack = status.heap_cleanup_stack;
|
|
11813
11815
|
const old_moved = status.moved;
|
|
11816
|
+
const old_outer_scope_declarations = status.outer_scope_declarations;
|
|
11814
11817
|
const return_label = `.inline_ret_${inline_counter++}`;
|
|
11815
11818
|
status.function_return_label = return_label;
|
|
11816
11819
|
status.scoped_declarations = [];
|
|
11820
|
+
status.outer_scope_declarations = [];
|
|
11817
11821
|
status.function_return_type = void 0;
|
|
11818
11822
|
status.struct_return_buffer = void 0;
|
|
11819
11823
|
status.return_buffer_stack_offset = void 0;
|
|
@@ -11900,6 +11904,7 @@ function build_inline_method(struct_node, func, status) {
|
|
|
11900
11904
|
status.buffer_data_cache = old_buffer_data_cache;
|
|
11901
11905
|
status.heap_cleanup_stack = old_heap_cleanup_stack;
|
|
11902
11906
|
status.moved = old_moved;
|
|
11907
|
+
status.outer_scope_declarations = old_outer_scope_declarations;
|
|
11903
11908
|
}
|
|
11904
11909
|
let inline_fn_depth = 0;
|
|
11905
11910
|
const MAX_INLINE_DEPTH = 2;
|
|
@@ -11919,9 +11924,11 @@ function build_inline_function(func, status) {
|
|
|
11919
11924
|
const old_buffer_data_cache = status.buffer_data_cache;
|
|
11920
11925
|
const old_heap_cleanup_stack = status.heap_cleanup_stack;
|
|
11921
11926
|
const old_moved = status.moved;
|
|
11927
|
+
const old_outer_scope_declarations = status.outer_scope_declarations;
|
|
11922
11928
|
const return_label = `.inline_fn_ret_${inline_counter++}`;
|
|
11923
11929
|
status.function_return_label = return_label;
|
|
11924
11930
|
status.scoped_declarations = [];
|
|
11931
|
+
status.outer_scope_declarations = [];
|
|
11925
11932
|
status.function_return_type = void 0;
|
|
11926
11933
|
status.struct_return_buffer = void 0;
|
|
11927
11934
|
status.return_buffer_stack_offset = void 0;
|
|
@@ -11996,6 +12003,7 @@ function build_inline_function(func, status) {
|
|
|
11996
12003
|
status.buffer_data_cache = old_buffer_data_cache;
|
|
11997
12004
|
status.heap_cleanup_stack = old_heap_cleanup_stack;
|
|
11998
12005
|
status.moved = old_moved;
|
|
12006
|
+
status.outer_scope_declarations = old_outer_scope_declarations;
|
|
11999
12007
|
inline_fn_depth--;
|
|
12000
12008
|
return true;
|
|
12001
12009
|
}
|
|
@@ -18257,7 +18265,7 @@ function build_value_node(node, status) {
|
|
|
18257
18265
|
else value = c_function_name(value);
|
|
18258
18266
|
if (value.startsWith("\"")) value = escape_c_string(value);
|
|
18259
18267
|
if (value !== "self" && status.function_ref_params?.has(value) && !status.suppress_dereference) status.code += `*`;
|
|
18260
|
-
if (value === "self" && status.function_ref_params?.has("self") && !status.suppress_dereference) status.code += `*`;
|
|
18268
|
+
if (value === "self" && status.function_ref_params?.has("self") && !status.suppress_dereference && status.current_struct?.name !== "string") status.code += `*`;
|
|
18261
18269
|
if (value !== "self" && status.ref_class_params?.has(value) && !status.suppress_dereference) {
|
|
18262
18270
|
status.code += `(*${value})`;
|
|
18263
18271
|
return;
|
|
@@ -20386,6 +20394,7 @@ function clone_node(node) {
|
|
|
20386
20394
|
c.return_constraint = n.return_constraint ? clone_node(n.return_constraint) : void 0;
|
|
20387
20395
|
c.returns_mov = n.returns_mov;
|
|
20388
20396
|
c.is_library = n.is_library;
|
|
20397
|
+
c.is_inline = n.is_inline;
|
|
20389
20398
|
return c;
|
|
20390
20399
|
}
|
|
20391
20400
|
case "param": {
|
|
@@ -20954,6 +20963,40 @@ function snapshot_bounds(name, status) {
|
|
|
20954
20963
|
lower_bound_inclusive_exprs: decl.lower_bound_inclusive_exprs?.slice()
|
|
20955
20964
|
};
|
|
20956
20965
|
}
|
|
20966
|
+
function snapshot_all_bounds(status) {
|
|
20967
|
+
const snap = /* @__PURE__ */ new Map();
|
|
20968
|
+
for (const v of status.values) snap.set(v, {
|
|
20969
|
+
range_lower: v.range_lower,
|
|
20970
|
+
range_upper: v.range_upper,
|
|
20971
|
+
upper_bound_exprs: v.upper_bound_exprs?.slice(),
|
|
20972
|
+
lower_bound_exprs: v.lower_bound_exprs?.slice(),
|
|
20973
|
+
upper_bound_inclusive_exprs: v.upper_bound_inclusive_exprs?.slice(),
|
|
20974
|
+
lower_bound_inclusive_exprs: v.lower_bound_inclusive_exprs?.slice(),
|
|
20975
|
+
upper_bound_expr: v.upper_bound_expr,
|
|
20976
|
+
lower_bound_expr: v.lower_bound_expr,
|
|
20977
|
+
alias_of: v.alias_of,
|
|
20978
|
+
known_length: v.known_length,
|
|
20979
|
+
path_bounds: v.path_bounds ? new Map(v.path_bounds) : void 0
|
|
20980
|
+
});
|
|
20981
|
+
return snap;
|
|
20982
|
+
}
|
|
20983
|
+
function restore_all_bounds(status, snap) {
|
|
20984
|
+
for (const v of status.values) {
|
|
20985
|
+
const s = snap.get(v);
|
|
20986
|
+
if (!s) continue;
|
|
20987
|
+
v.range_lower = s.range_lower;
|
|
20988
|
+
v.range_upper = s.range_upper;
|
|
20989
|
+
v.upper_bound_exprs = s.upper_bound_exprs;
|
|
20990
|
+
v.lower_bound_exprs = s.lower_bound_exprs;
|
|
20991
|
+
v.upper_bound_inclusive_exprs = s.upper_bound_inclusive_exprs;
|
|
20992
|
+
v.lower_bound_inclusive_exprs = s.lower_bound_inclusive_exprs;
|
|
20993
|
+
v.upper_bound_expr = s.upper_bound_expr;
|
|
20994
|
+
v.lower_bound_expr = s.lower_bound_expr;
|
|
20995
|
+
v.alias_of = s.alias_of;
|
|
20996
|
+
v.known_length = s.known_length;
|
|
20997
|
+
v.path_bounds = s.path_bounds;
|
|
20998
|
+
}
|
|
20999
|
+
}
|
|
20957
21000
|
/**
|
|
20958
21001
|
* Serialize an AST expression to a canonical string for comparison.
|
|
20959
21002
|
* E.g. `list.length` → "list.length", `self.length` → resolves alias to actual var.
|
|
@@ -20963,7 +21006,7 @@ function expr_to_string(node, status) {
|
|
|
20963
21006
|
const vn = node;
|
|
20964
21007
|
if (status) {
|
|
20965
21008
|
const decl = status.values.findLast((v) => v.name === vn.value);
|
|
20966
|
-
if (decl?.alias_of) return decl.alias_of;
|
|
21009
|
+
if (decl?.alias_of) return canonicalize_length_path(decl.alias_of, status);
|
|
20967
21010
|
}
|
|
20968
21011
|
return vn.value;
|
|
20969
21012
|
}
|
|
@@ -20972,7 +21015,10 @@ function expr_to_string(node, status) {
|
|
|
20972
21015
|
if (access.access.node_type === "access_field") {
|
|
20973
21016
|
const target = expr_to_string(access.target, status);
|
|
20974
21017
|
const field = access.access.name;
|
|
20975
|
-
if (target)
|
|
21018
|
+
if (target) {
|
|
21019
|
+
const path = `${target}.${field}`;
|
|
21020
|
+
return status ? canonicalize_length_path(path, status) : path;
|
|
21021
|
+
}
|
|
20976
21022
|
} else if (access.access.node_type === "access_func") {
|
|
20977
21023
|
const target = expr_to_string(access.target, status);
|
|
20978
21024
|
const method = access.access.name;
|
|
@@ -20981,9 +21027,88 @@ function expr_to_string(node, status) {
|
|
|
20981
21027
|
"size",
|
|
20982
21028
|
"count",
|
|
20983
21029
|
"cap"
|
|
20984
|
-
].includes(method))
|
|
20985
|
-
|
|
20986
|
-
|
|
21030
|
+
].includes(method)) {
|
|
21031
|
+
const path = `${target}.${method}`;
|
|
21032
|
+
return status ? canonicalize_length_path(path, status) : path;
|
|
21033
|
+
}
|
|
21034
|
+
}
|
|
21035
|
+
}
|
|
21036
|
+
}
|
|
21037
|
+
/**
|
|
21038
|
+
* Rewrite a `<base>.<field>` path through the assumed parallel-length
|
|
21039
|
+
* equalities (`a.length == b.length` param contracts) to a canonical
|
|
21040
|
+
* representative, so a bound phrased against either container's length
|
|
21041
|
+
* (`i < xs.length`) verifies a constraint on the other (`hash.length`).
|
|
21042
|
+
* Each equation's `a` side is the representative; following `b → a` edges
|
|
21043
|
+
* to a fixed point makes the rewrite idempotent and confluent for chains
|
|
21044
|
+
* (a==b, b==c all canonicalize to the chain head). Only touches paths whose
|
|
21045
|
+
* field has a recorded equation.
|
|
21046
|
+
*/
|
|
21047
|
+
function canonicalize_length_path(path, status) {
|
|
21048
|
+
const eqs = status.equal_lengths;
|
|
21049
|
+
if (!eqs?.length) return path;
|
|
21050
|
+
const m = /^(\w+)\.(length|count|size|cap)$/.exec(path);
|
|
21051
|
+
if (!m) return path;
|
|
21052
|
+
const field = m[2];
|
|
21053
|
+
let base = m[1];
|
|
21054
|
+
for (let step = 0; step < eqs.length; step++) {
|
|
21055
|
+
const eq = eqs.find((e) => e.field === field && (e.a === base || e.b === base));
|
|
21056
|
+
if (!eq || eq.a === base) break;
|
|
21057
|
+
base = eq.a;
|
|
21058
|
+
}
|
|
21059
|
+
return `${base}.${field}`;
|
|
21060
|
+
}
|
|
21061
|
+
/**
|
|
21062
|
+
* Remove `X.<f> == Y.<f>` clauses (same container-size field on two params,
|
|
21063
|
+
* one of them `param_name`) from a parameter constraint, recording each as an
|
|
21064
|
+
* ASSUMED parallel-length equality on `status.equal_lengths`. Such a clause
|
|
21065
|
+
* relates two runtime lengths, which the verifier can never prove at a call
|
|
21066
|
+
* site — instead it is trusted there (stripped from the contract) and made
|
|
21067
|
+
* available to the body's bounds machinery via expr_to_string
|
|
21068
|
+
* canonicalization. Returns the rewritten constraint, or undefined when
|
|
21069
|
+
* everything was stripped. Nodes are only rebuilt when something changed.
|
|
21070
|
+
*/
|
|
21071
|
+
function strip_length_equalities(constraint, param_name, status) {
|
|
21072
|
+
if (!constraint) return void 0;
|
|
21073
|
+
if (constraint.node_type !== "op") return constraint;
|
|
21074
|
+
const op = constraint;
|
|
21075
|
+
if (op.op === "&&") {
|
|
21076
|
+
const left = strip_length_equalities(op.left_value, param_name, status);
|
|
21077
|
+
const right = strip_length_equalities(op.right_value, param_name, status);
|
|
21078
|
+
if (left && right) {
|
|
21079
|
+
if (left === op.left_value && right === op.right_value) return constraint;
|
|
21080
|
+
return new OperationNode(op.start, "&&", left, right, op.type);
|
|
21081
|
+
}
|
|
21082
|
+
return left ?? right;
|
|
21083
|
+
}
|
|
21084
|
+
if (op.op !== "==") return constraint;
|
|
21085
|
+
const extract = (side) => {
|
|
21086
|
+
if (side.node_type !== "access") return void 0;
|
|
21087
|
+
const access = side;
|
|
21088
|
+
let field;
|
|
21089
|
+
if (access.access.node_type === "access_field") field = access.access.name;
|
|
21090
|
+
else if (access.access.node_type === "access_func") field = access.access.name;
|
|
21091
|
+
if (!field || !NON_NEGATIVE_FIELDS$1.has(field)) return void 0;
|
|
21092
|
+
if (access.target.node_type !== "value") return void 0;
|
|
21093
|
+
const base = access.target.value;
|
|
21094
|
+
if (base !== param_name && !status.values.some((v) => v.name === base)) return void 0;
|
|
21095
|
+
return {
|
|
21096
|
+
base,
|
|
21097
|
+
field
|
|
21098
|
+
};
|
|
21099
|
+
};
|
|
21100
|
+
const lhs = extract(op.left_value);
|
|
21101
|
+
const rhs = extract(op.right_value);
|
|
21102
|
+
if (!lhs || !rhs || lhs.field !== rhs.field) return constraint;
|
|
21103
|
+
if (lhs.base === rhs.base) return constraint;
|
|
21104
|
+
if (!status.equal_lengths) status.equal_lengths = [];
|
|
21105
|
+
const other = lhs.base;
|
|
21106
|
+
const paired = rhs.base;
|
|
21107
|
+
if (!status.equal_lengths.some((e) => e.field === lhs.field && (e.a === other && e.b === paired || e.a === paired && e.b === other))) status.equal_lengths.push({
|
|
21108
|
+
a: other,
|
|
21109
|
+
b: paired,
|
|
21110
|
+
field: lhs.field
|
|
21111
|
+
});
|
|
20987
21112
|
}
|
|
20988
21113
|
/**
|
|
20989
21114
|
* Extract flow-sensitive bounds from a comparison condition.
|
|
@@ -21079,32 +21204,59 @@ function apply_bounds(condition, status, is_loop = false) {
|
|
|
21079
21204
|
var_decl = status.values.findLast((v) => v.name === base);
|
|
21080
21205
|
}
|
|
21081
21206
|
if (!var_decl) return;
|
|
21207
|
+
let path_entry;
|
|
21208
|
+
if (bound.var_name.includes(".") && bound.var_name !== var_decl.name) {
|
|
21209
|
+
if (!var_decl.path_bounds) var_decl.path_bounds = /* @__PURE__ */ new Map();
|
|
21210
|
+
path_entry = var_decl.path_bounds.get(bound.var_name);
|
|
21211
|
+
if (!path_entry) {
|
|
21212
|
+
path_entry = {};
|
|
21213
|
+
var_decl.path_bounds.set(bound.var_name, path_entry);
|
|
21214
|
+
}
|
|
21215
|
+
}
|
|
21216
|
+
const push_unique = (arr, expr) => {
|
|
21217
|
+
const out = arr ?? [];
|
|
21218
|
+
if (!out.includes(expr)) out.push(expr);
|
|
21219
|
+
return out;
|
|
21220
|
+
};
|
|
21082
21221
|
if (bound.op === "<" || bound.op === "<=") {
|
|
21083
21222
|
const inclusive = bound.op === "<=";
|
|
21084
21223
|
if (!var_decl.upper_bound_exprs) var_decl.upper_bound_exprs = [];
|
|
21085
21224
|
if (!var_decl.upper_bound_inclusive_exprs) var_decl.upper_bound_inclusive_exprs = [];
|
|
21086
21225
|
if (inclusive) {
|
|
21087
|
-
|
|
21088
|
-
|
|
21226
|
+
var_decl.upper_bound_inclusive_exprs = push_unique(var_decl.upper_bound_inclusive_exprs, bound.expr);
|
|
21227
|
+
if (path_entry) path_entry.upper_inclusive = push_unique(path_entry.upper_inclusive, bound.expr);
|
|
21228
|
+
} else {
|
|
21229
|
+
var_decl.upper_bound_exprs = push_unique(var_decl.upper_bound_exprs, bound.expr);
|
|
21230
|
+
if (path_entry) path_entry.upper = push_unique(path_entry.upper, bound.expr);
|
|
21231
|
+
}
|
|
21089
21232
|
var_decl.upper_bound_expr = bound.expr;
|
|
21090
21233
|
const num = numeric_interval(string_to_node(bound.expr), status);
|
|
21091
21234
|
if (num) {
|
|
21092
21235
|
const hi = bound.op === "<=" ? num.upper : num.lower;
|
|
21093
21236
|
if (is_loop) var_decl.range_upper = hi;
|
|
21094
21237
|
else if (var_decl.range_upper === void 0 || hi < var_decl.range_upper) var_decl.range_upper = hi;
|
|
21238
|
+
if (path_entry) {
|
|
21239
|
+
if (is_loop) path_entry.range_upper = hi;
|
|
21240
|
+
else if (path_entry.range_upper === void 0 || hi < path_entry.range_upper) path_entry.range_upper = hi;
|
|
21241
|
+
}
|
|
21095
21242
|
}
|
|
21096
21243
|
} else if (bound.op === ">" || bound.op === ">=") {
|
|
21097
21244
|
const inclusive = bound.op === ">=";
|
|
21098
21245
|
if (!var_decl.lower_bound_exprs) var_decl.lower_bound_exprs = [];
|
|
21099
21246
|
if (!var_decl.lower_bound_inclusive_exprs) var_decl.lower_bound_inclusive_exprs = [];
|
|
21100
21247
|
if (inclusive) {
|
|
21101
|
-
|
|
21102
|
-
|
|
21248
|
+
var_decl.lower_bound_inclusive_exprs = push_unique(var_decl.lower_bound_inclusive_exprs, bound.expr);
|
|
21249
|
+
if (path_entry) path_entry.lower_inclusive = push_unique(path_entry.lower_inclusive, bound.expr);
|
|
21250
|
+
} else {
|
|
21251
|
+
var_decl.lower_bound_exprs = push_unique(var_decl.lower_bound_exprs, bound.expr);
|
|
21252
|
+
if (path_entry) path_entry.lower = push_unique(path_entry.lower, bound.expr);
|
|
21253
|
+
}
|
|
21103
21254
|
var_decl.lower_bound_expr = bound.expr;
|
|
21104
21255
|
const num = numeric_interval(string_to_node(bound.expr), status);
|
|
21105
21256
|
if (num) {
|
|
21106
21257
|
const lo = bound.op === ">=" ? num.lower : num.upper;
|
|
21107
21258
|
if (var_decl.range_lower === void 0 || lo > var_decl.range_lower) var_decl.range_lower = lo;
|
|
21259
|
+
if (path_entry && (path_entry.range_lower === void 0 || lo > path_entry.range_lower)) path_entry.range_lower = lo;
|
|
21108
21260
|
}
|
|
21109
21261
|
}
|
|
21110
21262
|
apply_inverse_numeric_bound(bound, var_decl, status);
|
|
@@ -21259,6 +21411,31 @@ function substitute_constraint(node, lhs_name, param_to_arg, visited = /* @__PUR
|
|
|
21259
21411
|
}
|
|
21260
21412
|
return node;
|
|
21261
21413
|
}
|
|
21414
|
+
/** Extract `return_bounds` from a value node that is a call (or wraps one). */
|
|
21415
|
+
function call_return_bounds(value) {
|
|
21416
|
+
if (!value) return void 0;
|
|
21417
|
+
if (value.node_type === "func_call" || value.node_type === "access_func") return value.return_bounds;
|
|
21418
|
+
if (value.node_type === "access" && value.access.node_type === "access_func") return value.access.return_bounds;
|
|
21419
|
+
}
|
|
21420
|
+
/**
|
|
21421
|
+
* Transfer a call's return-contract bounds onto the variable the call result
|
|
21422
|
+
* is bound to (`const int m = mid(xs)` / `m = mid(xs)`). Without this, only
|
|
21423
|
+
* the nested form `xs.at(mid(xs))` carried the contract (via the call node's
|
|
21424
|
+
* `return_bounds` decoration); binding to a variable dropped it and forced
|
|
21425
|
+
* unreadable nesting. Uses the same funneled semantics as the nested path
|
|
21426
|
+
* (collect_return_bounds: `<=` lands in the strict `upper` array), so both
|
|
21427
|
+
* forms verify identically.
|
|
21428
|
+
*/
|
|
21429
|
+
function apply_return_bounds_to_var(name, rb, status) {
|
|
21430
|
+
if (!rb) return;
|
|
21431
|
+
const decl = status.values.findLast((v) => v.name === name);
|
|
21432
|
+
if (!decl) return;
|
|
21433
|
+
const push = (arr, add) => arr ? (arr.push(...add.filter((e) => !arr.includes(e))), arr) : add.slice();
|
|
21434
|
+
if (rb.upper.length) decl.upper_bound_exprs = push(decl.upper_bound_exprs, rb.upper);
|
|
21435
|
+
if (rb.lower.length) decl.lower_bound_exprs = push(decl.lower_bound_exprs, rb.lower);
|
|
21436
|
+
if (rb.upper_inclusive.length) decl.upper_bound_inclusive_exprs = push(decl.upper_bound_inclusive_exprs, rb.upper_inclusive);
|
|
21437
|
+
if (rb.lower_inclusive.length) decl.lower_bound_inclusive_exprs = push(decl.lower_bound_inclusive_exprs, rb.lower_inclusive);
|
|
21438
|
+
}
|
|
21262
21439
|
/**
|
|
21263
21440
|
* Build an expression node tree from a dotted path string (e.g. "list",
|
|
21264
21441
|
* "self.items") for use in return-contract substitution. Used to map `self`
|
|
@@ -22474,6 +22651,18 @@ function check_function_call(node, status, func, target_type, self_value, self_p
|
|
|
22474
22651
|
if (access.access.node_type === "access_field") field_name = access.access.name;
|
|
22475
22652
|
else if (access.access.node_type === "access_func") field_name = access.access.name;
|
|
22476
22653
|
if (field_name && NON_NEGATIVE_FIELDS.has(field_name)) alias_of = expr_to_string(param, status);
|
|
22654
|
+
const access_path = expr_to_string(param, status);
|
|
22655
|
+
if (access_path && access_path.includes(".")) {
|
|
22656
|
+
const path_bounds = status.values.findLast((v) => v.name === access_path.split(".")[0])?.path_bounds?.get(access_path);
|
|
22657
|
+
if (path_bounds) {
|
|
22658
|
+
upper_bound_exprs = path_bounds.upper?.slice();
|
|
22659
|
+
lower_bound_exprs = path_bounds.lower?.slice();
|
|
22660
|
+
upper_bound_inclusive_exprs = path_bounds.upper_inclusive?.slice();
|
|
22661
|
+
lower_bound_inclusive_exprs = path_bounds.lower_inclusive?.slice();
|
|
22662
|
+
range_lower = path_bounds.range_lower;
|
|
22663
|
+
range_upper = path_bounds.range_upper;
|
|
22664
|
+
}
|
|
22665
|
+
}
|
|
22477
22666
|
}
|
|
22478
22667
|
let nested_rb;
|
|
22479
22668
|
if (param.node_type === "func_call" || param.node_type === "access_func") nested_rb = param.return_bounds;
|
|
@@ -22692,6 +22881,222 @@ function receiver_is_const(target_type, self_value, status) {
|
|
|
22692
22881
|
return !!binding && binding.declaration === "const" && !binding.type.is_ref;
|
|
22693
22882
|
}
|
|
22694
22883
|
//#endregion
|
|
22884
|
+
//#region ../src/build_common/fold_asm_constants.ts
|
|
22885
|
+
/**
|
|
22886
|
+
* A tiny constant-propagation peephole over monomorphized raw aarch64
|
|
22887
|
+
* assembly (`#arch: aarch64` blocks after T → concrete-type substitution).
|
|
22888
|
+
*
|
|
22889
|
+
* Raw library code is written generically: `mov x3, #T_SIZE` followed by a
|
|
22890
|
+
* runtime width dispatch (`cmp x3, #8 / b.gt … / cmp x3, #1 / b.eq …`).
|
|
22891
|
+
* After substitution T_SIZE is a literal, so every dispatched branch has a
|
|
22892
|
+
* compile-time outcome — but as TEXT, so nothing folds it. This pass tracks
|
|
22893
|
+
* `mov <reg>, #<imm>` definitions per basic block and:
|
|
22894
|
+
*
|
|
22895
|
+
* - folds `cmp <reg>, #<imm>` + following `b.<cond> <label>` when the
|
|
22896
|
+
* comparison's outcome is known: never-taken branches are dropped,
|
|
22897
|
+
* always-taken ones become unconditional `b <label>`,
|
|
22898
|
+
* - rewrites `madd <d>, <n>, <m>, xzr` / `mul <d>, <n>, <m>` as
|
|
22899
|
+
* `lsl <d>, <n>, #<log2>` when `<m>` is a known power-of-two constant
|
|
22900
|
+
* (the element-stride multiply in every load_T/store_T body).
|
|
22901
|
+
*
|
|
22902
|
+
* Soundness: a register's definition is only tracked from its immediate
|
|
22903
|
+
* `mov` to its next write; the map is cleared at labels (jump targets may
|
|
22904
|
+
* arrive with different values), at unconditional branches, at calls
|
|
22905
|
+
* (`bl`/`blr` clobber the caller-saved set), and at any instruction form we
|
|
22906
|
+
* do not model. Dead code left behind dropped branches (e.g. the
|
|
22907
|
+
* width-matched tails and the memcpy copy path) stays in place — it is
|
|
22908
|
+
* unreachable, and removing it would require a full reachability pass.
|
|
22909
|
+
* Comments (`// …`) are ignored.
|
|
22910
|
+
*/
|
|
22911
|
+
const REG = /^[wx][0-9]+$/;
|
|
22912
|
+
const COND_OPS = /* @__PURE__ */ new Set([
|
|
22913
|
+
"eq",
|
|
22914
|
+
"ne",
|
|
22915
|
+
"gt",
|
|
22916
|
+
"ge",
|
|
22917
|
+
"lt",
|
|
22918
|
+
"le",
|
|
22919
|
+
"hi",
|
|
22920
|
+
"hs",
|
|
22921
|
+
"lo",
|
|
22922
|
+
"ls",
|
|
22923
|
+
"al"
|
|
22924
|
+
]);
|
|
22925
|
+
function split_comment(line) {
|
|
22926
|
+
return line.replace(/\/\/.*$/, "");
|
|
22927
|
+
}
|
|
22928
|
+
/** Does `a <cond> b` hold? (signed semantics; immediates here are small.) */
|
|
22929
|
+
function cond_holds(cond, a, b) {
|
|
22930
|
+
switch (cond) {
|
|
22931
|
+
case "eq": return a === b;
|
|
22932
|
+
case "ne": return a !== b;
|
|
22933
|
+
case "gt": return a > b;
|
|
22934
|
+
case "ge": return a >= b;
|
|
22935
|
+
case "lt": return a < b;
|
|
22936
|
+
case "le": return a <= b;
|
|
22937
|
+
case "hi": return a > b;
|
|
22938
|
+
case "hs": return a >= b;
|
|
22939
|
+
case "lo": return a < b;
|
|
22940
|
+
case "ls": return a <= b;
|
|
22941
|
+
case "al": return true;
|
|
22942
|
+
default: return false;
|
|
22943
|
+
}
|
|
22944
|
+
}
|
|
22945
|
+
function is_pow2(n) {
|
|
22946
|
+
if (n > 0 && (n & n - 1) === 0) return Math.log2(n);
|
|
22947
|
+
}
|
|
22948
|
+
function fold_asm_constants(asm) {
|
|
22949
|
+
const lines = asm.split("\n");
|
|
22950
|
+
const out = [];
|
|
22951
|
+
const defs = /* @__PURE__ */ new Map();
|
|
22952
|
+
const clear = () => defs.clear();
|
|
22953
|
+
/** Invalidate the destination register of a general instruction. */
|
|
22954
|
+
const invalidate_dest = (ops) => {
|
|
22955
|
+
const first = ops.split(",")[0]?.trim();
|
|
22956
|
+
if (first && REG.test(first)) defs.delete(first);
|
|
22957
|
+
};
|
|
22958
|
+
let i = 0;
|
|
22959
|
+
while (i < lines.length) {
|
|
22960
|
+
const raw_line = lines[i];
|
|
22961
|
+
const line = split_comment(raw_line).trim();
|
|
22962
|
+
if (!line) {
|
|
22963
|
+
out.push(raw_line);
|
|
22964
|
+
i++;
|
|
22965
|
+
continue;
|
|
22966
|
+
}
|
|
22967
|
+
if (/^\.[\w$]+:$/.test(line) || line.startsWith(".")) {
|
|
22968
|
+
clear();
|
|
22969
|
+
out.push(raw_line);
|
|
22970
|
+
i++;
|
|
22971
|
+
continue;
|
|
22972
|
+
}
|
|
22973
|
+
const m = /^(\w+)\s*(.*)$/.exec(line);
|
|
22974
|
+
if (!m) {
|
|
22975
|
+
clear();
|
|
22976
|
+
out.push(raw_line);
|
|
22977
|
+
i++;
|
|
22978
|
+
continue;
|
|
22979
|
+
}
|
|
22980
|
+
const mn = m[1];
|
|
22981
|
+
const ops = m[2].trim();
|
|
22982
|
+
if (mn === "bl" || mn === "blr" || mn === "b" || mn === "br" || mn === "ret") {
|
|
22983
|
+
clear();
|
|
22984
|
+
out.push(raw_line);
|
|
22985
|
+
i++;
|
|
22986
|
+
continue;
|
|
22987
|
+
}
|
|
22988
|
+
if (mn === "mov") {
|
|
22989
|
+
const mm = /^([wx][0-9]+)\s*,\s*#(-?\d+)$/.exec(ops);
|
|
22990
|
+
if (mm) {
|
|
22991
|
+
defs.set(mm[1], parseInt(mm[2], 10));
|
|
22992
|
+
out.push(raw_line);
|
|
22993
|
+
i++;
|
|
22994
|
+
continue;
|
|
22995
|
+
}
|
|
22996
|
+
invalidate_dest(ops);
|
|
22997
|
+
out.push(raw_line);
|
|
22998
|
+
i++;
|
|
22999
|
+
continue;
|
|
23000
|
+
}
|
|
23001
|
+
if (mn === "cmp") {
|
|
23002
|
+
const cm = /^([wx][0-9]+)\s*,\s*#(-?\d+)$/.exec(ops);
|
|
23003
|
+
if (cm && defs.has(cm[1])) {
|
|
23004
|
+
const a = defs.get(cm[1]);
|
|
23005
|
+
const b = parseInt(cm[2], 10);
|
|
23006
|
+
const run = [];
|
|
23007
|
+
let j = i + 1;
|
|
23008
|
+
while (j < lines.length) {
|
|
23009
|
+
const t = split_comment(lines[j]).trim();
|
|
23010
|
+
if (!t) {
|
|
23011
|
+
j++;
|
|
23012
|
+
continue;
|
|
23013
|
+
}
|
|
23014
|
+
const bm = /^b\.(\w+)\s+(\S+)$/.exec(t);
|
|
23015
|
+
if (bm && COND_OPS.has(bm[1])) {
|
|
23016
|
+
run.push({
|
|
23017
|
+
cond: bm[1],
|
|
23018
|
+
label: bm[2],
|
|
23019
|
+
line_idx: j
|
|
23020
|
+
});
|
|
23021
|
+
j++;
|
|
23022
|
+
continue;
|
|
23023
|
+
}
|
|
23024
|
+
break;
|
|
23025
|
+
}
|
|
23026
|
+
if (run.length > 0) {
|
|
23027
|
+
let follower = "";
|
|
23028
|
+
for (let k = run[run.length - 1].line_idx + 1; k < lines.length; k++) {
|
|
23029
|
+
const t = split_comment(lines[k]).trim();
|
|
23030
|
+
if (!t) continue;
|
|
23031
|
+
follower = t;
|
|
23032
|
+
break;
|
|
23033
|
+
}
|
|
23034
|
+
const follower_mn = /^(\S+)/.exec(follower)?.[1] ?? "";
|
|
23035
|
+
if (follower === "" || follower.startsWith(".") || [
|
|
23036
|
+
"cmp",
|
|
23037
|
+
"tst",
|
|
23038
|
+
"cmn",
|
|
23039
|
+
"b",
|
|
23040
|
+
"bl",
|
|
23041
|
+
"ret",
|
|
23042
|
+
"cbz",
|
|
23043
|
+
"cbnz"
|
|
23044
|
+
].includes(follower_mn)) {
|
|
23045
|
+
for (const r of run) if (cond_holds(r.cond, a, b)) {
|
|
23046
|
+
out.push(`b ${r.label}`);
|
|
23047
|
+
break;
|
|
23048
|
+
}
|
|
23049
|
+
i = run[run.length - 1].line_idx + 1;
|
|
23050
|
+
continue;
|
|
23051
|
+
}
|
|
23052
|
+
}
|
|
23053
|
+
}
|
|
23054
|
+
out.push(raw_line);
|
|
23055
|
+
i++;
|
|
23056
|
+
continue;
|
|
23057
|
+
}
|
|
23058
|
+
if (mn === "madd" || mn === "mul") {
|
|
23059
|
+
const parts = ops.split(",").map((p) => p.trim());
|
|
23060
|
+
if (parts.length === (mn === "madd" ? 4 : 3)) {
|
|
23061
|
+
const dest = parts[0];
|
|
23062
|
+
const srcs = [parts[1], parts[2]];
|
|
23063
|
+
const addend = mn === "madd" ? parts[3] : void 0;
|
|
23064
|
+
const addend_zero = mn === "madd" ? /^(x|w)zr$/.test(addend ?? "") : true;
|
|
23065
|
+
let folded = false;
|
|
23066
|
+
if (addend_zero && REG.test(dest)) for (const [n_reg, m_reg] of [[srcs[0], srcs[1]], [srcs[1], srcs[0]]]) {
|
|
23067
|
+
const c = m_reg !== void 0 && REG.test(m_reg) ? defs.get(m_reg) : void 0;
|
|
23068
|
+
const shift = c !== void 0 ? is_pow2(c) : void 0;
|
|
23069
|
+
if (shift !== void 0 && REG.test(n_reg) && !defs.has(n_reg)) {
|
|
23070
|
+
out.push(`lsl ${dest}, ${n_reg}, #${shift}`);
|
|
23071
|
+
defs.delete(dest);
|
|
23072
|
+
i++;
|
|
23073
|
+
folded = true;
|
|
23074
|
+
break;
|
|
23075
|
+
}
|
|
23076
|
+
}
|
|
23077
|
+
if (folded) continue;
|
|
23078
|
+
}
|
|
23079
|
+
invalidate_dest(ops);
|
|
23080
|
+
out.push(raw_line);
|
|
23081
|
+
i++;
|
|
23082
|
+
continue;
|
|
23083
|
+
}
|
|
23084
|
+
if (![
|
|
23085
|
+
"str",
|
|
23086
|
+
"stp",
|
|
23087
|
+
"cmp",
|
|
23088
|
+
"tst",
|
|
23089
|
+
"cbz",
|
|
23090
|
+
"cbnz",
|
|
23091
|
+
"tbz",
|
|
23092
|
+
"tbnz"
|
|
23093
|
+
].includes(mn)) invalidate_dest(ops);
|
|
23094
|
+
out.push(raw_line);
|
|
23095
|
+
i++;
|
|
23096
|
+
}
|
|
23097
|
+
return out.join("\n");
|
|
23098
|
+
}
|
|
23099
|
+
//#endregion
|
|
22695
23100
|
//#region ../src/nodes/ExtendNode.ts
|
|
22696
23101
|
/**
|
|
22697
23102
|
* An `extend struct Name { ... }` / `extend class Name { ... }` declaration.
|
|
@@ -23089,6 +23494,7 @@ function check_declaration_node(decl, status) {
|
|
|
23089
23494
|
status.pending_return_bounds.delete(decl.name);
|
|
23090
23495
|
}
|
|
23091
23496
|
}
|
|
23497
|
+
if (decl.value) apply_return_bounds_to_var(decl.name, call_return_bounds(decl.value), status);
|
|
23092
23498
|
}
|
|
23093
23499
|
}
|
|
23094
23500
|
/**
|
|
@@ -23822,6 +24228,11 @@ function check_function_parameter_node(param, status) {
|
|
|
23822
24228
|
is_null: param.type.is_nullable ? true : void 0
|
|
23823
24229
|
});
|
|
23824
24230
|
if (param.constraint) {
|
|
24231
|
+
const stripped = strip_length_equalities(param.constraint, param.name, status);
|
|
24232
|
+
if (stripped !== param.constraint) {
|
|
24233
|
+
param.constraint = stripped;
|
|
24234
|
+
if (!param.constraint) return;
|
|
24235
|
+
}
|
|
23825
24236
|
check_node(param.constraint, status);
|
|
23826
24237
|
const constraint_type = type_from_value_node(param.constraint, status);
|
|
23827
24238
|
if (constraint_type.name && constraint_type.name !== "bool") add_error(status, `Constraint must be a boolean expression, got ${constraint_type.name}`, param.constraint.start);
|
|
@@ -23844,7 +24255,15 @@ function clone_status(status) {
|
|
|
23844
24255
|
upper_bound_exprs: v.upper_bound_exprs?.slice(),
|
|
23845
24256
|
lower_bound_exprs: v.lower_bound_exprs?.slice(),
|
|
23846
24257
|
upper_bound_inclusive_exprs: v.upper_bound_inclusive_exprs?.slice(),
|
|
23847
|
-
lower_bound_inclusive_exprs: v.lower_bound_inclusive_exprs?.slice()
|
|
24258
|
+
lower_bound_inclusive_exprs: v.lower_bound_inclusive_exprs?.slice(),
|
|
24259
|
+
path_bounds: v.path_bounds ? new Map([...v.path_bounds].map(([k, b]) => [k, {
|
|
24260
|
+
upper: b.upper?.slice(),
|
|
24261
|
+
lower: b.lower?.slice(),
|
|
24262
|
+
upper_inclusive: b.upper_inclusive?.slice(),
|
|
24263
|
+
lower_inclusive: b.lower_inclusive?.slice(),
|
|
24264
|
+
range_lower: b.range_lower,
|
|
24265
|
+
range_upper: b.range_upper
|
|
24266
|
+
}])) : void 0
|
|
23848
24267
|
})),
|
|
23849
24268
|
function_value_base: status.function_value_base,
|
|
23850
24269
|
structs: status.structs.slice(),
|
|
@@ -23857,6 +24276,7 @@ function clone_status(status) {
|
|
|
23857
24276
|
type_params: status.type_params,
|
|
23858
24277
|
errors: status.errors,
|
|
23859
24278
|
buffer_caps: status.buffer_caps,
|
|
24279
|
+
equal_lengths: status.equal_lengths?.slice(),
|
|
23860
24280
|
mutated_local_names: status.mutated_local_names
|
|
23861
24281
|
};
|
|
23862
24282
|
}
|
|
@@ -23875,6 +24295,7 @@ function check_function_node(func, status) {
|
|
|
23875
24295
|
}
|
|
23876
24296
|
let function_status = clone_status(status);
|
|
23877
24297
|
function_status.function_value_base = function_status.values.length;
|
|
24298
|
+
function_status.equal_lengths = [];
|
|
23878
24299
|
const structs_before = function_status.structs.length;
|
|
23879
24300
|
const enums_before = function_status.enums.length;
|
|
23880
24301
|
const types_before = function_status.types.length;
|
|
@@ -24812,6 +25233,7 @@ function substitute_raw_in_node(node, substitution, structs, deref_params = /* @
|
|
|
24812
25233
|
value = value.replace(new RegExp(`\\b${param}_NEEDS_STRDUP\\b`, "g"), type === "string" ? "1" : "0");
|
|
24813
25234
|
}
|
|
24814
25235
|
for (const pname of deref_params) value = value.replace(new RegExp(`(?<![&*.>\\w])\\b${pname}\\b(?![\\w])`, "g"), `(*${pname})`);
|
|
25236
|
+
if (raw_block_is_pure_asm(value)) value = fold_asm_constants(value);
|
|
24815
25237
|
raw.value = value;
|
|
24816
25238
|
return;
|
|
24817
25239
|
}
|
|
@@ -26037,13 +26459,17 @@ function check_assignment_node(assign, status) {
|
|
|
26037
26459
|
left_value.lower_bound_exprs = void 0;
|
|
26038
26460
|
left_value.upper_bound_inclusive_exprs = void 0;
|
|
26039
26461
|
left_value.lower_bound_inclusive_exprs = void 0;
|
|
26462
|
+
left_value.path_bounds = void 0;
|
|
26040
26463
|
left_value.alias_of = void 0;
|
|
26041
26464
|
left_value.class_alias_of = void 0;
|
|
26042
26465
|
left_value.type.length = void 0;
|
|
26043
26466
|
if (assign.left_value.node_type === "value") invalidate_view_borrows_of(status, left_value.name);
|
|
26044
26467
|
let synthetic_rhs;
|
|
26045
26468
|
if (is_trackable_compound) synthetic_rhs = new OperationNode(assign.right_value.start, compound_op, assign.left_value, assign.right_value, left_value.type);
|
|
26046
|
-
if ((!is_compound || synthetic_rhs) && left_value_name === left_value.name)
|
|
26469
|
+
if ((!is_compound || synthetic_rhs) && left_value_name === left_value.name) {
|
|
26470
|
+
track_assignment_bounds(left_value.name, synthetic_rhs ?? assign.right_value, status, self_snapshot);
|
|
26471
|
+
if (!is_compound) apply_return_bounds_to_var(left_value.name, call_return_bounds(assign.right_value), status);
|
|
26472
|
+
}
|
|
26047
26473
|
if (!is_compound && left_value.type.name === "string" && assign.right_value.node_type === "value" && assign.right_value.value.startsWith("\"") && assign.right_value.value.endsWith("\"")) {
|
|
26048
26474
|
const len = assign.right_value.value.length - 2;
|
|
26049
26475
|
left_value.type.length = new ValueNode(assign.right_value.start, len.toString(), new Type("int"));
|
|
@@ -26701,7 +27127,11 @@ function check_operation_node(op, status) {
|
|
|
26701
27127
|
var_obj.is_null = false;
|
|
26702
27128
|
}
|
|
26703
27129
|
}
|
|
26704
|
-
|
|
27130
|
+
const saved_bounds = snapshot_all_bounds(status);
|
|
27131
|
+
apply_bounds(op.left_value, status);
|
|
27132
|
+
const right_ok = check_node(op.right_value, status);
|
|
27133
|
+
restore_all_bounds(status, saved_bounds);
|
|
27134
|
+
if (!right_ok) return false;
|
|
26705
27135
|
if (saved_null !== void 0 && left_check) {
|
|
26706
27136
|
const var_obj = status.values.findLast((v) => v.name === left_check.name);
|
|
26707
27137
|
if (var_obj) var_obj.is_null = saved_null;
|
|
@@ -26709,6 +27139,16 @@ function check_operation_node(op, status) {
|
|
|
26709
27139
|
op.type = new Type("bool");
|
|
26710
27140
|
return true;
|
|
26711
27141
|
}
|
|
27142
|
+
if (op.op === "||") {
|
|
27143
|
+
if (!check_node(op.left_value, status)) return false;
|
|
27144
|
+
const saved_bounds = snapshot_all_bounds(status);
|
|
27145
|
+
apply_negated_bounds(op.left_value, status);
|
|
27146
|
+
const right_ok = check_node(op.right_value, status);
|
|
27147
|
+
restore_all_bounds(status, saved_bounds);
|
|
27148
|
+
if (!right_ok) return false;
|
|
27149
|
+
op.type = new Type("bool");
|
|
27150
|
+
return true;
|
|
27151
|
+
}
|
|
26712
27152
|
const is_equality = op.op === "==" || op.op === "!=";
|
|
26713
27153
|
const is_null_coalesce = op.op === "??";
|
|
26714
27154
|
const old_allow_null = status.allow_null_value;
|
|
@@ -26786,7 +27226,6 @@ function check_operation_node(op, status) {
|
|
|
26786
27226
|
case ">=":
|
|
26787
27227
|
case "<":
|
|
26788
27228
|
case "<=":
|
|
26789
|
-
case "||":
|
|
26790
27229
|
op.type = new Type("bool");
|
|
26791
27230
|
break;
|
|
26792
27231
|
case "??": {
|
|
@@ -29168,7 +29607,8 @@ function parse_visibility(visibility, status) {
|
|
|
29168
29607
|
break;
|
|
29169
29608
|
case "inline":
|
|
29170
29609
|
consume(status);
|
|
29171
|
-
if (
|
|
29610
|
+
if (peek_current(status) === "inline") consume(status);
|
|
29611
|
+
if (peek_current(status) === "func") parse_function(visibility, status, void 0, true);
|
|
29172
29612
|
else add_error(status, "Expected func after inline", get_index(status));
|
|
29173
29613
|
break;
|
|
29174
29614
|
case "#": {
|