jz 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +288 -314
- package/bench/README.md +319 -0
- package/bench/bench.svg +112 -0
- package/cli.js +32 -24
- package/index.js +177 -55
- package/interop.js +88 -159
- package/jz.svg +5 -0
- package/jzify/arguments.js +97 -0
- package/jzify/bundler.js +382 -0
- package/jzify/classes.js +328 -0
- package/jzify/hoist-vars.js +177 -0
- package/jzify/index.js +51 -0
- package/jzify/names.js +37 -0
- package/jzify/switch.js +106 -0
- package/jzify/transform.js +349 -0
- package/layout.js +179 -0
- package/module/array.js +322 -153
- package/module/collection.js +603 -145
- package/module/console.js +55 -43
- package/module/core.js +266 -153
- package/module/date.js +15 -3
- package/module/function.js +73 -6
- package/module/index.js +2 -1
- package/module/json.js +226 -61
- package/module/math.js +414 -186
- package/module/number.js +306 -60
- package/module/object.js +448 -184
- package/module/regex.js +255 -25
- package/module/schema.js +24 -6
- package/module/simd.js +85 -0
- package/module/string.js +586 -220
- package/module/symbol.js +1 -1
- package/module/timer.js +9 -14
- package/module/typedarray.js +45 -48
- package/package.json +41 -12
- package/src/abi/index.js +39 -23
- package/src/abi/string.js +38 -41
- package/src/ast.js +460 -0
- package/src/autoload.js +26 -24
- package/src/bridge.js +111 -0
- package/src/compile/analyze-scans.js +661 -0
- package/src/compile/analyze.js +1565 -0
- package/src/compile/emit-assign.js +408 -0
- package/src/compile/emit.js +3201 -0
- package/src/compile/flow-types.js +103 -0
- package/src/{compile.js → compile/index.js} +497 -125
- package/src/{infer.js → compile/infer.js} +27 -98
- package/src/{narrow.js → compile/narrow.js} +302 -96
- package/src/compile/plan/advise.js +316 -0
- package/src/compile/plan/common.js +150 -0
- package/src/compile/plan/index.js +118 -0
- package/src/compile/plan/inline.js +679 -0
- package/src/compile/plan/literals.js +984 -0
- package/src/compile/plan/loops.js +472 -0
- package/src/compile/plan/scope.js +573 -0
- package/src/compile/program-facts.js +404 -0
- package/src/ctx.js +176 -58
- package/src/ir.js +540 -171
- package/src/kind-traits.js +105 -0
- package/src/kind.js +462 -0
- package/src/op-policy.js +57 -0
- package/src/{optimize.js → optimize/index.js} +1106 -446
- package/src/optimize/vectorize.js +1874 -0
- package/src/param-reps.js +65 -0
- package/src/parse.js +44 -0
- package/src/{prepare.js → prepare/index.js} +589 -202
- package/src/reps.js +115 -0
- package/src/resolve.js +12 -3
- package/src/static.js +199 -0
- package/src/type.js +647 -0
- package/src/{assemble.js → wat/assemble.js} +86 -48
- package/src/wat/optimize.js +3760 -0
- package/transform.js +21 -0
- package/wasi.js +47 -5
- package/src/analyze.js +0 -3818
- package/src/emit.js +0 -3040
- package/src/jzify.js +0 -1580
- package/src/plan.js +0 -2132
- package/src/vectorize.js +0 -1088
- /package/src/{codegen.js → wat/codegen.js} +0 -0
package/module/regex.js
CHANGED
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
* @module regex
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { typed, asF64, asI64, UNDEF_NAN, mkPtrIR, temp, tempI32 } from '../src/ir.js'
|
|
11
|
-
import { emit } from '../src/
|
|
12
|
-
import { err, inc, PTR, LAYOUT } from '../src/ctx.js'
|
|
13
|
-
import {
|
|
10
|
+
import { typed, asF64, asI64, UNDEF_NAN, NULL_NAN, mkPtrIR, temp, tempI32 } from '../src/ir.js'
|
|
11
|
+
import { emit, deps } from '../src/bridge.js'
|
|
12
|
+
import { ctx, err, inc, PTR, LAYOUT, getter, declGlobal } from '../src/ctx.js'
|
|
13
|
+
import { valTypeOf } from '../src/kind.js'
|
|
14
|
+
import { VAL } from '../src/reps.js'
|
|
14
15
|
|
|
15
16
|
// Build IR that constructs a match array: [full, cap1, cap2, ...]
|
|
16
17
|
// strLocal, msLocal, meLocal are local names (i32 for ms/me, f64 for str).
|
|
@@ -27,7 +28,7 @@ const buildMatchArr = (strLocal, msLocal, meLocal, nGroups, groupNames = []) =>
|
|
|
27
28
|
if (groupNames[i]) named.push([i, groupNames[i]])
|
|
28
29
|
}
|
|
29
30
|
if (named.length) {
|
|
30
|
-
|
|
31
|
+
ctx.module.include('collection')
|
|
31
32
|
inc('__hash_new_small', '__hash_set', '__dyn_set')
|
|
32
33
|
}
|
|
33
34
|
const captureValue = i => ['if', ['result', 'f64'],
|
|
@@ -223,7 +224,22 @@ const parseGroup = () => {
|
|
|
223
224
|
const inner = parseAlt()
|
|
224
225
|
cur() === RPAREN || perr('Unclosed ('); skip()
|
|
225
226
|
if (groupName) groupNames[groupId] = groupName
|
|
226
|
-
|
|
227
|
+
// Carry the capture name IN the group node (4th element) as well as the module-level
|
|
228
|
+
// groupNames array. The AST structure survives the parse→compile handoff intact (it
|
|
229
|
+
// drives codegen), whereas the self-host kernel drops mutations to the module-level
|
|
230
|
+
// groupNames array — so `.groups` was never built. compileRegexToStdlib reads names
|
|
231
|
+
// back via collectGroupNames(ast), which works in both legs.
|
|
232
|
+
return groupId ? (groupName ? [type, inner, groupId, groupName] : [type, inner, groupId]) : [type, inner]
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Walk a parsed regex AST and return a `groupId → name` array for named captures,
|
|
236
|
+
// sourced from the group nodes' 4th element (set by parseGroup). Kernel-safe: relies
|
|
237
|
+
// only on the surviving AST structure, not module-level parse state.
|
|
238
|
+
const collectGroupNames = (node, out = []) => {
|
|
239
|
+
if (!Array.isArray(node)) return out
|
|
240
|
+
if (node[0] === '()' && typeof node[2] === 'number' && typeof node[3] === 'string') out[node[2]] = node[3]
|
|
241
|
+
for (let i = 1; i < node.length; i++) collectGroupNames(node[i], out)
|
|
242
|
+
return out
|
|
227
243
|
}
|
|
228
244
|
|
|
229
245
|
const isGroupNameStart = c =>
|
|
@@ -251,7 +267,8 @@ const parseGroupName = () => {
|
|
|
251
267
|
const CHAR_CLASS_WAT = {
|
|
252
268
|
d: '(i32.and (i32.ge_u (local.get $char) (i32.const 48)) (i32.le_u (local.get $char) (i32.const 57)))',
|
|
253
269
|
w: '(i32.or (i32.or (i32.and (i32.ge_u (local.get $char) (i32.const 97)) (i32.le_u (local.get $char) (i32.const 122))) (i32.and (i32.ge_u (local.get $char) (i32.const 65)) (i32.le_u (local.get $char) (i32.const 90)))) (i32.or (i32.and (i32.ge_u (local.get $char) (i32.const 48)) (i32.le_u (local.get $char) (i32.const 57))) (i32.eq (local.get $char) (i32.const 95))))',
|
|
254
|
-
|
|
270
|
+
// SP(32) TAB(9) LF(10) CR(13) VT(11) FF(12); NBSP/Unicode-Zs are multibyte under UTF-8 — out of scope
|
|
271
|
+
s: '(i32.or (i32.or (i32.or (i32.eq (local.get $char) (i32.const 32)) (i32.eq (local.get $char) (i32.const 9))) (i32.or (i32.eq (local.get $char) (i32.const 10)) (i32.eq (local.get $char) (i32.const 13)))) (i32.or (i32.eq (local.get $char) (i32.const 11)) (i32.eq (local.get $char) (i32.const 12))))'
|
|
255
272
|
}
|
|
256
273
|
|
|
257
274
|
// 8-bit char load at $str + $pos
|
|
@@ -714,7 +731,7 @@ const patternMinLen = node => {
|
|
|
714
731
|
// === Module init ===
|
|
715
732
|
|
|
716
733
|
export default (ctx) => {
|
|
717
|
-
|
|
734
|
+
deps({
|
|
718
735
|
__str_to_buf: ['__str_byteLen', '__char_at'],
|
|
719
736
|
})
|
|
720
737
|
|
|
@@ -750,12 +767,12 @@ export default (ctx) => {
|
|
|
750
767
|
// Reserve mutable globals for capture group start/end (shared across regexes by index)
|
|
751
768
|
for (let i = 1; i <= (ast.groups || 0); i++) {
|
|
752
769
|
if (!ctx.scope.globals.has(`__re_g${i}_start`)) {
|
|
753
|
-
|
|
754
|
-
|
|
770
|
+
declGlobal(`__re_g${i}_start`, 'i32', -1)
|
|
771
|
+
declGlobal(`__re_g${i}_end`, 'i32', -1)
|
|
755
772
|
}
|
|
756
773
|
}
|
|
757
774
|
ctx.runtime.regex.groups.set(id, ast.groups || 0)
|
|
758
|
-
ctx.runtime.regex.groupNames.set(id, ast
|
|
775
|
+
ctx.runtime.regex.groupNames.set(id, collectGroupNames(ast))
|
|
759
776
|
ctx.core.stdlib[funcName] = compileRegex(ast, funcName)
|
|
760
777
|
|
|
761
778
|
// Search wrapper: tries match at each position, returns (match_start, match_end) via locals
|
|
@@ -774,7 +791,30 @@ export default (ctx) => {
|
|
|
774
791
|
(br $next)))
|
|
775
792
|
(i32.const -1) (i32.const -1))`
|
|
776
793
|
|
|
777
|
-
|
|
794
|
+
// search_from: like search but starts scanning at $fromPos (used by global exec)
|
|
795
|
+
const searchFromName = `__regex_search_from_${id}`
|
|
796
|
+
ctx.core.stdlib[searchFromName] = `(func $${searchFromName} (param $str i64) (param $fromPos i32) (result i32 i32)
|
|
797
|
+
(local $off i32) (local $len i32) (local $pos i32) (local $result i32)
|
|
798
|
+
(local.set $off (call $__str_to_buf (local.get $str)))
|
|
799
|
+
(local.set $len (call $__str_byteLen (local.get $str)))
|
|
800
|
+
(local.set $pos (local.get $fromPos))
|
|
801
|
+
(block $done (loop $next
|
|
802
|
+
(br_if $done (i32.gt_s (local.get $pos) (local.get $len)))
|
|
803
|
+
(local.set $result (call $${funcName} (local.get $off) (local.get $len) (local.get $pos)))
|
|
804
|
+
(if (i32.ge_s (local.get $result) (i32.const 0))
|
|
805
|
+
(then (return (local.get $pos) (local.get $result))))
|
|
806
|
+
(local.set $pos (i32.add (local.get $pos) (i32.const 1)))
|
|
807
|
+
(br $next)))
|
|
808
|
+
(i32.const -1) (i32.const -1))`
|
|
809
|
+
|
|
810
|
+
// lastIndex mutable global for /g or /y regexes — tracks position across exec() calls
|
|
811
|
+
if ((flags || '').includes('g') || (flags || '').includes('y')) {
|
|
812
|
+
const liGlobal = `__re_lastIndex_${id}`
|
|
813
|
+
if (!ctx.scope.globals.has(liGlobal))
|
|
814
|
+
declGlobal(liGlobal, 'i32')
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
inc(funcName, searchName, searchFromName, '__str_to_buf')
|
|
778
818
|
ctx.runtime.regex.compiled.set(key, id)
|
|
779
819
|
return id
|
|
780
820
|
}
|
|
@@ -811,19 +851,46 @@ export default (ctx) => {
|
|
|
811
851
|
['else', ['f64.const', 0]]]], 'f64')
|
|
812
852
|
}
|
|
813
853
|
|
|
814
|
-
// regex.exec(str) → [match_text, cap1, ...] array or
|
|
854
|
+
// regex.exec(str) → [match_text, cap1, ...] array or null.
|
|
855
|
+
// Mirrors JS: returns null (NULL_NAN) on no-match so `!== null` and while-loop idioms work.
|
|
856
|
+
// For /g (and /y) regexes: stateful — reads lastIndex, advances on match, resets on miss.
|
|
815
857
|
ctx.core.emit['.regex:exec'] = (obj, str) => {
|
|
816
858
|
const id = resolveRegex(obj)
|
|
817
859
|
if (id == null) err('regex.exec requires a known regex')
|
|
818
860
|
const nGroups = ctx.runtime.regex.groups.get(id) || 0
|
|
819
861
|
const groupNames = ctx.runtime.regex.groupNames.get(id) || []
|
|
862
|
+
const flags = flagsOf(obj)
|
|
863
|
+
const isGlobal = flags.includes('g') || flags.includes('y')
|
|
820
864
|
const s = temp('re'), ms = tempI32('rems'), me = tempI32('reme')
|
|
865
|
+
const nullIR = ['f64.const', `nan:${NULL_NAN}`]
|
|
866
|
+
if (isGlobal) {
|
|
867
|
+
// Stateful path: read lastIndex, search from there, update/reset lastIndex.
|
|
868
|
+
const liGlobal = `$__re_lastIndex_${id}`
|
|
869
|
+
inc(`__regex_search_from_${id}`)
|
|
870
|
+
return typed(['block', ['result', 'f64'],
|
|
871
|
+
['local.set', `$${s}`, asF64(emit(str))],
|
|
872
|
+
['local.set', `$${ms}`, ['local.set', `$${me}`,
|
|
873
|
+
['call', `$__regex_search_from_${id}`,
|
|
874
|
+
['i64.reinterpret_f64', ['local.get', `$${s}`]],
|
|
875
|
+
['global.get', liGlobal]]]],
|
|
876
|
+
['if', ['result', 'f64'], ['i32.lt_s', ['local.get', `$${ms}`], ['i32.const', 0]],
|
|
877
|
+
// no match — reset lastIndex, return null
|
|
878
|
+
['then', ['global.set', liGlobal, ['i32.const', 0]], nullIR],
|
|
879
|
+
// match — advance lastIndex past match end (bump by 1 for zero-length)
|
|
880
|
+
['else',
|
|
881
|
+
['global.set', liGlobal,
|
|
882
|
+
['select',
|
|
883
|
+
['i32.add', ['local.get', `$${me}`], ['i32.const', 1]],
|
|
884
|
+
['local.get', `$${me}`],
|
|
885
|
+
['i32.eq', ['local.get', `$${ms}`], ['local.get', `$${me}`]]]],
|
|
886
|
+
buildMatchArr(s, ms, me, nGroups, groupNames)]]], 'f64')
|
|
887
|
+
}
|
|
821
888
|
return typed(['block', ['result', 'f64'],
|
|
822
889
|
['local.set', `$${s}`, asF64(emit(str))],
|
|
823
890
|
['local.set', `$${ms}`, ['local.set', `$${me}`,
|
|
824
891
|
['call', `$__regex_search_${id}`, ['i64.reinterpret_f64', ['local.get', `$${s}`]]]]],
|
|
825
892
|
['if', ['result', 'f64'], ['i32.lt_s', ['local.get', `$${ms}`], ['i32.const', 0]],
|
|
826
|
-
['then',
|
|
893
|
+
['then', nullIR],
|
|
827
894
|
['else', buildMatchArr(s, ms, me, nGroups, groupNames)]]], 'f64')
|
|
828
895
|
}
|
|
829
896
|
|
|
@@ -844,26 +911,34 @@ export default (ctx) => {
|
|
|
844
911
|
// RegExp.prototype.source — the pattern text. A literal stores it verbatim
|
|
845
912
|
// (already grammar-escaped), so `/A/.source` is the 6-char "A".
|
|
846
913
|
// An empty pattern serializes to "(?:)" so the result re-parses to a regex.
|
|
847
|
-
ctx.core.emit['.regex:source'] = (obj) => {
|
|
914
|
+
ctx.core.emit['.regex:source'] = getter((obj) => {
|
|
848
915
|
const a = regexAstOf(obj)
|
|
849
916
|
return emit(['str', (a && a[1]) || '(?:)'])
|
|
850
|
-
}
|
|
917
|
+
})
|
|
851
918
|
|
|
852
919
|
// RegExp.prototype.flags — flag characters in canonical order (sec-get-regexp.prototype.flags).
|
|
853
920
|
const FLAG_ORDER = 'dgimsvy'
|
|
854
|
-
ctx.core.emit['.regex:flags'] = (obj) => {
|
|
921
|
+
ctx.core.emit['.regex:flags'] = getter((obj) => {
|
|
855
922
|
const f = flagsOf(obj)
|
|
856
923
|
return emit(['str', [...FLAG_ORDER].filter(c => f.includes(c)).join('')])
|
|
857
|
-
}
|
|
924
|
+
})
|
|
858
925
|
|
|
859
926
|
// Individual flag accessors → 1/0 (jz carries booleans as f64).
|
|
860
927
|
for (const [prop, ch] of [
|
|
861
928
|
['global', 'g'], ['ignoreCase', 'i'], ['multiline', 'm'], ['dotAll', 's'],
|
|
862
929
|
['unicode', 'u'], ['sticky', 'y'], ['hasIndices', 'd'], ['unicodeSets', 'v'],
|
|
863
|
-
]) ctx.core.emit[`.regex:${prop}`] = (obj) => typed(['f64.const', flagsOf(obj).includes(ch) ? 1 : 0], 'f64')
|
|
930
|
+
]) ctx.core.emit[`.regex:${prop}`] = getter((obj) => typed(['f64.const', flagsOf(obj).includes(ch) ? 1 : 0], 'f64'))
|
|
864
931
|
|
|
865
|
-
// lastIndex —
|
|
866
|
-
ctx.core.emit['.regex:lastIndex'] = () =>
|
|
932
|
+
// lastIndex — for /g and /y regexes, reads the mutable global; others always 0.
|
|
933
|
+
ctx.core.emit['.regex:lastIndex'] = getter((obj) => {
|
|
934
|
+
const id = resolveRegex(obj)
|
|
935
|
+
if (id != null) {
|
|
936
|
+
const flags = flagsOf(obj)
|
|
937
|
+
if (flags.includes('g') || flags.includes('y'))
|
|
938
|
+
return typed(['f64.convert_i32_u', ['global.get', `$__re_lastIndex_${id}`]], 'f64')
|
|
939
|
+
}
|
|
940
|
+
return typed(['f64.const', 0], 'f64')
|
|
941
|
+
})
|
|
867
942
|
|
|
868
943
|
// str.search(/re/) → first match position or -1
|
|
869
944
|
ctx.core.emit['.string:search'] = (str, search) => {
|
|
@@ -913,15 +988,116 @@ export default (ctx) => {
|
|
|
913
988
|
['else', buildMatchArr(s, ms, me, nGroups, groupNames)]]], 'f64')
|
|
914
989
|
}
|
|
915
990
|
|
|
916
|
-
// str.replace(/re/, repl) → replaced string
|
|
991
|
+
// str.replace(/re/, repl) → replaced string. With the `g` flag every match is
|
|
992
|
+
// replaced (a per-regex loop, mirroring split); otherwise only the first.
|
|
917
993
|
ctx.core.emit['.string:replace'] = (str, search, repl) => {
|
|
918
994
|
const id = resolveRegex(search)
|
|
995
|
+
const isFn = valTypeOf(repl) === VAL.CLOSURE && ctx.closure?.call
|
|
996
|
+
// ToString(fn(matchStr)) → i64 string. The closure value is hoisted into
|
|
997
|
+
// `fnL` once by the caller; each match passes its substring as the lone arg.
|
|
998
|
+
const callbackRepl = (fnL, matchStrIR) =>
|
|
999
|
+
['call', '$__to_str', asI64(ctx.closure.call(typed(['local.get', `$${fnL}`], 'f64'), [matchStrIR]))]
|
|
919
1000
|
if (id == null) {
|
|
1001
|
+
if (isFn) {
|
|
1002
|
+
// String search + callback: replace the FIRST occurrence (spec: a string
|
|
1003
|
+
// search matches once). Mirror string.js `.replace`'s callback path.
|
|
1004
|
+
inc('__str_indexof', '__str_slice', '__str_concat', '__str_byteLen', '__to_str')
|
|
1005
|
+
const s = temp('rps'), q = temp('rpq'), fnL = temp('rpf'), idx = tempI32('rpi'), mlen = tempI32('rpm')
|
|
1006
|
+
const sI64 = () => ['i64.reinterpret_f64', ['local.get', `$${s}`]]
|
|
1007
|
+
const match = typed(['call', '$__str_slice', sI64(), ['local.get', `$${idx}`],
|
|
1008
|
+
['i32.add', ['local.get', `$${idx}`], ['local.get', `$${mlen}`]]], 'f64')
|
|
1009
|
+
return typed(['block', ['result', 'f64'],
|
|
1010
|
+
['local.set', `$${s}`, asF64(emit(str))],
|
|
1011
|
+
['local.set', `$${q}`, asF64(emit(search))],
|
|
1012
|
+
['local.set', `$${fnL}`, asF64(emit(repl))],
|
|
1013
|
+
['local.set', `$${mlen}`, ['call', '$__str_byteLen', ['i64.reinterpret_f64', ['local.get', `$${q}`]]]],
|
|
1014
|
+
['local.set', `$${idx}`, ['call', '$__str_indexof', sI64(), ['i64.reinterpret_f64', ['local.get', `$${q}`]], ['i32.const', 0]]],
|
|
1015
|
+
['if', ['result', 'f64'], ['i32.lt_s', ['local.get', `$${idx}`], ['i32.const', 0]],
|
|
1016
|
+
['then', ['local.get', `$${s}`]],
|
|
1017
|
+
['else', typed(['call', '$__str_concat',
|
|
1018
|
+
asI64(typed(['call', '$__str_concat',
|
|
1019
|
+
asI64(typed(['call', '$__str_slice', sI64(), ['i32.const', 0], ['local.get', `$${idx}`]], 'f64')),
|
|
1020
|
+
callbackRepl(fnL, match)], 'f64')),
|
|
1021
|
+
asI64(typed(['call', '$__str_slice', sI64(),
|
|
1022
|
+
['i32.add', ['local.get', `$${idx}`], ['local.get', `$${mlen}`]],
|
|
1023
|
+
['call', '$__str_byteLen', sI64()]], 'f64'))], 'f64')]]], 'f64')
|
|
1024
|
+
}
|
|
920
1025
|
// Fall back to string replace
|
|
921
1026
|
inc('__str_replace')
|
|
922
1027
|
return typed(['call', '$__str_replace', asI64(emit(str)), asI64(emit(search)), asI64(emit(repl))], 'f64')
|
|
923
1028
|
}
|
|
924
1029
|
inc('__str_slice', '__str_concat', '__str_byteLen')
|
|
1030
|
+
// Regex + callback: walk matches in IR (a WAT helper can't call a closure).
|
|
1031
|
+
// One unified loop covers /g (all matches) and non-/g (break after the first).
|
|
1032
|
+
if (isFn) {
|
|
1033
|
+
const global = flagsOf(search).includes('g')
|
|
1034
|
+
inc('__str_to_buf', '__to_str', `__regex_${id}`)
|
|
1035
|
+
const s = temp('rcs'), fnL = temp('rcf'), acc = temp('rca')
|
|
1036
|
+
const off = tempI32('rco'), len = tempI32('rcl'), pos = tempI32('rcp')
|
|
1037
|
+
const res = tempI32('rcr'), ms = tempI32('rcms'), me = tempI32('rcme'), pe = tempI32('rcpe')
|
|
1038
|
+
const sI64 = () => ['i64.reinterpret_f64', ['local.get', `$${s}`]]
|
|
1039
|
+
const accI64 = () => ['i64.reinterpret_f64', ['local.get', `$${acc}`]]
|
|
1040
|
+
const slice = (a, b) => typed(['call', '$__str_slice', sI64(), a, b], 'f64')
|
|
1041
|
+
const matchStr = slice(['local.get', `$${ms}`], ['local.get', `$${me}`])
|
|
1042
|
+
const step = [
|
|
1043
|
+
['local.set', `$${res}`, ['call', `$__regex_${id}`, ['local.get', `$${off}`], ['local.get', `$${len}`], ['local.get', `$${pos}`]]],
|
|
1044
|
+
['if', ['i32.lt_s', ['local.get', `$${res}`], ['i32.const', 0]],
|
|
1045
|
+
['then', ['local.set', `$${pos}`, ['i32.add', ['local.get', `$${pos}`], ['i32.const', 1]]], ['br', '$next']]],
|
|
1046
|
+
['local.set', `$${ms}`, ['local.get', `$${pos}`]],
|
|
1047
|
+
['local.set', `$${me}`, ['local.get', `$${res}`]],
|
|
1048
|
+
['local.set', `$${acc}`, ['call', '$__str_concat', accI64(), asI64(slice(['local.get', `$${pe}`], ['local.get', `$${ms}`]))]],
|
|
1049
|
+
['local.set', `$${acc}`, ['call', '$__str_concat', accI64(), callbackRepl(fnL, matchStr)]],
|
|
1050
|
+
['local.set', `$${pe}`, ['local.get', `$${me}`]],
|
|
1051
|
+
...(global ? [] : [['br', '$done']]),
|
|
1052
|
+
['local.set', `$${pos}`, ['select', ['i32.add', ['local.get', `$${me}`], ['i32.const', 1]], ['local.get', `$${me}`], ['i32.eq', ['local.get', `$${ms}`], ['local.get', `$${me}`]]]],
|
|
1053
|
+
['br', '$next'],
|
|
1054
|
+
]
|
|
1055
|
+
return typed(['block', ['result', 'f64'],
|
|
1056
|
+
['local.set', `$${s}`, asF64(emit(str))],
|
|
1057
|
+
['local.set', `$${fnL}`, asF64(emit(repl))],
|
|
1058
|
+
['local.set', `$${off}`, ['call', '$__str_to_buf', sI64()]],
|
|
1059
|
+
['local.set', `$${len}`, ['call', '$__str_byteLen', sI64()]],
|
|
1060
|
+
['local.set', `$${pe}`, ['i32.const', 0]],
|
|
1061
|
+
['local.set', `$${pos}`, ['i32.const', 0]],
|
|
1062
|
+
['local.set', `$${acc}`, slice(['i32.const', 0], ['i32.const', 0])],
|
|
1063
|
+
['block', '$done', ['loop', '$next',
|
|
1064
|
+
['br_if', '$done', ['i32.gt_s', ['local.get', `$${pos}`], ['local.get', `$${len}`]]],
|
|
1065
|
+
...step]],
|
|
1066
|
+
['call', '$__str_concat', accI64(), asI64(slice(['local.get', `$${pe}`], ['local.get', `$${len}`]))]], 'f64')
|
|
1067
|
+
}
|
|
1068
|
+
// Global replace: walk every match, accumulating slice(prevEnd,matchStart)+repl.
|
|
1069
|
+
// Empty seed via slice(str,0,0); zero-length matches advance by 1 (per split).
|
|
1070
|
+
if (flagsOf(search).includes('g')) {
|
|
1071
|
+
const replName = `__regex_replace_${id}`
|
|
1072
|
+
if (!ctx.core.stdlib[replName]) {
|
|
1073
|
+
inc('__str_to_buf')
|
|
1074
|
+
ctx.core.stdlib[replName] = `(func $${replName} (param $str i64) (param $repl i64) (result f64)
|
|
1075
|
+
(local $off i32) (local $len i32) (local $pos i32) (local $result i32)
|
|
1076
|
+
(local $mstart i32) (local $mend i32) (local $prevEnd i32) (local $acc f64)
|
|
1077
|
+
(local.set $off (call $__str_to_buf (local.get $str)))
|
|
1078
|
+
(local.set $len (call $__str_byteLen (local.get $str)))
|
|
1079
|
+
(local.set $prevEnd (i32.const 0))
|
|
1080
|
+
(local.set $pos (i32.const 0))
|
|
1081
|
+
(local.set $acc (call $__str_slice (local.get $str) (i32.const 0) (i32.const 0)))
|
|
1082
|
+
(block $done (loop $next
|
|
1083
|
+
(br_if $done (i32.gt_s (local.get $pos) (local.get $len)))
|
|
1084
|
+
(local.set $result (call $__regex_${id} (local.get $off) (local.get $len) (local.get $pos)))
|
|
1085
|
+
(if (i32.lt_s (local.get $result) (i32.const 0))
|
|
1086
|
+
(then (local.set $pos (i32.add (local.get $pos) (i32.const 1))) (br $next)))
|
|
1087
|
+
(local.set $mstart (local.get $pos))
|
|
1088
|
+
(local.set $mend (local.get $result))
|
|
1089
|
+
(local.set $acc (call $__str_concat (i64.reinterpret_f64 (local.get $acc))
|
|
1090
|
+
(i64.reinterpret_f64 (call $__str_slice (local.get $str) (local.get $prevEnd) (local.get $mstart)))))
|
|
1091
|
+
(local.set $acc (call $__str_concat (i64.reinterpret_f64 (local.get $acc)) (local.get $repl)))
|
|
1092
|
+
(local.set $prevEnd (local.get $mend))
|
|
1093
|
+
(local.set $pos (select (i32.add (local.get $mend) (i32.const 1)) (local.get $mend) (i32.eq (local.get $mstart) (local.get $mend))))
|
|
1094
|
+
(br $next)))
|
|
1095
|
+
(call $__str_concat (i64.reinterpret_f64 (local.get $acc))
|
|
1096
|
+
(i64.reinterpret_f64 (call $__str_slice (local.get $str) (local.get $prevEnd) (local.get $len)))))`
|
|
1097
|
+
inc(replName)
|
|
1098
|
+
}
|
|
1099
|
+
return typed(['call', `$${replName}`, asI64(emit(str)), asI64(emit(repl))], 'f64')
|
|
1100
|
+
}
|
|
925
1101
|
const s = temp('sr'), r = temp('srr'), ms = tempI32('srms'), me = tempI32('srme')
|
|
926
1102
|
return typed(['block', ['result', 'f64'],
|
|
927
1103
|
['local.set', `$${s}`, asF64(emit(str))],
|
|
@@ -939,13 +1115,67 @@ export default (ctx) => {
|
|
|
939
1115
|
['call', '$__str_byteLen', ['i64.reinterpret_f64', ['local.get', `$${s}`]]]]]]]]], 'f64')
|
|
940
1116
|
}
|
|
941
1117
|
|
|
1118
|
+
// str.matchAll(/re/g) → array of match arrays (each like exec's result: full
|
|
1119
|
+
// match at [0], capture groups after, named groups under `.groups`). JS yields
|
|
1120
|
+
// a lazy iterator, but an array satisfies both `[...m]` and `for (const x of m)`.
|
|
1121
|
+
// Two-pass over the same anchored matcher: count, then fill a sized array.
|
|
1122
|
+
ctx.core.emit['.string:matchAll'] = (str, search) => {
|
|
1123
|
+
const id = resolveRegex(search)
|
|
1124
|
+
if (id == null) err('matchAll requires a regex argument')
|
|
1125
|
+
const nGroups = ctx.runtime.regex.groups.get(id) || 0
|
|
1126
|
+
const groupNames = ctx.runtime.regex.groupNames.get(id) || []
|
|
1127
|
+
inc('__str_to_buf', '__str_byteLen', '__alloc', '__mkptr', `__regex_${id}`)
|
|
1128
|
+
const s = temp('mas'), outArr = tempI32('mao')
|
|
1129
|
+
const off = tempI32('maof'), len = tempI32('maln'), pos = tempI32('maps')
|
|
1130
|
+
const res = tempI32('mars'), cnt = tempI32('macn'), wi = tempI32('mawi')
|
|
1131
|
+
const ms = tempI32('mams'), me = tempI32('mame')
|
|
1132
|
+
const sI64 = () => ['i64.reinterpret_f64', ['local.get', `$${s}`]]
|
|
1133
|
+
// Anchored-matcher scan: __regex_${id}(off,len,pos) returns match-end (>=0) or
|
|
1134
|
+
// -1; `body` runs per match with ms=pos, me=res; pos advances (+1 on a
|
|
1135
|
+
// zero-length match to make progress). `posInit` resets the cursor per pass.
|
|
1136
|
+
const scan = (body) => ['block', '$d', ['loop', '$n',
|
|
1137
|
+
['br_if', '$d', ['i32.gt_s', ['local.get', `$${pos}`], ['local.get', `$${len}`]]],
|
|
1138
|
+
['local.set', `$${res}`, ['call', `$__regex_${id}`, ['local.get', `$${off}`], ['local.get', `$${len}`], ['local.get', `$${pos}`]]],
|
|
1139
|
+
['if', ['i32.lt_s', ['local.get', `$${res}`], ['i32.const', 0]],
|
|
1140
|
+
['then', ['local.set', `$${pos}`, ['i32.add', ['local.get', `$${pos}`], ['i32.const', 1]]], ['br', '$n']]],
|
|
1141
|
+
['local.set', `$${ms}`, ['local.get', `$${pos}`]],
|
|
1142
|
+
['local.set', `$${me}`, ['local.get', `$${res}`]],
|
|
1143
|
+
...body,
|
|
1144
|
+
['local.set', `$${pos}`, ['select', ['i32.add', ['local.get', `$${me}`], ['i32.const', 1]], ['local.get', `$${me}`], ['i32.eq', ['local.get', `$${ms}`], ['local.get', `$${me}`]]]],
|
|
1145
|
+
['br', '$n']]]
|
|
1146
|
+
// Re-running the matcher in the fill pass repopulates the $__re_g* capture
|
|
1147
|
+
// globals just before buildMatchArr reads them — correct per-match captures.
|
|
1148
|
+
const matchArr = buildMatchArr(s, ms, me, nGroups, groupNames)
|
|
1149
|
+
return typed(['block', ['result', 'f64'],
|
|
1150
|
+
['local.set', `$${s}`, asF64(emit(str))],
|
|
1151
|
+
['local.set', `$${off}`, ['call', '$__str_to_buf', sI64()]],
|
|
1152
|
+
['local.set', `$${len}`, ['call', '$__str_byteLen', sI64()]],
|
|
1153
|
+
['local.set', `$${cnt}`, ['i32.const', 0]],
|
|
1154
|
+
['local.set', `$${pos}`, ['i32.const', 0]],
|
|
1155
|
+
scan([['local.set', `$${cnt}`, ['i32.add', ['local.get', `$${cnt}`], ['i32.const', 1]]]]),
|
|
1156
|
+
['local.set', `$${outArr}`, ['call', '$__alloc', ['i32.add', ['i32.const', 8], ['i32.shl', ['local.get', `$${cnt}`], ['i32.const', 3]]]]],
|
|
1157
|
+
['i32.store', ['local.get', `$${outArr}`], ['local.get', `$${cnt}`]],
|
|
1158
|
+
['i32.store', ['i32.add', ['local.get', `$${outArr}`], ['i32.const', 4]], ['local.get', `$${cnt}`]],
|
|
1159
|
+
['local.set', `$${outArr}`, ['i32.add', ['local.get', `$${outArr}`], ['i32.const', 8]]],
|
|
1160
|
+
['local.set', `$${wi}`, ['i32.const', 0]],
|
|
1161
|
+
['local.set', `$${pos}`, ['i32.const', 0]],
|
|
1162
|
+
scan([
|
|
1163
|
+
['f64.store', ['i32.add', ['local.get', `$${outArr}`], ['i32.shl', ['local.get', `$${wi}`], ['i32.const', 3]]], matchArr],
|
|
1164
|
+
['local.set', `$${wi}`, ['i32.add', ['local.get', `$${wi}`], ['i32.const', 1]]],
|
|
1165
|
+
]),
|
|
1166
|
+
mkPtrIR(PTR.ARRAY, 0, ['local.get', `$${outArr}`])], 'f64')
|
|
1167
|
+
}
|
|
1168
|
+
|
|
942
1169
|
// str.split(/re/) → array of substrings
|
|
943
|
-
ctx.core.emit['.string:split'] = (str, sep) => {
|
|
1170
|
+
ctx.core.emit['.string:split'] = (str, sep, limit) => {
|
|
944
1171
|
const id = resolveRegex(sep)
|
|
945
1172
|
if (id == null) {
|
|
946
|
-
// Fall back to string split
|
|
1173
|
+
// Fall back to string split, forwarding the optional limit (0x7fffffff = no limit).
|
|
1174
|
+
// __str_split is 3-param (str, sep, limit); a 2-arg call here trips a wasm arity
|
|
1175
|
+
// error in any program with a known-string `.split` (e.g. the watr self-host).
|
|
947
1176
|
inc('__str_split')
|
|
948
|
-
|
|
1177
|
+
const limitIR = limit == null ? ['i32.const', 0x7fffffff] : ['i32.trunc_sat_f64_u', asF64(emit(limit))]
|
|
1178
|
+
return typed(['call', '$__str_split', asI64(emit(str)), asI64(emit(sep)), limitIR], 'f64')
|
|
949
1179
|
}
|
|
950
1180
|
|
|
951
1181
|
// Generate a split-by-regex WAT function for this regex
|
package/module/schema.js
CHANGED
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { typed, asF64 } from '../src/ir.js'
|
|
11
|
-
import { emit } from '../src/
|
|
12
|
-
import {
|
|
11
|
+
import { emit } from '../src/bridge.js'
|
|
12
|
+
import { valTypeOf } from '../src/kind.js'
|
|
13
|
+
import { VAL, lookupValType, repOf } from '../src/reps.js'
|
|
13
14
|
import { err, inc } from '../src/ctx.js'
|
|
14
15
|
|
|
15
16
|
/** Initialize schema helpers on ctx. Called once per compilation from core module. */
|
|
@@ -40,8 +41,12 @@ export function initSchema(ctx) {
|
|
|
40
41
|
|
|
41
42
|
/** schemaId for a variable name: ValueRep first, then module-level ctx.schema.vars.
|
|
42
43
|
* Both paths exist because vars covers names without a per-function ValueRep
|
|
43
|
-
* (prepare-phase rest/destructure tracking, module-level autoboxes).
|
|
44
|
-
|
|
44
|
+
* (prepare-phase rest/destructure tracking, module-level autoboxes).
|
|
45
|
+
* Poisoned names (shape-disagreeing assignments, see prepare's
|
|
46
|
+
* bindAssignSchema) resolve to NO schema regardless of store: a fixed-slot
|
|
47
|
+
* read against one literal's layout would misread the other sources. */
|
|
48
|
+
ctx.schema.idOf = (name) => ctx.schema.poisoned?.has(name) ? undefined
|
|
49
|
+
: repOf(name)?.schemaId ?? ctx.schema.vars.get(name)
|
|
45
50
|
|
|
46
51
|
/** Resolve variable name to its schema props array, or null. */
|
|
47
52
|
ctx.schema.resolve = (varName) => {
|
|
@@ -67,8 +72,16 @@ export function initSchema(ctx) {
|
|
|
67
72
|
* 2. Receiver is a string variable, but its valType is unknown or not OBJECT
|
|
68
73
|
* 3. Receiver is not a string variable (varName == null) — no type evidence
|
|
69
74
|
* 4. Structural search finds the property at inconsistent offsets across schemas
|
|
70
|
-
* Case 4 is a real ambiguity — the caller must route to runtime dispatch.
|
|
71
|
-
|
|
75
|
+
* Case 4 is a real ambiguity — the caller must route to runtime dispatch.
|
|
76
|
+
*
|
|
77
|
+
* Named `slotOf`, not `find`: under self-host the compiler calls this as
|
|
78
|
+
* `ctx.schema.find(...)` on the statically-untyped `ctx.schema` receiver, and
|
|
79
|
+
* `find` collides with `Array.prototype.find` — the method-call dispatcher
|
|
80
|
+
* hijacks it into a bogus array `find` (predicate scan), returning null. Every
|
|
81
|
+
* schema-slot property read then mis-resolved through jz.wasm (e.g. a boxed
|
|
82
|
+
* `Object.assign(arr, {p}); arr.p`). A non-builtin name dispatches correctly.
|
|
83
|
+
* Mirrors the abi string `concat`→`cat` rename for the same root cause. */
|
|
84
|
+
ctx.schema.slotOf = (varName, prop) => {
|
|
72
85
|
// Precise: variable has known schema
|
|
73
86
|
const id = ctx.schema.idOf(varName)
|
|
74
87
|
if (id != null) return ctx.schema.list[id]?.indexOf(prop) ?? -1
|
|
@@ -77,6 +90,11 @@ export function initSchema(ctx) {
|
|
|
77
90
|
// can match HASH/ARRAY/etc. values as if they had OBJECT layout, producing
|
|
78
91
|
// slot reads on unrelated memory. Funnel those through dynamic dispatch.
|
|
79
92
|
if (typeof varName !== 'string') return -1
|
|
93
|
+
// Poisoned names hold objects of KNOWN-disagreeing shapes; the structural
|
|
94
|
+
// closed-world bet (receiver is one of the schemas containing the prop) is
|
|
95
|
+
// exactly wrong for them — its other shapes may lack the prop while another
|
|
96
|
+
// value occupies the would-be slot. Always dynamic.
|
|
97
|
+
if (ctx.schema.poisoned?.has(varName)) return -1
|
|
80
98
|
const vt = lookupValType(varName)
|
|
81
99
|
if (vt !== VAL.OBJECT) return -1
|
|
82
100
|
// Structural subtyping: walk only schemas that contain this prop.
|
package/module/simd.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SIMD module — source-level f32x4 / i32x4 intrinsics that lower 1:1 to wasm SIMD
|
|
3
|
+
* (v128). Lets a jz kernel process 4 lanes per instruction: the per-pixel-parallel
|
|
4
|
+
* kernels (mandelbrot, raymarcher) run 4 pixels/rays in masked lockstep and beat
|
|
5
|
+
* scalar V8 several-fold (validated: SIMD-4 mandelbrot ≈ 4.6× a warm-V8 scalar loop).
|
|
6
|
+
*
|
|
7
|
+
* A v128 value is just a local whose wasm type is `v128` (exprType returns 'v128'
|
|
8
|
+
* for these calls; emit passes it through without an f64/i32 coercion). Building
|
|
9
|
+
* blocks for the masked-lockstep idiom:
|
|
10
|
+
*
|
|
11
|
+
* f32x4.splat(x) broadcast a scalar to 4 f32 lanes
|
|
12
|
+
* f32x4.lanes(a,b,c,d) build a vector from 4 scalars (compile-time consts ok)
|
|
13
|
+
* f32x4.add/sub/mul/div(a,b)
|
|
14
|
+
* f32x4.sqrt/abs/neg/floor(a)
|
|
15
|
+
* f32x4.min/max(a,b)
|
|
16
|
+
* f32x4.le/lt/ge/gt/eq(a,b) → i32x4 lane mask (all-ones / all-zero per lane)
|
|
17
|
+
* f32x4.lane(v, k) extract lane k (0..3) as a number
|
|
18
|
+
* i32x4.splat(n) / add/sub(a,b) / lane(v,k)
|
|
19
|
+
* v128.and/or/xor(a,b) / not(a) / bitselect(t,f,mask)
|
|
20
|
+
* v128.anyTrue(v) / v128.allTrue(v) → i32 (loop-exit tests)
|
|
21
|
+
*
|
|
22
|
+
* @module simd
|
|
23
|
+
*/
|
|
24
|
+
import { typed, asF64, asI32 } from '../src/ir.js'
|
|
25
|
+
import { emit } from '../src/bridge.js'
|
|
26
|
+
import { err } from '../src/ctx.js'
|
|
27
|
+
|
|
28
|
+
export default (ctx) => {
|
|
29
|
+
const e = ctx.core.emit
|
|
30
|
+
const V = (node) => typed(node, 'v128') // a v128-typed (lane vector) result
|
|
31
|
+
const I = (node) => typed(node, 'i32') // an i32 result (lane extract / any_true)
|
|
32
|
+
const F = (node) => typed(node, 'f64') // an f64 result (f32 lane → number)
|
|
33
|
+
const op = (a) => emit(a) // operand IR (v128 local.get, or a nested v128 expr) — NOT coerced to f64
|
|
34
|
+
const f32 = (a) => ['f32.demote_f64', asF64(emit(a))] // scalar → f32 for splat/lanes
|
|
35
|
+
// lane index must be a 0..3 literal — wasm extract_lane takes an immediate, so a
|
|
36
|
+
// runtime index can't lower (and would silently read lane 0). Fail loudly instead.
|
|
37
|
+
const laneIdx = (k) => {
|
|
38
|
+
const v = typeof k === 'number' ? k : (Array.isArray(k) && k.length === 2 && k[0] == null && typeof k[1] === 'number') ? k[1] : null
|
|
39
|
+
if (v == null || (v | 0) !== v || v < 0 || v > 3)
|
|
40
|
+
err(`SIMD lane index must be a 0..3 literal (got ${JSON.stringify(k)}) — wasm extract_lane needs a constant lane.`)
|
|
41
|
+
return v
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ── build / broadcast ──────────────────────────────────────────────────────
|
|
45
|
+
e['f32x4.splat'] = (a) => V(['f32x4.splat', f32(a)])
|
|
46
|
+
e['f32x4.lanes'] = (a, b, c, d) => V(['f32x4.replace_lane', 3,
|
|
47
|
+
['f32x4.replace_lane', 2,
|
|
48
|
+
['f32x4.replace_lane', 1, ['f32x4.splat', f32(a)], f32(b)], f32(c)], f32(d)])
|
|
49
|
+
e['i32x4.splat'] = (a) => V(['i32x4.splat', asI32(emit(a))])
|
|
50
|
+
|
|
51
|
+
// ── f32x4 arithmetic ─────────────────────────────────────────────────────────
|
|
52
|
+
for (const o of ['add', 'sub', 'mul', 'div', 'min', 'max'])
|
|
53
|
+
e[`f32x4.${o}`] = (a, b) => V([`f32x4.${o}`, op(a), op(b)])
|
|
54
|
+
for (const o of ['sqrt', 'abs', 'neg', 'floor', 'ceil', 'trunc', 'nearest'])
|
|
55
|
+
e[`f32x4.${o}`] = (a) => V([`f32x4.${o}`, op(a)])
|
|
56
|
+
|
|
57
|
+
// ── f32x4 comparisons → i32x4 lane mask (v128 of all-ones/all-zero lanes) ─────
|
|
58
|
+
for (const o of ['eq', 'ne', 'lt', 'le', 'gt', 'ge'])
|
|
59
|
+
e[`f32x4.${o}`] = (a, b) => V([`f32x4.${o}`, op(a), op(b)])
|
|
60
|
+
|
|
61
|
+
// ── i32x4 arithmetic (iteration counters, masks as ±1) ───────────────────────
|
|
62
|
+
for (const o of ['add', 'sub', 'mul'])
|
|
63
|
+
e[`i32x4.${o}`] = (a, b) => V([`i32x4.${o}`, op(a), op(b)])
|
|
64
|
+
// i32x4 comparisons → lane mask (signed). eq/ne have no suffix; the rest are _s.
|
|
65
|
+
for (const o of ['eq', 'ne', 'lt', 'le', 'gt', 'ge'])
|
|
66
|
+
e[`i32x4.${o}`] = (a, b) => V([`i32x4.${o}${o === 'eq' || o === 'ne' ? '' : '_s'}`, op(a), op(b)])
|
|
67
|
+
// shifts by a scalar count (for float-bit twiddling: exponent extract, etc.)
|
|
68
|
+
e['i32x4.shl'] = (a, n) => V(['i32x4.shl', op(a), asI32(emit(n))])
|
|
69
|
+
e['i32x4.shr'] = (a, n) => V(['i32x4.shr_s', op(a), asI32(emit(n))])
|
|
70
|
+
e['i32x4.shrU'] = (a, n) => V(['i32x4.shr_u', op(a), asI32(emit(n))])
|
|
71
|
+
// lane-wise int → float conversion (i32x4.convert) — the value, not a bit reinterpret
|
|
72
|
+
e['f32x4.convertI32'] = (a) => V(['f32x4.convert_i32x4_s', op(a)])
|
|
73
|
+
|
|
74
|
+
// ── v128 bitwise + select + reductions ───────────────────────────────────────
|
|
75
|
+
for (const o of ['and', 'or', 'xor'])
|
|
76
|
+
e[`v128.${o}`] = (a, b) => V([`v128.${o}`, op(a), op(b)])
|
|
77
|
+
e['v128.not'] = (a) => V(['v128.not', op(a)])
|
|
78
|
+
e['v128.bitselect'] = (t, f, m) => V(['v128.bitselect', op(t), op(f), op(m)])
|
|
79
|
+
e['v128.anyTrue'] = (a) => I(['v128.any_true', op(a)])
|
|
80
|
+
e['v128.allTrue'] = (a) => I(['i32x4.all_true', op(a)])
|
|
81
|
+
|
|
82
|
+
// ── lane extract (read a single lane back to a scalar) ───────────────────────
|
|
83
|
+
e['f32x4.lane'] = (v, k) => F(['f64.promote_f32', ['f32x4.extract_lane', laneIdx(k), op(v)]])
|
|
84
|
+
e['i32x4.lane'] = (v, k) => I(['i32x4.extract_lane', laneIdx(k), op(v)])
|
|
85
|
+
}
|