Implemented many operations

This commit is contained in:
Louis Burke 2023-12-19 00:31:53 -05:00
parent a89fe4f0c6
commit 13f9330eea
8 changed files with 1309 additions and 122 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
build/
arctic

View file

@ -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

7
doc/documentation.css Normal file
View file

@ -0,0 +1,7 @@
#instructions ~ table td:first-child {
font-family: monospace;
}
#instructions ~ table td:first-child + td {
font-family: monospace;
}

View file

@ -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)<i?0:((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]

13
samples/hello.ctc Normal file
View file

@ -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;

View file

@ -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

View file

@ -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,

View file

@ -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