OISC:3
Obfuscated Indirect Subleq with Coprocessor: 3 word instructions
This is a continuation and improvement of the OISC: (pronounced "whiskey") project. The intent is to make a Subleq like language more powerful by intentionally obfuscating it. Unfortunately, there is now a parser for OISC:3 that accepts command macros, so it is a bit too easy to program in. Of course, if you want a challenge, you could still work with the raw numbers.
The most important changes over OISC:2, besides a working assembler, are:
- 3 words per instruction: A B C where C = B - A
- Indirect addressing is now accomplished through the use of decimal addresses. "121.0" redirects, whereas "121" is a direct address.
- Negative memory is much easier to use.
- There is a stack. Coprocessor instructions are executed via the stack.
- There is a return stack with call and return functions.
Instruction formats:
A B C C = B - A /sub A B 0 B = B - literal A /lit- A 0 C if A <= 0, call C /call pushes NextIP onto the return stack 0 B C if B <= 0, jump to C /jump A 0 0 push A onto the stack /push 0 B 0 pop the top of stack to B /pop 0 0 C execute instruction C /exec operates on the stack 0 0 0 return /ret pops the top of return stack to NextIP
Assembler formats:
# comment % data name: naming a memory word (label) name using a named word (label) *name indirect reference from name (if N is 23, *N is 23.0) @ this word ? next word % --NEGATIVE--: --NEGATIVE-- mandatory memory separator ! 0 ; end of instruction , separator " or ' string delimiters ZERO automatically created word containing 0
/sub can be called with 1, 2 or 3 words A B C C = B - A A B B = B - A A A = 0 /lit- must be called with 2 words A B B = B - literal A /call and /jump can be called with 1 or 2 words. A B if A <= 0, branch to B A unconditional branch to A /push and /pop and /exec are called with 1 word A push A / pop to A / execute instruction at A /ret takes no arguments. If the return stack is empty, the program will halt. Branching to a negative address will also halt.
Stack instructions:
Positive Negative 1 input char ( -- a) output char (a -- ) 2 input digit ( -- a) output number (a -- ) 3 DUP (a -- aa) DROP (a -- ) 4 OVER (ab -- aba) SWAP (ab -- ba) 5 roll left N (abcd1 -- bcda) roll right N (abcd2 -- cdab) 6 reverse stack (abcd -- dcba) clear stack (abcd -- ) 7 depth of stack ( -- a) pick N (abcd3 -- abcdb) 8 bitwise true ( -- -1) bitwise false ( -- 0) 9 bitwise AND (ab -- a&b) bitwise NOT (a -- ~a) 10 bitwise OR (ab -- a|b) bitwise XOR (ab -- a^b) 11 shift left N bits (aN -- a) shift right N bits (aN -- a) 12 times (ab -- a*b) divide (ab -- a/b) 13 int division (ab -- a//b) remainder (ab -- a%b) 14 exponent e (a -- exp(a)) natural log (a -- log(a)) 15 convert to integer (a -- a) convert to float (a -- a) 16 alloc N words (sign matters) free N words (sign matters) 17 plus (ab -- a+b) minus (ab -- a-b) 18 sin (a -- sin(a)) asin (a -- asin(a)) 19 cos (a -- cos(a)) acos (a -- acos(a)) 20 tan (a -- tan(a)) atan (a -- atan(a)) 21 sinh (a -- sinh(a)) asinh (a -- asinh(a)) 22 cosh (a -- cos(a)) acosh (a -- acosh(a)) 23 tanh (a -- cos(a)) atanh (a -- atanh(a))
Sample program
1 1 1 # Harmless fun for the first instruction. Can't directly jump back to location 0. # Should really be "/jump Main", which allows for front loading data. Main: /push Text0* # -132 0 0 /call Print # -133 0 42 /push Ten # -1 0 0 /exec Malloc # 0 0 -2 /push Mten # -4 0 0 /exec Malloc # 0 0 -2 /push Mten # -4 0 0 /exec Free # 0 0 -3 /push Ten # -1 0 0 /exec Free # 0 0 -3 /push Text1* # -50 0 0 This is much easier than the previous methods of moving a pointer around. /call Print # -133 0 42 Unconditional call, adds in a reference to 0. NextIP is pushed to the return stack. /jump -1 # 0 -133 -1 Halt Print: /pop print* # 0 66 0 pop beginning of text reference to print* Ploop: /push *print* # 66.0 0 0 Indirect reference. /exec writechar # 0 0 -51 /lit- 1 print* # 1 66 0 Text data is in negative memory, so subtracting one advances along the string. /jump *print* Pend # 0 66.0 60 Test for end of string marker (<= 0). /jump Ploop # 0 -133 45 Unconditional jump. Parser appends "ZERO: 0" automatically (in this case, at location -133). Pend: /sub print* # 66 66 66 Zeroes out print*. Not strictly necessary, but good form. /ret # 0 0 0 Return stack is popped to NextIP. % print*: 0 # 0 Data in positive memory must be preceded by the '%' indicator. % --NEGATIVE--: --NEGATIVE-- # MUST be here in this form. Ten: 10 # 10 Malloc: 16 # 16 Free: -16 # -16 Mten: -10 # -10 Text1: '"' "These are the times that try men's souls." '"' 10 0 # 10 is newline. 0 is end of string marker. # 34 84 104 101 115 101 32 97 114 101 32 116 104 101 32 116 105 109 101 115 32 116 104 97 116 32 # 116 114 121 32 109 101 110 39 115 32 115 111 117 108 115 46 34 10 0 Text1*: Text1 # -5 writechar: -1 # -1 Text0: "Malloc & Free test." 10 "Use a negative number to allocate or free negative memory." 10 0 # 77 97 108 108 111 99 32 38 32 70 114 101 101 32 116 101 115 116 46 10 85 115 101 32 # 97 32 110 101 103 97 116 105 118 101 32 110 117 109 98 101 114 32 116 111 32 # 97 108 108 111 99 97 116 101 32 111 114 32 102 114 101 101 32 110 101 103 97 116 105 118 101 32 # 109 101 109 111 114 121 46 10 0 Text0*: Text0 # -52
Philosophy
What's the point of all this? What is an instruction?
Subleq is, to me, the premier one instruction set computer (OISC). Three memory addresses, A B C, where B=B-A, if B<=0, jump to C. That's it. Simple, elegant, usable. An operating system (Dawn, sadly no longer available) was once written using it. Since there is only one instruction, it is assumed. But to be useful, any language has to perform I/O. So Subleq uses memory mappings for input, output, and halt. Each of these attaches to a different part of the instruction (A, B, or C). It works well. Where is the instruction?
Subleq+ uses negative numbers to indicate indirect addressing. It is a touch more complicated, but works well and enhances the power of the language. Where is the instruction?
OISC: uses the trichotomy of numbers (positive, negative and zero), along with floating points and integers, to accomplish several different things using the same three (or two) words, which are memory addresses. There is even a parser which uses recognizable commands. But these commands are seen nowhere in the actual executable code. It's nothing but memory addresses expressed as numbers in different ways. Where is the instruction?
Complexity arises from relationships. Power arises from complexity.
Computational class
OISC:3 is Turing-complete. It derives from its ancestor Subleq, but has additional capabilities, including a stack.
See also
- OISC One instruction set computing
- Subleq Subleq
- Subleq+ Subleq with indirection through negative addressing
- OISC:2 The original OISC: project
- OISC:2bis New and improved, with a stack and parser derived from OISC:3
External resources
- OISC:3 github repository
- Improving Subleq on Lawrence's Woodman's Tech Tinkering