# 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, 1) 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, 1) 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, 1) 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()) self.assertEqual(ex.cenv.prgend, 2) self.assertEqual(ex.cenv.prg[0].opcode, 1) self.assertEqual(ex.cenv.prg[0].w_flags[0], 1) self.assertEqual(ex.cenv.prg[0].w_flags[1], 0) self.assertEqual(ex.cenv.prg[0].w_flags[2], 0) self.assertEqual(ex.cenv.prg[0].w[0], 0) self.assertEqual(ex.cenv.prg[0].w[1], 0) self.assertEqual(ex.cenv.prg[0].w[2], 0) self.assertEqual(ex.cenv.prg[1].opcode, 1) self.assertEqual(ex.cenv.prg[1].w_flags[0], 0) self.assertEqual(ex.cenv.prg[1].w_flags[1], 0) self.assertEqual(ex.cenv.prg[1].w_flags[2], 0) self.assertEqual(ex.cenv.prg[1].w[0], 6) self.assertEqual(ex.cenv.prg[1].w[1], 0) self.assertEqual(ex.cenv.prg[1].w[2], 0) 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) self.assertEqual(ex.cenv.prgend, 1) self.assertEqual(ex.cenv.prg[0].opcode, 2) self.assertEqual(ex.cenv.prg[0].w_flags[0], 1) self.assertEqual(ex.cenv.prg[0].w_flags[1], 0) self.assertEqual(ex.cenv.prg[0].w_flags[2], 0) self.assertEqual(ex.cenv.prg[0].w[0], 9) self.assertEqual(ex.cenv.prg[0].w[1], 0) self.assertEqual(ex.cenv.prg[0].w[2], 0) 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, 2) 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, 2) 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, 2) 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, 2) 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, 3) 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, 3) 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, 3) 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, 4) 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, 4) 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, 4) 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, 5) 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, 5) 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, 5) 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", "CLB 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", "CLB l0", "add r0 r0 -1", "add r1 r1 1", "jl l0 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", "CLB 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", "CLB 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 "CLB l0", "mov r1 0", # inner loop counter "CLB l1", "add r1 r1 1", "add r2 r2 1", "jl l1 r1 50", "add r0 r0 1", "jl l0 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) 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 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()