commit 6f165b564f3f0712be1cb04442bc5d5f35288b1c
parent 15a28880ad491a8e69132ca7ce657436fb109380
Author: Virgil Dupras <hsoft@hardcoded.net>
Date: Tue, 28 Mar 2023 15:55:17 -0400
halcc: consolidate
Diffstat:
5 files changed, 65 insertions(+), 44 deletions(-)
diff --git a/fs/comp/c/egen.fs b/fs/comp/c/egen.fs
@@ -46,7 +46,7 @@ UOPSCNT wordtbl uoptbl ( res -- res )
\ ops that can freely swap their operands
: _prep ( left right -- left halop )
- dup Result :isW? if swap then over Result :?>W Result :hal# ;
+ dup Result :isW? if swap then over Result :?>W Result :hal$ ;
: _*, _prep *, ; : _&, _prep and, ; : _^, _prep xor, ; : _|, _prep or, ;
: _&&, _prep and, ;
: _||, _prep or, W=0>Z, NZ) C>W, ;
@@ -58,7 +58,7 @@ UOPSCNT wordtbl uoptbl ( res -- res )
else _prep +, then ;
\ ops that can't freely swap their operands
-: _prep ( left right -- left halop ) Result :?>A over Result :?>W ;
+: _prep ( left right -- left halop ) Result :?>A$ over Result :?>W ;
: _/, _prep /, ; : _%, _prep %, ;
: _<<, _prep <<, ; : _>>, _prep >>, ;
@@ -74,15 +74,15 @@ assign _&=, _&, assign _^=, _^, assign _|=, _|,
assign _-=, _-, assign _/=, _/, assign _%=, _%,
assign _<<=, _<<, assign _>>=, _>>,
-: _=, tuck Result :?>W Result :hal# !, ;
+: _=, tuck Result :?>W Result :hal$ !, ;
\ To avoid W juggling, we check if our right operand is W. If it is, no need
\ for juggling, all we need is to invert the condition we use.
\ data: unsigned cond, unsigned swapped cond, signed cond, signed swapped cond
: cmpop doer 4 for ' execute , next does> ( left right 'conds )
- over Result unsigned? not if CELLSZ << + then
+ over Result :unsigned? not if CELLSZ << + then
over Result :isW? if CELLSZ + @ >r swap else @ >r then ( left right )
- Result :hal# over Result :?>W cmp, r> C>W, ;
+ Result :hal$ over Result :?>W cmp, r> C>W, ;
cmpop _==, Z) Z) Z) Z) cmpop _!=, NZ) NZ) NZ) NZ)
cmpop _<, <) >=) s<) s>=) cmpop _<=, <=) >) s<=) s>)
cmpop _>, >) <=) s>) s<=) cmpop _>=, >=) <) s>=) s<)
@@ -120,7 +120,7 @@ code _callA branchA,
',' readChar? while nextt repeat ')' expectChar then ( funcres )
dup Result :cdecl# dup CDecl :funcsig? if ( funcres cdecl )
nip dup CDecl offset execute,
- else swap Result :hal# A>) @, ['] _callA execute, then ( cdecl )
+ else swap Result :hal$ A>) @, ['] _callA execute, then ( cdecl )
rdrop r> ( psinitlvl ) to psoff
Result currentW ?dup if PS- Result :release then
\ TODO: arilvl of fun rettype isn't properly preserved here
diff --git a/fs/comp/c/expr.fs b/fs/comp/c/expr.fs
@@ -10,23 +10,22 @@ struct[ Result
0 const NONE \ Nothing (probably a released W)
1 const CONST \ Is a constant (value in arg)
2 const W \ Value in W register
- 3 const CDECL \ CDecl pointer is in arg.
+ 3 const CDECL
4 const PS \ Result pushed to PS, offset in arg
5 const ARRAY \ Result is a constant array in a Stack. arg is a pointer to it.
sfield type
- sfield arg
- sfield basesz \ size, in bytes of the base type
+ sfield arg \ PS offset, constant or array pointer
+ sfield cdecl
sfield lvl \ lvl changed applied within the expression
sfield blvl \ Bottom Level
- sfield unsigned?
\ There can only be one result using W at once. Whenever a W result is
\ created, it takes the lock. If it's already taken, there's an error.
0 value currentW \ link to Result
: :Wfree# currentW if abort" W is already taken!" then ;
- : :new ( arg type -- res ) SZ syspad :[ , , CELLSZ , 0 , 0 , 0 , syspad :] ;
+ : :new ( arg type -- res ) SZ syspad :[ , , 0 , 0 , 0 , syspad :] ;
: :none ( -- res ) 0 NONE :new ;
: :const ( n -- res ) CONST :new ;
: :W ( -- res ) :Wfree# 0 W :new dup to currentW ;
@@ -41,35 +40,34 @@ struct[ Result
create _ ," NIWCPA"
: :. ( self -- )
dup type _ + c@ emit spc>
- dup arg over :iscdecl? if CDecl :. else .x then spc>
- dup basesz . spc> dup lvl . spc> blvl . spc>
+ dup arg .x spc> dup cdecl ?dup if CDecl :. spc> then
+ dup lvl . spc> blvl . spc>
." W=" currentW bool . nl> ;
: :W! ( self -- ) dup to currentW W swap to type ;
: :& ( self -- ) -1 swap to+ lvl ;
: :cdecl ( cdecl -- res )
- dup CDECL :new ( cdecl res )
- over CDecl type typesize over to basesz
- over typeunsigned? over to unsigned?
- over bi CDecl lvl | CDecl nbelem bool + over to blvl ( cdecl res )
+ 0 CDECL :new ( cdecl res )
+ 2dup to cdecl
+ over CDecl :lvl over to blvl ( cdecl res )
swap CDecl nbelem if dup :& -1 over to+ blvl then ;
- : :cdecl# dup :iscdecl? _assert arg ;
- : :opsz ( halop self -- halop )
- \ TODO: this logic is the same as in CDecl :halop. deduplicate.
- bi+ lvl | blvl = if
- basesz case 1 of = 8b) endof 2 of = 16b) endof endcase
- else drop then ;
+ : :basesz cdecl ?dup if CDecl type typesize else 4 then ;
+ : :unsigned? cdecl ?dup if typeunsigned? else 1 then ;
+ : :cdecl# dup :iscdecl? _assert cdecl ;
+ : :nb) ( halop self -- halop )
+ bi+ lvl | blvl = if :basesz nb) else drop then ;
: :hal# ( self -- halop ) dup type case ( self )
CONST of = arg i) endof
CDECL of =
- bi+ arg CDecl :halop | lvl case ( self halop )
+ bi+ cdecl CDecl :halop | lvl case ( self halop )
0 of = endof
-1 of = A>) lea, A*) endof
of 0>= r@ for A>) @, A) next drop A) endof
_err endcase ( self halop )
- swap :opsz endof
+ swap :nb) endof
PS of = arg PSP+) endof
abort" :hal# error" endcase ;
+ : :hal$ dup :hal# swap :release ;
: :>W ( self -- )
dup :isW? if drop else
:Wfree# dup :hal# @,
@@ -83,31 +81,29 @@ struct[ Result
: :?>W dup :isW? if drop else :?freeCurrentW :>W then ;
: :?>W$ dup :?>W :release ;
\ Free up W by sending it to A if needed.
- : :?>A ( self -- halop )
- dup :isW? if :release W>A, A*) else :hal# then ;
+ : :?>A$ ( self -- halop )
+ dup :isW? if :release W>A, A*) else :hal$ then ;
: :* ( self -- )
- 1 over to+ lvl dup :isW? if W) swap :opsz @, else drop then ;
+ 1 over to+ lvl dup :isW? if W) swap :nb) @, else drop then ;
: :*n ( n self -- )
- dup :isconst? if
- dup arg rot * swap to arg else :?>W i) *, then ;
+ dup :isconst? if dup arg rot * swap to arg else :?>W i) *, then ;
: :/n ( n self -- )
- dup :isconst? if
- dup arg rot / swap to arg else :?>W i) /, then ;
+ dup :isconst? if dup arg rot / swap to arg else :?>W i) /, then ;
\ For pointer arithmetics, we apply the "bottom level" logic one level higher.
- \ That is, when lvl=blvl, our "arisz" is 1 (regular arithmetics), when
- \ lvl=blvl+1, our arisz is "basesz" (we add and subtract by chunks of the
+ \ That is, when lvl=blvl our "arisz" is 1 (regular arithmetics), when
+ \ lvl=blvl our arisz is "basesz" (we add and subtract by chunks of the
\ base type), otherwise it's 4 (we deal with pointers).
: :*arisz ( self -- n ) \ pointer arithmetics multiplier
dup bi blvl | lvl - case
0 of = drop 1 endof
- 1 of = basesz endof
+ 1 of = :basesz endof
drop 4 endcase ;
- \ Copy meta information (basesz, lvl, blvl) from "other" result
+ \ Copy meta information (basesz, lvl, blvl from "other" result
: :copymeta ( other self -- )
- over basesz over to basesz
+ over cdecl over to cdecl
over lvl over to lvl
swap blvl swap to blvl ;
- : :toint ( self -- ) 4 over to basesz 0 over to lvl 0 swap to blvl ;
+ : :toint ( self -- ) 0 over to cdecl 0 over to lvl 0 swap to blvl ;
]struct
BOPSCNT wordtbl _tbl ( a b -- n )
diff --git a/fs/comp/c/fgen.fs b/fs/comp/c/fgen.fs
@@ -96,7 +96,7 @@ current ' parseStatement realias
_initcode not if here to _initcode then
nextt parseExpression ( cdecl res )
dup Result :isarray? if Result arg over _copyArray else
- Result :>W$ dup CDecl :halop !, then
+ Result :>W$ dup Result :cdecl Result :hal# !, then
psneutral nextt then ( cdecl tok )
dup ';' isChar? not while ( cdecl tok )
',' expectChar CDecl type parseDeclarator ( cdecl )
diff --git a/fs/comp/c/type.fs b/fs/comp/c/type.fs
@@ -43,6 +43,9 @@ STORAGE_MEM value curstorage
: cdecl? ( type -- f ) $f > ;
+: nb) ( halop sz -- halop )
+ case 1 of = 8b) endof 2 of = 16b) endof 4 of = endof _err endcase ;
+
\ CDecl flags
\ b0=is a struct? if 1, this is an "empty" CDecl with the name of the struct.
\ First field is nexttype.
@@ -76,13 +79,13 @@ struct[ CDecl
: :isvar? ( self -- f ) storage STORAGE_RS = ;
: :isglobal? ( self -- f ) storage STORAGE_MEM = ;
+ : :lvl bi lvl | nbelem bool + ;
+
: :halop ( self -- operand ) dup bi offset | storage case ( self offset )
STORAGE_RS of = RSP) swap +) endof
STORAGE_PS of = PSP+) endof
STORAGE_MEM of = over :funcsig? if i) else m) then endof
- _err endcase ( self operand )
- over lvl if nip else
- swap type _typesize case 1 of = 8b) endof 2 of = 16b) endof endcase then ;
+ _err endcase ( self operand ) nip ;
\ Combined size of all fields in the LL.
: :size ( self -- size )
diff --git a/fs/doc/cc/impl.txt b/fs/doc/cc/impl.txt
@@ -144,8 +144,8 @@ we use PS as a temporary location, which we'll refer to later as we resolve the
expression. That's your fourth building block right there.
These are the four main result types, which all have constants in the Result
-namespace: CONST, W, CDECL, PS. There are 2 other types for special contexts,
-NONE and ARRAY.
+namespace: CONST, W, CDECL, PS. There are 3 other types for special contexts,
+A, NONE and ARRAY.
The goal of the Result structure is to keep track of where the operands live at
so that we can generate the proper HAL operands to feed to the HAL operators
@@ -168,6 +168,28 @@ to "release" Result structs when they're finally consumed.
Then, this result is either used as an operand of an assign operatorm used as a
call argument, or used as a return value. The cycle is complete.
+### :hal# vs :hal$
+
+The main purpose of a Result is to, at some point, produce a HAL operand (or to
+"be W"). This is done through the :hal# ( self -- halop ) method. This method
+works for CONST, CDECL and PS types, but not for W because even though it's
+technically possible to produce a HAL operand for W (the "W)" operand), this
+isn't ever supposed to happen. That's why there's this "#" at the end (for
+"assert"), because we abort when it doesn't make sense.
+
+This method produces the right halop for the right context, including
+indirection levels and operand size.
+
+Once a HAL operand has been generated, we usually don't reuse the Result because
+the resulting operation lives in W. To protect us from ourselves, it's better to
+use the ":hal$" method which additionally releases the Result (sets it to NONE).
+
+### A register usage
+
+The A register is used in some places during code generation, but only in
+contexts that resolve immediately. Results stored in there can't be, for
+example, still stored when a function call is generated.
+
### Result indirection levels
We track two types of indirection levels in the Result struct. First, there's