diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..33f82a0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build/ +arctic + diff --git a/README.md b/README.md index 867733f..bc4546f 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,13 @@ Like the JVM, but the "binaries" consist of printable ASCII characters and the result can be more easily precompiled for native execution. +## TODO + + - multiprocessing support (call-through to fork()? codeN for each process?) + - probably want to repurpose breakpoint into fork + - overall philosophy + - easy to target for compilers + - easy to implement with overhead + - possible to implement with no overhead + - can use easy but "slow" or DIY + - e.g. MALL vs static memory and custom allocation diff --git a/doc/documentation.css b/doc/documentation.css new file mode 100644 index 0000000..1639c2b --- /dev/null +++ b/doc/documentation.css @@ -0,0 +1,7 @@ +#instructions ~ table td:first-child { + font-family: monospace; +} + +#instructions ~ table td:first-child + td { + font-family: monospace; +} diff --git a/doc/documentation.md b/doc/documentation.md index 3d83196..62303fb 100644 --- a/doc/documentation.md +++ b/doc/documentation.md @@ -1,9 +1,86 @@ -# ASCII Rendered, Compiled Target Invocation Code +--- +Title: ARCTIC Documentation +Author: Louis A. Burke +CSS: documentation.css +--- + +# ARCTIC Code + + ASCII Rendered, Cross-Target Invocation Character Codes Like the JVM, but the "binaries" consist of printable ASCII characters and the -result can be more easily precompiled for native execution. +result can be more easily precompiled for native execution. Also far less safety +and verification, feel free to shoot yourself in the foot! -## Opcodes +## Structure + +### Data Types + +### The Stack + +### Memory + +### Sections + +```{=todo} +code is LE \ w.r.t +CODE is BE / LDAx + +Specifically: + +A.* is BE atomic data +a.* is LE atomic data +[bB].* is BSS data +C.* is BE code +c.* is LE code +D.* is BE data +d.* is LE data +E.* is BE externals +e.* is LE externals +[fF].* is embedded file ("file X") +[gG].* is embedded resource ("get X") +[hH].* is embedded resource ("http...") +[iI].* is embedded code ("include X") +[lL].* is embedded arctic file ("load X") +M.* is BE macros +m.* is LE macros +P.* is BE per-thread data (POOL) +p.* is LE per-thread data (pool) +R.* is BE read-only data +r.* is LE read-only data +[wW].* is load module ("with X") +``` + +## File Format + +## Loading, Compiling, and Running + +## Instructions + +This section describes the various instructions that are defined for an ARCTIC +system. When stack manipulation is shown, the following conventions apply. + +*Stack direction:* The stack is shown from left to right where the top of the +stack is on the right. For example `1 2 3` shows a stack with 3 on top of 2 on +top of 1. + +*Ellipses:* When only the top of the stack is relevant, the rest may be +abbreviated with `…`. For example `… 10` shows a stack with a value of 10 at the +top and unknown data possibly below it. + +*Production:* To indicate the change in a stack, an arrow is used. For example +`1 2 3 → 3 1 2` shows a stack containing 1, 2, 3 becoming a stack containing 3, +1, 2. Similarly, if the arrow is doubled, it indicates an immediate value is +also consumed. For example `1 2 ⇒ 3` shows a stack containing 1 and 2 becoming +a stack containing 3 after consuming an immediate value. + +*Variables/Values:* Variables and values are presented as space-separated +c expressions. These are interpreted as if declared as follows: + + int64_t x, y, z; + double a, b, c; + void *p, *q; // data pointers + void *f; // function pointer ```{=todo} Notate that immediates can be: …###, …###.###, …$name, or …v (for abcinxyz) @@ -22,22 +99,490 @@ The ops below are probably code exclusive. The remaining " and ' can switch modes? Maybe just "section" to start a section? Leaving ' as the only impl-def? ``` + +### Memory + +The following table summarizes the memory operations that are described below. + +⋄ |Code| … | Stack | … | Description +--|---:|----------:|:-:|:----------------|:------------------------------------- +1 |LDAB| … p| → | … x | load byte x from p +2 |LDAS| … p| → | … x | load short x from p +3 |LDAI| … p| → | … x | load int x from p +4 |LDAW| … p| → | … x | load word x from p +o |LDOB| … p| ⇒ | … x | load byte at p plus immediate offset in x +H |LDOS| … p| ⇒ | … x | load short at p plus immediate offset in x +G |LDOI| … p| ⇒ | … x | load int at p plus immediate offset in x +Q |LDOW| … p| ⇒ | … x | load word at p plus immediate offset in x +5 |STAB| … p x| → | … | store byte x to p +6 |STAS| … p x| → | … | store short x to p +7 |STAI| … p x| → | … | store int x to p +8 |STAW| … p x| → | … | store word x to p +D |MCLR| … p x| → | … | memclear x bytes from p +d |MSET| … p x| ⇒ | … | set x bytes of memory to immediate value at p +K |MCPY| … p q x| → | … | copy x bytes of memory from p to q +J |MOFF| … p x| → | … q | set q to memory pointer p shifted by x bytes +M |MALL| … p| ⇒ | … p | (re)allocate/free memory for immediate bytes at p +R |REAL| … p x| → | … p | (re)allocate/free memory for x bytes at p +9 |CASS| … p x y| → | … z | compare and swap x and y at p, return success + +#### LDAx + +The LDAx instructions take a pointer on the top of the stack and replace it with +the value of 1, 2, 4, or 8 bytes loaded from its address as 2's compliment. The +endianness is determined by the endianness of the code section under execution. + + - LDAB: `… p → … (int64)*(int8*)p` + - LDAS: `… p → … (int64)*(int16*)p` + - LDAI: `… p → … (int64)*(int32*)p` + - LDAW: `… p → … *(int64*)p` + +#### LDOx + +The LDOx instructions take an immediate offset following the instruction code +and a pointer on top of the stack and act like their LDAx counterparts, but +operating on the memory address specified by adding the immediate value to the +pointer. + + - LDOB(i): `… p ⇒ … (int64)((int8*)p)[i]` + - LDOS(i): `… p ⇒ … (int64)((int16*)p)[i]` + - LDOI(i): `… p ⇒ … (int64)((int32*)p)[i]` + - LDOW(i): `… p ⇒ … ((int64*)p)[i]` + + +#### STAx + +The STAx instructions take a value on top of the stack and a pointer underneath +it and write the least significant 1, 2, 4, or 8 bytes of the value to the +address of the pointer, removing both from the stack. + + - STAB: `… p x → … (void)(*(int8*)p=x&0xFF)` + - STAS: `… p x → … (void)(*(int16*)p=x&0xFFFF)` + - STAI: `… p x → … (void)(*(int32*)p=x&0xFFFFFFFF)` + - STAW: `… p x → … (void)(*p=x)` + +#### MOFF + +The MOFF instruction takes an integer on top of the stack and a memory address +underneath it and shifts the memory address by the integer, removing the integer +in the process. + + - MOFF: `… p x → … ((int8*p)+x)` + +#### MCLR + +The MCLR instruction takes an integer value on top of the stack and a pointer +underneath it. It then clears as many bytes of memory in order as indicated by +the integer value starting from the pointer address. Both the value and pointer +are removed from the stack. + + - MCLR: `… p x → … (void)memset(p,x,0)` + +#### MSET + +The MSET instruction takes an immediate byte value following the instruction +code, as well as an integer value on top of the stack and a pointer underneath +it. It then sets as many bytes of memory in order as indicated by the integer +value starting from the pointer address to the immediate value. Both the value +and pointer are removed from the stack. + + - MSET(i): `… p x ⇒ … (void)memset(p,x,i)` + +#### MCPY + +The MCPY instruction takes an integer value on top of the stack and a pair of +pointers underneath it. It then copies as many bytes of memory in order as +indicated by the integer value starting from the bottom pointer into memory +starting from the top pointer. All three arguments are removed from the stack. + + - MCPY: `… p q x → … (void)memmove(q, p, x)` + +#### MALL + +The MALL instruction takes an immediate offset following the instruction code +and a pointer on top of the stack and allocates or reallocates memory of that +many bytes, replacing the pointer with the new pointer on the stack. + +If the size is zero, this frees the memory and pushes a null pointer. + + - MALL(i): `… p ⇒ … realloc(p, i)` + +#### REAL + +The REAL instruction takes an offset value on top of the stack and a pointer +underneath it and allocates or reallocates memory of that many bytes, replacing +the pointer with the new pointer on the stack. + +If the size is zero, this frees the memory and pushes a null pointer. + + - REAL: `… p x → … realloc(p, x)` + +#### CASS + +The CASS instruction takes a new value on top of the stack, an expected old +value underneath it, and a pointer underneath that. It atomically compares the +8 bytes of memory referenced by the pointer with the expected old value; if they +match, the memory content is updated to the new value and all three arguments +are replaced with the number 1 as a 64-bit integer; if they do not match, no +memory is updated and all three arguments are replaced with the number 0. + + - CASS: `… p x y → … atomic_compare_exchange_strong(p,&x,y)` + +### Math + +⋄ |Code| … | Stack | … | Description +--|---:|----------:|:-:|:----------------|:------------------------------------- +\+|IADD| … x y| → | … z | set z to the sum of x and y +\-|ISUB| … x y| → | … z | set z to the difference between x and y (x - y) +\*|IMUL| … x y| → | … z | set z to the product of x and y +/ |IDIV| … x y| → | … z | set z to the quotient of x by y +% |IMOD| … x y| → | … z | set z to the modulo of x by y +\\|IREM| … x y| → | … z | set z to the remainder of x by y +\_|INEG| … x| → | … x | negate x +\||IABS| … x| → | … x | take the absolute value of x +s |FADD| … a b| → | … c | set c to the sum of a and b +m |FSUB| … a b| → | … c | set c to the difference between a and b (a - b) +p |FMUL| … a b| → | … c | set c to the product of a and b +q |FDIV| … a b| → | … c | set c to the quotient of a by b +f |FMOD| … a b| → | … c | set c to the modulo of a by b +r |FREM| … a b| → | … c | set c to the remainder of a by b +j |FNEG| … a| → | … a | negate a +v |FABS| … a| → | … a | take the absolute value of a +V |BIOR| … x y| → | … z | bitwise OR x and y and store in z +\&|BAND| … x y| → | … z | set z to the bitwise and of x and y +^ |BXOR| … x y| → | … z | set z to the bitwise xor of x and y +l |BITC| … x y| → | … z | bit clear y from x to z (z = x and not y) +! |BNOT| … x| → | … y | set y to the bitwise not of x +u |USHR| … x| ⇒ | … x | logical shift x by immediate bits right +[ |ROTR| … x| ⇒ | … x | rotate x by immediate bits +] |BSHR| … x| ⇒ | … x | arithmetic right shift x by immediate bits +? |CMPI| … x y| → | … z | compare x to y and set z such that x o y is z o 0 +~ |CMPF| … a b| ⇒ | … c | compare a to b and set c such that a o b is z o c within immediate ULPs +U |CMPU| … x y| → | … z | compare x to y unsigned and set z such that x o y is z o 0 +F |BMIS| … x| ⇒ | … x | bit manipulation instructions + +#### xADD/xSUB/xMUL/xDIV/xMOD/xREM + +The xADD/xSUB/xMUL/xDIV/xMOD/xREM instructions take two values on top of the +stack (either two integers or two floating point numbers) and perform the +specified mathematical operation, replacing the values with the result of the +operation. + +The difference between MOD and REM is that MOD is floored while REM is +truncated. + + - IADD: `… x y → (x+y)` + - FADD: `… a b → (a+b)` + - ISUB: `… x y → (x-y)` + - FSUB: `… a b → (a-b)` + - IMUL: `… x y → (x*y)` + - FMUL: `… a b → (a*b)` + - IDIV: `… x y → (x/y)` + - FDIV: `… a b → (a/b)` + - IMOD: `… x y → (y>0?abs(x%y):-abs(x%y))` + - FMOD: `… a b → (b>0?fabs(fmod(x,y)):-fabs(fmod(x,y)))` + - IREM: `… x y → x%y` + - FREM: `… a b → fmod(x,y)` + +#### xNEG/xABS + +The xNEG/xABS instructions take a value on top of the stack (either an integer +or a floating point number) and replaces it with either its negated value or its +absolute value. + + - INEG: `… x → -x` + - FNEG: `… a → -a` + - IABS: `… x → abs(x)` + - FABS: `… a → fabs(a)` + +#### BIOR/BAND/BXOR/BITC + +The BIOR/BAND/BXOR/BITC instructions take two values on top of the stack and +perform the specified bitwise operation, replacing the values with the result of +the operation. + +BITC is a single instruction that performs A AND NOT B between its arguments. + + - BIOR: `… x y → x|y` + - BAND: `… x y → x&y` + - BXOR: `… x y → x^y` + - BITC: `… x y → x&~y` + +#### USHR/BSHR/ROTR + +The USHR/BSHR instructions take a value on top of the stack and an +immediate offset and shift the value on top of the stack that number of bits in +the given direction. + +USHR and BSHR differ in that BSHR performs an arithmetic shift, duplicating the +highest order bit instead of shifting in a 0. + +ROTR performs a rightward rotation, however with a negative shift amount, it +performs a leftward rotation. + + - USHR(i): `… x ⇒ ((uint64_t)x)>>i` + - BSHR(i): `… x ⇒ x>>i` + - ROTR(i): `… x ⇒ (x>>shift)|(value<<(64-shift))` + +#### CMPx + +The CMPx instructions perform comparisons between two values on top of the stack +and replace them with either 1, 0, or -1 if the top value is less than, equal +to, or greater than the second value respectively. In the case of CMPF, +a floating point precision is specified by an immediate value, with equality +spanning all values within the given precision of each other. + + - CMPI: `… x y → ((x-y)>0)-((x-y)<0)` + - CMPF(i): `… a b ⇒ fabs(a-b)0)-((a-b)<0)` + - CMPU: `… x y → (((uint64_t)x-(uint64_t)y)>0)-(((uint64_t)x-(uint64_t)y)<0)` + +#### BMIS + +The BMIS instruction bundles many unary bitwise operators into a single +instruction, indexed by an immediate integer based on the following table: + + Immediate | Operation +-----------|-------------------------------------------------------------------- + 0 | ??? + +### Type Conversions + +⋄ |Code| … | Stack | … | Description +--|---:|----------:|:-:|:----------------|:------------------------------------- +E |FTOI| … a| ⇒ | … x | round a to integer and store in x +O |ITOC| … x| → | … x | truncate x to 8 bits, then sign extend +S |ITOS| … x| → | … x | truncate x to 16 bits, then sign extend +W |ITOW| … x| → | … x | truncate x to 32 bits, then sign extend +. |ITOF| … x| → | … a | convert x to a floating point number + +#### FTOI + +The FTOI instruction takes a floating point value on top of the stack and +replaces it with an integer by rounding. The direction of rounding is determined +by the immediate integer value: + + Value | Rounding Mode +------:|:----------------------------------------------------------------------- + 1 | Round towards infinity. "Round up" + -1 | Round towards negative infinity. "Round down" + 0 | Round towards 0. "Truncate" + others| Round to even. "Unbiased" + + - FTOI(1): `… a ⇒ fesetround(FE_UPWARD),(int64_t)a` + - FTOI(-1): `… a ⇒ fesetround(FE_DOWNWARD),(int64_t)a` + - FTOI(0): `… a ⇒ fesetround(FE_TOWARDZERO),(int64_t)a` + - FTOI(i): `… a ⇒ fesetround(FE_TONEAREST),(int64_t)a` + +#### ITOC/ITOS/ITOW + +The ITOC/ITOS/ITOW instructions take a 64-bit integer on top of the stack and +replace it with a truncated and sign extended smaller integer. + + - ITOC: `… x → (int64_t)*(int8_t*)&x` + - ITOS: `… x → (int64_t)*(int16_t*)&x` + - ITOW: `… x → (int64_t)*(int32_t*)&x` + +#### ITOF + +The ITOF instruction takes a 64-bit integer on top of the stack and replaces it +with the closest 64-bit floating point approximation. + + - ITOF: `… x → (double)x` + +### Stack Management + +⋄ |Code| … | Stack | … | Description +--|---:|----------:|:-:|:----------------|:------------------------------------- +w |SDUP| … x| → | … x x | stack duplicate +k |SPOP| … x| → | … | stack pop +t |SWAP| … x y| → | … y x | stack swap +h |OVER| … x y| → | … x y x | stack over +e |SROT| … x y z| → | … y z x | rotate stack +P |PICK| … x ‥ z| ⇒ | … x ‥ z x | pick stack element at immediate place +L |ROLL| … x y ‥ z| ⇒ | … y ‥ z x | roll stack by immediate places +T |PUSH| … x ‥ y z| ⇒ | … y x ‥ z | push top of stack down by immediate places (opposite of ROLL) +( |DPTH| …| → | … x | set x to depth of stack (before x) +) |PACK| … p x| → | … p | pack x elements of stack (before p) into array p + +#### SDUP + +The SDUP instruction takes a value on top of the stack and pushes a copy of it +to the top of the stack. + + - SDUP: `… x → x x` + +#### SPOP + +The SPOP instruction removes the value on top of the stack. + + - SPOP: `… x → ` + +#### SWAP/OVER/SROT + +The SWAP/OVER/SROT instructions manipulate the values near the top of the stack. + + - SWAP: `… x y → … y x` + - OVER: `… x y → … x y x` + - SROT: `… x y z → … y z x` + +#### PICK + +The PICK instruction acts like an OVER command with an arbitrary depth, copying an +element indicated by the immediate value to the top of the stack. OVER can be +defined in terms of PICK as `P1` while SDUP is equivalent to `P0`. + + - PICK(i): `… x …i… y ⇒ … x …i… y x` + +#### ROLL/PUSH + +The ROLL and PUSH instructions act like SROT with an arbitrary depth, allowing +a value from any depth in the stack to be rotated to the top or pushed back down +to depth. SROT can be defined in terms of ROLL as `L1` while SWAP is equivalent +to `L0` or `T0`. + + - ROLL(i): `… x y …i… z ⇒ … y …i… z x` + - PUSH(i): `… x …i… y z ⇒ … z x …i… y` + +#### DPTH/PACK + +The DPTH instruction pushes the total depth of the stack on top of the stack. +The PACK instruction takes an integer on top of a pointer and saves the top +elements of the stack beneath the pointer to the memory pointed to by it. + +These can be combined to store the entire stack in a new array via: `(w$0 Rt)` + + - DPTH: `…x… → …x… x` + - PACK: `… p x → p` + +### Control Flow + +⋄ |Code| … | Stack | … | Description +--|---:|----------:|:-:|:----------------|:------------------------------------- +\=|BEQZ| … x| ⇒ | … | branch to immediate if x is zero +0 |BNEZ| … x| ⇒ | … | branch to immediate if x not zero +\<|BLTZ| … x| ⇒ | … | branch to immediate if x less than zero +\>|BGTZ| … x| ⇒ | … | branch to immediate if x is greater than zero +{ |BLEZ| … x| ⇒ | … | branch to immediate if x is less than or equal to zero +} |BGEZ| … x| ⇒ | … | branch to immediate if x is greater or equal to zero +g |FINV| … f| → | f … | invoke f, saving return address on stack +@ |CALL| …| → | f … | call immediate name +, |JUMP| …| ⇒ | … | jump to immediate without pushing return address +; |RETN| … f| → | … | return from subroutine (jump but for stack) +\:|LABL| … | ↔ | … | label code location + +#### BEQZ/BNEZ/BLTZ/BGTZ/BLEZ/BGEZ + +The BEQZ/BNEZ/BLTZ/BGTZ/BLEZ/BGEZ instructions are the basic branching +instructions. They take an immediate offset and, if their condition is met, jump +that many beats (lines) forward or backward. + + - BEQZ(i): `… x ⇒ …` + - BNEZ(i): `… x ⇒ …` + - BLTZ(i): `… x ⇒ …` + - BGTZ(i): `… x ⇒ …` + - BLEZ(i): `… x ⇒ …` + - BGEZ(i): `… x ⇒ …` + +#### FINV/CALL + +The FINV/CALL instructions invoke functions by jumping to their first +instruction after pushing the address of the next instruction on the stack. + + - FINV: `… f → g …` + - CALL(f): `… → g …` + +#### JUMP/RETN + +The JUMP/RETN instructions are the complement to the FINV/CALL instructions. +They jump to the first instruction of the given function, but do not push +a return address on the stack. + + - JUMP(f): `… ⇒ …` + - RETN: `… f ⇒ …` + +#### LABL + +The LABL instruction takes an immediate name and marks the next current beat in +memory as having that name for future or past references. + + - LABL: `… ↔ …` + +### Variables + +⋄ |Code| … | Stack | … | Description +--|---:|----------:|:-:|:----------------|:------------------------------------- +A |PUTA| … x| → | … | store x in rA +B |PUTB| … x| → | … | store x in rB +C |PUTC| … x| → | … | store x in rC +I |PUTI| … x| → | … | store x in rI +N |PUTN| … x| → | … | store x in rN +X |PUTX| … x| → | … | store x in rX +Y |PUTY| … x| → | … | store x in rY +Z |PUTZ| … x| → | … | store x in rZ +a |GETA| …| → | … x | load x from rA +b |GETB| …| → | … x | load x from rB +c |GETC| …| → | … x | load x from rC +i |GETI| …| → | … x | load x from rI +n |GETN| …| → | … x | load x from rN +x |GETX| …| → | … x | load x from rX +y |GETY| …| → | … x | load x from rY +z |GETZ| …| → | … x | load x from rZ + +#### PUTA/PUTB/PUTC/PUTI/PUTN/PUTX/PUTY/PUTZ + +The PUTA/PUTB/PUTC/PUTI/PUTN/PUTX/PUTY/PUTZ instructions take a value on top of +the stack and store it in a register for later recall. + + - PUTA: `… x → …` + - PUTB: `… x → …` + - PUTC: `… x → …` + - PUTI: `… x → …` + - PUTN: `… x → …` + - PUTX: `… x → …` + - PUTY: `… x → …` + - PUTZ: `… x → …` + +#### GETA/GETB/GETC/GETI/GETN/GETX/GETY/GETZ + +The GETA/GETB/GETC/GETI/GETN/GETX/GETY/GETZ instructions push the previously +saved value in a register onto the stack. + + - GETA: `… → … x` + - GETB: `… → … x` + - GETC: `… → … x` + - GETI: `… → … x` + - GETN: `… → … x` + - GETX: `… → … x` + - GETY: `… → … x` + - GETZ: `… → … x` + +### Miscellaneous + +⋄ |Code| … | Stack | … | Description +--|---:|----------:|:-:|:----------------|:------------------------------------- +$ |VALU| … | ⇒ | … \* | load constant value +' |DATA| | | | Embed data +" |SECT| | | | Change section +\`|BIFC| … ? | ↔ | … ? | call builtin with given name/value +␣ |NOOP| … | ↔ | … | do nothing, maybe end identifier definition +¶ |BEAT| … | ↔ | … | mark a beat for relative branching + ### Summary | -0 | -1 | -2 | -3 | -4 | -5 | -6 | -7 ---:|:------:|:------:|:------:|:------:|:------:|:------:|:------:|:------: 000-|`0 BNEZ`|`1 LDAB`|`2 LDAS`|`3 LDAI`|`4 LDAW`|`5 STAB`|`6 STAS`|`7 STAI` -001-|`8 STAW`|`9 CASS`|`A PUTA`|`B PUTB`|`C PUTC`|`D MCLR`|`E FTOI`|`F FREE` -010-|`G LDOI`|`H LDOS`|`I PUTI`|`J MOFF`|`K MCPY`|`L ROLL`|`M MNEW`|`N PUTN` -011-|`O ITOC`|`P PICK`|`Q LDOW`|`R MALL`|`S ITOS`|`T PUSH`|`U CMPU`|`V B_OR` +001-|`8 STAW`|`9 CASS`|`A PUTA`|`B PUTB`|`C PUTC`|`D MCLR`|`E FTOI`|`F BMIS` +010-|`G LDOI`|`H LDOS`|`I PUTI`|`J MOFF`|`K MCPY`|`L ROLL`|`M MALL`|`N PUTN` +011-|`O ITOC`|`P PICK`|`Q LDOW`|`R REAL`|`S ITOS`|`T PUSH`|`U CMPU`|`V BIOR` 100-|`W ITOW`|`X PUTX`|`Y PUTY`|`Z PUTZ`|`a GETA`|`b GETB`|`c GETC`|`d MSET` 101-|`e SROT`|`f FMOD`|`g FINV`|`h OVER`|`i GETI`|`j FNEG`|`k SPOP`|`l BITC` 110-|`m FSUB`|`n GETN`|`o LDOB`|`p FMUL`|`q FDIV`|`r FREM`|`s FADD`|`t SWAP` 111-|`u USHR`|`v FABS`|`w SDUP`|`x GETX`|`y GETY`|`z GETZ`|`_ INEG`|`. ITOF` 200-|`+ IADD`|`- ISUB`|`* IMUL`|`/ IDIV`|`% IMOD`|`\ IREM`|`| IABS`|`$ VALU` 201-|`@ CALL`|`< BLTZ`|`{ BLEZ`|`= BEQZ`|`} BGEZ`|`> BGTZ`|`, JUMP`|`; RTRN` -210-|`& BAND`|`^ BXOR`|`! BNOT`|`[ BSHL`|`] BSHR`|`? CMPI`|`~ CMPF`|`: LABL` -211-|`( DPTH`|`) PACK`|`' DATA`|`" SECT`|`\` BKPT`|`␣ NOP`|`# COMM`|`¶ BEAT` +210-|`& BAND`|`^ BXOR`|`! BNOT`|`[ ROTR`|`] BSHR`|`? CMPI`|`~ CMPF`|`: LABL` +211-|`( DPTH`|`) PACK`|`' DATA`|`" SECT`|`\` BIFC`|`␣ NOP`|`# COMM`|`¶ BEAT` [Operations by code][ops-by-code] In this table x, y, and z are integers; a, b, and c are floating point numbers; @@ -45,101 +590,101 @@ p and q are memory pointers; and f is a function pointer. Additionally, → indicates a basic operation, while ⇒ indicates an operation using an immediate value, and ↔ indicates a meta operation. - Code | Stack | Description ------:|:-------------------:|:-------------------------------------------------- -0 BNEZ| … x ⇒ … | branch to immediate if x not zero -1 LDAB| … p → … x | load byte x from p -2 LDAS| … p → … x | load short x from p -3 LDAI| … p → … x | load int x from p -4 LDAW| … p → … x | load word x from p -5 STAB| … p x → … | store byte x to p -6 STAS| … p x → … | store short x to p -7 STAI| … p x → … | store int x to p -8 STAW| … p x → … | store word x to p -9 CASS| … p x y → … z | compare and swap x and y at p, return success -A PUTA| … x → … | store x in rA -B PUTB| … x → … | store x in rB -C PUTC| … x → … | store x in rC -D MCLR| … p x → … | memclear x bytes from p -E FTOI| … a → … x | round a to integer and store in x -F FREE| … p → … | free memory at p -G LDOI| … p ⇒ … x | load int at p plus immediate offset in x -H LDOS| … p ⇒ … x | load short at p plus immediate offset in x -I PUTI| … x → … | store x in rI -J MOFF| … p x → … q | set q to memory pointer p shifted by x bytes -K MCPY| … p q x → … | copy x bytes of memory from p to q -L ROLL| … x y ‥ z ⇒ … y ‥ z x | roll stack by immediate places -M MNEW| … ⇒ … p | allocate memory for immediate bytes at p -N PUTN| … x → … | store x in rN -O ITOC| … x → … x | truncate x to 8 bits, then sign extend -P PICK| … x ‥ z ⇒ … x ‥ z x | pick stack element at immediate place -Q LDOW| … p ⇒ … x | load word at p plus immediate offset in x -R MALL| … x → … p | allocate memory for x bytes at p -S ITOS| … x → … x | truncate x to 16 bits, then sign extend -T PUSH| … x . y z ⇒ … y x ‥ z | push top of stack down by immediate places (opposite of ROLL) -U CMPU| … x y → … z | compare x to y unsigned and set z such that x o y is z o 0 -V B_OR| … x y → … z | bitwise OR x and y and store in z -W ITOW| … x → … x | truncate x to 32 bits, then sign extend -X PUTX| … x → … | store x in rX -Y PUTY| … x → … | store x in rY -Z PUTZ| … x → … | store x in rZ -a GETA| … → … x | load x from rA -b GETB| … → … x | load x from rB -c GETC| … → … x | load x from rC -d MSET| … p x ⇒ … | set x bytes of memory to immediate value at p -e SROT| … x y z → … y z x | rotate stack -f FMOD| … a b → … c | set c to the modulo of a by b -g FINV| … f → f … | invoke f, saving return address on stack -h OVER| … x y → … x y x | stack over -i GETI| … → … x | load x from rI -j FNEG| … a → … a | negate a -k SPOP| … x → … | stack pop -l BITC| … x y → … z | bit clear y from x to z (z = x and not y) -m FSUB| … a b → … c | set c to the difference between a and b (a - b) -n GETN| … → … x | load x from rN -o LDOB| … p ⇒ … x | load byte at p plus immediate offset in x -p FMUL| … a b → … c | set c to the product of a and b -q FDIV| … a b → … c | set c to the quotient of a by b -r FREM| … a b → … c | set c to the remainder of a by b -s FADD| … a b → … c | set c to the sum of a and b -t SWAP| … x y → … y x | stack swap -u USHR| … x ⇒ … x | logical shift x by immediate bits right -v FABS| … a → … a | take the absolute value of a -w SDUP| … x → … x x | stack duplicate -x GETX| … → … x | load x from rX -y GETY| … → … x | load x from rY -z GETZ| … → … x | load x from rZ -_ INEG| … x → … x | negate x -. ITOF| … x → … a | convert x to a floating point number -\+ IADD|… x y → … z | set z to the sum of x and y -\- ISUB|… x y → … z | set z to the difference between x and y (x - y) -\* IMUL|… x y → … z | set z to the product of x and y -/ IDIV| … x y → … z | set z to the quotient of x by y -% IMOD| … x y → … z | set z to the modulo of x by y -\ IREM| … x y → … z | set z to the remainder of x by y -\| IABS|… x → … x | take the absolute value of x -$ VALU| … ↔ … * | load constant value -@ CALL| … ↔ … | call immediate name -< BLTZ| … x ⇒ … | branch to immediate if x less than zero -{ BLEZ| … x ⇒ … | branch to immediate if x is less than or equal to zero -\= BEQZ|… x ⇒ … | branch to immediate if x is zero -} BGEZ| … x ⇒ … | branch to immediate if x is greater or equal to zero -\> BGTZ|… x ⇒ … | branch to immediate if x is greater than zero -, JUMP| … ⇒ … | jump to immediate without pushing return address -; RETN| … f ⇒ … | return from subroutine (jump but for stack) -& BAND| … x y → … z | set z to the bitwise and of x and y -^ BXOR| … x y → … z | set z to the bitwise xor of x and y -! BNOT| … x → y | set y to the bitwise not of x -[ BSHL| … x ⇒ … x | left shift x by immediate bits -] BSHR| … x ⇒ … x | arithmetic right shift x by immediate bits -? CMPI| … x y → … z | compare x to y and set z such that x o y is z o 0 -~ CMPF| … a b ⇒ … c | compare a to b and set c such that a o b is z o c within an immediate error -\: LABL|… f ↔ … | label code location -( DPTH| … → … x | set x to depth of stack (before x) -) PACK| … p x → … | pack x elements of stack (before p) into array p -' DATA| | Embed data -" SECT| | Change section -\` BKPT| … ↔ … | trigger breakpoint, or exit if not debugging -␣ NOOP| … ↔ … | do nothing, maybe end identifier definition -¶ BEAT| … ↔ … | mark a beat for relative branching +⋄ |Code| … | Stack | … | Description +--|---:|----------:|:-:|:----------------|:------------------------------------- +0 |BNEZ| … x| ⇒ | … | branch to immediate if x not zero +1 |LDAB| … p| → | … x | load byte x from p +2 |LDAS| … p| → | … x | load short x from p +3 |LDAI| … p| → | … x | load int x from p +4 |LDAW| … p| → | … x | load word x from p +5 |STAB| … p x| → | … | store byte x to p +6 |STAS| … p x| → | … | store short x to p +7 |STAI| … p x| → | … | store int x to p +8 |STAW| … p x| → | … | store word x to p +9 |CASS| … p x y| → | … z | compare and swap x and y at p, return success +A |PUTA| … x| → | … | store x in rA +B |PUTB| … x| → | … | store x in rB +C |PUTC| … x| → | … | store x in rC +D |MCLR| … p x| → | … | memclear x bytes from p +E |FTOI| … a| ⇒ | … x | round a to integer and store in x +F |BMIS| … x| ⇒ | … x | bit manipulation instructions +G |LDOI| … p| ⇒ | … x | load int at p plus immediate offset in x +H |LDOS| … p| ⇒ | … x | load short at p plus immediate offset in x +I |PUTI| … x| → | … | store x in rI +J |MOFF| … p x| → | … q | set q to memory pointer p shifted by x bytes +K |MCPY| … p q x| → | … | copy x bytes of memory from p to q +L |ROLL| … x y ‥ z| ⇒ | … y ‥ z x | roll stack by immediate places +M |MALL| … p| ⇒ | … p | (re)allocate/free memory for immediate bytes at p +N |PUTN| … x| → | … | store x in rN +O |ITOC| … x| → | … x | truncate x to 8 bits, then sign extend +P |PICK| … x ‥ z| ⇒ | … x ‥ z x | pick stack element at immediate place +Q |LDOW| … p| ⇒ | … x | load word at p plus immediate offset in x +R |REAL| … p x| → | … p | (re)allocate/free memory for x bytes at p +S |ITOS| … x| → | … x | truncate x to 16 bits, then sign extend +T |PUSH| … x ‥ y z| ⇒ | … y x ‥ z | push top of stack down by immediate places (opposite of ROLL) +U |CMPU| … x y| → | … z | compare x to y unsigned and set z such that x o y is z o 0 +V |BIOR| … x y| → | … z | bitwise OR x and y and store in z +W |ITOW| … x| → | … x | truncate x to 32 bits, then sign extend +X |PUTX| … x| → | … | store x in rX +Y |PUTY| … x| → | … | store x in rY +Z |PUTZ| … x| → | … | store x in rZ +a |GETA| …| → | … x | load x from rA +b |GETB| …| → | … x | load x from rB +c |GETC| …| → | … x | load x from rC +d |MSET| … p x| ⇒ | … | set x bytes of memory to immediate value at p +e |SROT| … x y z| → | … y z x | rotate stack +f |FMOD| … a b| → | … c | set c to the modulo of a by b +g |FINV| … f| → | f … | invoke f, saving return address on stack +h |OVER| … x y| → | … x y x | stack over +i |GETI| …| → | … x | load x from rI +j |FNEG| … a| → | … a | negate a +k |SPOP| … x| → | … | stack pop +l |BITC| … x y| → | … z | bit clear y from x to z (z = x and not y) +m |FSUB| … a b| → | … c | set c to the difference between a and b (a - b) +n |GETN| …| → | … x | load x from rN +o |LDOB| … p| ⇒ | … x | load byte at p plus immediate offset in x +p |FMUL| … a b| → | … c | set c to the product of a and b +q |FDIV| … a b| → | … c | set c to the quotient of a by b +r |FREM| … a b| → | … c | set c to the remainder of a by b +s |FADD| … a b| → | … c | set c to the sum of a and b +t |SWAP| … x y| → | … y x | stack swap +u |USHR| … x| ⇒ | … x | logical shift x by immediate bits right +v |FABS| … a| → | … a | take the absolute value of a +w |SDUP| … x| → | … x x | stack duplicate +x |GETX| …| → | … x | load x from rX +y |GETY| …| → | … x | load x from rY +z |GETZ| …| → | … x | load x from rZ +\_|INEG| … x| → | … x | negate x +. |ITOF| … x| → | … a | convert x to a floating point number +\+|IADD| … x y| → | … z | set z to the sum of x and y +\-|ISUB| … x y| → | … z | set z to the difference between x and y (x - y) +\*|IMUL| … x y| → | … z | set z to the product of x and y +/ |IDIV| … x y| → | … z | set z to the quotient of x by y +% |IMOD| … x y| → | … z | set z to the modulo of x by y +\\|IREM| … x y| → | … z | set z to the remainder of x by y +\||IABS| … x| → | … x | take the absolute value of x +$ |VALU| …| ⇒ | … \* | load constant value +@ |CALL| …| → | f … | call immediate name +\<|BLTZ| … x| ⇒ | … | branch to immediate if x less than zero +{ |BLEZ| … x| ⇒ | … | branch to immediate if x is less than or equal to zero +\=|BEQZ| … x| ⇒ | … | branch to immediate if x is zero +} |BGEZ| … x| ⇒ | … | branch to immediate if x is greater or equal to zero +\>|BGTZ| … x| ⇒ | … | branch to immediate if x is greater than zero +, |JUMP| …| ⇒ | … | jump to immediate without pushing return address +; |RETN| … f| ⇒ | … | return from subroutine (jump but for stack) +\&|BAND| … x y| → | … z | set z to the bitwise and of x and y +^ |BXOR| … x y| → | … z | set z to the bitwise xor of x and y +! |BNOT| … x| → | y | set y to the bitwise not of x +[ |ROTR| … x| ⇒ | … x | rotate x by immediate bits +] |BSHR| … x| ⇒ | … x | arithmetic right shift x by immediate bits +? |CMPI| … x y| → | … z | compare x to y and set z such that x o y is z o 0 +~ |CMPF| … a b| ⇒ | … c | compare a to b and set c such that a o b is z o c within an immediate error +\:|LABL| … | ↔ | … | label code location +( |DPTH| … | → | … x | set x to depth of stack (before x) +) |PACK| … p x| → | … p | pack x elements of stack (before p) into array p +' |DATA| | | | Embed data +" |SECT| | | | Change section +\`|BIFC| … ? | ↔ | … ? | call builtin with given name/value +␣ |NOOP| …| ↔ | … | do nothing, maybe end identifier definition +¶ |BEAT| …| ↔ | … | mark a beat for relative branching [Operations in order][ops-in-order] diff --git a/samples/hello.ctc b/samples/hello.ctc new file mode 100644 index 0000000..f1ed7cd --- /dev/null +++ b/samples/hello.ctc @@ -0,0 +1,13 @@ +#!/usr/bin/env arctic + +@main + +"with stdio" + +"data" +:message 'Hello, World!' + +"code" +:main T3 kk # main is called as: &argv[0] argc retaddr +$message @putstrln $0; + diff --git a/samples/syntax.atc b/samples/syntax.atc index 09d83f1..6d0ef7f 100644 --- a/samples/syntax.atc +++ b/samples/syntax.atc @@ -1,29 +1,41 @@ #!/usr/bin/env arctic +# TODO: consider .ctc format instead? + +@main + "data" # read-only memory -:value '10' # 1 int -:array '[1, 2, 3]' # 3 int array +:values 'i10w10s10b10f10' # 1 each of int/word/short/byte/double in LE order +:hexval 'I10W10S10B10F10' # 1 each of int/word/short/byte/double in BE order +:bytes 'xffffffx' # 3 hexadecimal bytes +:string '"hello"' # 5 utf-8 encoded bytes +:array 'i1i2i3' # 3 int array "state" # initialized read-write memory -:variable '10' # 1 int -:buffer '[1, 2, 3]' # 3 int array +:variable 'i10' # 1 int +:buffer 'i1i2i3' # 3 int array "memory" # uninitialized read-write memory :pool '{10}' # 10 byte pool "constants" # ephemeral data -:const '10' # 1 universal integer +:const 'i10' # 1 universal integer "macros" # ephemeral code -:foo +:foo '[ # body of foo here +]' "code" # loaded code :bar # body of bar here +:main :_baz # private label, not "exported" to other "code" sections # body of baz here "code" # more loaded code, but in here _baz not yet defined # in general, all labels beginning with _ are section-local + +"extern libfoo" +:foobar # loaded from either libfoo.a or libfoo.so diff --git a/src/arctic.c b/src/arctic.c index 94b3afd..f356038 100644 --- a/src/arctic.c +++ b/src/arctic.c @@ -14,7 +14,7 @@ const char ARCTIC_CODE_PAGE[97] = /* ops with an immediate argument */ #define FOR_IMMEDIATE_OPS(X) \ X('0') X('G') X('H') X('L') X('M') X('P') X('Q') X('d') X('o') X('u') \ - X('<') X('{') X('=') X('}') X('>') X(',') X(';') X('[') X(']') + X('@') X('<') X('{') X('=') X('}') X('>') X(',') X(';') X('[') X(']') #define FOR_PLAIN_OPS(X) \ X('1') X('2') X('3') X('4') X('5') X('6') X('7') X('8') X('9') X('A') \ @@ -81,9 +81,9 @@ enum ArcticErrorCode arctic_scan(struct ArcticScanner *scanner, char next) { return ARCTIC_OK; case ':': /* label name */ - if (next == ' ') { + if (next == ' ' || next == '\n' || next == '#') { scanner->label_callback(scanner->buf + 1, scanner->data); - scanner->buf[0] = 0; + scanner->buf[0] = (next == '#' ? '#' : 0); } else { chrncat( scanner->buf, next, ARCTIC_BUFSIZE, @@ -105,9 +105,9 @@ enum ArcticErrorCode arctic_scan(struct ArcticScanner *scanner, char next) { return ARCTIC_OK; FOR_IMMEDIATE_OPS(CASE) /* immediate ops */ - if (next == ' ') { + if (next == ' ' || next == '\n' || next == '#') { scanner->op_callback(scanner->buf[0], scanner->buf + 1, scanner->data); - scanner->buf[0] = 0; + scanner->buf[0] = (next == '#' ? '#' : 0); } else { chrncat( scanner->buf, next, ARCTIC_BUFSIZE, diff --git a/src/arctic.nim b/src/arctic.nim index 92234f0..f7140be 100644 --- a/src/arctic.nim +++ b/src/arctic.nim @@ -1,8 +1,9 @@ -import std/[critbits, strformat, strutils, strmisc, re] +import std/[atomics, bitops, critbits, strformat, strutils, strmisc, math, re] import bio import itertools type + BitShiftType = range[0 .. 64] ArcticTypeKind* = enum BYTE, SHORT, INT, WORD, DOUBLE @@ -10,10 +11,14 @@ type b: int8 s: int16 i: int32 - w: int64 + w: int64 # TODO: swap i and w to match instruction naming -.- + u: uint64 d: float64 - p: pointer # only used for dynamically allocated memory - f: ArcticSymbol # also used for pointers to section memory + f: ArcticSymbol # also used for pointers + # TODO: Eventually replace f with p/f and each is just a uint64 + # Then load all memory into one long seq instead of 1 seq per + # section. Probably actually have +ve be data memory, and -ve be + # code memory (with sections mapping to ranges) ArcticVariableIndex* = enum VARIABLE_A, VARIABLE_B, VARIABLE_C, @@ -60,6 +65,9 @@ type ArcticBuiltin* = proc (state: var ArcticState): ArcticStepResult +var + next_malloc_id: int = 1 + const ImmediateOps: set[char] = {'0', 'E', 'F', 'G', 'H', 'L', 'M', 'P', 'Q', 'd', 'o', 'u', '<', '{', '=', '}', '>', ',', ';', '[', ']'} PlainOps : set[char] = { '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'I', 'J', 'K', 'N', 'O', 'R', 'S', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z', '_', '.', '+', '-', '*', '/', '%', '\\', '|', '&', '^', '!', '?', '(', ')', '`' } @@ -309,6 +317,30 @@ func load*(code: string): ArcticState = else: discard +proc `[]`(state: ArcticState, symbol: ArcticSymbol): uint8 = + assert symbol.section in state.sections + let section = state.sections[symbol.section] + + if section.iscode: + return section.code[symbol.index].code.uint8 + else: + return section.data[symbol.index] + +proc `[]=`(state: var ArcticState, symbol: ArcticSymbol, value: uint8) = + if not (symbol.section in state.sections): + state.sections[symbol.section] = ArcticSection(iscode: false, data: @[]) + while state.sections[symbol.section].len <= symbol.index: + state.sections[symbol.section].data.add 0 + state.sections[symbol.section].data[symbol.index] = value + else: + let section = state.sections[symbol.section] + + while state.sections[symbol.section].data.len <= symbol.index: + state.sections[symbol.section].data.add 0 + state.sections[symbol.section].data[symbol.index] = value + +proc `+`(left: ArcticSymbol, offset: int): ArcticSymbol = (left.section, left.index + offset) + proc step*(state: var ArcticState, builtins: CritBitTree[ArcticBuiltin]): ArcticStepResult = let codesec = state.sections[state.pc.section] @@ -319,14 +351,579 @@ proc step*(state: var ArcticState, builtins: CritBitTree[ArcticBuiltin]): Arctic return ERROR let op = codesec.code[state.pc.index] + state.pc.index.inc + case op.code: - case '1': # LDAB - let top = state.stack[^1] + # Memory Ops ----------------------------------------------------------- + of '1': # LDAB + let p = state.stack.pop.f + state.stack.add ArcticType(w: state[p].int64) + + of '2': # LDAS + let + p = state.stack.pop.f + a = state[p].int64 + b = state[p+1].int64 + if state.pc.section == "CODE": # bigendian + state.stack.add ArcticType(w: a or b shl 8) + else: + state.stack.add ArcticType(w: b or a shl 8) + + of '3': # LDAI + let + p = state.stack.pop.f + a = state[p].int64 + b = state[p+1].int64 + c = state[p+2].int64 + d = state[p+3].int64 + if state.pc.section == "CODE": # bigendian + state.stack.add ArcticType(w: d or c shl 8 or b shl 16 or a shl 24) + else: + state.stack.add ArcticType(w: a or b shl 8 or c shl 16 or d shl 24) + + of '4': # LDAW + let + p = state.stack.pop.f + a = state[p].int64 + b = state[p+1].int64 + c = state[p+2].int64 + d = state[p+3].int64 + e = state[p+4].int64 + f = state[p+5].int64 + g = state[p+6].int64 + h = state[p+7].int64 + if state.pc.section == "CODE": # bigendian + state.stack.add ArcticType(w: h or g shl 8 or f shl 16 or e shl 24 or d shl 32 or c shl 40 or b shl 48 or a shl 56) + else: + state.stack.add ArcticType(w: a or b shl 8 or c shl 16 or d shl 24 or e shl 32 or f shl 40 or g shl 48 or h shl 56) + + of 'o': # LDOB + assert op.immediate.kind == INTEGER + let p = state.stack.pop.f + state.stack.add ArcticType(w: state[p+op.immediate.i].int64) + + of 'H': # LDOS + assert op.immediate.kind == INTEGER + let + p = state.stack.pop.f + op.immediate.i + a = state[p].int64 + b = state[p+1].int64 + if state.pc.section == "CODE": # bigendian + state.stack.add ArcticType(w: a or b shl 8) + else: + state.stack.add ArcticType(w: b or a shl 8) + + of 'G': # LDOI + let + p = state.stack.pop.f + op.immediate.i + a = state[p].int64 + b = state[p+1].int64 + c = state[p+2].int64 + d = state[p+3].int64 + if state.pc.section == "CODE": # bigendian + state.stack.add ArcticType(w: d or c shl 8 or b shl 16 or a shl 24) + else: + state.stack.add ArcticType(w: a or b shl 8 or c shl 16 or d shl 24) + + of 'Q': # LDOW + let + p = state.stack.pop.f + a = state[p].int64 + b = state[p+1].int64 + c = state[p+2].int64 + d = state[p+3].int64 + e = state[p+4].int64 + f = state[p+5].int64 + g = state[p+6].int64 + h = state[p+7].int64 + if state.pc.section == "CODE": # bigendian + state.stack.add ArcticType(w: h or g shl 8 or f shl 16 or e shl 24 or d shl 32 or c shl 40 or b shl 48 or a shl 56) + else: + state.stack.add ArcticType(w: a or b shl 8 or c shl 16 or d shl 24 or e shl 32 or f shl 40 or g shl 48 or h shl 56) + + of '5': # STAB + let + x = cast[uint8](state.stack.pop.b) + p = state.stack.pop.f + state[p] = x + + of '6': # STAS + let + x = cast[uint16](state.stack.pop.s) + p = state.stack.pop.f + if state.pc.section == "CODE": # bigendian + state[p] = uint8(x and 0xFF) + state[p+1] = uint8(x shr 8) + else: + state[p] = uint8(x shr 8) + state[p+1] = uint8(x and 0xFF) + + of '7': # STAI + let + x = cast[uint32](state.stack.pop.i) + p = state.stack.pop.f + if state.pc.section == "CODE": # bigendian + state[p] = uint8(x and 0xFF) + state[p+1] = uint8((x shr 8) and 0xFF) + state[p+2] = uint8((x shr 16) and 0xFF) + state[p+3] = uint8((x shr 24) and 0xFF) + else: + state[p] = uint8((x shr 24) and 0xFF) + state[p+1] = uint8((x shr 16) and 0xFF) + state[p+2] = uint8((x shr 8) and 0xFF) + state[p+3] = uint8(x and 0xFF) + + of '8': # STAW + let + x = cast[uint64](state.stack.pop.w) + p = state.stack.pop.f + if state.pc.section == "CODE": # bigendian + state[p] = uint8(x and 0xFF) + state[p+1] = uint8((x shr 8) and 0xFF) + state[p+2] = uint8((x shr 16) and 0xFF) + state[p+3] = uint8((x shr 24) and 0xFF) + state[p+4] = uint8((x shr 32) and 0xFF) + state[p+5] = uint8((x shr 40) and 0xFF) + state[p+6] = uint8((x shr 48) and 0xFF) + state[p+7] = uint8((x shr 56) and 0xFF) + else: + state[p] = uint8((x shr 56) and 0xFF) + state[p+1] = uint8((x shr 48) and 0xFF) + state[p+2] = uint8((x shr 40) and 0xFF) + state[p+3] = uint8((x shr 32) and 0xFF) + state[p+4] = uint8((x shr 24) and 0xFF) + state[p+5] = uint8((x shr 16) and 0xFF) + state[p+6] = uint8((x shr 8) and 0xFF) + state[p+7] = uint8(x and 0xFF) + + of 'D': # MCLR + let + x = state.stack.pop.w + p = state.stack.pop.f + for i in 0 .. x: + state[p+i] = 0 + + of 'd': # MSET + assert op.immediate.kind == INTEGER + let + x = state.stack.pop.w + p = state.stack.pop.f + b = op.immediate.i.uint8 + for i in 0 .. x: + state[p+i] = b + + of 'K': # MCPY + let + x = state.stack.pop.w + q = state.stack.pop.f + p = state.stack.pop.f + for i in 0 .. x: + state[q+i] = state[p+i] + + of 'J': # MOFF + let + x = state.stack.pop.w + p = state.stack.pop.f + state.stack.add ArcticType(f: (section: p.section, index: p.index + 1)) + + of 'M': # MALL + assert op.immediate.kind == INTEGER + let + top = state.stack.pop + n = op.immediate.i + + if n == 0: + if top.w != 0: + state.sections.excl top.f.section + state.stack.add ArcticType(w: 0) + elif n > 0: + if top.w == 0: + state.sections.incl("allocated " & $next_malloc_id, ArcticSection(iscode: false, data: @[])) + inc next_malloc_id + else: + discard # we use dynamic memory, realloc is a no-op + else: + discard # realloc negative is a nop + of 'R': # REAL + let + top = state.stack.pop + x = state.stack.pop.i + if x == 0: + if top.w != 0: + state.sections.excl top.f.section + state.stack.add ArcticType(w: 0) + elif x > 0: + if top.w == 0: + state.sections.incl("allocated " & $next_malloc_id, ArcticSection(iscode: false, data: @[])) + inc next_malloc_id + else: + discard # we use dynamic memory, realloc is a no-op + else: + discard # realloc negative is a nop + + + of '9': # CASS + # TODO: Actually CAS? need to use Atomic[T] for that... + # implementation would probably require an "atomic" memory + # section + let + y = state.stack.pop.w + x = state.stack.pop.w + p = state.stack.pop.f + a = state[p].int64 + b = state[p+1].int64 + c = state[p+2].int64 + d = state[p+3].int64 + e = state[p+4].int64 + f = state[p+5].int64 + g = state[p+6].int64 + h = state[p+7].int64 + + var old: int64 + if state.pc.section == "CODE": # bigendian + old = h or g shl 8 or f shl 16 or e shl 24 or d shl 32 or c shl 40 or b shl 48 or a shl 56 + else: + old = a or b shl 8 or c shl 16 or d shl 24 or e shl 32 or f shl 40 or g shl 48 or h shl 56 + + if x == old: + state.stack.add ArcticType(i: 1) + if state.pc.section == "CODE": # bigendian + state[p] = uint8(y and 0xFF) + state[p+1] = uint8((y shr 8) and 0xFF) + state[p+2] = uint8((y shr 16) and 0xFF) + state[p+3] = uint8((y shr 24) and 0xFF) + state[p+4] = uint8((y shr 32) and 0xFF) + state[p+5] = uint8((y shr 40) and 0xFF) + state[p+6] = uint8((y shr 48) and 0xFF) + state[p+7] = uint8((y shr 56) and 0xFF) + else: + state[p] = uint8((y shr 56) and 0xFF) + state[p+1] = uint8((y shr 48) and 0xFF) + state[p+2] = uint8((y shr 40) and 0xFF) + state[p+3] = uint8((y shr 32) and 0xFF) + state[p+4] = uint8((y shr 24) and 0xFF) + state[p+5] = uint8((y shr 16) and 0xFF) + state[p+6] = uint8((y shr 8) and 0xFF) + state[p+7] = uint8(y and 0xFF) + else: + state.stack.add ArcticType(w: 0) + + + # Math Ops ----------------------------------------------------------- + of '+': # IADD + let + y = state.stack.pop.w + x = state.stack.pop.w + + state.stack.add ArcticType(w: x + y) + + of '-': # ISUB + let + y = state.stack.pop.w + x = state.stack.pop.w + + state.stack.add ArcticType(w: x - y) + + of '*': # IMUL + let + y = state.stack.pop.w + x = state.stack.pop.w + + state.stack.add ArcticType(w: x * y) + + of '/': # IDIV + let + y = state.stack.pop.w + x = state.stack.pop.w + + state.stack.add ArcticType(w: int(x / y)) + + of '%': # IMOD + let + y = state.stack.pop.w + x = state.stack.pop.w + + state.stack.add ArcticType(w: x.floorMod(y)) + + of '\\': # IREM + let + y = state.stack.pop.w + x = state.stack.pop.w + + state.stack.add ArcticType(w: x mod y) + + of '_': # INEG + let x = state.stack.pop.w + state.stack.add ArcticType(w: -x) + + of '|': # IABS + let x = state.stack.pop.w + state.stack.add ArcticType(w: x.abs) + + of 's': # FADD + let + b = state.stack.pop.d + a = state.stack.pop.d + + state.stack.add ArcticType(d: a + b) + + of 'm': # FSUB + let + b = state.stack.pop.d + a = state.stack.pop.d + + state.stack.add ArcticType(d: a - b) + + of 'p': # FMUL + let + b = state.stack.pop.d + a = state.stack.pop.d + + state.stack.add ArcticType(d: a * b) + + of 'q': # FDIV + let + b = state.stack.pop.d + a = state.stack.pop.d + + state.stack.add ArcticType(d: a / b) + + of 'f': # FMOD + let + b = state.stack.pop.d + a = state.stack.pop.d + + state.stack.add ArcticType(d: floorMod(a, b)) + + of 'r': # FREM + let + b = state.stack.pop.d + a = state.stack.pop.d + + state.stack.add ArcticType(d: a mod b) + + of 'j': # FNEG + let + a = state.stack.pop.d + + state.stack.add ArcticType(d: -a) + + of 'v': # FABS + let + a = state.stack.pop.d + + state.stack.add ArcticType(d: a.abs) + + of 'V': # BIOR + let + y = state.stack.pop.w + x = state.stack.pop.w + + state.stack.add ArcticType(w: x or y) + + of '&': # BAND + let + y = state.stack.pop.w + x = state.stack.pop.w + + state.stack.add ArcticType(w: x and y) + + of '^': # BXOR + let + y = state.stack.pop.w + x = state.stack.pop.w + + state.stack.add ArcticType(w: x xor y) + + of 'l': # BITC + let + y = state.stack.pop.w + x = state.stack.pop.w + + state.stack.add ArcticType(w: x and (not y)) + + of '!': # BNOT + let x = state.stack.pop.w + + state.stack.add ArcticType(w: not x) + + of 'u': # USHR + let x = state.stack.pop.w + var i: int64 + case op.immediate.kind: + of INTEGER: + i = op.immediate.i + of VARIABLE: + let v = op.immediate.v + i = state.registers[v].w + else: + assert false + + if i > 0: + state.stack.add ArcticType(w: x shr i) + else: + state.stack.add ArcticType(w: x shl (-i)) + + of '[': # ROTR + let x = state.stack.pop.u + var i: int64 + case op.immediate.kind: + of INTEGER: + i = op.immediate.i + of VARIABLE: + let v = op.immediate.v + i = state.registers[v].w + else: + assert false + + if i > 0: + state.stack.add ArcticType(u: rotateRightBits(x, i)) + else: + state.stack.add ArcticType(u: rotateLeftBits(x, -i)) + + of ']': # BSHR + let x = state.stack.pop.u + var i: int64 + case op.immediate.kind: + of INTEGER: + i = op.immediate.i + of VARIABLE: + let v = op.immediate.v + i = state.registers[v].w + else: + assert false + + if i > 0: + state.stack.add ArcticType(u: rotateRightBits(x, i)) + else: + state.stack.add ArcticType(u: rotateLeftBits(x, -i)) + + of '?': # CMPI + let + y = state.stack.pop.w + x = state.stack.pop.w + if x > y: + state.stack.add ArcticType(w: 1) + elif y > x: + state.stack.add ArcticType(w: -1) + else: + state.stack.add ArcticType(w: 0) + + of '~': # CMPF + assert op.immediate.kind == INTEGER + let + b = state.stack.pop.d + a = state.stack.pop.d + if almostEqual(a, b, op.immediate.i.Natural): + state.stack.add ArcticType(d: 0.0) + elif a > b: + state.stack.add ArcticType(d: 1.0) + else: + state.stack.add ArcticType(d: -1.0) + + of 'U': # CMPU + let + y = state.stack.pop.u + x = state.stack.pop.u + if x > y: + state.stack.add ArcticType(w: 1) + elif y > x: + state.stack.add ArcticType(w: -1) + else: + state.stack.add ArcticType(w: 0) + + of 'F': # BMIS + discard # TODO: define BMIS + + # Type Conversion Ops -------------------------------------------------- + of 'E': # FTOI + let a = state.stack.pop.d + state.stack.add ArcticType(w: a.int64) + + of 'O': # ITOC + let x = state.stack.pop.b + state.stack.add ArcticType(w: x.int64) + + of 'S': # ITOS + let x = state.stack.pop.s + state.stack.add ArcticType(w: x.int64) + + of 'W': # ITOW + let x = state.stack.pop.i + state.stack.add ArcticType(w: x.int64) + + of '.': # ITOF + let x = state.stack.pop.w + state.stack.add ArcticType(d: x.float64) + + # Stack Ops ------------------------------------------------------------ + of 'w': # SDUP + let x = state.stack.pop + state.stack.add x + state.stack.add x + + of 'k': # SPOP + discard state.stack.pop + + of 't': # SWAP + let + y = state.stack.pop + x = state.stack.pop + state.stack.add y + state.stack.add x + + of 'h': # OVER + let + y = state.stack.pop + x = state.stack.pop + state.stack.add x + state.stack.add y + state.stack.add x + + of 'e': # SROT + let + z = state.stack.pop + y = state.stack.pop + x = state.stack.pop + state.stack.add y + state.stack.add z + state.stack.add x + + of 'P': # PICK + assert op.immediate.kind == INTEGER + let val = state.stack[^op.immediate.i] + state.stack.add val + + of 'L': # ROLL + assert op.immediate.kind == INTEGER + let idx: int = state.stack.len - op.immediate.i # TODO: check for OBOEs + state.stack.delete(idx) + + of 'T': # PUSH + assert op.immediate.kind == INTEGER + let + idx: int = state.stack.len - op.immediate.i # TODO: check for OBOEs + x = state.stack.pop + state.stack.insert(x, idx) + + of '(': # DPTH + state.stack.add ArcticType(w: state.stack.len) + + of ')': # PACK + let + x = state.stack.pop.w + p = state.stack.pop.f + # TODO: Can't implement this until we have unified addresses + # Once we have the addresses, can pack x items of stack at p+... + + # Control Ops ---------------------------------------------------------- ## TODO + ## TODO + when isMainModule: let state = stdin.readAll.load