duskos

dusk os fork
git clone git://git.alexwennerberg.com/duskos
Log | Files | Refs | README | LICENSE

commit f3e8faf244c41fe6dcf6d5bfcbaae8e6d71fa145
parent 13900fdb77d1ae7457dd39463ac7cb5a0f664672
Author: Virgil Dupras <hsoft@hardcoded.net>
Date:   Sat,  1 Apr 2023 22:14:22 -0400

halcc: cleanup old units

I won't need to salvage anything from them any more. Final LOC count: 1338

Diffstat:
Dfs/comp/c/pgen.fs | 522-------------------------------------------------------------------------------
Dfs/comp/c/vm/commonhi.fs | 90-------------------------------------------------------------------------------
Dfs/comp/c/vm/commonlo.fs | 115-------------------------------------------------------------------------------
Dfs/comp/c/vm/forth.fs | 169-------------------------------------------------------------------------------
Dfs/comp/c/vm/i386.fs | 309-------------------------------------------------------------------------------
Dfs/comp/c/vm/vm.fs | 6------
Mfs/home/codesz.fs | 13++++---------
7 files changed, 4 insertions(+), 1220 deletions(-)

diff --git a/fs/comp/c/pgen.fs b/fs/comp/c/pgen.fs @@ -1,522 +0,0 @@ -\ C compiler parse+generate -\ This CC used to build an AST in memory and then, in a subsequent phase, -\ generate the corresponding code. It turned out to be needlessly complex. In -\ this unit, we are fed with tokens, we parse it, then we spit code directly. -require /sys/scratch.fs -?f<< /lib/str.fs -?f<< /lib/wordtbl.fs -?f<< /lib/stack.fs -?f<< /lib/arena.fs -?f<< /comp/c/tok.fs -?f<< /comp/c/type.fs -\ This unit also requires vm/(ARCH).fs, but it's loaded in comp/c/cc.fs - -\ This arena is for *runtime* string and array literals. We use an arena rather -\ than writing directly to here because at the time when we want to write the -\ literal, we might be in the middle of code generation. This arena, which is -\ never resetted, gives us a safe space to write literals. The idea is that at -\ the prelude of each function, we call :reserve to ensure that we won't -\ allocate a new arena in the middle of the function (this might fail if a -\ single function allocates more than ARENASZ bytes of literals). -Arena :new structbind Arena _litarena - -\ Maximum number that a function call can have -$10 const MAXARGCNT -\ Maximum size in bytes that a single literal can have -$400 const MAXLITSZ - -0 value _ccdebug -: _err ( -- ) tokdbg abort" pgen error" ; -: _assert ( f -- ) not if _err then ; -: spit ( a u -- ) swap >r for - i 40 mod not if nl> then - 8b to@+ V1 .x1 next rdrop ; - -\ Unary operators -7 const UOPSCNT -UOPSCNT stringlist UOPTlist "-" "~" "!" "&" "*" "++" "--" -3 const UOP& -4 const UOP* - -: uopid ( tok -- opid? f ) - UOPTlist sfind dup 0< if drop 0 else 1 then ; -: uoptoken ( opid -- tok ) UOPTlist slistiter ; - -UOPSCNT wordtbl uopgentbl ( -- ) -:w ( - ) vmneg, ; -:w ( ~ ) vmnot, ; -:w ( ! ) vmboolnot, ; -:w ( & ) vmop :&op ; -:w ( * ) vmop :*op ; -:w ( ++ ) vm++op, ; -:w ( -- ) vm--op, ; - -\ Postfix operators -2 const POPSCNT -POPSCNT stringlist POPTlist "++" "--" - -POPSCNT wordtbl popgentbl ( -- ) -:w ( ++ ) vmop++, ; -:w ( -- ) vmop--, ; - -: popid ( tok -- opid? f ) - POPTlist sfind dup 0< if drop 0 else 1 then ; -: poptoken ( opid -- tok ) POPTlist slistiter ; - -\ Binary operators -31 const BOPSCNT -BOPSCNT stringlist BOPTlist - "+" "-" "*" "/" "%" "<<" ">>" "<" ">" "<=" ">=" "==" "!=" "&" "^" "|" - "&&" "||" "=" "+=" "-=" "*=" "/=" "%=" "<<=" ">>=" "&=" "^=" "|=" "?" ":" - -\ binary ops precedence. lower means more precedence -create bopsprectbl BOPSCNT nc, - 1 1 0 0 0 2 2 3 3 3 3 4 4 5 5 5 - 6 6 7 7 7 7 7 7 7 7 7 7 7 8 9 - -MAXARGCNT Stack :new structbind Stack _stack?: -: _? vmop :push _stack?: :push selop^ ; -: _: _stack?: :pop vm?:, ; - -BOPSCNT wordtbl bopgentbl ( -- ) -'w vm+, 'w vm-, 'w vm*, 'w vm/, -'w vm%, 'w vm<<, 'w vm>>, 'w vm<, -'w vm>, 'w vm<=, 'w vm>=, 'w vm==, -'w vm!=, 'w vm&, 'w vm^, 'w vm|, -'w vm&&, 'w vm||, 'w vm=, 'w vm+=, -'w vm-=, 'w vm*=, 'w vm/=, 'w vm%=, -'w vm<<=, 'w vm>>=, 'w vm&=, 'w vm^=, -'w vm|=, 'w _? 'w _: - -\ Constant expressions have a reduced set of operators so that they don't -\ conflict with some syntax where they're used (namely, inside a "case xxx:") -0 value _isconstexpr -: bopid ( tok -- opid? f ) - BOPTlist sfind dup 0< if drop 0 - else dup 17 > _isconstexpr and if drop 0 else 1 then then ; -: bopprec ( opid -- precedence ) BOPSCNT min bopsprectbl + c@ ; -: boptoken ( opid -- tok ) BOPTlist slistiter ; -: ptrbop? ( opid -- f ) 2 < ; \ can op be applied to pointers? - -0 value _curfunc \ ctype of the current function (includes arguments) -0 value _locvars \ the root ctype of local variables for current function - -: findIdent ( name -- ctype-or-0 ) - _curfunc if dup _curfunc CType :find ?dup if nip exit then then ( name ) - dup _locvars if to' _locvars CType :find else drop 0 then ( name ctype-or-0 ) - ?dup if nip else findSymbol then ; - -alias _err parseType ( tok -- type? f ) \ forward declaration -alias _err parseDeclarator ( type -- ctype ) \ forward declaration -alias noop parseExpression ( tok -- ) \ forward declaration - -\ Parsing strategy: we dig down recursively through nextt until we get to our -\ identifier. Before that identifier, we can hit chars like ( and *. -\ For *, it's easy, we inc our current indirection level. For (, we enter a -\ recursion. -\ After we hit the identifier, we continue parsing forward, where -\ we can hit chars like [, an array specifier and (, a function specifier. We -\ process them, amending our ctype structure as we go. If we hit a ), we go up -\ one level in recursion and apply previously recorded indirection levels to the -\ returned type. - -: _arg ( parent-ctype tok -- offset ) - parseType _assert parseDeclarator ( ctype newtype ) - tuck swap to CType nexttype ( newtype ) - ')' readChar? if 0 swap CType :offset! else - ',' expectChar dup nextt _arg ( ctype offset ) - swap CType :offset! then ( offset ) ; - -: parseConstExpr ( tok -- n ) - 1 to _isconstexpr parseExpression 0 to _isconstexpr - vmop^ :noop# vmop :isconst# vmop arg ops$ ; - -\ parsing after the identifier -: _post ( ctype -- ctype ) - begin ( ctype ) nextt case - '[' of isChar?^ - nextt parseConstExpr nextt ']' expectChar ( ctype nbelem ) - over to CType nbelem endof - '(' of isChar?^ - dup CType :funcsig! STORAGE_PS to@! curstorage >r - ')' readChar? not if ( ctype tok ) - over swap _arg ( ctype offset ) drop then - r> to curstorage endof - r> to nexttputback exit - endcase again ; - -: _addlvl ( lvl type -- type ) tuck type*lvl + dup 4 < _assert swap type*lvl! ; - -: _parseDeclarator ( type -- ctype ) - 0 begin ( type lvl ) - '*' readChar? while ( type lvl tok ) 1+ repeat ( type lvl tok ) - dup '(' isChar? if ( type lvl tok ) - drop swap parseDeclarator read) - >r >r \ V1=outer-ctype v2=inner-lvl - \ type recursion in C is "inside out". The ctype we have now is the outer - \ type. We'll forward its current "type" field to the ctype we're about to - \ finish parsing, and that new ctype will be placed in our outer type's - \ "type" field. One thing we have to be careful about is to keep our *lvl - \ where it belongs. - V1 CType type dup type*lvl ( inner-type outer-lvl ) - swap r> ( inner-lvl ) swap type*lvl! ( outer-lvl inner-type ) - NULLSTR swap CType :new _post ( outer-lvl inner-type ) - _addlvl ( inner-type ) r@ ( outer-type ) to CType type r> ( ctype ) - else ( type lvl tok ) - dup isIdent? not if to nexttputback NULLSTR then ( type lvl name ) - rot CType :new ( lvl ctype ) - _post tuck CType type ( ctype lvl type ) - _addlvl ( ctype type ) - over to CType type ( ctype ) then ; -current ' parseDeclarator realias - -: _parseStruct ( -- ctype ) - nextt dup isIdent? if nextt else NULLSTR swap then - '{' expectChar ( name ) TYPE_VOID CType :new ( res ) - dup CType :struct! dup addTypedef - 0 >r dup begin ( res prev ) \ V1=offset - '}' readChar? not while ( res prev tok ) - parseType _assert parseDeclarator begin ( res prev new ) - tuck swap to CType nexttype ( res new ) - V1 over to CType offset - dup typesize to+ V1 - ';' readChar? not while ( res prev tok ) - ',' expectChar dup CType type parseDeclarator repeat ( res prev ) - repeat ( res prev ) rdrop drop ; - -\ parse a type from stream, starting with "tok". This only parses the "type" -\ part without the "*" part or the name part. The result can be a "base" type -\ (type < $100) or a CType if the type is a struct, union or enum. -: _parseType ( tok -- type? f ) - dup S" typedef" s= if - drop nextt parseType _assert parseDeclarator ( ctype ) - dup addTypedef 1 exit then - dup S" struct" s= if drop _parseStruct 1 else - dup S" unsigned" s= if drop $10 nextt else $00 swap then ( type tok ) - dup typenames sfind dup 0>= if ( type tok idx ) - nip << << or 1 - else drop nip findTypedef ( type-or-0 ) ?dup bool then then ; -current ' parseType realias - -alias noop parseFactor ( tok -- ) \ forward declaration - -\ we have a func call and its target in in vmop -: _funcall ( -- ) - vmop type ctype? _assert - \ We either have a direct function signature or a pointer to it. - \ TODO: :funcptr? doesn't work correctly here. fix this - vmop type ctype' - dup CType :funcsig? not if CType type ctype' then - dup CType :funcsig? _assert CType type ( type ) - ')' readChar? if 0 else ( type tok ) - to nexttputback vmop :push >r \ V1=callop - MAXARGCNT CELLSZ * Stack SZ + - \ TODO: I had a strange failure if, instead of using the result of :new - \ below, I used the result of :]. They're supposed to be equivalent, but :] - \ was wrong. investigate. - syspad :[ MAXARGCNT Stack :new syspad :] drop >r ( type ) \ V2=args - begin ( type ) - nextt parseExpression vmop :push V2 Stack :push ( type ) - ',' readChar? not until ( type tok ) - ')' expectChar - \ now, we want to push the args to PS in the reverse order, with first - \ arg on top of PS. - V2 Stack :count dup >r for V2 Stack :pop next ( type argN .. arg0 ) - r> rdrop r> vmop :pop ( type argN .. arg0 nargs ) then ( type ... narg ) - vmcall, ( type ) if vmpspop, then ; - -: _arrow ( -- ) \ struct in vmop - nextt vmop type dup ctype? _assert dup type*lvl 1 = _assert ( name type ) - ctype' dup CType :struct? _assert ( name ctype ) - CType :find# ( field-ctype ) - dup CType offset vmop :+n vmop :*op ( field-ctype ) - dup CType type to vmop type - CType nbelem if vmop :&op then ; - -\ parses, if possible, a postfix operator. If none, this is a noop. -\ We parse postfix args as long as there are any. -: parsePostfixOp ( -- ) - nextt case ( ) - '[' of isChar?^ \ x[y] is the equivalent of *(x+y) - vmop^ :push vmop :push - nextt parseExpression selop^ vmop :pop vm+, vmop :*op vmop^ :pop - nextt ']' expectChar parsePostfixOp endof - '(' of isChar?^ _funcall parsePostfixOp endof - S" ->" of s= _arrow parsePostfixOp endof - '.' of isChar?^ vmop :&op _arrow parsePostfixOp endof - of popid ( opid ) - popgentbl swap wexec parsePostfixOp endof - r@ to nexttputback - endcase ; - -\ We need to parse the entire list before we begin writing to _litarena if we -\ want to support the possibility that some of these elements use _litarena -\ themselves (for example, string literals). *then*, we write. -MAXLITSZ Stack :new structbind Stack _list -: _, ( sz -- ) case 1 of = c, endof 2 of = 16b , endof , endcase ; -: parseList ( typesize -- ) >r \ V1=typesize - _list :empty begin ( ) - nextt parseFactor vmop :isconst# vmop arg _list :push vmop :init - ',' readChar? not until ( tok ) - '}' expectChar _list :count V1 * dup CELLSZ + _litarena :[ ( listsize ) - , _list :buf( _list :count for ( a ) @+ V1 _, next ( a ) drop - _litarena :] constarray>op rdrop ; - -\ A factor can be: -\ 1. A constant -\ 2. A lvalue -\ 3. A unaryop/postfixop containing a factor -\ 4. A function call -\ 5. An expression inside () parens. -\ 6. A string literal -\ 7. pspop() -\ 8. a typecast followed by a factor -\ 9. NULL -\ 10. sizeof() -: _ ( tok -- ) vmop :init case ( ) - '(' of isChar?^ - \ can be an expression or a typecast - nextt dup parseType if ( tok type ) - \ TODO: actually process the typecast - nip parseDeclarator read) nextt parseFactor vmop :typecast - else ( tok ) parseExpression read) parsePostfixOp then - endof - '"' of isChar?^ MAXLITSZ _litarena :[ - here 0 c, ['] ," with-stdin< - ccin dup '0' = if - drop 1+ 0 c, \ null terminated - else ccputback here over - 1- over c! then ( saddr ) - _litarena :] drop const>op endof - '{' of isChar?^ \ vmop^ must be set to target for list - vmop^ :hasop# vmop^ type *ariunitsz parseList endof - S" pspop" of s= read( read) vmpspop, parsePostfixOp endof - S" NULL" of s= 0 const>op endof - S" sizeof" of s= read( nextt parseType _assert typesize const>op read) endof - of uopid ( opid ) - nextt parseFactor ( opid ) \ vmop is set - uopgentbl swap wexec endof - of isIdent? \ lvalue, FunCall or macro - r@ findIdent ?dup _assert ctype>op parsePostfixOp endof - r@ parse if const>op else _err then - endcase ; -current ' parseFactor realias - -\ Parse the "right" part of an expression with the leftmost factor and leftmost -\ binary operator already parsed. We expect vmop to already contain the left -\ factor. -: parseRExpr ( binop -- ) - nextt selop^ parseFactor nextt ( binop tok ) - \ left factor in vmop^ right factor in vmop - dup bopid if ( opleft tok opright ) - \ another binop! let's apply precedence rules. - nip over bopprec over bopprec ( l r lprec rprec ) > if ( l r ) - \ the right part has more precedence. - vmop^ :push ( l r op ) swap parseRExpr vmop^ :pop ( binop ) - \ vmop has rexpr result, vmop^ has left operator - else ( l r ) \ the left part has more precedence - selop^ swap bopgentbl swap wexec ( r ) - \ vmop has result, vmop^ is empty - parseRExpr ( ) exit then ( binop ) - else ( opleft tok ) to nexttputback then ( binop ) - \ left factor in vmop^ right factor in vmop - selop^ bopgentbl swap wexec ; - -\ An expression can be 2 things: -\ 1. a factor -\ 3. A binaryop containing two expressions. -: _ ( tok -- ) \ parseExpression - \ first tok is always a factor - parseFactor nextt ( tok ) \ factor in vmop - dup bopid if ( tok binop ) - nip vmop^ :push swap parseRExpr vmop^ :pop - else to nexttputback then ; - -current ' parseExpression realias - -$40 const MAXSWITCHCASES -\ breaks are a list of forward jumps addr that need to be resolved at the end -\ of the "breakeable" structure. -MAXSWITCHCASES Stack :new structbind Stack _breaks -: resolvebreaks ( tgtlvl -- ) - begin ( tgt ) _breaks :count over > while _breaks :pop ]vmjmp repeat drop ; -10 Stack :new structbind Stack _conts -: resolvecontinues ( tgtlvl -- ) - begin ( tgt ) _conts :count over > while _conts :pop ]vmjmp repeat drop ; - -alias noop parseStatement ( tok -- ) \ forward declaration - -: parseStatements ( -- ) - begin '}' readChar? not while parseStatement repeat ; - -: _return \ empty returns are allowed - ';' readChar? not if parseExpression read; then vmret, ops$ ; - -: _if - read( nextt parseExpression read) vmjz[, ops$ - nextt parseStatement ops$ - nextt dup S" else" s= if ( jump_addr tok ) - drop vmjmp[, swap ]vmjmp nextt parseStatement ops$ - else to nexttputback then ( jump_addr ) - ]vmjmp ; - -: _for - _breaks :count >r _conts :count >r - read( ';' readChar? not if parseExpression ops$ read; then \ initialization - here nextt parseExpression read; vmjz[, vmjmp[, ( caddr cjmpz cjmp ) \ control - rot here ')' readChar? not if ( cjmpz cjmp caddr aaddr ) - parseExpression ops$ read) then - swap vmjmp, ( cjmpz cjmp aaddr ) \ adjustment - swap ]vmjmp nextt parseStatement ( cjmpz aaddr ) - r> resolvecontinues - vmjmp, ]vmjmp r> resolvebreaks ; - -: _pspush read( nextt parseExpression vmpspush, read) read; ; - -: _break vmjmp[, _breaks :push read; ; - -: _continue vmjmp[, _conts :push read; ; - -: _while - _breaks :count >r _conts :count >r - here read( nextt parseExpression read) - vmjz[, ops$ nextt parseStatement ( wjmp waddr ) - r> resolvecontinues - swap vmjmp, ]vmjmp r> resolvebreaks ; - -: _do - _breaks :count >r _conts :count >r - here nextt parseStatement ( daddr ) - r> resolvecontinues - nextt S" while" s= _assert - read( nextt parseExpression read) ( daddr ) - vmjnz, read; r> resolvebreaks ; - -\ Switch works by constructing a lookup table of all the cases and generating -\ all statements one after the other. Whenever there's a "case", we associate it -\ to "here". Then, we evaluate the switch query and check in the lookup. Those -\ lookup tables live in _litarena. -\ However, this is tricky. We don't know beforehand how many cases we have for -\ our lookup table. To palliate to this, we add a level of indirection. We -\ generate our switch code so that it fetches its pointer to a lookup table at -\ a specific literal. It's only when we're done generating the case code that -\ we generate the lookup table and place a pointer to it at that placeholder. -MAXSWITCHCASES << Stack :new structbind Stack _cases -: _switch - _breaks :count >r \ V1=breakcnt - read( nextt parseExpression read) - CELLSZ _litarena :allot dup vmswitch, >r \ V2='lookup - vmjmp[, >r \ V3=defjump - nextt '{' expectChar nextt begin ( tok ) - dup '}' isChar? not while ( tok ) - dup S" default" s= not while ( tok ) - dup S" case" s= if - drop nextt parseConstExpr _cases :push here _cases :push - nextt ':' expectChar ( ) - else parseStatement then ( ) nextt repeat ( tok ) \ default - r> ( defjump ) ]vmjmp nextt ':' expectChar parseStatements else ( tok ) \ } - r> ( defjump ) ]vmjmp then ( tok ) drop - _cases :count 1+ CELLSZ * _litarena :[ - _cases :count >> dup , begin ( totcnt ) - _cases :count while - _cases :pop over CELLSZ * here + ! _cases :pop , repeat ( totcnt ) - CELLSZ * allot _litarena :] r> ( 'lookup ) ! r> ( breakcnt ) resolvebreaks ; - -10 stringlist statementnames - "{" "return" "if" "for" "pspush" "break" "continue" "while" "do" "switch" -10 wordtbl statementhandler ( -- ) -'w parseStatements 'w _return 'w _if 'w _for -'w _pspush 'w _break 'w _continue 'w _while -'w _do 'w _switch - -0 value _laststmtid -: _ ( tok -- ) \ parseStatement - dup statementnames sfind dup >r dup 0< if ( tok -1 ) \ V1=stmtidx - drop parseExpression read; else nip statementhandler swap wexec then - ops$ r> to _laststmtid ; -current ' parseStatement realias - -\ When there's variable initialization code, it has to come before the prelude -\ and we jump to it after we've created the stack frame. -0 value _initcode - -: parseDeclLine ( type -- ) - parseDeclarator ( ctype ) - dup _locvars ?dup if CType :append else to _locvars then begin ( ctype ) - '=' readChar? if ( ctype ) - _initcode not if here to _initcode then - dup ctype>op selop^ nextt parseExpression - selop^ vm=, ops$ nextt then ( ctype tok ) - dup ';' isChar? not while ( ctype tok ) - ',' expectChar CType type parseDeclarator ( ctype ) - dup _locvars CType :append repeat ( ctype tok ) 2drop ; - -\ Given a ctype for a function body that has a proper offset field, but that -\ hasn't been added to the symbol list yet, check if we have a prototype in the -\ symbols that correspond to this function. If yes, update the jump of that -\ prototype. -: ?updateFunctionPrototype ( ctype -- ) - dup CType name findSymbol ?dup if - dup CType :funcsig? over CType :incomplete? and if ( ctype found ) - CType offset to@! here >r ( ctype ) - dup CType offset vmjmp, r> to here ( ctype ) - else drop then then ( ctype ) drop ; - -\ '{' is already parsed -: parseFunctionBody ( ctype -- ) - 0 to _locvars 0 to _initcode to _curfunc _litarena :reserve ( ) - STORAGE_SF to@! curstorage >r - begin nextt dup parseType while ( tok type ) nip parseDeclLine repeat ( tok ) - to nexttputback r> to curstorage - _initcode if vmjmp[, >r then - _curfunc CType :static? not if sysdict _curfunc CType name entry then ( ) - here _curfunc to CType offset ( ) - _curfunc ?updateFunctionPrototype _curfunc addSymbol - _curfunc CType :argssize _locvars CType :size vmprelude, ( ) - _initcode ?dup if vmjmp, r> ]vmjmp then - 0 to _laststmtid parseStatements - _laststmtid 1 <> if vmret, then \ emit implicit return if needed - 0 to _curfunc ; - -: parseFunctionProto ( ctype tok -- ) - ';' expectChar dup addSymbol curstatic if - dup CType :incomplete! here ['] _err vmjmp, - \ allot a little extra space in case the replacement jump is wider - CELLSZ allot - else dup CType name sysdict @ find ?dup not if - CType name stype abort" not found" then then ( ctype addr ) - swap to CType offset ; - -: parseGlobalDecl ( ctype -- ) - dup addSymbol - dup CType :static? not if \ not static - dup CType name NEXTWORD ! create then ( ctype ) - here over to CType offset ( ctype ) - '=' readChar? if ( ctype ) - dup ctype>op selop^ nextt parseExpression vmop loc case ( ctype ) - VM_CONSTANT of = vmop arg , endof - VM_CONSTARRAY of = vmop arg @+ move, endof - _err endcase ops$ - else to nexttputback dup CType :size allot then ( ctype ) - ',' readChar? if - CType type parseDeclarator parseGlobalDecl - else ';' expectChar drop then ; - -\ Begin parsing incoming tokens for a new "element" (a function or a -\ declaration) and consume tokens until that element is finished parsing. That -\ element is written to memory at "here". -: cparse ( tok -- ) - cctypereserve 0 to curstatic - dup S" static" s= if drop nextt 1 to curstatic then - parseType _assert ( type ) - ';' readChar? if \ Only a type on a line is fine, carry on - drop exit then to nexttputback - parseDeclarator ( ctype ) - curstatic if dup CType :static! then - _ccdebug if ." parsing: " dup printtype nl> then - dup CType :funcsig? if ( ctype ) - '{' readChar? if dup parseFunctionBody - _ccdebug if - ." complete: " dup printtype nl> CType offset here over - spit nl> - else drop then - else parseFunctionProto then - else parseGlobalDecl then ( ) ; diff --git a/fs/comp/c/vm/commonhi.fs b/fs/comp/c/vm/commonhi.fs @@ -1,90 +0,0 @@ -\ Code common to all VM implementations (high part) -?f<< /lib/wordtbl.fs - -: ops$ vmop :init vmop^ :init neutral# ; - -: bothconst? vmop :foldable? vmop^ :foldable? and ; - -struct+[ VMOp - : :*n over _assert over 1 <> if - dup :isconst? if tuck arg * swap to arg else VMOp :*n then - else 2drop then ; - : :/n over _assert over 1 <> if - dup :isconst? if tuck arg swap / swap to arg else VMOp :/n then - else 2drop then ; - : :+n over if dup :isconst? if to+ arg else VMOp :+n then else 2drop then ; - : :&n dup :isconst? if tuck arg and swap to arg else VMOp :&n then ; - - : :typecast ( type self -- ) - 2dup to type swap typesize case ( self ) - 1 of = $ff swap :&n endof - 2 of = $ffff swap :&n endof - drop - endcase ; -]struct - -UNOPCNT wordtbl constops ( n -- n ) -'w neg 'w ^ 'w bool 'w not - -: unop doer , does> @ vmop :foldable? if - vmop arg swap constops swap wexec ( n ) to vmop arg - else unop, then ; -0 unop vmneg, 1 unop vmnot, 2 unop vmboolify, 3 unop vmboolnot, - -: unopmut doer , does> @ vmop :isconst? not _assert unopmut, ; -0 unopmut vm++op, 1 unopmut vm--op, 2 unopmut vmop++, 3 unopmut vmop--, - -\ perform necessary adjustments if we have pointer +/- scalar. -: _ptrariadj ( -- ) - vmop :typeptr? vmop^ :typeptr? not and if - vmop^ :>res vmop :*arisz vmop^ :*n then - vmop^ :typeptr? vmop :typeptr? not and if - vmop :>res vmop^ :*arisz vmop :*n then ; - -ARIOPCNT wordtbl constops ( n n -- n ) -'w + 'w - 'w * 'w / -'w mod 'w and 'w or 'w xor -'w lshift 'w rshift - -: _constbinop ( idx tbl -- ) - swap wtbl@ ( w ) vmop arg vmop^ arg rot execute ( n ) - to vmop arg vmop^ :init ; -: _ariop, bothconst? if constops _constbinop else ariop, then ; -: ariop doer , does> @ _ariop, ; -2 ariop vm*, 3 ariop vm/, -4 ariop vm%, 5 ariop vm&, 6 ariop vm|, 7 ariop vm^, -8 ariop vm<<, 9 ariop vm>>, - -: vm+, _ptrariadj 0 ( + ) _ariop, ; - -\ vm-, is special because it handles the special "pointer-pointer" situation. -: vm-, _ptrariadj vmop :typeptr? vmop^ :typeptr? and if - vmop :*arisz 1 ( - ) _ariop, ( divisor ) vmop :/n - TYPE_INT to vmop type - else 1 ( - ) _ariop, then ; - -: _movarray, \ special case, we have a {1, 2, 3} assign - vmop loc VM_STACKFRAME = _assert - vmop^ arg vmop^ :init @+ ( a sz ) - swap litn dup, RSP) vmop arg +) lea, litn \ on compiled PS: src dst len - compile move ; - -: _assignop, ( opid -- ) - vmop :isconst? not _assert - vmop^ loc VM_CONSTARRAY = if 10 = _assert _movarray, else assignop, then ; -: assignop doer , does> @ _assignop, ; -2 assignop vm*=, 3 assignop vm/=, -4 assignop vm%=, 5 assignop vm&=, 6 assignop vm|=, 7 assignop vm^=, -8 assignop vm<<=, 9 assignop vm>>=, 10 assignop vm=, - -: vm+=, _ptrariadj 0 ( + ) _assignop, ; -: vm-=, _ptrariadj 1 ( - ) _assignop, ; - -LOGOPCNT wordtbl constops ( n n -- n ) -'w < 'w > 'w <= 'w >= -'w = 'w <> :w and bool ; :w or bool ; - -: logop doer , does> @ - bothconst? if constops _constbinop else logop, then TYPE_INT to vmop type ; -0 logop vm<, 1 logop vm>, 2 logop vm<=, 3 logop vm>=, -4 logop vm==, 5 logop vm!=, 6 logop vm&&, 7 logop vm||, diff --git a/fs/comp/c/vm/commonlo.fs b/fs/comp/c/vm/commonlo.fs @@ -1,115 +0,0 @@ -\ Code common to all VM implementations (low part) - -\ Note: there can only be one implementation of the VM loaded in memory at once. -\ Loading another VM impl will break the previous one. In Dusk, it's not a -\ problem because there is no CC cross-compiling. You'll always want to compile -\ with one VM: your CPU's. - -\ Relation between "loc" and type: -\ Location is where the value is stored. We track a level of indirection here -\ for performance reasons: CPUs generally can access locations with a level of -\ indirection. The type contains the "logical" level of indirection of the value -\ stored in the location. If, for example, we have a int* stored in a -\ VM_REGISTER, it's the exact equivalent of having an int stored in a -\ VM_*REGISTER. The number is the same. -require /sys/scratch.fs -?f<< /comp/c/type.fs - -4 const UNOPCNT \ neg not boolify boolnot -4 const UNOPMUTCNT \ ++op --op op++ op-- -10 const ARIOPCNT \ + - * / % & | ^ << >> -11 const ASSIGNOPCNT \ += -= *= /= %= &= |= ^= <<= >>= = -8 const LOGOPCNT \ < > <= >= == != && || - -: _err abort" vm error" ; -: _assert not if _err then ; - -\ Execution context (function) - -0 value argsz \ size of the argument portion of the SF. -0 value locsz \ size of the "local vars" portion of the SF. - -\ Operands definition and selection -\ Locations constant: where is the op located? -$00 const VM_NONE \ nowhere -$01 const VM_CONSTANT \ a constant of value "arg" -$02 const VM_STACKFRAME \ on RS at RSP+arg -$03 const VM_ARGSFRAME \ on PS at PSP+arg -$04 const VM_REGISTER \ in an implementation-specific register of id "arg" -$05 const VM_CONSTARRAY \ pointer to bytes beginning with 4b size (in bytes) -\ Below, references to a location (points to X) -$11 const VM_*CONSTANT -$12 const VM_*STACKFRAME -$13 const VM_*ARGSFRAME -$14 const VM_*REGISTER - -struct[ VMOp - sfield loc \ one of the VM_ constants - sfield arg - sfield type \ from comp/c/type - sfield other \ link to the "other" op - 4 &+ 'arg - \ Initialize op to VM_NONE, "freeing" any resource it held. - : :init VM_NONE over to loc TYPE_INT swap to type ; - : :loclo loc $f and ; - \ Is loc a pointer? - : :locptr? loc $10 and bool ; - \ Is type a pointer? - : :typeptr? type type*lvl bool ; - : :isconst? loc VM_CONSTANT = ; - : :isconstlo? :loclo VM_CONSTANT = ; - : :foldable? dup :isconst? swap :typeptr? not and ; - : :noop# loc VM_NONE = _assert ; - : :hasop# loc VM_NONE <> _assert ; - : :isconst# :isconst? _assert ; - : :>const ( n self -- ) dup :noop# VM_CONSTANT over to loc ( n self ) to arg ; - \ Ensure that op is a proper "result", that is, a proper destination operand - \ that is not going to mutate its original value. - : :>res abort" Not implemented" ; - : :keep ( self -- 'copy ) 12 syspad :move ; - : :push ( self -- 'copy ) dup >r :keep VM_NONE to r> loc ; - : :pop ( 'copy self -- ) dup :noop# 12 move ; - : :&loc dup :locptr? _assert dup :loclo swap to loc ; - : :&op dup :&loc dup type type*lvl+ swap to type ; - : :type- dup type type*lvl- swap to type ; - : :*op dup loc case - VM_CONSTANT of = endof - VM_STACKFRAME of = endof - VM_ARGSFRAME of = endof - VM_REGISTER of = endof - _err endcase - dup loc $10 or over to loc :type- ; - : :*arisz ( self -- n ) type *ariunitsz ; -]struct - -create operands VMOp SZ 2 * allot -operands structbind VMOp vmop -operands VMOp SZ + structbind VMOp vmop^ \ the "other" op -vmop^ :self to vmop other -vmop :self to vmop^ other - -: _sel ['] vmop rebind vmop other ['] vmop^ rebind ; -: selop^ vmop other _sel ; - -: .ops - vmop loc .x1 spc> vmop type printtype spc> vmop arg .x spc> - vmop^ loc .x1 spc> vmop^ type printtype spc> vmop^ arg .x nl> ; - -\ Managing operands - -: isconst# vmop :isconst? _assert ; -: const>op ( n -- ) vmop :>const ; -: constarray>op ( a -- ) vmop :noop# VM_CONSTARRAY to vmop loc to vmop arg ; -: sf+>op ( off -- ) vmop :noop# VM_*STACKFRAME to vmop loc to vmop arg ; -: ps+>op ( off -- ) vmop :noop# VM_*ARGSFRAME to vmop loc to vmop arg ; -: mem>op ( n -- ) vmop :noop# VM_*CONSTANT to vmop loc to vmop arg ; -: ctype>op ( ctype -- ) - vmop :noop# - dup CType type to vmop type ( ctype ) - dup case - of CType :funcsig? r@ to vmop type r@ CType offset const>op endof - of CType :isglobal? r@ CType offset mem>op endof - of CType :isarg? r@ CType offset ps+>op endof - r@ CType offset sf+>op - endcase - CType nbelem if vmop :&op then ; diff --git a/fs/comp/c/vm/forth.fs b/fs/comp/c/vm/forth.fs @@ -1,169 +0,0 @@ -\ C compiler virtual machine for Forth targets - -?f<< /comp/c/vm/commonlo.fs -?f<< /lib/wordtbl.fs -?f<< /asm/hal.fs - -0 value psoff -: PS+ CELLSZ to+ psoff ; -: PS- CELLSZ neg to+ psoff ; -: halsz! ( operand sz -- operand ) - case 1 of = 8b) endof 2 of = 16b) endof endcase ; - -struct+[ VMOp - : _compile ( arg loc -- ) \ compile "straight" operands, errors on * ops. - case ( arg ) - VM_CONSTANT of = litn PS+ endof - VM_STACKFRAME of = dup, PS+ RSP) swap +) lea, endof - VM_ARGSFRAME of = dup, PS+ PSP) swap psoff CELLSZ - + +) lea, endof - VM_REGISTER of = dup, PS+ PSP) swap psoff CELLSZ - + +) @, endof - _err endcase ; - : :compile& dup :locptr? _assert bi arg | :loclo _compile ; - \ Dereference PS TOS using this operand's type size - : :TOS@ ( self -- ) W) swap type typesize halsz! @, ; - \ Resolve current operand and dereferences it if needed - : :compile ( self -- ) - bi+ arg | :locptr? - if over :loclo _compile :TOS@ else swap loc _compile then ; - : :compile$ - \ special case that happens often: our op is current top of stack. When we - \ don't want to keep the op (hence the "$"), then no compiling is necessary. - \ just do nothing. It saves a lot of bytecode. - dup loc VM_REGISTER = over arg neg psoff = and - not if dup :compile then :init ; - \ "save" the value currently on PS TOS as a "register" (see doc/cc/forth) - : :>reg VM_REGISTER over to loc psoff neg swap to arg ; - \ dereference current operand - : :*op dup :locptr? if dup :compile dup :>reg then VMOp :*op ; - : :*n ( n self -- ) tuck :compile litn compile * :>reg ; - : :/n ( n self -- ) tuck :compile litn compile / :>reg ; - : :+n ( n self -- ) tuck :compile W+n, :>reg ; - : :&n ( n self -- ) tuck :compile andn, :>reg ; - : :>res dup :compile :>reg ; -]struct - -: _psrewind ?dup if CELLSZ - ?dup if ps+, then drop, then ; -\ Free elements leaked to PS during the execution of the function -: neutral# 0 to@! psoff _psrewind ; - -\ generate function prelude code by allocating "locsz" bytes on RS. -: vmprelude, ( argsz locsz -- ) - to locsz to argsz 0 to psoff - locsz if locsz neg rs+, then ; - -\ deallocate locsz and argsz. If result is set, keep a 4b in here and push the -\ result there. -: vmret, - vmop^ :noop# \ returning with a second operand? something's wrong - vmop loc dup >r if vmop :compile$ PS- then - 0 to@! psoff argsz + ?dup if - r@ ( had loc? ) if ps+, else _psrewind then then rdrop - locsz ?dup if rs+, then exit, ; - -: vmcall, ( ?argN .. ?arg0 nargs -- ) dup >r \ V1=nargs - vmop :push >r - for vmop :pop vmop :compile$ next - r> vmop :pop VM_CONSTANT vmop loc = if - vmop arg execute, vmop :init - else vmop :compile$ compile execute PS- then - r> ( nargs ) CELLSZ * neg to+ psoff ; - -\ Allocate a new register for active op and pop 4b from PS into it. -: vmpspop, vmop :noop# PS+ vmop :>reg ; - -\ Push active op to PS. -: vmpspush, vmop :compile$ PS- ; - -\ Jumping -: ]vmjmp [compile] then ; -: vmjmp, [compile] again ; -: vmjmp[, [compile] ahead ; -\ In conditional jumps below, the source of the test is in current op -\ However, because we don't track "psoff" across branches, we *have* to have a -\ neutral level before the jump, which means that this flag that we're pushing -\ on PS *has* to be right after the last argument of the args frame. -\ The same logic applies to vmswitch,. -: _compileFinal - vmop^ :noop# vmop :compile$ PS- 0 to@! psoff ?dup if ps+, then ; -: vmjz, ( a -- ) _compileFinal [compile] until ; -: vmjz[, ( -- a ) _compileFinal [compile] if ; -: vmjnz, ( a -- ) _compileFinal compile not [compile] until ; -: vmjnz[, ( -- a ) _compileFinal compile not [compile] if ; - -UNOPCNT wordtbl unop -'w neg 'w ^ 'w bool 'w not -: unop, ( opid -- ) vmop :compile$ unop swap wexec, vmop :>reg ; - -\ Comptime sig: incsz -- Runtime sig: -- n -UNOPMUTCNT >> wordtbl _tbl32 -:w ( ++op/--op ) W) [+n], W) @, ; -:w ( op++/op-- ) W>A, W) @, A) [+n], ; - -UNOPMUTCNT >> wordtbl _tbl16 -:w ( ++op/--op ) W) 16b) [+n], W) 16b) @, ; -:w ( op++/op-- ) W>A, W) 16b) @, A) 16b) [+n], ; - -UNOPMUTCNT >> wordtbl _tbl8 -:w ( ++op/--op ) W) 8b) [+n], W) 8b) @, ; -:w ( op++/op-- ) W>A, W) 8b) @, A) 8b) [+n], ; - -: unopmut, ( opid -- ) - vmop type typesize - case 1 of = _tbl8 endof 2 of = _tbl16 endof _tbl32 endcase ( opid tbl ) - over >> wtbl@ ( opid w ) vmop :compile& - vmop :*arisz rot 1 and if neg then swap ( incsz w ) execute vmop :>reg ; - -ARIOPCNT 1+ ( for = ) wordtbl _tbl -'w + 'w - 'w * 'w / -'w mod 'w and 'w or 'w xor -'w lshift 'w rshift 'w nip - -: _binop, ( opid tbl -- ) - vmop :compile vmop^ :compile$ - swap wexec, PS- vmop :>reg ; - -: ariop, ( opid -- ) _tbl _binop, ; - -\ Copy the contents of op2 in the memory address pointed out by op1 and deinit -\ op2. In other words, perform an assign with the right part as op2 and the left -\ part as op1. -\ an assignop, is like a unop in the sense that it operates directly on op1, but -\ with the participation of op2. -: assignop, ( opid -- ) - vmop type typesize ( opid sz ) vmop :compile& \ addr in W - dup, PS+ W) over halsz! @, vmop^ :compile$ ( opid sz ) - swap _tbl swap wexec, PS- ( sz ) \ result on TOS - PSP) swap halsz! [!], nip, PS- vmop :>reg ; - -: _s $80000000 + swap $80000000 + swap ; -LOGOPCNT wordtbl _tblsigned -:w _s < ; :w _s > ; :w _s <= ; :w _s >= ; -'w = 'w <> 'w and? 'w or? - -LOGOPCNT wordtbl _tblunsigned -'w < 'w > 'w <= 'w >= -'w = 'w <> 'w and? 'w or? - -: logop, ( opid -- ) - vmop type typeunsigned? if _tblunsigned else _tblsigned then _binop, ; - -: vm?:, ( condop -- ) - vmop^ :compile$ \ false-res on TOS - vmop^ :pop vmop^ :compile$ [compile] if PS- - \ we're in the "true" branch. drop the false res, replace with true. - compile drop PS- vmop :compile vmop :init - ]vmjmp vmop :>reg ; - -code _lookup ( nref lookup -- ) - W>A, A) @, - -8 rs+, RSP) 4 +) !, 0 LIT>W, RSP) !, begin \ RS+0=i RS+4=totcnt - RSP) @, RSP) 4 +) cmp, 0 NZ) branchC, \ not found - 8 rs+, nip, drop, exit, then - 1 RSP) [+n], CELLSZ A+n, A) @, - PSP) cmp, NZ) branchC, drop \ Z=match - \ we have a match, add totcnt*CELLSZ to A, dereference. that's our target. - RSP) 4 +) @, 2 <<n, RSP) !, W<>A, RSP) +, W) @, \ W=target - 12 rs+, W>A, nip, drop, branchA, - -: vmswitch, ( 'lookup -- ) - _compileFinal litn W) @, compile _lookup ; diff --git a/fs/comp/c/vm/i386.fs b/fs/comp/c/vm/i386.fs @@ -1,309 +0,0 @@ -\ C compiler virtual machine for i386 - -\ For usage example, see tests/comp/c/vm.fs -?f<< /asm/i386.fs -?f<< /lib/bit.fs -?f<< /lib/wordtbl.fs -?f<< /comp/c/vm/commonlo.fs - -\ Mask of registers (by their ID in asm/i386) that can be allocated as -\ VM_REGISTER. DX is not used in the regular allot stack so that it can be used -\ "freely" in code generation. -$cb const USABLEREGS \ 11001011 = di si bx cx ax -0 value regused \ a bit mask of register used. - -\ i386 register ID constants -0 const AX 1 const CX 2 const DX 7 const DI - -: regusable? ( regid -- f ) USABLEREGS swap bit? ; -: regused? ( regid -- f ) regused swap bit? ; -: reguse ( regid -- ) - dup regused? not _assert - regused swap bit1! to regused ; -\ Allocate a free register. -: regallot ( -- regid ) - 0 8 for ( regid ) - dup regusable? if dup regused? not if ( regid ) - dup reguse ( regid ) break then then - 1+ next abort" TODO: support deeper expressions" then ; -: regfree ( regid -- ) - regused over bit? not if - .x1 spc> regused .x1 spc> abort" register allocation imbalance" then - regused swap bit0! to regused ; -: pushregs - 8 for i 1- dup regused? if r) push, else drop then next ; -: popregs - 8 for 8 i - dup regused? if r) pop, else drop then next ; - -struct+[ VMOp - : :?regfree dup :loclo VM_REGISTER = if arg regfree else drop then ; - \ reinitialize selected op to VM_NONE and dealloc registers if needed - : :init dup :?regfree VMOp :init ; - - : :dest# loc VM_CONSTANT <> _assert ; - : :isAX? dup :loclo VM_REGISTER = swap arg AX = and ; - : :typesz! ( opmod self -- opmod ) - type typesize case 1 of = 8b) endof 2 of = 16b) endof endcase ; - \ Resolve current operand as an assembler "src" argument. - : :compile ( self -- opmod ) - bi arg | loc case - VM_CONSTANT of = i) endof - VM_STACKFRAME of = abort" can't address VM_STACKFRAME directly" endof - VM_REGISTER of = r) endof - VM_*CONSTANT of = m) endof - VM_*STACKFRAME of = sp swap d) endof - VM_*ARGSFRAME of = bp swap d) endof - VM_*REGISTER of = r) 0 d) endof - abort" can't :compile location" endcase ; - : :compilesz bi :compile | :typesz! ; - : :compiletest - bi :compilesz | loc VM_REGISTER = if dup test, else 0 i) cmp, then ; - - \ Force current operand to be copied to a register - : _ regallot tuck r) ( rid self dst ) over :compilesz ?movzx, to arg ; - : :>reg dup >r loc case \ V1=self - VM_CONSTANT of = V1 _ VM_REGISTER V1 to loc endof - VM_*CONSTANT of = V1 _ VM_REGISTER V1 to loc endof - VM_REGISTER of = endof - VM_*REGISTER of = endof - VM_STACKFRAME of = - regallot dup r) sp mov, - V1 arg if dup r) V1 arg i) add, then ( regid ) - V1 to arg VM_REGISTER V1 to loc endof - VM_*STACKFRAME of = V1 _ VM_REGISTER V1 to loc endof - VM_*ARGSFRAME of = V1 _ VM_REGISTER V1 to loc endof - _err - endcase rdrop ; - - : :complex? - dup loc VM_*REGISTER = over :loclo VM_STACKFRAME = or - over loc VM_*ARGSFRAME = or swap loc VM_*CONSTANT = or ; - - \ Resolve any referencing into a "simple" operand, that is, an operand that - \ can be combined to a "complex" operand. There are 2 options: VM_REGISTER and - \ VM_CONSTANT. If the op isn't one of those, we transform it. - : :>simple - dup >r loc case \ V1=self - VM_STACKFRAME of = V1 :>reg endof - VM_*CONSTANT of = V1 :>reg endof - VM_*STACKFRAME of = V1 :>reg endof - VM_*ARGSFRAME of = V1 :>reg endof - VM_*REGISTER of = - V1 :compile V1 :compilesz ?movzx, VM_REGISTER V1 to loc endof - endcase rdrop ; - - \ Before doing an operation on two operands, we verify that they are - \ compatible, that is, whether we have at most one "complex" operand. if we - \ have two complex operands, "other" is made "simple". - : :?>simple dup :complex? if other :>simple else drop then ; - - \ if possible, dereference current operand - : :*op dup loc case - VM_*CONSTANT of = dup :>reg endof - VM_*STACKFRAME of = dup :>reg endof - VM_*ARGSFRAME of = dup :>reg endof - VM_*REGISTER of = dup :>simple endof - endcase VMOp :*op ; - - : :>res dup :>reg :>simple ; - - : :+n ( n self -- ) dup :>res :compile swap i) add, ; - : :&n ( n self -- ) dup :>res :compile swap i) and, ; - -]struct - -\ Verify that we're in "neutral" position with regards to registers -: neutral# regused if abort" unbalanced reg allot/free" then ; - -\ If one op is larger than the other, copy the smaller one to a register and -\ copy the type of the larger op to the smaller. -: harmonizeops vmop type typesize vmop^ type typesize 2dup < if - 2drop vmop :>res vmop^ type to vmop type else > if - vmop^ :>res vmop type to vmop^ type then then ; - -\ Jumping -: ]vmjmp ( 'jump_addr -- ) forward! ; -: vmjmp, ( a -- ) abs>rel jmp, ; -: vmjmp[, ( -- a ) forward jmp, ; -\ we take current op and test whether it's zero, setting Z. If the op is a -\ simple register, the "test eax, eax" form is more compact. Otherwise, use -\ test ..., -1. -: vmtest, vmop :>reg vmop :compiletest vmop :init ; -: vmjz, ( a -- ) vmtest, abs>rel jz, ; -: vmjz[, ( -- a ) vmtest, forward jz, ; -: vmjnz, ( a -- ) vmtest, abs>rel jnz, ; -: vmjnz[, ( -- a ) vmtest, forward jnz, ; - -\ Code generation - Functions, calls, ret, pspush, pspop - -\ generate function prelude code by allocating "locsz" bytes on RS. -: vmprelude, ( argsz locsz -- ) - to locsz to argsz - locsz if sp locsz i) sub, then ; -\ deallocate locsz and argsz. If result is set, keep a 4b in here and push the -\ result there. -: vmret, - vmop^ :noop# \ returning with a second operand? something's wrong - argsz vmop loc if CELLSZ - then - vmop :>simple \ for bp 0 d) src mov, to work, "src" has to be "simple" - locsz if sp locsz i) add, then - ?dup if bp swap i) add, then - vmop loc if bp 0 d) vmop :compile mov, then - ret, ; - -: vmcall, ( ?argN .. ?arg0 nargs -- ) - vmop :push >r \ V1=callop - ?dup if dup >r \ V2=nargs - for ( ... arg ) - vmop :pop vmop :>simple - bp V2 i - 1+ CELLSZ * neg d) vmop :compile mov, vmop :init next - bp r> ( nargs ) CELLSZ * i) sub, - then - pushregs - r> vmop :pop VM_CONSTANT vmop loc = if - vmop arg VM_NONE to vmop loc abs>rel else vmop :compile then - call, popregs vmop :init ; - -\ Allocate a new register for active op and pop 4b from PS into it. -: vmpspop, - vmop :noop# VM_REGISTER to vmop loc regallot dup to vmop arg r) bp 0 d) mov, - bp CELLSZ i) add, ; - -\ Push active op to PS. -: vmpspush, - vmop :>simple bp CELLSZ i) sub, bp 0 d) vmop :compile mov, vmop :init ; - -\ mul and div are special and cannot use binopprep for two reasons: their target -\ operand is hardcoded to EAX, the other operand needs to be a register and EDX -\ gets overwritten by the operation (and, for div, it needs to be set to 0). -struct+[ VMOp - 0 value _restoreAX - : :forceAX - 0 to _restoreAX - dup :isAX? not if - AX regused? if - dup other :isAX? if - regallot dup r) ax mov, ( self regid ) - over other to arg ( self ) - else 1 to _restoreAX ax push, then - else regallot drop then - ax swap :compilesz ?movzx, - else drop then ; - : :?restoreAX dup :isAX? not if - :compilesz ax mov, _restoreAX if ax pop, else AX regfree then - else drop then ; - : :*n ( n self -- ) - dup :>res dup :forceAX dx rot i) mov, dx mul, :?restoreAX ; - : :/n ( n self -- ) - DI regused? not _assert - dup :>res dup :forceAX dx dx xor, di rot i) mov, di div, :?restoreAX ; -]struct - -: _pre vmop :forceAX vmop^ :>res ; -: _post vmop^ :init vmop :?restoreAX ; -: _*= _pre vmop^ :compile mul, _post ; -: _/= _pre dx dx xor, vmop^ :compile div, _post ; -: _%= _pre dx dx xor, vmop^ :compile div, ax dx mov, _post ; - -ASSIGNOPCNT wordtbl _tbl -'w add, 'w sub, 'w _*= 'w _/= -'w _%= 'w and, 'w or, 'w xor, -'w shl, 'w shr, 'w mov, - -: _mulop? ( opid -- f ) 2 - 3 < ; -: _mulop ( opid ) _tbl swap wexec ; -: _shcl? ( opid -- f ) 8 - 2 < vmop^ :isconst? not and ; -: _shcl - CX regused? dup >r if - dx cx mov, vmop arg CX = if DX to vmop arg then - else CX reguse then \ V1=CX used? - vmop^ arg CX <> if cx vmop^ :compilesz ?movzx, then - vmop :compilesz swap ( opid ) 8 - if cl shr, else cl shl, then - vmop^ :init r> ( CXused? ) if - cx dx mov, vmop arg DX = if CX to vmop arg then - else CX regfree then ; -: ariop, ( opid -- ) - vmop :>res harmonizeops - dup _mulop? if _mulop exit then - dup _shcl? if _shcl exit then - >r vmop :compilesz vmop^ :compile - _tbl r> ( opid ) wexec vmop^ :init ; - -: assignop, ( opid -- ) - dup _mulop? if _mulop exit then - dup _shcl? if _shcl exit then - >r vmop :?>simple vmop :compilesz vmop^ :hasop# vmop^ :compile - _tbl r> ( opid ) wexec vmop^ :init ; - -: _z>vmop vmop :compile 0 i) mov, vmop :compile setnz, ; -UNOPCNT wordtbl _tbl -'w neg, 'w not, -:w ( boolify ) neg, _z>vmop ; -:w ( boolnot ) neg, vmop :compile 0 i) mov, vmop :compile setz, ; - -: unop, ( opid -- ) >r vmop :>res vmop :compilesz _tbl r> ( opid ) wexec ; - -: _doit vmop :compilesz swap ( opmod n ) case - 1 of = inc, endof - -1 of = dec, endof - r@ i) add, endcase ; -: _pre ( n -- ) vmop :dest# _doit ; -\ It's a bit complicated here. Before we inc/dec, we need a copy of the current -\ value in a new register, which will be our result. -: _post ( n -- ) - regallot dup r) vmop :compilesz ?movzx, ( n regid ) - swap _doit vmop :?regfree ( regid ) to vmop arg VM_REGISTER to vmop loc ; -UNOPMUTCNT wordtbl _tbl -:w ( ++op ) _pre ; -:w ( --op ) neg _pre ; -:w ( op++ ) _post ; -:w ( op-- ) neg _post ; - -: unopmut, ( opid -- ) >r vmop :*arisz _tbl r> ( opid ) wexec ; - -: _ - vmop :>res vmop :compilesz vmop^ :compile cmp, vmop^ :init - vmop :compile 0 i) mov, vmop :compile ; -: _&& vmop :>reg vmop :compilesz vmop :compile test, forward8 jz, - \ TODO: The :>reg below is a quick hack to go through cases where vmop^ is a - \ const. If we do proper constant propagation at the AST level, this should - \ never happen and :>reg becomes spurious. - vmop^ :>reg vmop^ :compiletest vmop^ :init forward! _z>vmop ; -: _|| vmop :>reg vmop :compile vmop^ :compile or, vmop^ :init - vmop :compile neg, _z>vmop ; - -LOGOPCNT wordtbl _tblsigned -:w _ setl, ; :w _ setg, ; :w _ setle, ; :w _ setge, ; -:w _ setz, ; :w _ setnz, ; 'w _&& 'w _|| - -LOGOPCNT wordtbl _tblunsigned -:w _ setb, ; :w _ seta, ; :w _ setbe, ; :w _ setae, ; -:w _ setz, ; :w _ setnz, ; 'w _&& 'w _|| - -: logop, ( opid -- ) >r - harmonizeops vmop type typeunsigned? - if _tblunsigned else _tblsigned then r> ( opid ) wexec ; - -: vm?:, ( condop -- ) - vmop :>res \ true op in reg - vmop :push swap vmop :pop vmjnz[, swap vmop :pop \ vmop back to its res - vmop :compile vmop^ :compile mov, vmop^ :init \ move false op to true reg - ]vmjmp ; - -: vmswitch, ( 'lookup -- ) - vmop^ :noop# - \ Let's force vmop into AX. After that, none of the other regs are used, so - \ we're clear to use them in our lookup algo - vmop :isAX? if vmop :>simple - else AX regused? not _assert ax vmop :compilesz ?movzx, then - vmop :init - di swap ( 'lookup ) m) mov, - cx di 0 d) mov, - cx push, - di CELLSZ i) add, - repnz, scas, - cx pop, \ Z preserved - forward8 jnz, - \ we have a match - cx dec, cx 2 i) shl, di cx add, di 0 d) jmp, - forward! ; diff --git a/fs/comp/c/vm/vm.fs b/fs/comp/c/vm/vm.fs @@ -1,6 +0,0 @@ -require sys/scratch.fs - -S" /comp/c/vm" curpath :find# ( path ) -$10 syspad :[ ARCH c@+ dup 3 + c, move, ," .fs" syspad :] ( path fname ) -swap Path :child Path :fload -f<< /comp/c/vm/commonhi.fs diff --git a/fs/home/codesz.fs b/fs/home/codesz.fs @@ -19,12 +19,7 @@ ." CC\n" 7 stringlist core "asm" "comp" "drv" "fs" "lib" "sys" "xcomp" core slistlc . nl> -." C compiler, excluding arch-specific backends\n" -p" comp/c" dirlc value cclc -p" comp/c/vm/forth.fs" lc neg to+ cclc -p" comp/c/vm/i386.fs" lc value i386lc -i386lc neg to+ cclc -cclc . nl> -." i386 assembler and CC backend\n" -p" asm/i386.fs" lc to+ i386lc -i386lc . nl> +." C compiler\n" +p" comp/c" dirlc . nl> +." i386 assembler\n" +p" asm/i386.fs" lc . nl>