ICFP Contest 2006
Team: Abstraction Anonymous

um.rb

Download

  1. #!/opt/local/bin/ruby
  2.  
  3. # Abstraction Anonymous
  4. # Initial Ruby VM Implementation
  5. # 2006-07-24 @ 2:43 PM
  6. # Daniel Lyons, Bill Weiss and David Baird
  7. # <fusion AT storytotell dot org>
  8.  
  9. # This is pretty gross, because it has a lot of stillborn ideas in
  10. # it. Ultimately we wound up scrapping it altogether and writing a C
  11. # implementation which rocked our world. Check it out: um.c.
  12. #
  13. # Amid the chaos there are some interesting ideas which people might
  14. # want to rip off. I'll discuss the gross with the glorious.
  15.  
  16. # This is for the Ruby profiler, which totally rocks. Unfortunately,
  17. # it also was one of the things which convinced us Ruby just wasn't
  18. # powerful enough.
  19. #require 'profile'
  20.  
  21. # There were several FileArrayProxy implementations before we arrived
  22. # at this one. I know you aren't supposed to subclass Array but it was
  23. # simply the easiest way to achieve to achieve this.
  24. class FileArrayProxy < Array
  25. def initialize(file)
  26. @file = file
  27. super()
  28. end
  29. # This method was way slow, because of calling read(4).unpack('N')
  30. # Let's think about what really happens when we call
  31. # read(4).unpack('N'):
  32. #
  33. # read(1) -> string * 4
  34. # unpack('N') -> load the string into a byte array and screw with
  35. # it
  36. #
  37. # Compare to this:
  38. #
  39. # File.read.unpack('N*')
  40. #
  41. # That loads all of the file into big endian integers in one
  42. # pass. Much faster.
  43. def [](i)
  44. (i+1 - length).times do
  45. #self << @file.read(4).unpack("N")[0]
  46. self << ((@file.getc << 24) | (@file.getc << 16) | (@file.getc << 8) | (@file.getc))
  47. end
  48. super
  49. end
  50. # Oh, right. All of the methods on array wound up needing to be
  51. # overridden to call [] and load the rest of the array to that location
  52. def []=(i, val)
  53. self[i]
  54. super
  55. end
  56. def index(i)
  57. self[i]
  58. super
  59. end
  60. def insert(i, b)
  61. self[i]
  62. super
  63. end
  64. end
  65.  
  66. # We learned in this project that every Ruby program begins by molding
  67. # the built-in types into what you need for this particular program.
  68. #
  69. # This reminds me of Lisp, a lot.
  70. class Array
  71.  
  72. # Basically, we rewrote Array's print method to print itself as an
  73. # array rather than as a string, which it does by defaut
  74. def to_s
  75. return '[' + self.collect { |x| x.to_s }.join(', ') + ']'
  76. end
  77. end
  78.  
  79. # We wanted nil to print "nil" rather than "" when we print it. Easy.
  80. class NilClass
  81. def to_s
  82. 'nil'
  83. end
  84. end
  85.  
  86. # We also changed both integer types to display themselves in
  87. # hex. This took far more work than it should have, because the to_hex
  88. # function depended on the old to_s method, and we were rewriting the
  89. # new to_s method to call to_hex. And thus we entered the Hell of Many
  90. # Aliases.
  91. class Fixnum
  92. alias old_to_s to_s
  93. def to_hex
  94. '0x' + old_to_s(16)
  95. end
  96. alias to_s to_hex
  97. end
  98.  
  99. class Bignum
  100. alias old_to_s to_s
  101. def too_big?
  102. (self >> 32) != 0
  103. end
  104. # One different thing about Bignum: we wanted it to tell us if it
  105. # was bigger than 1 bit larger than Fixnum. In retrospect, this may
  106. # have been a part of the performance hit.
  107. def to_hex
  108. (too_big? ? 'BIG: ' : '') + '0x' + old_to_s(16)
  109. end
  110. alias to_s to_hex
  111. end
  112.  
  113. # This object represents, well, the machine. :) It keeps track of the
  114. # registers, finger, and platters. Ruby's memory management meant we
  115. # didn't have to think particularly hard about handling the
  116. # platters. The printing bit was added later for some signal-based
  117. # debugging: we would "kill -USR1 um.rb" to get it to spit out what it
  118. # was working on more cheaply than printing it out always.
  119. class MachineState
  120. attr_accessor :registers, :finger, :platters, :printing
  121. def initialize(stream)
  122. @printing = false
  123. @registers = Array.new(8,0) # eight registers of 0
  124. @platters = [stream]
  125. @finger = 0
  126. @file = File.open("key", "r")
  127. end
  128. # We wrote this to facilitate debugging the machine. Platters tended
  129. # to either be very small or very huge, so we only printed it when we
  130. # really needed to.
  131. def to_s(with_platters=false)
  132. "Instruction: #{Instruction.new(@platters[0][@finger-1]).to_s}\n" +
  133. "Registers: #{@registers.to_s}\n" +
  134. (with_platters ? "Platters: #{@platters[1..-1]}\n" : "") +
  135. "Finger: #{@finger}\n"
  136. end
  137.  
  138. # A couple of convenience methods
  139. def debug_out
  140. $stderr.puts self + "\n"
  141. end
  142.  
  143. def debug_out2
  144. $stderr.puts self.to_s(true) + "\n"
  145. end
  146. # Step the execution forward by one instruction. This method was
  147. # rewritten a lot.
  148. def step
  149. # ins = Instruction.new(@platters[0][@finger])
  150. ins = Instruction.new(@platters.at(0).at(@finger))
  151. @finger += 1
  152. execute(ins)
  153. end
  154. # We used Ruby's dispatch method to handle executing
  155. # instructions. The line
  156. #
  157. # send(inst.name, inst)
  158. #
  159. # deserves some special attention. Using the Instruction ADT we
  160. # wrote, which has a Instruction#name method which returns a symbol
  161. # of the instruction name, we can then apply that instruction to the
  162. # MachineState, passing in the instruction itself. We thought it was
  163. # a pretty classy design, but it seemed to add ~12% overhead.
  164. def execute(inst)
  165. # $stderr.puts self.to_s(true) + "\n\n"
  166. send(inst.name, inst)
  167. end
  168.  
  169. # From here, we started putting the spec into the code so we could
  170. # check it more easily. It turned out not to be broken.
  171.  
  172. # #0. Conditional Move.
  173. # The register A receives the value in register B,
  174. # unless the register C contains 0.
  175. def cond_mov(inst)
  176. # unless (@registers[inst.c] == 0)
  177. unless (@registers.at(inst.c) == 0)
  178. # @registers[inst.a] = @registers[inst.b]
  179. @registers[inst.a] = @registers.at(inst.b)
  180. end
  181. end
  182. # #1. Array Index.
  183. # The register A receives the value stored at offset
  184. # in register C in the array identified by B.
  185. def index(inst)
  186. @registers[inst.a] = @platters.at(@registers.at(inst.b)).at(@registers.at(inst.c))
  187. # @registers[inst.a] = @platters[@registers[inst.b]][@registers[inst.c]]
  188. end
  189.  
  190. # #2. Array Amendment.
  191. # The array identified by A is amended at the offset
  192. # in register B to store the value in register C.
  193. def amend(inst)
  194. # @platters[@registers[inst.a]][@registers[inst.b]] = @registers[inst.c]
  195. @platters.at(@registers.at(inst.a))[@registers.at(inst.b)] = @registers.at(inst.c)
  196. end
  197. # #3. Addition.
  198. # The register A receives the value in register B plus
  199. # the value in register C, modulo 2^32.
  200. def add(inst)
  201. # @registers[inst.a] = (@registers[inst.b] + @registers[inst.c]) % (1 << 32)
  202. @registers[inst.a] = (@registers.at(inst.b) + @registers.at(inst.c)) % (1 << 32)
  203. end
  204.  
  205. # #4. Multiplication.
  206. # The register A receives the value in register B times
  207. # the value in register C, modulo 2^32.
  208. def mult(inst)
  209. # @registers[inst.a] = (@registers[inst.b] * @registers[inst.c]) % (1 << 32)
  210. @registers[inst.a] = (@registers.at(inst.b) * @registers.at(inst.c)) % (1 << 32)
  211. end
  212. # #5. Division.
  213. # The register A receives the value in register B
  214. # divided by the value in register C, if any, where
  215. # each quantity is treated treated as an unsigned 32
  216. # bit number.
  217. def div(inst)
  218. # @registers[inst.a] = ((@registers[inst.b] % (1 << 32)) / (@registers[inst.c] % (1 << 32))) % (1 << 32)
  219. @registers[inst.a] = (@registers.at(inst.b) / @registers.at(inst.c)) % (1 << 32)
  220. end
  221.  
  222. # #6. Not-And.
  223. # Each bit in the register A receives the 1 bit if
  224. # either register B or register C has a 0 bit in that
  225. # position. Otherwise the bit in register A receives
  226. # the 0 bit.
  227. def not_and(inst)
  228. # @registers[inst.a] = (~ (@registers[inst.b] & @registers[inst.c])) % (1 << 32)
  229. @registers[inst.a] = (~ (@registers.at(inst.b) & @registers.at(inst.c))) % (1 << 32)
  230. end
  231.  
  232. # #7. Halt.
  233. # The universal machine stops computation.
  234. def halt(inst)
  235. raise "Halt encountered"
  236. end
  237.  
  238. # #8. Allocation.
  239. # A new array is created with a capacity of platters
  240. # commensurate to the value in the register C. This
  241. # new array is initialized entirely with platters
  242. # holding the value 0. A bit pattern not consisting of
  243. # exclusively the 0 bit, and that identifies no other
  244. # active allocated array, is placed in the B register.
  245. def alloc(inst)
  246. if (i = @platters.index(nil))
  247. # @platters[i] = Array.new(@registers[inst.c], 0)
  248. @platters[i] = Array.new(@registers.at(inst.c), 0)
  249. @registers[inst.b] = i
  250. else
  251. # @platters << Array.new(@registers[inst.c], 0)
  252. @platters << Array.new(@registers.at(inst.c), 0)
  253. @registers[inst.b] = @platters.length - 1
  254. end
  255. end
  256.  
  257. # #9. Abandonment.
  258. # The array identified by the register C is abandoned.
  259. # Future allocations may then reuse that identifier.
  260. def abandon(inst)
  261. # @platters[@registers[inst.c]] = nil
  262. @platters[@registers.at(inst.c)] = nil
  263. end
  264.  
  265. # #10. Output.
  266. # The value in the register C is displayed on the console
  267. # immediately. Only values between and including 0 and 255
  268. # are allowed.
  269. def out(inst)
  270. # putc @registers[inst.c] unless @registers[inst.c] > 255
  271. putc @registers.at(inst.c) unless @registers.at(inst.c) > 255
  272. $stdout.flush
  273. $stderr.puts "Wrote #{@registers[inst.c].chr} at Time: #{Time.now}"
  274. end
  275.  
  276. # #11. Input.
  277. # The universal machine waits for input on the console.
  278. # When input arrives, the register C is loaded with the
  279. # input, which must be between and including 0 and 255.
  280. # If the end of input has been signaled, then the
  281. # register C is endowed with a uniform value pattern
  282. # where every place is pregnant with the 1 bit.
  283. def in(inst)
  284. if (c = @file.getc) == nil
  285. @registers[inst.c] = 0xFFFFFFFF
  286. else
  287. @registers[inst.c] = c
  288. end
  289. #if (c = STDIN.getc) == nil
  290. # @registers[inst.c] = 0xFFFFFFFF
  291. #else
  292. # @registers[inst.c] = c
  293. #end
  294. end
  295.  
  296. # #12. Load Program.
  297. # The array identified by the B register is duplicated
  298. # and the duplicate shall replace the '0' array,
  299. # regardless of size. The execution finger is placed
  300. # to indicate the platter of this array that is
  301. # described by the offset given in C, where the value
  302. # 0 denotes the first platter, 1 the second, et
  303. # cetera.
  304. #
  305. # The '0' array shall be the most sublime choice for
  306. # loading, and shall be handled with the utmost
  307. # velocity.
  308. def load(inst)
  309. # $stderr.puts self.to_s unless @registers[inst.b] == 0
  310. # @platters[0] = @platters[@registers[inst.b]].dup unless @registers[inst.b] == 0
  311. # @finger = @registers[inst.c]
  312. # $stderr.puts self.to_s + "\n\n" unless @registers[inst.b] == 0
  313. $stderr.puts self.to_s unless @registers.at(inst.b) == 0
  314. @platters[0] = @platters.at(@registers.at(inst.b)).dup unless @registers.at(inst.b) == 0
  315. @finger = @registers.at(inst.c)
  316. $stderr.puts self.to_s + "\n\n" unless @registers.at(inst.b) == 0
  317. end
  318.  
  319. # #13. Orthography.
  320. # The value indicated is loaded into the register A
  321. # forthwith.
  322. def data(inst)
  323. @registers[inst.a13] = inst.data
  324. end
  325. def inv(inst)
  326. raise "inv:" + inst
  327. end
  328. end
  329.  
  330. # This was our first instruction loader. It was then replaced with two
  331. # different FileArray implementations: first FileArrayCacher which
  332. # used a Ruby Generator to load instructions, and then the Array
  333. # subclass. Then we came back and rewrote this to use unpack('N*') and
  334. # things were better; it took about 0.5 seconds to load and cache the
  335. # whole file with no per-step hit.
  336. #
  337. # Of course, it turned out not to help enough in the end.
  338. class Runtime
  339. def Runtime.load(filename)
  340. #arr = []
  341. #File.open(filename, 'r') do |f|
  342. # while not f.eof?
  343. # arr << f.read(4).unpack('N')[0]
  344. # end
  345. #end
  346. #arr
  347. File.open(filename, 'r').read.unpack('L*')
  348. end
  349. end
  350.  
  351. # Our beautiful Instruction ADT. I'm convinced that without the heavy
  352. # designs we have in this file, we would have taken at least twice as
  353. # long to write the C. I'm not sure whether that's a substantial gain
  354. # or not.
  355. class Instruction
  356. attr_accessor :value
  357. @@OPCODE_NAMES = [:cond_mov, :index, :amend,
  358. :add, :mult, :div, :not_and,
  359. :halt, :alloc, :abandon, :out, :in,
  360. :load, :data, :inv, :inv]
  361. def initialize(value)
  362. @value = value
  363. end
  364. # This was our instruction disassembler routine. We ran this against
  365. # the codex once and then had a reference disassemble which we used
  366. # to step through the execution on a white board to check
  367. # ourselves.
  368. #
  369. # Later on I wrote another disassembler in OCaml which ran much
  370. # faster. Of course, it wasn't much use by then.
  371. def to_s
  372. sprintf("%-10s ", name) +
  373. if (opcode < 13)
  374. sprintf("%d %d %d", a, b, c)
  375. elsif (opcode == 13)
  376. sprintf("%d 0x%X", a13, data)
  377. else
  378. ""
  379. end
  380. end
  381. # Bitmath
  382. def opcode
  383. value >> 28
  384. end
  385. def name
  386. @@OPCODE_NAMES[opcode]
  387. end
  388. def data
  389. value & 0x1ffffff
  390. end
  391. def a13
  392. (value >> 25) & 7
  393. end
  394. def a
  395. (value >> 6) & 7
  396. end
  397. def b
  398. (value >> 3) & 7
  399. end
  400. def c
  401. value & 7
  402. end
  403. end
  404.  
  405. # Observe the evolution:
  406. #m = MachineState.new(Runtime.load(ARGV[0]))
  407. #m = MachineState.new(FileArrayProxy.new(File.open(ARGV[0])))
  408. m = MachineState.new(Runtime.load(ARGV[0]))
  409.  
  410. # Debugging signals
  411. Signal.trap("USR1") {m.debug_out2; m.printing = true}
  412. Signal.trap("USR2") {m.printing = false}
  413.  
  414. # The main loop
  415. while 1
  416. m.step
  417. end
  418.  
  419.  

Hell is other programming languages. -- Sartran
Hell is that programming language! -- Dan
Ordinarily, one would enrich this language with more powerful means of computation. Instead I take a different tack... -- Harmonious Monk