1 //
2 // Nga.swift
3 //
4 // Nga is the virtual machine at the heart
5 // of RETRO. It provides a MISC based dual
6 // stack processor that's capable of hosting
7 // RETRO or other environments.
8 //
9 // This is a reimplementation in Swift. I've
10 // held off doing this until now, but as I
11 // plan to continue using Apple computers and
12 // iPads, it's becoming clear that they see
13 // this as the future, and it'll become more
14 // difficult to justify holding out on
15 // Objective-C going forward.
16 //
17 // So here we go...
18 //
19 // Copyright 2020, Charles Childers
20 //
21
22 import Foundation
23
24 // Limits
25 let imageSize: Int = 512000
26 let stackSize: Int = 2048
27 let CELLMAX = Int64.max - 1
28 let CELLMIN = Int64.min
29
30 // Notes:
31 // While Nga is designed as a 32-bit system,
32 // this implementation is 64-bit internally
33 // to allow greater numeric range.
34
35 // I'm adding some extensions to existing types
36 // for easier access to individual characters
37 // in strings and bytes in integers.
38
39 extension StringProtocol {
40 var asciiValues: [UInt8] { compactMap(\.asciiValue) }
41 }
42
43 extension FixedWidthInteger where Self: SignedInteger {
44 var bytes: [Int8] {
45 var _endian = littleEndian
46 let bytePtr = withUnsafePointer(to: &_endian) {
47 $0.withMemoryRebound(to: Int8.self, capacity: MemoryLayout<Self>.size) {
48 UnsafeBufferPointer(start: $0, count: MemoryLayout<Self>.size)
49 }
50 }
51 return [Int8](bytePtr)
52 }
53 }
54
55 // I implement a stack class here for handling
56 // the details of the stacks. Stacks in Nga are
57 // LIFO, holding only signed integer values.
58
59 class Stack {
60 private var data = [Int64](repeating: 0, count: stackSize)
61 private var sp: Int = 0
62
tosnull63 public func tos() -> Int64 {
64 return data[sp]
65 }
66
nosnull67 public func nos() -> Int64 {
68 return data[sp - 1]
69 }
70
pushnull71 public func push(_ n: Int64) {
72 sp += 1
73 data[sp] = n
74 }
75
popnull76 public func pop() -> Int64 {
77 let v = data[sp]
78 data[sp] = 0
79 sp -= 1
80 return v
81 }
82
dropnull83 public func drop() {
84 data[sp] = 0
85 sp -= 1
86 }
87
swapnull88 public func swap() {
89 let a = tos()
90 let b = nos()
91 data[sp] = b
92 data[sp - 1] = a
93 }
94
depthnull95 public func depth() -> Int {
96 return sp
97 }
98
itemnull99 public func item(_ n: Int) -> Int64 {
100 return data[n]
101 }
102 }
103
104 // Instruction Pointer and Memory
105 // I'm adding some extra padding at EOM for now.
106 // This may be changed in the future.
107
108 var ip: Int = 0
109 var memory = [Int64](repeating: 0, count: imageSize + 1024)
110
111 // Create the stacks
112
113 var data = Stack()
114 var address = Stack()
115
116 // Now, I implement the instructions. Each gets
117 // a dedicated function.
118
inst_nonull119 func inst_no() {
120 }
121
inst_linull122 func inst_li() {
123 ip += 1
124 data.push(memory[ip])
125 }
126
inst_dunull127 func inst_du() {
128 data.push(data.tos())
129 }
130
inst_drnull131 func inst_dr() {
132 data.drop()
133 }
134
inst_swnull135 func inst_sw() {
136 data.swap()
137 }
138
inst_punull139 func inst_pu() {
140 address.push(data.pop())
141 }
142
inst_ponull143 func inst_po() {
144 data.push(address.pop())
145 }
146
inst_junull147 func inst_ju() {
148 ip = Int(data.pop() - 1)
149 }
150
inst_canull151 func inst_ca() {
152 address.push(Int64(ip))
153 ip = Int(data.pop() - 1)
154 }
155
inst_ccnull156 func inst_cc() {
157 let dest = data.pop()
158 let flag = data.pop()
159 if (flag != 0) {
160 address.push(Int64(ip))
161 ip = Int(dest - 1)
162 }
163 }
164
inst_renull165 func inst_re() {
166 ip = Int(address.pop())
167 }
168
inst_eqnull169 func inst_eq() {
170 let tos = data.pop()
171 let nos = data.pop()
172 data.push((nos == tos) ? -1 : 0)
173 }
174
inst_nenull175 func inst_ne() {
176 let tos = data.pop()
177 let nos = data.pop()
178 data.push((nos != tos) ? -1 : 0)
179 }
180
inst_ltnull181 func inst_lt() {
182 let tos = data.pop()
183 let nos = data.pop()
184 data.push((nos < tos) ? -1 : 0)
185 }
186
inst_gtnull187 func inst_gt() {
188 let tos = data.pop()
189 let nos = data.pop()
190 data.push((nos > tos) ? -1 : 0)
191 }
192
inst_fenull193 func inst_fe() {
194 let target = data.pop()
195 switch (target) {
196 case -1:
197 data.push(Int64(data.depth()))
198 case -2:
199 data.push(Int64(address.depth()))
200 case -3:
201 data.push(Int64(imageSize))
202 case -4:
203 data.push(CELLMIN)
204 case -5:
205 data.push(CELLMAX)
206 default:
207 data.push(memory[Int(target)])
208 }
209 }
210
inst_stnull211 func inst_st() {
212 let addr = data.pop()
213 let value = data.pop()
214 memory[Int(addr)] = value;
215 }
216
inst_adnull217 func inst_ad() {
218 let tos = data.pop()
219 let nos = data.pop()
220 data.push(nos + tos)
221 }
222
inst_sunull223 func inst_su() {
224 let tos = data.pop()
225 let nos = data.pop()
226 data.push(nos - tos)
227 }
228
inst_munull229 func inst_mu() {
230 let tos = data.pop()
231 let nos = data.pop()
232 data.push(nos * tos)
233 }
234
inst_dinull235 func inst_di() {
236 let a = data.pop()
237 let b = data.pop()
238 data.push(b % a)
239 data.push(b / a)
240 }
241
inst_annull242 func inst_an() {
243 let tos = data.pop()
244 let nos = data.pop()
245 data.push(tos & nos)
246 }
247
inst_ornull248 func inst_or() {
249 let tos = data.pop()
250 let nos = data.pop()
251 data.push(nos | tos)
252 }
253
inst_xonull254 func inst_xo() {
255 let tos = data.pop()
256 let nos = data.pop()
257 data.push(nos ^ tos)
258 }
259
inst_shnull260 func inst_sh() {
261 let tos = data.pop()
262 let nos = data.pop()
263 if (tos < 0) {
264 data.push(nos << (tos * -1))
265 }
266 else {
267 if (nos < 0 && tos > 0) {
268 data.push(nos >> tos | ~(~0 >> tos))
269 }
270 else {
271 data.push(nos >> tos)
272 }
273 }
274 }
275
inst_zrnull276 func inst_zr() {
277 if (data.tos() == 0) {
278 data.drop()
279 ip = Int(address.pop())
280 }
281 }
282
inst_hanull283 func inst_ha() {
284 ip = imageSize;
285 }
286
inst_ienull287 func inst_ie() {
288 data.push(1)
289 }
290
inst_iqnull291 func inst_iq() {
292 data.drop()
293 data.push(0)
294 data.push(0)
295 }
296
inst_iinull297 func inst_ii() {
298 data.drop()
299 let v = UnicodeScalar(Int(data.pop())) ?? UnicodeScalar(32)
300 print(Character(v!), terminator: "")
301 }
302
303 // With those done, I turn to handling the
304 // instructions.
305
306 // In C, I use a jump table for this. I don't
307 // know how to do this in Swift.
308
ngaProcessOpcodenull309 func ngaProcessOpcode(opcode: Int64) {
310 switch opcode {
311 case 0:
312 ()
313 case 1:
314 inst_li()
315 case 2:
316 inst_du()
317 case 3:
318 inst_dr()
319 case 4:
320 inst_sw()
321 case 5:
322 inst_pu()
323 case 6:
324 inst_po()
325 case 7:
326 inst_ju()
327 case 8:
328 inst_ca()
329 case 9:
330 inst_cc()
331 case 10:
332 inst_re()
333 case 11:
334 inst_eq()
335 case 12:
336 inst_ne()
337 case 13:
338 inst_lt()
339 case 14:
340 inst_gt()
341 case 15:
342 inst_fe()
343 case 16:
344 inst_st()
345 case 17:
346 inst_ad()
347 case 18:
348 inst_su()
349 case 19:
350 inst_mu()
351 case 20:
352 inst_di()
353 case 21:
354 inst_an()
355 case 22:
356 inst_or()
357 case 23:
358 inst_xo()
359 case 24:
360 inst_sh()
361 case 25:
362 inst_zr()
363 case 26:
364 inst_ha()
365 case 27:
366 inst_ie()
367 case 28:
368 inst_iq()
369 case 29:
370 inst_ii()
371 default:
372 inst_ha()
373 }
374 }
375
376 // Each opcode can have up to four instructions
377 // This validates them, letting us know if it's
378 // safe to run them.
379
ngaValidatePackedOpcodesnull380 func ngaValidatePackedOpcodes(_ opcode: Int64) -> Bool {
381 var valid = true
382 for inst in Int32(opcode).bytes {
383 if !(inst >= 0 && inst <= 29) {
384 valid = false
385 }
386 }
387 return valid
388 }
389
390 // This will process an opcode bundle
391
ngaProcessPackedOpcodesnull392 func ngaProcessPackedOpcodes(_ opcode: Int64) {
393 for inst in Int32(opcode).bytes {
394 ngaProcessOpcode(opcode: Int64(inst))
395 }
396 }
397
398 // For debugging purposes
399
dumpnull400 func dump() {
401 for i in 0 ... data.depth() {
402 if i == data.depth() {
403 print("TOS")
404 }
405 print(Int(data.item(i)))
406 }
407 }
408
409 // Interfacing
410
extractStringnull411 func extractString(at: Int) -> String {
412 var s: String = ""
413 var next = at
414 while memory[next] != 0 {
415 let value = Int(memory[next])
416 let u = UnicodeScalar(value)
417 let char = Character(u!)
418 s.append(char)
419 next += 1
420 }
421 return s
422 }
423
injectStringnull424 func injectString(_ s: String, to: Int) {
425 var i: Int = 0
426 for ch in s.asciiValues {
427 let n = Int64(ch)
428 memory[to + i] = n
429 memory[to + i + 1] = 0
430 i += 1
431 }
432 }
433
434 // Run through an image, quiting when EOM
435 // is reached
436
executenull437 func execute(_ word: Int) {
438 address.push(0)
439 ip = word
440 while ip < imageSize {
441 // print("ip:", ip, "bundle", memory[ip], "insts", Int32(memory[ip]).bytes)
442 let opcode = memory[ip]
443 if ngaValidatePackedOpcodes(opcode) {
444 ngaProcessPackedOpcodes(opcode)
445 } else {
446 print("Error: invalid opcode")
447 ip = imageSize
448 }
449 ip += 1
450 if address.depth() == 0 {
451 ip = imageSize
452 }
453 }
454 }
455
processnull456 func process() {
457 ip = 0
458 while ip < imageSize - 1 {
459 ngaProcessPackedOpcodes(memory[ip])
460 ip += 1
461 }
462 }
463
464 // Tests
465
466 loadImage()
467
468 // Display the dictionary
469 var i = memory[2]
470 var interpret: Int = 0
471 while i != 0 {
472 let name = extractString(at: Int(i + 3))
473 // print(name)
474 if name == "interpret" {
475 interpret = Int(memory[Int(i) + 1])
476 }
477 i = memory[Int(i)]
478 }
479
480
481 var done = false
482 print("RETRO (using nga.swift)")
483 while !done {
484 let code = readLine()
485 if code == "bye" {
486 done = true
487 } else {
488 injectString(code ?? "()", to: 1025)
489 data.push(1025)
490 execute(interpret)
491 }
492 }
493 dump()
494
495