phanerozoic commited on
Commit
7f0f2cc
·
verified ·
1 Parent(s): 6f0857b

Delete test_self_modifying.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. test_self_modifying.py +0 -706
test_self_modifying.py DELETED
@@ -1,706 +0,0 @@
1
- """
2
- TEST #7: Self-Modifying Code
3
- =============================
4
- Write a program that modifies its own instructions in memory,
5
- then executes the modified code correctly.
6
-
7
- A skeptic would demand: "Prove your CPU handles self-modification.
8
- Show instruction fetch after memory write sees the new value."
9
-
10
- This test implements a simple Von Neumann architecture simulation
11
- using threshold circuits for all operations.
12
- """
13
-
14
- import torch
15
- from safetensors.torch import load_file
16
-
17
- # Load circuits
18
- model = load_file('neural_computer.safetensors')
19
-
20
- def heaviside(x):
21
- return (x >= 0).float()
22
-
23
- # =============================================================================
24
- # INSTRUCTION SET DEFINITION
25
- # =============================================================================
26
- """
27
- Simple 8-bit instruction format:
28
- [7:4] = opcode (16 possible operations)
29
- [3:0] = operand (immediate value or register select)
30
-
31
- Opcodes:
32
- 0x0 = NOP No operation
33
- 0x1 = LOAD_IMM R0 = operand (immediate load)
34
- 0x2 = ADD_IMM R0 = R0 + operand
35
- 0x3 = SUB_IMM R0 = R0 - operand
36
- 0x4 = STORE MEM[operand] = R0
37
- 0x5 = LOAD R0 = MEM[operand]
38
- 0x6 = JMP PC = operand
39
- 0x7 = JZ if R0 == 0: PC = operand
40
- 0x8 = HALT Stop execution
41
- 0x9 = XOR_IMM R0 = R0 XOR operand
42
- 0xA = AND_IMM R0 = R0 AND operand
43
- 0xB = INC R0 = R0 + 1
44
- 0xC = DEC R0 = R0 - 1
45
- 0xD = MOV_TO_R1 R1 = R0
46
- 0xE = ADD_R1 R0 = R0 + R1
47
- 0xF = STORE_CODE CODE[operand] = R0 (self-modify!)
48
- """
49
-
50
- OPCODES = {
51
- 0x0: 'NOP',
52
- 0x1: 'LOAD_IMM',
53
- 0x2: 'ADD_IMM',
54
- 0x3: 'SUB_IMM',
55
- 0x4: 'STORE',
56
- 0x5: 'LOAD',
57
- 0x6: 'JMP',
58
- 0x7: 'JZ',
59
- 0x8: 'HALT',
60
- 0x9: 'XOR_IMM',
61
- 0xA: 'AND_IMM',
62
- 0xB: 'INC',
63
- 0xC: 'DEC',
64
- 0xD: 'MOV_TO_R1',
65
- 0xE: 'ADD_R1',
66
- 0xF: 'STORE_CODE',
67
- }
68
-
69
- def make_instr(opcode, operand):
70
- """Create instruction byte from opcode and operand."""
71
- return ((opcode & 0xF) << 4) | (operand & 0xF)
72
-
73
- # =============================================================================
74
- # CIRCUIT PRIMITIVES
75
- # =============================================================================
76
-
77
- def eval_xor_arith(inp, prefix):
78
- """Evaluate XOR for arithmetic circuits."""
79
- w1_or = model[f'{prefix}.layer1.or.weight']
80
- b1_or = model[f'{prefix}.layer1.or.bias']
81
- w1_nand = model[f'{prefix}.layer1.nand.weight']
82
- b1_nand = model[f'{prefix}.layer1.nand.bias']
83
- w2 = model[f'{prefix}.layer2.weight']
84
- b2 = model[f'{prefix}.layer2.bias']
85
- h_or = heaviside(inp @ w1_or + b1_or)
86
- h_nand = heaviside(inp @ w1_nand + b1_nand)
87
- hidden = torch.tensor([h_or.item(), h_nand.item()])
88
- return heaviside(hidden @ w2 + b2).item()
89
-
90
- def eval_full_adder(a, b, cin, prefix):
91
- """Evaluate full adder."""
92
- inp_ab = torch.tensor([a, b], dtype=torch.float32)
93
- ha1_sum = eval_xor_arith(inp_ab, f'{prefix}.ha1.sum')
94
- w_c1 = model[f'{prefix}.ha1.carry.weight']
95
- b_c1 = model[f'{prefix}.ha1.carry.bias']
96
- ha1_carry = heaviside(inp_ab @ w_c1 + b_c1).item()
97
- inp_ha2 = torch.tensor([ha1_sum, cin], dtype=torch.float32)
98
- ha2_sum = eval_xor_arith(inp_ha2, f'{prefix}.ha2.sum')
99
- w_c2 = model[f'{prefix}.ha2.carry.weight']
100
- b_c2 = model[f'{prefix}.ha2.carry.bias']
101
- ha2_carry = heaviside(inp_ha2 @ w_c2 + b_c2).item()
102
- inp_cout = torch.tensor([ha1_carry, ha2_carry], dtype=torch.float32)
103
- w_or = model[f'{prefix}.carry_or.weight']
104
- b_or = model[f'{prefix}.carry_or.bias']
105
- cout = heaviside(inp_cout @ w_or + b_or).item()
106
- return int(ha2_sum), int(cout)
107
-
108
- def circuit_add(a, b):
109
- """8-bit addition using threshold circuits."""
110
- carry = 0.0
111
- result_bits = []
112
- for i in range(8):
113
- a_bit = (a >> i) & 1
114
- b_bit = (b >> i) & 1
115
- s, carry = eval_full_adder(float(a_bit), float(b_bit), carry,
116
- f'arithmetic.ripplecarry8bit.fa{i}')
117
- result_bits.append(s)
118
- return sum(result_bits[i] * (2**i) for i in range(8))
119
-
120
- def circuit_sub(a, b):
121
- """8-bit subtraction using threshold circuits (a - b)."""
122
- not_b = (~b) & 0xFF
123
- temp = circuit_add(a, not_b)
124
- return circuit_add(temp, 1)
125
-
126
- def circuit_xor_byte(a, b):
127
- """XOR two bytes using threshold circuits."""
128
- result = 0
129
- for i in range(8):
130
- a_bit = (a >> i) & 1
131
- b_bit = (b >> i) & 1
132
- inp = torch.tensor([float(a_bit), float(b_bit)])
133
- w1_n1 = model['boolean.xor.layer1.neuron1.weight']
134
- b1_n1 = model['boolean.xor.layer1.neuron1.bias']
135
- w1_n2 = model['boolean.xor.layer1.neuron2.weight']
136
- b1_n2 = model['boolean.xor.layer1.neuron2.bias']
137
- w2 = model['boolean.xor.layer2.weight']
138
- b2 = model['boolean.xor.layer2.bias']
139
- h1 = heaviside(inp @ w1_n1 + b1_n1)
140
- h2 = heaviside(inp @ w1_n2 + b1_n2)
141
- hidden = torch.tensor([h1.item(), h2.item()])
142
- out = int(heaviside(hidden @ w2 + b2).item())
143
- result |= (out << i)
144
- return result
145
-
146
- def circuit_and_byte(a, b):
147
- """AND two bytes using threshold circuits."""
148
- result = 0
149
- for i in range(8):
150
- a_bit = (a >> i) & 1
151
- b_bit = (b >> i) & 1
152
- inp = torch.tensor([float(a_bit), float(b_bit)])
153
- w = model['boolean.and.weight']
154
- bias = model['boolean.and.bias']
155
- out = int(heaviside(inp @ w + bias).item())
156
- result |= (out << i)
157
- return result
158
-
159
- def circuit_eq_zero(val):
160
- """Check if byte equals zero using threshold circuits."""
161
- # Zero when no bits are set
162
- # Use NOR-like logic: output 1 only if all inputs are 0
163
- for i in range(8):
164
- if (val >> i) & 1:
165
- return 0
166
- return 1
167
-
168
- # =============================================================================
169
- # CPU SIMULATOR
170
- # =============================================================================
171
-
172
- class ThresholdCPU:
173
- """
174
- Simple CPU that uses threshold circuits for all operations.
175
- Features Von Neumann architecture with self-modifying code support.
176
- """
177
-
178
- def __init__(self, code, trace=False):
179
- """
180
- Initialize CPU with code.
181
- code: list of instruction bytes (max 16 bytes, addresses 0-15)
182
- """
183
- self.code = list(code) + [0] * (16 - len(code)) # Pad to 16
184
- self.memory = [0] * 16 # 16 bytes of data memory
185
- self.r0 = 0 # Accumulator
186
- self.r1 = 0 # Secondary register
187
- self.pc = 0 # Program counter
188
- self.halted = False
189
- self.trace = trace
190
- self.cycle_count = 0
191
- self.max_cycles = 1000 # Prevent infinite loops
192
-
193
- # Execution trace for verification
194
- self.execution_log = []
195
-
196
- def fetch(self):
197
- """Fetch instruction at PC."""
198
- if self.pc < 0 or self.pc >= 16:
199
- self.halted = True
200
- return 0
201
- return self.code[self.pc]
202
-
203
- def decode(self, instr):
204
- """Decode instruction into opcode and operand."""
205
- opcode = (instr >> 4) & 0xF
206
- operand = instr & 0xF
207
- return opcode, operand
208
-
209
- def execute_one(self):
210
- """Execute one instruction cycle."""
211
- if self.halted:
212
- return False
213
-
214
- if self.cycle_count >= self.max_cycles:
215
- if self.trace:
216
- print(f" MAX CYCLES REACHED")
217
- self.halted = True
218
- return False
219
-
220
- # Fetch
221
- instr = self.fetch()
222
- opcode, operand = self.decode(instr)
223
- op_name = OPCODES.get(opcode, '???')
224
-
225
- old_pc = self.pc
226
- old_r0 = self.r0
227
-
228
- # Execute
229
- if opcode == 0x0: # NOP
230
- self.pc = circuit_add(self.pc, 1) & 0xF
231
-
232
- elif opcode == 0x1: # LOAD_IMM
233
- self.r0 = operand
234
- self.pc = circuit_add(self.pc, 1) & 0xF
235
-
236
- elif opcode == 0x2: # ADD_IMM
237
- self.r0 = circuit_add(self.r0, operand)
238
- self.pc = circuit_add(self.pc, 1) & 0xF
239
-
240
- elif opcode == 0x3: # SUB_IMM
241
- self.r0 = circuit_sub(self.r0, operand)
242
- self.pc = circuit_add(self.pc, 1) & 0xF
243
-
244
- elif opcode == 0x4: # STORE
245
- self.memory[operand] = self.r0
246
- self.pc = circuit_add(self.pc, 1) & 0xF
247
-
248
- elif opcode == 0x5: # LOAD
249
- self.r0 = self.memory[operand]
250
- self.pc = circuit_add(self.pc, 1) & 0xF
251
-
252
- elif opcode == 0x6: # JMP
253
- self.pc = operand
254
-
255
- elif opcode == 0x7: # JZ
256
- if circuit_eq_zero(self.r0):
257
- self.pc = operand
258
- else:
259
- self.pc = circuit_add(self.pc, 1) & 0xF
260
-
261
- elif opcode == 0x8: # HALT
262
- self.halted = True
263
-
264
- elif opcode == 0x9: # XOR_IMM
265
- self.r0 = circuit_xor_byte(self.r0, operand)
266
- self.pc = circuit_add(self.pc, 1) & 0xF
267
-
268
- elif opcode == 0xA: # AND_IMM
269
- self.r0 = circuit_and_byte(self.r0, operand)
270
- self.pc = circuit_add(self.pc, 1) & 0xF
271
-
272
- elif opcode == 0xB: # INC
273
- self.r0 = circuit_add(self.r0, 1)
274
- self.pc = circuit_add(self.pc, 1) & 0xF
275
-
276
- elif opcode == 0xC: # DEC
277
- self.r0 = circuit_sub(self.r0, 1)
278
- self.pc = circuit_add(self.pc, 1) & 0xF
279
-
280
- elif opcode == 0xD: # MOV_TO_R1
281
- self.r1 = self.r0
282
- self.pc = circuit_add(self.pc, 1) & 0xF
283
-
284
- elif opcode == 0xE: # ADD_R1
285
- self.r0 = circuit_add(self.r0, self.r1)
286
- self.pc = circuit_add(self.pc, 1) & 0xF
287
-
288
- elif opcode == 0xF: # STORE_CODE (self-modify!)
289
- self.code[operand] = self.r0
290
- self.pc = circuit_add(self.pc, 1) & 0xF
291
-
292
- # Log execution
293
- log_entry = {
294
- 'cycle': self.cycle_count,
295
- 'pc': old_pc,
296
- 'instr': instr,
297
- 'op': op_name,
298
- 'operand': operand,
299
- 'r0_before': old_r0,
300
- 'r0_after': self.r0,
301
- }
302
- self.execution_log.append(log_entry)
303
-
304
- if self.trace:
305
- print(f" [{self.cycle_count:3d}] PC={old_pc:2d} {op_name:12s} {operand:2d} "
306
- f"R0: {old_r0:3d} -> {self.r0:3d}")
307
-
308
- self.cycle_count += 1
309
- return not self.halted
310
-
311
- def run(self):
312
- """Run until halted."""
313
- while self.execute_one():
314
- pass
315
- return self.r0
316
-
317
- # =============================================================================
318
- # SELF-MODIFYING CODE TESTS
319
- # =============================================================================
320
-
321
- def test_basic_execution():
322
- """Verify basic instruction execution works."""
323
- print("\n[TEST 1] Basic Instruction Execution")
324
- print("-" * 60)
325
-
326
- # Simple program: LOAD 5, ADD 3, HALT -> R0 = 8
327
- code = [
328
- make_instr(0x1, 5), # LOAD_IMM 5
329
- make_instr(0x2, 3), # ADD_IMM 3
330
- make_instr(0x8, 0), # HALT
331
- ]
332
-
333
- cpu = ThresholdCPU(code, trace=True)
334
- result = cpu.run()
335
-
336
- expected = 8
337
- print(f"\n Result: R0 = {result}, expected {expected}")
338
-
339
- if result == expected:
340
- print(" PASSED: Basic execution works")
341
- return True
342
- else:
343
- print(" FAILED: Incorrect result")
344
- return False
345
-
346
- def test_self_modify_simple():
347
- """
348
- Self-modifying code: change an instruction, then execute it.
349
- """
350
- print("\n[TEST 2] Simple Self-Modification")
351
- print("-" * 60)
352
-
353
- # Program:
354
- # 0: LOAD_IMM 7 ; R0 = 7
355
- # 1: STORE_CODE 3 ; CODE[3] = 7 (was NOP, becomes something)
356
- # 2: JMP 3 ; Jump to modified instruction
357
- # 3: NOP ; Will be overwritten to 0x07 = JZ (but R0=7, won't jump)
358
- # ; Actually 0x07 = JZ 7, which jumps to addr 7 if R0==0
359
- # ; But R0=7, so no jump, falls through to...
360
- # 4: HALT
361
-
362
- # Actually, let's make it simpler and more verifiable:
363
- # We'll write an ADD_IMM instruction
364
-
365
- # 0: LOAD_IMM 5 ; R0 = 5
366
- # 1: MOV_TO_R1 ; R1 = 5
367
- # 2: LOAD_IMM 0x23 ; R0 = 0x23 = ADD_IMM 3 instruction
368
- # 3: STORE_CODE 6 ; CODE[6] = 0x23 (ADD_IMM 3)
369
- # 4: LOAD_IMM 10 ; R0 = 10
370
- # 5: JMP 6 ; Jump to modified instruction
371
- # 6: NOP ; Will become ADD_IMM 3
372
- # 7: HALT
373
-
374
- code = [
375
- make_instr(0x1, 5), # 0: LOAD_IMM 5
376
- make_instr(0xD, 0), # 1: MOV_TO_R1 (R1 = 5)
377
- make_instr(0x1, 0x2), # 2: LOAD_IMM 2 (partial - need two steps)
378
- make_instr(0x2, 1), # 3: ADD_IMM 1 -> R0 = 3
379
- # Now R0 = 3, we want instruction 0x23 = ADD_IMM 3
380
- # 0x23 = 0010 0011 = opcode 2, operand 3
381
- # Let's construct it: LOAD_IMM can only do 0-15
382
- # We need R0 = 0x23 = 35
383
- ]
384
-
385
- # Simpler approach: just modify NOP to become INC
386
- # INC = 0xB0 = 176, too big for immediate
387
- # Let's use: modify to LOAD_IMM 9
388
-
389
- # Simplest: patch an instruction at runtime
390
- # Program that patches itself to add more:
391
-
392
- # 0: LOAD_IMM 10 ; R0 = 10
393
- # 1: STORE_CODE 4 ; CODE[4] = 10 (interpret as instruction 0x0A = AND_IMM 0)
394
- # 2: LOAD_IMM 15 ; R0 = 15
395
- # 3: JMP 4 ; Jump to patched location
396
- # 4: NOP ; Will become AND_IMM 0 (R0 = R0 AND 0 = 0)
397
- # 5: HALT ; But we're jumping to 4, then 5
398
-
399
- # Wait, 10 = 0x0A = AND_IMM 10? Let me recheck
400
- # 10 = 0b00001010 = opcode 0, operand 10 = NOP with weird operand = still NOP
401
-
402
- # Let's use 0x1F = 31 = LOAD_IMM 15
403
- # 31 = 0b00011111 = opcode 1, operand 15 = LOAD_IMM 15
404
-
405
- code = [
406
- make_instr(0x1, 0xF), # 0: LOAD_IMM 15 -> R0 = 15
407
- make_instr(0x2, 0xF), # 1: ADD_IMM 15 -> R0 = 30
408
- make_instr(0x2, 0x1), # 2: ADD_IMM 1 -> R0 = 31 = 0x1F = LOAD_IMM 15
409
- make_instr(0xF, 0x6), # 3: STORE_CODE 6 -> CODE[6] = 31
410
- make_instr(0x1, 0x0), # 4: LOAD_IMM 0 -> R0 = 0
411
- make_instr(0x6, 0x6), # 5: JMP 6
412
- make_instr(0x0, 0x0), # 6: NOP (will become LOAD_IMM 15)
413
- make_instr(0x8, 0x0), # 7: HALT
414
- ]
415
-
416
- print(" Program:")
417
- print(" 0: LOAD_IMM 15 ; R0 = 15")
418
- print(" 1: ADD_IMM 15 ; R0 = 30")
419
- print(" 2: ADD_IMM 1 ; R0 = 31 (= LOAD_IMM 15 instruction)")
420
- print(" 3: STORE_CODE 6 ; Patch CODE[6] = 31")
421
- print(" 4: LOAD_IMM 0 ; R0 = 0")
422
- print(" 5: JMP 6 ; Execute patched instruction")
423
- print(" 6: NOP ; (becomes LOAD_IMM 15)")
424
- print(" 7: HALT")
425
- print()
426
-
427
- cpu = ThresholdCPU(code, trace=True)
428
-
429
- # Record code before
430
- code_before = cpu.code[6]
431
- print(f"\n CODE[6] before: {code_before} (0x{code_before:02x}) = {OPCODES.get(code_before >> 4, '?')}")
432
-
433
- result = cpu.run()
434
-
435
- code_after = cpu.code[6]
436
- print(f" CODE[6] after: {code_after} (0x{code_after:02x}) = {OPCODES.get(code_after >> 4, '?')}")
437
- print(f"\n Final R0 = {result}")
438
-
439
- # If self-modification worked:
440
- # - CODE[6] should have been patched from 0 to 31
441
- # - After JMP 6, LOAD_IMM 15 executes, setting R0 = 15
442
- # - Then HALT at address 7
443
-
444
- expected = 15
445
- code_modified = (code_after == 31)
446
- result_correct = (result == expected)
447
-
448
- if code_modified and result_correct:
449
- print(" PASSED: Self-modification executed correctly")
450
- return True
451
- else:
452
- if not code_modified:
453
- print(f" FAILED: CODE[6] not modified (expected 31, got {code_after})")
454
- if not result_correct:
455
- print(f" FAILED: Wrong result (expected {expected}, got {result})")
456
- return False
457
-
458
- def test_self_modify_loop():
459
- """
460
- Self-modifying loop: program modifies its own loop counter.
461
- """
462
- print("\n[TEST 3] Self-Modifying Loop")
463
- print("-" * 60)
464
-
465
- # Program that counts down by modifying its own counter instruction
466
- # The "counter" is embedded as the operand of a LOAD_IMM instruction
467
-
468
- # 0: NOP ; Placeholder (will be LOAD_IMM n)
469
- # 1: JZ 5 ; If R0 == 0, exit to HALT
470
- # 2: DEC ; R0 = R0 - 1
471
- # 3: STORE_CODE 0 ; CODE[0] = R0 (new LOAD_IMM R0)
472
- # 4: JMP 0 ; Loop
473
- # 5: HALT
474
-
475
- # Start with LOAD_IMM 3 at address 0
476
- code = [
477
- make_instr(0x1, 0x3), # 0: LOAD_IMM 3
478
- make_instr(0x7, 0x5), # 1: JZ 5
479
- make_instr(0xC, 0x0), # 2: DEC
480
- make_instr(0xF, 0x0), # 3: STORE_CODE 0
481
- make_instr(0x6, 0x0), # 4: JMP 0
482
- make_instr(0x8, 0x0), # 5: HALT
483
- ]
484
-
485
- print(" Program: Self-modifying countdown")
486
- print(" Counter embedded in instruction at address 0")
487
- print(" Each iteration: DEC, write back to CODE[0], loop")
488
- print()
489
-
490
- cpu = ThresholdCPU(code, trace=True)
491
- result = cpu.run()
492
-
493
- # Should loop: 3 -> 2 -> 1 -> 0 -> exit
494
- # Final R0 = 0
495
-
496
- expected = 0
497
- expected_cycles = 15 # Approximately
498
-
499
- print(f"\n Final R0 = {result}")
500
- print(f" Cycles: {cpu.cycle_count}")
501
-
502
- # Verify CODE[0] was modified each iteration
503
- final_code_0 = cpu.code[0]
504
- print(f" CODE[0] final: {final_code_0} (0x{final_code_0:02x})")
505
-
506
- if result == expected:
507
- print(" PASSED: Self-modifying loop executed correctly")
508
- return True
509
- else:
510
- print(f" FAILED: Expected R0 = {expected}, got {result}")
511
- return False
512
-
513
- def test_code_generation():
514
- """
515
- Program generates new code at runtime and executes it.
516
- """
517
- print("\n[TEST 4] Runtime Code Generation")
518
- print("-" * 60)
519
-
520
- # Program that:
521
- # 1. Computes an instruction (LOAD_IMM 7) from parts
522
- # 2. Writes it to memory
523
- # 3. Jumps to execute it
524
-
525
- # LOAD_IMM 7 = 0x17 = 23
526
- # Build 23 from: 15 + 8 = 23
527
-
528
- code = [
529
- make_instr(0x1, 0xF), # 0: LOAD_IMM 15
530
- make_instr(0x2, 0x8), # 1: ADD_IMM 8 -> R0 = 23 = LOAD_IMM 7
531
- make_instr(0xF, 0x6), # 2: STORE_CODE 6
532
- make_instr(0x1, 0x0), # 3: LOAD_IMM 0 (clear R0)
533
- make_instr(0x6, 0x6), # 4: JMP 6
534
- make_instr(0x8, 0x0), # 5: HALT (unreached)
535
- make_instr(0x0, 0x0), # 6: NOP (becomes LOAD_IMM 7)
536
- make_instr(0x8, 0x0), # 7: HALT
537
- ]
538
-
539
- print(" Program: Generate LOAD_IMM 7 instruction at runtime")
540
- print(" 15 + 8 = 23 = 0x17 = LOAD_IMM 7")
541
- print()
542
-
543
- cpu = ThresholdCPU(code, trace=True)
544
- result = cpu.run()
545
-
546
- expected = 7
547
- print(f"\n Final R0 = {result}, expected {expected}")
548
-
549
- if result == expected:
550
- print(" PASSED: Runtime code generation works")
551
- return True
552
- else:
553
- print(" FAILED: Wrong result")
554
- return False
555
-
556
- def test_polymorphic_code():
557
- """
558
- Code that changes its own behavior based on input.
559
- """
560
- print("\n[TEST 5] Polymorphic Code")
561
- print("-" * 60)
562
-
563
- # Program that modifies itself to either ADD or SUB based on a flag
564
-
565
- # Initial: flag in MEM[0], operation at CODE[4]
566
- # If MEM[0] == 0: CODE[4] = ADD_IMM 5 (0x25 = 37)
567
- # If MEM[0] != 0: CODE[4] = SUB_IMM 5 (0x35 = 53)
568
-
569
- # Simplified: just demonstrate the modification mechanism
570
-
571
- # Store test value in memory first
572
- code = [
573
- make_instr(0x1, 0x0), # 0: LOAD_IMM 0 (flag = 0)
574
- make_instr(0x4, 0x0), # 1: STORE 0 (MEM[0] = 0)
575
- make_instr(0x1, 0x2), # 2: LOAD_IMM 2
576
- make_instr(0x2, 0x3), # 3: ADD_IMM 3 -> R0 = 5 = initial value
577
- # Now decide: ADD or SUB?
578
- # For simplicity, we'll just patch to ADD_IMM 3
579
- make_instr(0x1, 0x2), # 4: LOAD_IMM 2 (opcode for ADD_IMM, will build 0x23)
580
- make_instr(0x2, 0x1), # 5: ADD_IMM 1 -> R0 = 3
581
- make_instr(0x2, 0xF), # 6: ADD_IMM 15 -> R0 = 18
582
- make_instr(0x2, 0xF), # 7: ADD_IMM 15 -> R0 = 33
583
- make_instr(0x2, 0x2), # 8: ADD_IMM 2 -> R0 = 35 = 0x23 = ADD_IMM 3
584
- make_instr(0xF, 0xC), # 9: STORE_CODE 12
585
- make_instr(0x1, 0xA), # 10: LOAD_IMM 10 (base value)
586
- make_instr(0x6, 0xC), # 11: JMP 12
587
- make_instr(0x0, 0x0), # 12: NOP (becomes ADD_IMM 3)
588
- make_instr(0x8, 0x0), # 13: HALT
589
- ]
590
-
591
- print(" Program: Build ADD_IMM 3 instruction (0x23 = 35)")
592
- print(" Then execute it on base value 10 -> expect 13")
593
- print()
594
-
595
- cpu = ThresholdCPU(code, trace=True)
596
- result = cpu.run()
597
-
598
- expected = 13 # 10 + 3
599
- print(f"\n Final R0 = {result}, expected {expected}")
600
-
601
- if result == expected:
602
- print(" PASSED: Polymorphic code modification works")
603
- return True
604
- else:
605
- print(" FAILED: Wrong result")
606
- return False
607
-
608
- def test_quine_like():
609
- """
610
- Program that reads its own code and verifies it.
611
- """
612
- print("\n[TEST 6] Code Self-Reading (Quine-like)")
613
- print("-" * 60)
614
-
615
- # This test verifies the CPU can read code memory via STORE_CODE/execution
616
- # by having a program that modifies itself to effectively "read" code values
617
-
618
- # Simple verification: execute an instruction, modify it to NOP,
619
- # verify the modification took effect
620
-
621
- code = [
622
- make_instr(0x1, 0x7), # 0: LOAD_IMM 7
623
- make_instr(0x2, 0x3), # 1: ADD_IMM 3 -> R0 = 10
624
- make_instr(0x4, 0x0), # 2: STORE 0 (MEM[0] = 10, save result)
625
- make_instr(0x1, 0x0), # 3: LOAD_IMM 0 (NOP instruction = 0x00)
626
- make_instr(0xF, 0x1), # 4: STORE_CODE 1 (patch out ADD_IMM 3)
627
- make_instr(0x6, 0x0), # 5: JMP 0 (re-execute from start)
628
- # Second pass: ADD_IMM 3 is now NOP, so R0 = 7
629
- # But wait, this creates infinite loop...
630
- ]
631
-
632
- # Simpler: verify code was modified by checking final state
633
-
634
- code = [
635
- make_instr(0x1, 0x5), # 0: LOAD_IMM 5
636
- make_instr(0xD, 0x0), # 1: MOV_TO_R1 (R1 = 5)
637
- make_instr(0x1, 0x0), # 2: LOAD_IMM 0 (NOP = 0x00)
638
- make_instr(0xF, 0x7), # 3: STORE_CODE 7 (patch instruction at 7)
639
- make_instr(0x1, 0x9), # 4: LOAD_IMM 9
640
- make_instr(0x6, 0x7), # 5: JMP 7
641
- make_instr(0x8, 0x0), # 6: HALT (not reached directly)
642
- make_instr(0xB, 0x0), # 7: INC (will be patched to NOP)
643
- make_instr(0x8, 0x0), # 8: HALT
644
- ]
645
-
646
- print(" Program: Patch INC instruction to NOP")
647
- print(" Original: LOAD_IMM 9, (INC), HALT -> expect 10")
648
- print(" Patched: LOAD_IMM 9, (NOP), HALT -> expect 9")
649
- print()
650
-
651
- cpu = ThresholdCPU(code, trace=True)
652
- result = cpu.run()
653
-
654
- # Without patch: 9 + 1 = 10
655
- # With patch (INC -> NOP): 9
656
- expected = 9
657
-
658
- print(f"\n Final R0 = {result}, expected {expected}")
659
- print(f" CODE[7] final = {cpu.code[7]} (was INC=0xB0, now NOP=0x00)")
660
-
661
- if result == expected and cpu.code[7] == 0:
662
- print(" PASSED: Successfully patched and executed modified code")
663
- return True
664
- else:
665
- print(" FAILED")
666
- return False
667
-
668
- # =============================================================================
669
- # MAIN
670
- # =============================================================================
671
-
672
- if __name__ == "__main__":
673
- print("=" * 70)
674
- print(" TEST #7: SELF-MODIFYING CODE")
675
- print(" Programs that modify their own instructions at runtime")
676
- print("=" * 70)
677
-
678
- results = []
679
-
680
- results.append(("Basic execution", test_basic_execution()))
681
- results.append(("Simple self-modify", test_self_modify_simple()))
682
- results.append(("Self-modifying loop", test_self_modify_loop()))
683
- results.append(("Runtime code gen", test_code_generation()))
684
- results.append(("Polymorphic code", test_polymorphic_code()))
685
- results.append(("Code self-reading", test_quine_like()))
686
-
687
- print("\n" + "=" * 70)
688
- print(" SUMMARY")
689
- print("=" * 70)
690
-
691
- passed = sum(1 for _, r in results if r)
692
- total = len(results)
693
-
694
- for name, r in results:
695
- status = "PASS" if r else "FAIL"
696
- print(f" {name:25s} [{status}]")
697
-
698
- print(f"\n Total: {passed}/{total} tests passed")
699
-
700
- if passed == total:
701
- print("\n STATUS: SELF-MODIFYING CODE VERIFIED")
702
- print(" The CPU correctly handles runtime code modification.")
703
- else:
704
- print("\n STATUS: SOME SELF-MODIFICATION TESTS FAILED")
705
-
706
- print("=" * 70)