def _pop(register): """ Pop off the stack, manipulating both TOS and PSP. """ ucode = assemble(SET, register, Z) ucode += assemble(SET, Z, POP) return ucode
def POPRSP(register): """ Pop from RSP. """ ucode = assemble(SET, register, [Y]) ucode += assemble(ADD, Y, 0x1) return ucode
def _push(register): """ Push onto the stack, manipulating both TOS and PSP. """ ucode = assemble(SET, PUSH, Z) ucode += assemble(SET, Z, register) return ucode
def if_else(target, otherwise): """ Add a call to a block directly after an if statement. The block will only be executed if the if block was not executed. """ print "Making if/else", hex(target), hex(otherwise) # We don't know the size of the block we wish to jump over quite yet; # let's figure that out first. ifblock = call(target) # Let's also make the else block. elseblock = call(otherwise) # Same as before, but with a twist: At the end of the ifblock, we're going # to jump over the else block in the same style. ifblock += assemble(ADD, PC, len(elseblock) // 2) # Now assemble as before. First, the test. ucode = assemble(IFE, 0x0, POP) # Now we jump over the block... ucode += assemble(ADD, PC, len(ifblock) // 2) # And insert the call to the block. ucode += ifblock # Now the else block. ucode += elseblock # All done! return ucode
def PUSHRSP(register): """ Push onto RSP. """ ucode = assemble(SUB, Y, 0x1) ucode += assemble(SET, [Y], register) return ucode
def binop(op): """ Compile a binary operation. """ opcode = binops[op] ucode = assemble(SET, A, POP) ucode += assemble(opcode, PEEK, A) return ucode
def read(register): """ Get a byte from the keyboard and put it in the given register. This blocks. """ ucode = assemble(SET, register, [0x9010]) ucode = until(ucode, (IFE, register, 0x0)) ucode += assemble(SET, [0x9010], 0x0) ucode += assemble(SET, PC, POP) return ucode
def bootloader(start): """ Set up stacks and registers, and then jump to a starting point. After things are finished, pop some of the stack to registers, and halt with an illegal opcode. """ # First things first. Set up the call stack. Currently hardcoded. ucode = assemble(SET, Z, 0xd000) # Hardcode the location of the tail, and call. ucode += call(start) # And we're off! As soon as we come back down, pop I and J so we can see # them easily. ucode += assemble(SET, I, POP) ucode += assemble(SET, J, POP) # Finish off with an illegal opcode. ucode += pack(">H", 0x0) return ucode
def ret(): """ Return to the caller. It's totally possible to return to lala-land with this function. Don't use it if you are not confident that the caller actually pushed a return location onto the return/call stack. """ return assemble(SET, PC, [Z])
def call(target): """ Call a subroutine. This call is built to be position-independent. The return value pushed onto the stack is calculated at runtime, and the return/call stack is managed by this function, so no effort is required beyond ensuring that the target is already fixed in location. Safety not guaranteed; you might not ever come back. """ # Make space on the call stack. ucode = assemble(SUB, Z, 0x1) # Hax. Calculate where we currently are based on PC, and then expect that # we will take a certain number of words to make our actual jump. # Grab PC into a GPR. Note that PC increments before it's grabbed here, so # this instruction doesn't count towards our total. ucode += assemble(SET, A, PC) # 0x0+1: Add our offset to PC in A. ucode += assemble(ADD, A, 0x4) # 0x1+1: Push our offset into the ret/call stack on Z. ucode += assemble(SET, [Z], A) # 0x2+2: Make our call, rigged so that it will always be two words. ucode += assemble(SET, PC, Absolute(target)) # 0x4 is business as usual. Whereever we were from, we *probably* wanna # decrement Z again. ucode += assemble(ADD, Z, 0x1) return ucode
def if_alone(target): """ Consider the current value on the stack. If it's true, then execute a given code block. Otherwise, jump to the next code block. """ print "Making if", hex(target) # We don't know the size of the block we wish to jump over quite yet; # let's figure that out first. block = call(target) # Our strategy is to put together a small jump over the block if the value # is false. If it's true, then the IFE will jump over the jump. Double # negatives fail to lose again! ucode = assemble(IFE, 0x0, POP) # Now we jump over the block... ucode += assemble(ADD, PC, len(block) // 2) # And insert the call to the block. ucode += block # All done! return ucode
def rot(): ucode = assemble(SET, A, POP) ucode += assemble(SET, B, POP) ucode += assemble(SET, C, POP) ucode += assemble(SET, PUSH, B) ucode += assemble(SET, PUSH, A) ucode += assemble(SET, PUSH, C) return ucode
def asm(self, name, ucode, flags=None): """ Write an assembly-level word into the core. Here's what the word looks like: |prev|len |name|asm |NEXT| """ print "Adding assembly word %s" % name self.create(name, flags) self.space.write(ucode) self.space.write(assemble(SET, PC, self.asmwords["next"]))
def bootloader(self): """ Set up the bootloader. """ self.space.write(assemble(SET, Y, 0xD000)) self.space.write(assemble(SET, J, 0x5)) self.space.write(assemble(SET, PC, [J])) # Allocate space for the address of QUIT. self.space.write("\x00\x00") # Allocate space for STATE. self.STATE = self.space.tell() self.space.write("\x00\x00") # And HERE. self.HERE = self.space.tell() self.space.write("\x00\x00") # And LATEST, too. self.LATEST = self.space.tell() self.space.write("\x00\x00") # Don't forget FB. self.FB = self.space.tell() self.space.write("\x80\x00") # NEXT. Increment IP and move through it. ucode = assemble(ADD, J, 0x1) ucode += assemble(SET, PC, [J]) self.prim("next", ucode) # EXIT. Pop RSP into IP and then call NEXT. ucode = POPRSP(J) ucode += assemble(SET, PC, self.asmwords["next"]) self.prim("exit", ucode) # ENTER. Save IP to RSP, dereference IP to find the caller, enter the # new word, call NEXT. ucode = PUSHRSP(J) ucode += assemble(SET, J, [J]) ucode += assemble(SET, PC, self.asmwords["next"]) self.prim("enter", ucode)
def builtin(word): """ Compile a builtin word. """ try: i = int(word) ucode = assemble(SET, PUSH, i) return ucode except ValueError: pass if word in prims: return prims[word]() if word in binops: return binop(word) raise Exception("Don't know builtin %r" % word)
def memcpy(): """ Copy A bytes from B to C. Clobbers A. The copy is made back to front. No overlapping check is done. """ preamble = assemble(ADD, B, A) preamble += assemble(ADD, C, A) # Top of the loop. ucode = assemble(SUB, A, 0x1) ucode += assemble(SUB, B, 0x1) ucode += assemble(SUB, C, 0x1) ucode += assemble(SET, [C], [B]) ucode = until(ucode, (IFN, A, 0x0)) # And return. ucode += assemble(SET, PC, POP) return preamble + ucode
def thread(self, name, words, flags=None): """ Assemble a thread of words into the core. Here's what a thread looks like: |prev|len |name|ENTER|word|EXIT| """ print "Adding Forth thread %s" % name self.create(name, flags) # ENTER/DOCOL bytecode. ucode = assemble(SET, PC, self.asmwords["enter"]) self.space.write(ucode) for word in words: if isinstance(word, int): self.space.write(pack(">H", word)) elif word in self.codewords: self.space.write(pack(">H", self.codewords[word])) else: raise Exception("Can't reference unknown word %r" % word) self.space.write(pack(">H", self.asmwords["exit"]))
def memcmp(): """ Put a length in A, two addresses in B and C, and fill A with whether they match (non-zero) or don't match (zero). """ # Save X. preamble = assemble(SET, PUSH, X) preamble += assemble(SET, X, 0x0) preamble += assemble(ADD, B, A) preamble += assemble(ADD, C, A) # Top of the loop. ucode = assemble(SUB, B, 0x1) ucode += assemble(SUB, C, 0x1) ucode += assemble(IFE, [B], [C]) ucode += assemble(BOR, X, 0xffff) ucode = until(ucode, (IFN, A, 0x0)) ucode += assemble(SET, A, X) ucode += assemble(XOR, A, 0xffff) # Restore X. ucode += assemble(SET, X, POP) ucode += assemble(SET, PC, POP) return preamble + ucode
def dup(): ucode = assemble(SET, A, PEEK) ucode += assemble(SET, PUSH, A) return ucode
def test_set_register_literal(self): expected = "\x7c\x01\x00\x30" self.assertEqual(expected, assemble(SET, A, 0x30))
def rdrop(): return assemble(ADD, X, 0x1)
def r_at(): return assemble(SET, PUSH, [X])
def drop(): return assemble(ADD, SP, 0x1)
def to_r(): ucode = assemble(SUB, X, 0x1) ucode += assemble(SET, [X], POP) return ucode
def swap(): ucode = assemble(SET, A, POP) ucode += assemble(SET, B, POP) ucode += assemble(SET, PUSH, A) ucode += assemble(SET, PUSH, B) return ucode
def test_jsr_literal(self): expected = "\x7c\x10\x00\x42" self.assertEqual(expected, assemble(JSR, 0x42))
def over(): ucode = assemble(SET, A, SP) ucode += assemble(SET, PUSH, [A + 0x1]) return ucode
elif word in self.codewords: self.space.write(pack(">H", self.codewords[word])) else: raise Exception("Can't reference unknown word %r" % word) self.space.write(pack(">H", self.asmwords["exit"])) ma = MetaAssembler() # Deep primitives. ma.prim("read", read(A)) ma.prim("write", write(A)) # Top of the line: Go back to the beginning of the string. ucode = assemble(SET, B, 0x0) ucode += assemble(SET, C, ma.workspace) # Read a character into A. ucode += call(ma.asmwords["read"]) ucode += assemble(SET, [C], A) ucode += assemble(ADD, B, 0x1) ucode += assemble(ADD, C, 0x1) # If it's a space, then we're done. Otherwise, go back to reading things from # the keyboard. ucode = until(ucode, (IFN, 0x20, [C])) ucode += assemble(SET, C, ma.workspace) ma.prim("word", ucode) preamble = assemble(SET, C, 0x0) ucode = assemble(MUL, C, 10) ucode += assemble(SET, X, [A])
def write(register): """ Write a byte to the framebuffer. The register needs to not be PEEK or POP, because SP is modified when this function is entered. Self-modifying code is used to track the cursor in the framebuffer. """ # Save Y. ucode = assemble(SET, PUSH, Y) # Save Z. ucode += assemble(SET, PUSH, Z) # Save the data that we're supposed to push. ucode = assemble(SET, PUSH, register) # Do some tricky PC manipulation to get a bareword into the code, and # sneak its address into Z. ucode += assemble(SET, Z, PC) ucode += assemble(IFE, 0x0, 0x0) ucode += "\x80\x00" ucode += assemble(ADD, Z, 0x1) # Dereference the framebuffer. ucode += assemble(SET, Y, [Z]) # Write to the framebuffer. ucode += assemble(SET, [Y], POP) # Advance the framebuffer. ucode += assemble(ADD, [Z], 0x1) # If the framebuffer has wrapped, wrap the pointer. ucode += assemble(IFG, 0x8200, [Z]) ucode += assemble(SUB, [Z], 0x200) # Restore registers and leave. ucode += assemble(SET, Z, POP) ucode += assemble(SET, Y, POP) ucode += assemble(SET, PC, POP) return ucode
def test_set_push_z(self): expected = "\x15\xa1" self.assertEqual(expected, assemble(SET, PUSH, Z))