# Copyright (c) 2023 Peter McGoron # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. from creole import * import unittest import ffi class PushTest(unittest.TestCase): def test_parse_push_reg(self): p = Program() p.parse_asm_line("push r5") self.assertEqual(p(), b'\x01\xC2\x85\x00') def test_parse_push_imm(self): p = Program() p.parse_asm_line("push 5") b = p() self.assertEqual(b, b'\x01\xC0\x85\x00') def test_parse_push_catch_typecheck_push_lab(self): p = Program() with self.assertRaises(TypecheckException) as cm: p.parse_asm_line("push .l0") self.assertEqual(cm.exception.argtype, ArgType.VAL) self.assertEqual(cm.exception.sarg, '.l0') self.assertEqual(cm.exception.i, 0) self.assertEqual(cm.exception.opcode, Instruction.PUSH) def test_parse_push_catch_typecheck_argument_overflow(self): p = Program() with self.assertRaises(TypecheckLenException) as cm: p.parse_asm_line("push r1 r2") self.assertEqual(cm.exception.opcode, Instruction.PUSH) self.assertEqual(cm.exception.insargs, ["r1", "r2"]) self.assertEqual(cm.exception.argtypelen, 1) def test_parse_push_catch_typecheck_argument_underflow(self): p = Program() with self.assertRaises(TypecheckLenException) as cm: p.parse_asm_line("push") self.assertEqual(cm.exception.opcode, Instruction.PUSH) self.assertEqual(cm.exception.insargs, []) self.assertEqual(cm.exception.argtypelen, 1) def test_large_reg(self): p = Program(reglen=0x8000000) p.parse_asm_line("PUSH r134217727") b = p() self.assertEqual(b, b'\x01\xFC\x8f\xbf\xbf\xbf\xbf\x00') def test_compile_push(self): p = Program() p.parse_asm_line("PUSH r0") p.parse_asm_line("PUSH 6") ex = ffi.Environment(p()) def test_push_many(self): p = Program() stklen = 40 for n in range(0,stklen): p.parse_asm_line("PUSH 5") ex = ffi.Environment(p(), stklen=stklen) self.assertEqual(ex(), ffi.RunRet.STOP) for n in range(0,stklen): self.assertEqual(ex.getstk(n), 5) def test_push_overflow(self): p = Program() stklen = 40 for n in range(0, stklen + 1): p.parse_asm_line("PUSH 5") ex = ffi.Environment(p(), stklen=stklen) self.assertEqual(ex(), ffi.RunRet.STACK_OVERFLOW) class PopTest(unittest.TestCase): def test_compile_pop_reg(self): p = Program() p.parse_asm_line("pop r9") b = p() self.assertEqual(b, b'\x02\xC2\x89\x00') ex = ffi.Environment(b) def test_compile_throw_pop_literal(self): p = Program() with self.assertRaises(TypecheckException) as cm: p.parse_asm_line("pop 6") self.assertEqual(cm.exception.argtype, ArgType.REG) self.assertEqual(cm.exception.sarg, '6') self.assertEqual(cm.exception.i, 0) self.assertEqual(cm.exception.opcode, Instruction.POP) def test_compile_throw_pop_label(self): p = Program() with self.assertRaises(TypecheckException) as cm: p.parse_asm_line("pop .l9") self.assertEqual(cm.exception.argtype, ArgType.REG) self.assertEqual(cm.exception.sarg, '.l9') self.assertEqual(cm.exception.i, 0) self.assertEqual(cm.exception.opcode, Instruction.POP) def test_compile_throw_argument_overflow(self): p = Program() with self.assertRaises(TypecheckLenException) as cm: p.parse_asm_line("pop r1 r2") self.assertEqual(cm.exception.opcode, Instruction.POP) self.assertEqual(cm.exception.insargs, ["r1", "r2"]) self.assertEqual(cm.exception.argtypelen, 1) def test_catch_typecheck_argument_underflow(self): p = Program() with self.assertRaises(TypecheckLenException) as cm: p.parse_asm_line("pop") self.assertEqual(cm.exception.opcode, Instruction.POP) self.assertEqual(cm.exception.insargs, []) def test_pop_underflow(self): p = Program() p.parse_asm_line("pop r0") ex = ffi.Environment(p()) self.assertEqual(ex(), ffi.RunRet.STACK_UNDERFLOW) def test_pop_underflow_2(self): p = Program() p.parse_asm_line("push 5") p.parse_asm_line("pop r0") p.parse_asm_line("pop r1") ex = ffi.Environment(p()) self.assertEqual(ex(), ffi.RunRet.STACK_UNDERFLOW) class AddTest(unittest.TestCase): def test_exec_add(self): p = Program() p.parse_asm_line("add r0 1 1") ex = ffi.Environment(p) self.assertEqual(ex(), ffi.RunRet.STOP) self.assertEqual(ex.cenv.reg[0], 2) def test_exec_add_neg(self): p = Program() p.parse_asm_line("add r0 10 20") p.parse_asm_line("add r1 5 0") p.parse_asm_line("add r1 r0 -40") ex = ffi.Environment(p()) self.assertEqual(ex(), ffi.RunRet.STOP) self.assertEqual(ex.getreg(0), 30) self.assertEqual(ex.getreg(1, signed=True), -10) def test_exec_add_throw_imm(self): p = Program() with self.assertRaises(TypecheckException) as cm: p.parse_asm_line("add 5 6 7") self.assertEqual(cm.exception.argtype, ArgType.REG) self.assertEqual(cm.exception.sarg, '5') self.assertEqual(cm.exception.i, 0) self.assertEqual(cm.exception.opcode, Instruction.ADD) def test_exec_add_throw_lab_1(self): p = Program() with self.assertRaises(TypecheckException) as cm: p.parse_asm_line("add r0 .label 7") self.assertEqual(cm.exception.argtype, ArgType.VAL) self.assertEqual(cm.exception.sarg, '.label') self.assertEqual(cm.exception.i, 1) self.assertEqual(cm.exception.opcode, Instruction.ADD) def test_exec_add_throw_lab_2(self): p = Program() with self.assertRaises(TypecheckException) as cm: p.parse_asm_line("add r0 12 .ab") self.assertEqual(cm.exception.argtype, ArgType.VAL) self.assertEqual(cm.exception.sarg, '.ab') self.assertEqual(cm.exception.i, 2) self.assertEqual(cm.exception.opcode, Instruction.ADD) class MulTest(unittest.TestCase): def test_exec_mul_imm_imm(self): p = Program() p.parse_asm_line("mul r0 2 2") ex = ffi.Environment(p()) self.assertEqual(ex(), ffi.RunRet.STOP) self.assertEqual(ex.getreg(0), 4) def test_exec_mul_imm_neg_imm(self): p = Program() p.parse_asm_line("mul r0 -5 5") p.parse_asm_line("mul r1 r0 -5") ex = ffi.Environment(p()) self.assertEqual(ex(), ffi.RunRet.STOP) self.assertEqual(ex.getreg(0, signed=True), -25) self.assertEqual(ex.getreg(1), 125) def test_exec_mul_throw_imm(self): p = Program() with self.assertRaises(TypecheckException) as cm: p.parse_asm_line("mul 942 6 7") self.assertEqual(cm.exception.argtype, ArgType.REG) self.assertEqual(cm.exception.sarg, '942') self.assertEqual(cm.exception.i, 0) self.assertEqual(cm.exception.opcode, Instruction.MUL) def test_exec_mul_throw_lab_1(self): p = Program() with self.assertRaises(TypecheckException) as cm: p.parse_asm_line("mul r9 .l2 1991") self.assertEqual(cm.exception.argtype, ArgType.VAL) self.assertEqual(cm.exception.sarg, '.l2') self.assertEqual(cm.exception.i, 1) self.assertEqual(cm.exception.opcode, Instruction.MUL) def test_exec_mul_throw_lab_2(self): p = Program() with self.assertRaises(TypecheckException) as cm: p.parse_asm_line("mul r0 -11 .l48") self.assertEqual(cm.exception.argtype, ArgType.VAL) self.assertEqual(cm.exception.sarg, '.l48') self.assertEqual(cm.exception.i, 2) self.assertEqual(cm.exception.opcode, Instruction.MUL) class DivTest(unittest.TestCase): def test_div(self): p = Program() p.parse_asm_line("div r0 8 4") ex = ffi.Environment(p()) self.assertEqual(ex(), ffi.RunRet.STOP) self.assertEqual(ex.getreg(0), 2) def test_div_round_down(self): p = Program() p.parse_asm_line("div r0 8 10") ex = ffi.Environment(p()) self.assertEqual(ex(), ffi.RunRet.STOP) def test_div_by_zero(self): p = Program() p.parse_asm_line("div r0 8 0") ex = ffi.Environment(p()) self.assertEqual(ex(), ffi.RunRet.DIVIDE_BY_ZERO) def test_sdiv_by_zero(self): p = Program() p.parse_asm_line("sdiv r0 8 0") ex = ffi.Environment(p()) self.assertEqual(ex(), ffi.RunRet.DIVIDE_BY_ZERO) def test_sdiv_neg(self): p = Program() p.parse_asm_line("sdiv r0 16 -4") p.parse_asm_line("sdiv r1 r0 -4") ex = ffi.Environment(p()) self.assertEqual(ex(), ffi.RunRet.STOP) self.assertEqual(ex.getreg(0, signed=True), -4) self.assertEqual(ex.getreg(1), 1) def test_exec_div_throw_imm(self): p = Program() with self.assertRaises(TypecheckException) as cm: p.parse_asm_line("div 5 1 2") self.assertEqual(cm.exception.argtype, ArgType.REG) self.assertEqual(cm.exception.sarg, '5') self.assertEqual(cm.exception.i, 0) self.assertEqual(cm.exception.opcode, Instruction.DIV) def test_exec_div_throw_lab_1(self): p = Program() with self.assertRaises(TypecheckException) as cm: p.parse_asm_line("div r0 .qqweq 456") self.assertEqual(cm.exception.argtype, ArgType.VAL) self.assertEqual(cm.exception.sarg, '.qqweq') self.assertEqual(cm.exception.i, 1) self.assertEqual(cm.exception.opcode, Instruction.DIV) def test_exec_div_throw_lab_2(self): p = Program() with self.assertRaises(TypecheckException) as cm: p.parse_asm_line("div r5 1919 .24") self.assertEqual(cm.exception.argtype, ArgType.VAL) self.assertEqual(cm.exception.sarg, '.24') self.assertEqual(cm.exception.i, 2) self.assertEqual(cm.exception.opcode, Instruction.DIV) class LabelTest(unittest.TestCase): def test_unconditional_jump(self): p = Program() p.parse_lines([ "mov r0 5", "mov r0 6", "j .l0", "mov r0 7", ".l0", ]) ex = ffi.Environment(p()) self.assertEqual(ex(), ffi.RunRet.STOP) self.assertEqual(ex.getreg(0), 6) def test_simple_loop(self): p = Program() p.parse_lines([ "add r0 10 0", "add r1 20 0", ".loop_head", "add r0 r0 -1", "add r1 r1 1", "jl .loop_head 0 r0" ]) ex = ffi.Environment(p()) self.assertEqual(ex(), ffi.RunRet.STOP) self.assertEqual(ex.getreg(0), 0) self.assertEqual(ex.getreg(1), 30) def test_signed_jmp(self): p = Program() p.parse_lines([ "mov r0 30", "mov r1 0", ".l0", "add r0 r0 -1", "add r1 r1 1", "jls .l0 -30 r0" ]) ex = ffi.Environment(p()) self.assertEqual(ex(), ffi.RunRet.STOP) self.assertEqual(ex.getreg(0, signed=True), -30) self.assertEqual(ex.getreg(1), 60) def test_jeq(self): p = Program() p.parse_lines([ "mov r0 50", "mov r1 0", ".l0", "add r1 r1 1", "mul r2 r0 -1", "add r2 r2 r1", "jne .l0 r2 0" ]) ex = ffi.Environment(p()) self.assertEqual(ex(), ffi.RunRet.STOP) self.assertEqual(ex.getreg(0, signed=True), 50) self.assertEqual(ex.getreg(1), 50) self.assertEqual(ex.getreg(2), 0) def test_nested_loop(self): p = Program() p.parse_lines([ "mov r0 0", # outer loop counter "mov r2 0", # total iteration counter ".outer_loop", "mov r1 0", # inner loop counter ".inner_loop", "add r1 r1 1", "add r2 r2 1", "jl .inner_loop r1 50", "add r0 r0 1", "jl .outer_loop r0 50" ]) ex = ffi.Environment(p()) self.assertEqual(ex(), ffi.RunRet.STOP) self.assertEqual(ex.getreg(0), 50) self.assertEqual(ex.getreg(1), 50) self.assertEqual(ex.getreg(2), 50*50) def test_many_jumps(self): p = Program() p.parse_lines([ "j .f1", ".f4", "j .f5", ".f2", "j .f3", ".f3", "j .f4", ".f5", "j .f6", ".f1", "j .f2", ".f6" ]) ex = ffi.Environment(p()) self.assertEqual(ex(), ffi.RunRet.STOP) class ProgramTest(unittest.TestCase): def test_exec_simple_reg(self): p = Program() p.parse_asm_line("push 5") p.parse_asm_line("push 6") p.parse_asm_line("pop r0") p.parse_asm_line("pop r1") ex = ffi.Environment() self.assertEqual(ex.load(p()), ffi.CompileRet.OK) self.assertEqual(ex(), ffi.RunRet.STOP) self.assertEqual(ex.getreg(0), 6) self.assertEqual(ex.getreg(1), 5) def range_test(self, st, en, sgn=False): for i in range(st, en): p = Program() p.parse_asm_line(f"mov r0 {i}") ex = ffi.Environment(p()) self.assertEqual(ex(), ffi.RunRet.STOP) self.assertEqual(ex.getreg(0, signed=sgn), i) @unittest.skip("slow") def test_parse_imm_compile(self): self.range_test(0, 0x1000) self.range_test(0x1000, 0x1100) self.range_test(0x1FF00, 0x20000) self.range_test(0x20000, 0x20100) self.range_test(0x3FFF00, 0x400000) self.range_test(0x400000, 0x400100) self.range_test(0x7FFFF00, 0x8000000) self.range_test(0x8000000, 0x8000100) self.range_test(0xFFFFFF00, 0x100000000) @unittest.skip("slow") def test_parse_imm_signed(self): self.range_test(0, 0x1000, sgn=True) self.range_test(0x1000, 0x1100, sgn=True) self.range_test(0x1FF00, 0x20000, sgn=True) self.range_test(0x20000, 0x20100, sgn=True) self.range_test(0x3FFF00, 0x400000, sgn=True) self.range_test(0x400000, 0x400100, sgn=True) self.range_test(0x7FFFF00, 0x8000000, sgn=True) self.range_test(-100, 0, sgn=True) class DataTest(unittest.TestCase): def test_parse_db(self): p = Program() p.parse_asm_line("db d0 [4d2,1234,0,5]") self.assertEqual(p(), b'\x0b\xc0\x80\xe0\x93\x92\xf0\x81\x88\xb4\xc0\x80\xc0\x85\x00') def test_alloc_multiple(self): p = Program() p.parse_lines([ "db d0 [1,2,3,4]", "db d1 [10,11,12,13]" ]) ex = ffi.Environment(p()) self.assertEqual(ex(), ffi.RunRet.STOP) self.assertEqual(ex.getdat(0), [1,2,3,4]) self.assertEqual(ex.getdat(1), [0x10,0x11,0x12,0x13]) def test_alloc_repeat(self): p = Program() p.parse_lines([ "db d0 [1,2,3,4]", "db d1 [10,11,12,13]", "db d0 [5,6,7,8,9]" ]) ex = ffi.Environment(p()) self.assertEqual(ex(), ffi.RunRet.STOP) self.assertEqual(ex.getdat(0), [5,6,7,8,9]) self.assertEqual(ex.getdat(1), [0x10,0x11,0x12,0x13]) def test_alloc_repeat_jump_skip(self): p = Program() p.parse_lines([ "db d0 [1,2,3,4]", "db d1 [10,11,12,13]", "j .end", "db d1 [5,6,7,8,9]", ".end" ]) ex = ffi.Environment(p()) self.assertEqual(ex(), ffi.RunRet.STOP) self.assertEqual(ex.getdat(0), [1,2,3,4]) self.assertEqual(ex.getdat(1), [0x10,0x11,0x12,0x13]) class SCEnv(ffi.Environment): def syscall(self, s): self.s = s.value def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) class ProgramTest(unittest.TestCase): def range_test(self, st, en, sgn=False): for i in range(st, en): p = Program() p.parse_asm_line(f"sys {i}") ex = SCEnv(p()) self.assertEqual(ex(), ffi.RunRet.STOP) self.assertEqual(ex.s, i) def test_syscall_imm(self): self.range_test(0, 1000) if __name__ == "__main__": unittest.main()