@@ -37,6 +37,13 @@ struct Parser<'a> {
3737 funcs : Vec < crate :: Func > ,
3838 ownership : Vec < ( usize , Vec < crate :: Ownership > , crate :: Ownership ) > ,
3939 region_imports : Vec < typed_wasm_verify:: RegionImportEntry > ,
40+ /// Declared instance count per region (`region Name[N]`; 1 if bare).
41+ /// Parallel to `regions`. Bounds `region.scan` loops and sizes the
42+ /// synthesised memory.
43+ region_instances : Vec < u64 > ,
44+ /// True when the source declared `memory { … }` — a synthesised
45+ /// memory must never override it.
46+ memory_declared : bool ,
4047}
4148
4249impl < ' a > Parser < ' a > {
@@ -51,6 +58,8 @@ impl<'a> Parser<'a> {
5158 funcs : Vec :: new ( ) ,
5259 ownership : Vec :: new ( ) ,
5360 region_imports : Vec :: new ( ) ,
61+ region_instances : Vec :: new ( ) ,
62+ memory_declared : false ,
5463 }
5564 }
5665
@@ -177,11 +186,12 @@ impl<'a> Parser<'a> {
177186 let name = self . parse_ident ( ) ;
178187 self . skip_whitespace ( ) ;
179188
180- // Parse optional array specifier: region Name[N]. We don't yet track
181- // region cardinality, so the parsed size is discarded.
189+ // Parse optional array specifier: region Name[N] — the declared
190+ // instance count (bounds `region.scan`, sizes synthesised memory).
191+ let mut instances = 1u64 ;
182192 if self . peek_char ( '[' ) {
183193 self . expect ( "[" ) ?;
184- let _n : u64 = self . parse_number ( ) ?;
194+ instances = self . parse_number ( ) ?. max ( 1 ) ;
185195 self . expect ( "]" ) ?;
186196 }
187197
@@ -273,6 +283,7 @@ impl<'a> Parser<'a> {
273283 fields,
274284 byte_size,
275285 } ) ;
286+ self . region_instances . push ( instances) ;
276287
277288 self . skip_whitespace ( ) ;
278289 Ok ( ( ) )
@@ -510,6 +521,7 @@ impl<'a> Parser<'a> {
510521 }
511522
512523 fn parse_memory ( & mut self ) -> Result < ( ) , String > {
524+ self . memory_declared = true ;
513525 self . expect ( "memory" ) ?;
514526 let _name = self . parse_ident ( ) ;
515527 self . skip_whitespace ( ) ;
@@ -1022,12 +1034,21 @@ impl<'a> Parser<'a> {
10221034 /// cannot point past the declared memory and trap at runtime. Only fills in
10231035 /// a memory when the module declared none; a declared memory is the author's.
10241036 fn ensure_memory_for_region ( & mut self , region : usize ) {
1025- if self . memory . is_none ( ) {
1026- let bytes = self . regions . get ( region) . map_or ( 0 , |r| r. byte_size as u64 ) ;
1027- self . memory = Some ( Memory {
1028- min_pages : bytes. div_ceil ( 65536 ) . max ( 1 ) ,
1029- max_pages : None ,
1030- } ) ;
1037+ if self . memory_declared {
1038+ return ; // never override a source-declared memory
1039+ }
1040+ let bytes = self . regions . get ( region) . map_or ( 0 , |r| r. byte_size as u64 )
1041+ * self . region_instances . get ( region) . copied ( ) . unwrap_or ( 1 ) ;
1042+ let pages = bytes. div_ceil ( 65536 ) . max ( 1 ) ;
1043+ match & mut self . memory {
1044+ Some ( m) if m. min_pages >= pages => { }
1045+ Some ( m) => m. min_pages = pages,
1046+ None => {
1047+ self . memory = Some ( Memory {
1048+ min_pages : pages,
1049+ max_pages : None ,
1050+ } )
1051+ }
10311052 }
10321053 }
10331054
@@ -1452,6 +1473,10 @@ struct Lower {
14521473 accesses : Vec < crate :: AccessSite > ,
14531474 /// Regions touched — memory is synthesised only on successful lowering.
14541475 used_regions : Vec < usize > ,
1476+ /// Active `region.scan` contexts, innermost last: (region index,
1477+ /// slot of the current-instance address local). Bare idents in scan
1478+ /// predicates/bodies resolve to fields of the innermost scan region.
1479+ scan_stack : Vec < ( usize , u32 ) > ,
14551480}
14561481
14571482impl Lower {
@@ -1481,6 +1506,7 @@ impl<'a> Parser<'a> {
14811506 ops : Vec :: new ( ) ,
14821507 accesses : Vec :: new ( ) ,
14831508 used_regions : Vec :: new ( ) ,
1509+ scan_stack : Vec :: new ( ) ,
14841510 } ;
14851511 // Prologue: copy each region param into a base local (its single
14861512 // use), bind scalar params by name.
@@ -1542,7 +1568,10 @@ impl<'a> Parser<'a> {
15421568 if self . try_keyword ( "set" ) {
15431569 return self . lower_region_set ( cx) . map ( |_| false ) ;
15441570 }
1545- return None ; // alloc / free / scan / place: unsupported here
1571+ if self . try_keyword ( "scan" ) {
1572+ return self . lower_region_scan ( cx, results) . map ( |_| false ) ;
1573+ }
1574+ return None ; // alloc / free / place: unsupported here
15461575 }
15471576 if self . try_keyword ( "let" ) {
15481577 self . try_keyword ( "mut" ) ;
@@ -1729,12 +1758,84 @@ impl<'a> Parser<'a> {
17291758 Some ( ( ) )
17301759 }
17311760
1732- // --- Typed expression lowering (precedence: cmp > add > mul > primary).
1761+ /// `region.scan $h [where <pred>] -> |e| { body }` — a bounded loop
1762+ /// over the region's declared instances. Bare idents in the
1763+ /// predicate resolve to fields of the scanned region; `$e` in the
1764+ /// body is the current instance's handle.
1765+ fn lower_region_scan ( & mut self , cx : & mut Lower , results : & [ Wty ] ) -> Option < ( ) > {
1766+ if !self . try_char ( '$' ) {
1767+ return None ;
1768+ }
1769+ let hname = self . parse_ident ( ) ;
1770+ let & ( region, base) = cx. handles . get ( & hname) ?;
1771+ let instances = i32:: try_from ( * self . region_instances . get ( region) ?) . ok ( ) ?;
1772+ let stride = i32:: try_from ( self . regions . get ( region) ?. byte_size ) . ok ( ) ?;
1773+
1774+ let i_slot = cx. alloc ( Wty :: I32 ) ;
1775+ let addr_slot = cx. alloc ( Wty :: I32 ) ;
1776+
1777+ // i = 0; block { loop { i < N eqz br_if 1; addr = base + i*stride; …
1778+ cx. ops . push ( crate :: Op :: I32Const ( 0 ) ) ;
1779+ cx. ops . push ( crate :: Op :: LocalSet ( i_slot) ) ;
1780+ cx. ops . push ( crate :: Op :: Block ) ;
1781+ cx. ops . push ( crate :: Op :: Loop ) ;
1782+ cx. ops . push ( crate :: Op :: LocalGet ( i_slot) ) ;
1783+ cx. ops . push ( crate :: Op :: I32Const ( instances) ) ;
1784+ cx. ops . push ( crate :: Op :: I32LtS ) ;
1785+ cx. ops . push ( crate :: Op :: I32Eqz ) ;
1786+ cx. ops . push ( crate :: Op :: BrIf ( 1 ) ) ;
1787+ cx. ops . push ( crate :: Op :: LocalGet ( base) ) ;
1788+ cx. ops . push ( crate :: Op :: LocalGet ( i_slot) ) ;
1789+ cx. ops . push ( crate :: Op :: I32Const ( stride) ) ;
1790+ cx. ops . push ( crate :: Op :: I32Mul ) ;
1791+ cx. ops . push ( crate :: Op :: I32Add ) ;
1792+ cx. ops . push ( crate :: Op :: LocalSet ( addr_slot) ) ;
1793+
1794+ cx. scan_stack . push ( ( region, addr_slot) ) ;
1795+
1796+ let has_pred = self . try_keyword ( "where" ) ;
1797+ if has_pred {
1798+ if self . lower_expr ( cx, None ) ? != Wty :: I32 {
1799+ cx. scan_stack . pop ( ) ;
1800+ return None ;
1801+ }
1802+ cx. ops . push ( crate :: Op :: If ) ;
1803+ }
1804+
1805+ let ok = ( || {
1806+ if !self . try_str ( "->" ) || !self . try_char ( '|' ) {
1807+ return None ;
1808+ }
1809+ let ename = self . parse_ident ( ) ;
1810+ if ename. is_empty ( ) || !self . try_char ( '|' ) || !self . try_char ( '{' ) {
1811+ return None ;
1812+ }
1813+ cx. handles . insert ( ename, ( region, addr_slot) ) ;
1814+ self . lower_block ( cx, results) ?;
1815+ Some ( ( ) )
1816+ } ) ( ) ;
1817+ cx. scan_stack . pop ( ) ;
1818+ ok?;
1819+
1820+ if has_pred {
1821+ cx. ops . push ( crate :: Op :: End ) ;
1822+ }
1823+ cx. ops . push ( crate :: Op :: LocalGet ( i_slot) ) ;
1824+ cx. ops . push ( crate :: Op :: I32Const ( 1 ) ) ;
1825+ cx. ops . push ( crate :: Op :: I32Add ) ;
1826+ cx. ops . push ( crate :: Op :: LocalSet ( i_slot) ) ;
1827+ cx. ops . push ( crate :: Op :: Br ( 0 ) ) ;
1828+ cx. ops . push ( crate :: Op :: End ) ;
1829+ cx. ops . push ( crate :: Op :: End ) ;
1830+ Some ( ( ) )
1831+ }
1832+
1833+ // --- Typed expression lowering (precedence: cmp > band > add > mul > primary).
17331834 // `expected` seeds literal typing; operands of a binary op must agree
17341835 // exactly (no implicit promotion — a mismatch bails the whole body).
17351836
17361837 fn lower_expr ( & mut self , cx : & mut Lower , expected : Option < Wty > ) -> Option < Wty > {
1737- let lty = self . lower_add ( cx, expected) ?;
1838+ let lty = self . lower_band ( cx, expected) ?;
17381839 self . skip_whitespace ( ) ;
17391840 let cmp = if self . try_str ( "==" ) {
17401841 "=="
@@ -1753,7 +1854,7 @@ impl<'a> Parser<'a> {
17531854 } else {
17541855 return Some ( lty) ;
17551856 } ;
1756- if self . lower_add ( cx, Some ( lty) ) ? != lty {
1857+ if self . lower_band ( cx, Some ( lty) ) ? != lty {
17571858 return None ;
17581859 }
17591860 use crate :: Op :: * ;
@@ -1781,13 +1882,39 @@ impl<'a> Parser<'a> {
17811882 Some ( Wty :: I32 )
17821883 }
17831884
1885+ /// Bitwise tier (`&`, `|`) between comparison and additive — i32
1886+ /// only. The examples parenthesise (`(flags & 1) == 1`), so the
1887+ /// C-precedence footgun does not arise.
1888+ fn lower_band ( & mut self , cx : & mut Lower , expected : Option < Wty > ) -> Option < Wty > {
1889+ let ty = self . lower_add ( cx, expected) ?;
1890+ loop {
1891+ self . skip_whitespace ( ) ;
1892+ let and = match self . src . as_bytes ( ) . get ( self . pos ) {
1893+ Some ( & b'&' ) if self . src . as_bytes ( ) . get ( self . pos + 1 ) != Some ( & b'&' ) => true ,
1894+ Some ( & b'|' ) if self . src . as_bytes ( ) . get ( self . pos + 1 ) != Some ( & b'|' ) => false ,
1895+ _ => return Some ( ty) ,
1896+ } ;
1897+ if ty != Wty :: I32 {
1898+ return None ;
1899+ }
1900+ self . pos += 1 ;
1901+ if self . lower_add ( cx, Some ( Wty :: I32 ) ) ? != Wty :: I32 {
1902+ return None ;
1903+ }
1904+ cx. ops . push ( if and { crate :: Op :: I32And } else { crate :: Op :: I32Or } ) ;
1905+ }
1906+ }
1907+
17841908 fn lower_add ( & mut self , cx : & mut Lower , expected : Option < Wty > ) -> Option < Wty > {
17851909 let ty = self . lower_mul ( cx, expected) ?;
17861910 loop {
17871911 self . skip_whitespace ( ) ;
17881912 let op = match self . src . as_bytes ( ) . get ( self . pos ) {
17891913 Some ( & b'+' ) => crate :: Op :: I32Add ,
1790- Some ( & b'-' ) => crate :: Op :: I32Sub ,
1914+ // `->` is the scan/get arrow, never subtraction.
1915+ Some ( & b'-' ) if self . src . as_bytes ( ) . get ( self . pos + 1 ) != Some ( & b'>' ) => {
1916+ crate :: Op :: I32Sub
1917+ }
17911918 _ => return Some ( ty) ,
17921919 } ;
17931920 let plus = matches ! ( op, crate :: Op :: I32Add ) ;
@@ -1839,6 +1966,15 @@ impl<'a> Parser<'a> {
18391966 fn lower_primary ( & mut self , cx : & mut Lower , expected : Option < Wty > ) -> Option < Wty > {
18401967 self . skip_whitespace ( ) ;
18411968 let & b = self . src . as_bytes ( ) . get ( self . pos ) ?;
1969+ if b == b'!' && self . src . as_bytes ( ) . get ( self . pos + 1 ) != Some ( & b'=' ) {
1970+ // Logical not on an i32 truth value.
1971+ self . pos += 1 ;
1972+ if self . lower_primary ( cx, Some ( Wty :: I32 ) ) ? != Wty :: I32 {
1973+ return None ;
1974+ }
1975+ cx. ops . push ( crate :: Op :: I32Eqz ) ;
1976+ return Some ( Wty :: I32 ) ;
1977+ }
18421978 if b == b'(' {
18431979 self . pos += 1 ;
18441980 let ty = self . lower_expr ( cx, expected) ?;
@@ -1864,6 +2000,20 @@ impl<'a> Parser<'a> {
18642000 cx. ops . push ( crate :: Op :: I32Const ( 0 ) ) ;
18652001 return Some ( Wty :: I32 ) ;
18662002 }
2003+ "is_null" => {
2004+ // v0 null representation: 0 — matching wasm's null
2005+ // pointer convention (`opt<T>` carries `Nullability` in
2006+ // the regions carrier; a richer representation is an L4
2007+ // follow-up). `is_null(x)` = `x == 0`.
2008+ if !self . try_char ( '(' ) {
2009+ return None ;
2010+ }
2011+ if self . lower_expr ( cx, Some ( Wty :: I32 ) ) ? != Wty :: I32 || !self . try_char ( ')' ) {
2012+ return None ;
2013+ }
2014+ cx. ops . push ( crate :: Op :: I32Eqz ) ;
2015+ return Some ( Wty :: I32 ) ;
2016+ }
18672017 "cast" => {
18682018 if !self . try_char ( '<' ) {
18692019 return None ;
@@ -1892,8 +2042,23 @@ impl<'a> Parser<'a> {
18922042 cx. ops . push ( crate :: Op :: LocalGet ( slot) ) ;
18932043 return Some ( wty) ;
18942044 }
2045+ // Inside a `region.scan`, a bare ident is a field of the scanned
2046+ // region — a typed load off the current-instance address.
2047+ if let Some ( & ( region, addr) ) = cx. scan_stack . last ( ) {
2048+ if let Some ( ( field_idx, offset, scalar) ) = self . resolve_field ( region, & ident) {
2049+ cx. ops . push ( crate :: Op :: LocalGet ( addr) ) ;
2050+ let load_at = cx. ops . len ( ) ;
2051+ cx. ops . push ( scalar_load_op ( & scalar, offset as u64 ) ) ;
2052+ cx. accesses . push ( crate :: AccessSite {
2053+ region,
2054+ field : field_idx,
2055+ instr_index : Some ( load_at) ,
2056+ } ) ;
2057+ return Some ( wty_from_scalar ( & scalar) ) ;
2058+ }
2059+ }
18952060 self . pos = save;
1896- None // unknown ident: is_null / sizeof / handle-as-value / call
2061+ None // unknown ident: sizeof / handle-as-value / call
18972062 }
18982063
18992064 /// Numeric literal, typed by `expected` (default: `.`-bearing → F32,
0 commit comments