duskos

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

commit 7aae1a82b27a74605f0988f3350dad7ba87264f8
parent d500f29ba56b38180cca8c7a3f9be1367746be2c
Author: Virgil Dupras <hsoft@hardcoded.net>
Date:   Tue, 21 Jun 2022 11:22:27 -0400

cc: straighten out binop generation and SF offsets

I was a bit cowboy-like with my design of stack frames. Things are a bit icky
when we consider that in x86, EBP points directly to the PS TOS (rather than
EBP+4). This complicates things. See doc/x86.

I don't think I'll go with making the TOS point to EBP+4, but I'll at least
contain this ickiness to cc/vm. Right now, it leaks in cc/map and cc/gen.

Diffstat:
Mfs/cc/gen.fs | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mfs/cc/map.fs | 6+++---
Mfs/cc/vm.fs | 7++++++-
Mfs/doc/x86.txt | 20++++++++++++++++++--
Mfs/tests/cc/cc.fs | 1+
5 files changed, 92 insertions(+), 12 deletions(-)

diff --git a/fs/cc/gen.fs b/fs/cc/gen.fs @@ -3,6 +3,49 @@ \ Code generation +\ This unit takes an AST and generates native code using cc/vm operations. We +\ do so by starting from the root Unit node, iterate children (Functions) and +\ recursively "resolve" them. + +\ As a general rule, we stay with Op1 (in cc/vm) active. Op2 usage is an +\ exception. However, it's very important that "regular" node handler *don't* +\ explicitely select Op1, because when we select Op2 for these exceptional +\ reasons, we generally want to to this recursively, all the way down. This is +\ why we don't see "selop" calls except in those exceptional places. We always +\ target the "active" op. + +\ Binary Op resolution strategy +\ The Binary Op node generation logic a few lines below needs a bit of an +\ explanation. A binary op needs the two VM operands at once, apply an operation +\ on them and return the result in Op1. Yes, Op1, not "active" op. + +\ A binary op can have 3 basic configuration for its two children: +\ 1. Both children are "single ops" (or lvalues, or constants... nothing that +\ requires 2 ops. +\ 2. One of the child is or contains a node that needs 2 ops and the other is +\ simple. +\ 3. Both children are or contain nodes that need 2 ops. + +\ The first configuration is easy to solve, no need for anything. You resolve +\ the first in op1, the second in op2, then apply the operation. + +\ The second configuration needs careful threading. Because the binop requires +\ both VM operands, it should be executed first. If the binop is "left" (the +\ first child), then nothing special needs to be done, Op1 has the proper value. +\ If the binop is "right", then we need to swap Op1 and Op2 after having +\ resolved it so that its result sits in the proper Op. + +\ The third configuration is the tricky one. If nothing is done, the result of +\ the first binop will be overwritten by the calculation made by the second +\ binop. We need to: +\ 1. resolve the first node +\ 2. save the result +\ 3. resolve the second node +\ 4. swap the result to Op2 +\ 5. restore the result to Op1. + +\ cc/vm abstracts away the save/restore mechanism through oppush/oppop. + : _err ( node -- ) printast abort" unexpected node" ; alias noop gennode ( node -- ) \ forward declaration @@ -39,6 +82,12 @@ alias noop gennode ( node -- ) \ forward declaration ( n1 n2 sz2 sz1 ) < if swap then ( node-to-adjust pointer-node ) node*arisz swap node*=n then ; +\ Does node need 2 VM operands? +: needs2ops? ( node -- f ) + dup astid dup AST_BINARYOP = swap AST_FUNCALL = or if drop 1 exit then + firstchild begin ?dup while dup needs2ops? not while nextsibling repeat + ( needs2ops? == true ) drop 1 else ( end of children ) 0 then ; + UOPSCNT wordtbl uopgentbl ( -- ) :w ( - ) vmneg, ; :w ( ~ ) vmnot, ; @@ -103,22 +152,31 @@ ASTIDCNT wordtbl gentbl ( node -- ) 'w genchildren ( ArgSpecs ) :w ( LValue ) lvvar vmap.sfoff sf+>op ; :w ( UnaryOp ) + _debug if ." unaryop: " dup printast nl> .ops then dup genchildren data1 uopgentbl swap wexec ; :w ( PostfixOp ) dup genchildren data1 popgentbl swap wexec ; +\ See "Binary op resolution strategy" in opening comment :w ( BinaryOp ) - ( node ) >r - selop2 noop# selop1 optype if oppush 1 else 0 then ( reg? f ) - r@ childcount 2 = not if abort" binop node with more than 2 children!" then + _debug if ." binop: " dup printast nl> .ops then + selectedop >r ( node ) >r r@ bopgentblpre r@ data1 wexec ( node ) firstchild dup nextsibling swap ( n2 n1 ) - selop1 gennode selop2 gennode bopgentblpost r> data1 wexec - ( reg? f ) if op1<>op2 selop1 oppop then ; + over needs2ops? if \ n2 == 2ops + \ Resolve n2 before n1 + swap gennode \ result in op1 + dup needs2ops? if \ both need 2ops + oppush rot gennode selop2 oppop else + selop2 gennode op1<>op2 then + else \ nothing special needed, regular resolution + selop1 gennode selop2 gennode then + bopgentblpost r> data1 wexec + r> ( selectedop ) if op1<>op2 else selop1 then ; 'w _err ( unused ) :w ( If ) - firstchild ?dup not if _err then dup selop1 gennode ( exprnode ) + firstchild ?dup not if _err then dup gennode ( exprnode ) vmjz, swap ( jump_addr exprnode ) ops$ nextsibling ?dup not if _err then dup gennode ( jump_addr condnode ) nextsibling ?dup if ( jump_addr elsenode ) diff --git a/fs/cc/map.fs b/fs/cc/map.fs @@ -47,10 +47,10 @@ newxdict curmap here swap , 16 allot0 ( entry ) ; : Variable ( dnode -- ) + dup data2 ( dnode type ) typesize over data3 ( nbelem ) * ( dnode sfsize ) + curmap @ fmap.sfsize+ ( dnode ) dup data1 curmap @ fmap.vmap xentry ( dnode ) - curmap @ fmap.sfsize , dup , ( dnode ) - dup data2 ( dnode type ) typesize swap data3 ( nbelem ) * ( sfsize ) - curmap @ fmap.sfsize+ ; + curmap @ fmap.sfsize 4 - , , ; : findvarinmap ( name funcentry -- varentry ) fmap.vmap xfind not if _err then ; diff --git a/fs/cc/vm.fs b/fs/cc/vm.fs @@ -85,12 +85,17 @@ create operands 16 allot0 operands value 'curop : selop1 ( -- ) operands to 'curop ; : selop2 ( -- ) operands 8 + to 'curop ; +: selectedop ( -- n ) \ 0 == Op1 1 == Op2 + 'curop operands = not ; : optype ( -- type ) 'curop @ ; : optype! ( type -- ) 'curop ! ; : oparg ( -- arg ) 'curop 4 + @ ; : oparg! ( arg -- ) 'curop 4 + ! ; + +\ reinitialize selected op to VM_NONE and dealloc registers if needed : opdeinit optype $f and VM_REGISTER = if regfree then VM_NONE optype! ; -\ reinitialize both ops to VM_NONE and dealloc registers if needed + +\ Deinit both ops and select Op1 : ops$ selop2 opdeinit selop1 opdeinit reglvl if abort" unbalanced reg allot/free" then diff --git a/fs/doc/x86.txt b/fs/doc/x86.txt @@ -1,14 +1,30 @@ # x86 architecture -The x86 nkernel source code is /dusk.asm. Register roles: +The x86 kernel source code is /dusk.asm. Register roles: PSP: EBP RSP: ESP All other registers are free. -For now, this nkernel needs to run on a Linux kernel and uses its syscalls +For now, this kernel needs to run on a Linux kernel and uses its syscalls for user interaction and file reading. It includes the boot source in its data section and makes boot< point to it at initialization. + +## EBP and PS + +It's important to note that EBP points to PSP TOS *directly*. This means that +you access the TOS with [ebp], the second element with [ebp+4] and so on. + +This is a bit confusing with regards to the C stack frame. There is constantly +a 4 byte offset to consider. Here's a visual example of a 16 bytes wide stack +frame + +--------|--------|--------|--------|--------|--------|--------| + ebp+16 | ebp+12 | ebp+8 | ebp+4 | ebp+0 | ebp-4 | ebp-8 | +--------|--------|--------|--------|--------|--------|--------| + ^ ^ + |--------------------------------------------| + 16 bytes stack frame diff --git a/fs/tests/cc/cc.fs b/fs/tests/cc/cc.fs @@ -22,4 +22,5 @@ ptrset 54 #eq exprparens 9 #eq cnoop ( no result! ) scnt 0 #eq 42 ptrari 46 #eq +\ array 54 #eq BROKEN: b = a+1 computes at the wrong level (b = *a+1) testend