duskos

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

commit 69600129406d3e2a7d2e06d6567325c158f22ced
parent 39758c47430b9d5cf0b8989ab82f983f1b64599b
Author: Virgil Dupras <hsoft@hardcoded.net>
Date:   Sat,  3 Dec 2022 21:29:03 -0500

comp/c: add switch statement

Diffstat:
MROADMAP.md | 1-
Mfs/asm/i386.fs | 2+-
Mfs/comp/c/pgen.fs | 78+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Mfs/comp/c/vm/forth.fs | 11+++++++++++
Mfs/comp/c/vm/i386.fs | 16++++++++++++++++
Mfs/doc/cc/impl.txt | 12++++++++++++
Mfs/tests/comp/c/cc.fs | 4++++
Mfs/tests/comp/c/test.c | 11+++++++++++
8 files changed, 116 insertions(+), 19 deletions(-)

diff --git a/ROADMAP.md b/ROADMAP.md @@ -10,7 +10,6 @@ In a general way, here's the list of things missing from Dusk C for it to be consider complete: * union -* switch * enum * goto * float diff --git a/fs/asm/i386.fs b/fs/asm/i386.fs @@ -164,7 +164,7 @@ ES _ es SS _ ss DS _ ds FS _ fs GS _ gs $c3 op ret, $90 op nop, $fa op cli, $fb op sti, $fc op cld, $ac op lodsb, $ad op lods, $a6 op cmpsb, $a7 op cmps, -$a4 op movsb, $a5 op movs, +$a4 op movsb, $a5 op movs, $ae op scasb, $af op scas, $f3 op repz, $f2 op repnz, $f3 op rep, $9c op pushf, $9d op popf, $cf op iret, diff --git a/fs/comp/c/pgen.fs b/fs/comp/c/pgen.fs @@ -88,8 +88,12 @@ BOPSCNT wordtbl bopgentbl ( -- ) '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 1 then ; + 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? @@ -123,13 +127,15 @@ alias noop parseExpression ( tok -- ) \ forward declaration ',' 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 parseExpression - vmop^ :noop# vmop :isconst# vmop arg ops$ ( ctype nbelem ) - nextt ']' expectChar ( ctype nbelem ) + nextt parseConstExpr nextt ']' expectChar ( ctype nbelem ) over to CType nbelem endof '(' of isChar?^ dup CType :funcsig! STORAGE_PS to@! curstorage >r @@ -336,21 +342,18 @@ alias noop parseStatement ( tok -- ) \ forward declaration : parseStatements ( -- ) begin '}' readChar? not while parseStatement repeat ; -8 stringlist statementnames - "{" "return" "if" "for" "pspush" "break" "while" "do" -8 wordtbl statementhandler ( -- ) -'w parseStatements ( { ) -:w ( return ) - \ empty returns are allowed +: _return \ empty returns are allowed ';' readChar? not if parseExpression read; then vmret, ops$ ; -:w ( if ) + +: _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 ; -:w ( for ) + +: _for breaks :count >r read( ';' readChar? not if parseExpression ops$ read; then \ initialization here nextt parseExpression read; vmjz[, vmjmp[, ( caddr cjmpz cjmp ) \ control @@ -359,21 +362,62 @@ alias noop parseStatement ( tok -- ) \ forward declaration swap vmjmp, ( cjmpz cjmp aaddr ) \ adjustment swap ]vmjmp nextt parseStatement ( cjmpz aaddr ) vmjmp, ]vmjmp r> resolvebreaks ; -:w ( pspush ) - read( nextt parseExpression vmpspush, read) read; ; -:w ( break ) addbreak read; ; -:w ( while ) + +: _pspush read( nextt parseExpression vmpspush, read) read; ; + +: _break addbreak read; ; + +: _while breaks :count >r here read( nextt parseExpression read) vmjz[, ops$ nextt parseStatement ( wjmp waddr ) swap vmjmp, ]vmjmp r> resolvebreaks ; -:w ( do ) + +: _do breaks :count >r here nextt parseStatement ( daddr ) 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. +$40 const MAXSWITCHCASES +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 S" case" s= if + drop nextt parseConstExpr _cases :push here _cases :push + nextt ':' expectChar nextt then ( tok ) + dup '}' isChar? not while ( tok ) + dup S" default" s= not while ( tok ) + parseStatement 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 ; + +9 stringlist statementnames + "{" "return" "if" "for" "pspush" "break" "while" "do" "switch" +9 wordtbl statementhandler ( -- ) +'w parseStatements 'w _return 'w _if 'w _for +'w _pspush 'w _break 'w _while 'w _do +'w _switch + 0 value _laststmtid : _ ( tok -- ) \ parseStatement dup statementnames sfind dup >r dup 0< if ( tok -1 ) \ V1=stmtidx diff --git a/fs/comp/c/vm/forth.fs b/fs/comp/c/vm/forth.fs @@ -167,3 +167,14 @@ LOGOPCNT wordtbl _tblunsigned \ we're in the "true" branch. drop the false res, replace with true. compile drop PS- vmop :compile vmop :init ]vmjmp vmop :>reg ; + +: _lookup ( nref lookup -- ) + @+ ?dup if rot >r dup >r >r begin ( a ) \ V1=nref V2=totcnt V3=loop + @+ V1 = if ( a ) + \ match! we jump to a blind spot by dropping the return address of this + \ call and replacing it with our target. + rdrop ( loop ) r> ( a totcnt ) 1- CELLSZ * + @ ( tgtaddr ) + rdrop ( nref ) rdrop ( oldaddr ) >r exit then + next ( a ) drop rdrop rdrop else ( nref lookup ) 2drop then ; +: vmswitch, ( 'lookup -- ) + vmop :compile$ litn compile @ compile _lookup PS- PS- ; diff --git a/fs/comp/c/vm/i386.fs b/fs/comp/c/vm/i386.fs @@ -298,3 +298,19 @@ LOGOPCNT wordtbl _tblunsigned 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 -- ) + DI regused? not _assert CX regused? not _assert + vmop :isAX? not if AX regused? if dx ax mov, then ax vmop :compile mov, then + di ( 'lookup ) m) mov, + cx di 0 d) mov, + cx push, + di CELLSZ i) add, + repnz, scas, + cx pop, \ Z preserved + vmop :isAX? not if AX regused? if ax dx mov, then then \ Z preserved + vmop :init + forward8 jnz, + \ we have a match + cx dec, cx 2 i) shl, di cx add, di 0 d) jmp, + forward! ; diff --git a/fs/doc/cc/impl.txt b/fs/doc/cc/impl.txt @@ -179,6 +179,18 @@ importantly, it will "merge" vmop and vmop^ into one single op, that is, the location that *both* branches of the condition will return to. That will be the result of this binop. This "merge" is arch-specific. +### vmswitch, ( 'lookup -- ) + +Resolve vmop and apply "switch" logic to the supplied *pointer* to lookup table. +The lookup table has this format: + +- 4b count +- count*4b values to compare +- count*4b jump addresses + +We generate code to compare vmop with all elements of the lookup table and, if +found, jump to the corresponding address. If no element match, no jump is made. + ### Jumping in the VM There are 2 kinds of jumps: forward and backward. In forward jumps, we need to diff --git a/fs/tests/comp/c/cc.fs b/fs/tests/comp/c/cc.fs @@ -69,6 +69,10 @@ mydata $42 structset mydata 4 + @ $42345678 #eq globdata 4 + 16b @ 42 #eq 0 callfuncidx 42 #eq 2 callfuncidx 82 #eq +33 switchstmt 0 #eq +42 switchstmt 12 #eq +1234 switchstmt 3 #eq +5678 switchstmt 2 #eq 2 3 binop1 1 #eq '2' binop2 44 #eq diff --git a/fs/tests/comp/c/test.c b/fs/tests/comp/c/test.c @@ -276,6 +276,17 @@ int callfuncidx(int idx) { } } +int switchstmt(int x) { + int y = 1; + switch (x) { + case 42: return 12; + case 1234: ++y; + case 5678: ++y; break; + default: --y; + } + return y; +} + // Below this comment are simple construct that were buggy before int binop1(int a, int b) { int c;