bitescript 0.0.9 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,458 @@
1
+ require 'bitescript/asm3/asm'
2
+ require 'bitescript/asm3/signature'
3
+
4
+ module BiteScript
5
+ # Bytecode is a simple adapter around an ASM MethodVisitor that makes it look like
6
+ # JVM assembly code. Included classes must just provide a method_visitor accessor
7
+ module Bytecode
8
+ include Signature
9
+ include ASM
10
+
11
+ JObject = java.lang.Object
12
+ JSystem = java.lang.System
13
+ JPrintStream = java.io.PrintStream
14
+ JVoid = java.lang.Void
15
+ JInteger = java.lang.Integer
16
+ JFloat = java.lang.Float
17
+ JLong = java.lang.Long
18
+ JDobule = java.lang.Double
19
+
20
+ b = binding
21
+ OpcodeStackDeltas = {}
22
+ %w[ALOAD ILOAD FLOAD BIPUSH SIPUSH DUP DUP_X1 DUP_X2 ACONST_NULL ICONST_M1
23
+ ICONST_0 ICONST_1 ICONST_2 ICONST_3 ICONST_4 ICONST_5 I2L I2D FCONST_0
24
+ FCONST_1 FCONST_2 F2L F2D NEW JSR].each {|opcode| OpcodeStackDeltas[opcode] = 1}
25
+ %w[LLOAD DLOAD DUP2 DUP2_X1 DUP2_X2 LCONST_0 LCONST_1 DCONST_0 DCONST_1].each {|opcode| OpcodeStackDeltas[opcode] = 2}
26
+ %w[ASTORE ISTORE FSTORE POP ARETURN IRETURN ATHROW AALOAD BALOAD CALOAD
27
+ SALOAD IALOAD FALOAD IADD ISUB IDIV IMUL IAND IOR IXOR IREM L2I L2F
28
+ FRETURN FADD FSUB FDIV FMUL FREM FCMPG FCMPL D2I D2F IFEQ IFNE IFNULL
29
+ IFNONNULL IFLT IFGT IFLE IFGE LOOKUPSWITCH TABLESWITCH MONITORENTER
30
+ MONITOREXIT].each {|opcode| OpcodeStackDeltas[opcode] = -1}
31
+ %w[LSTORE DSTORE POP2 LREM LADD LSUB LDIV LMUL LAND LOR LXOR LRETURN DRETURN
32
+ DADD DSUB DDIV DMUL DREM IF_ACMPEQ IF_ACMPNE IF_ICMPEQ IF_ICMPNE
33
+ IF_ICMPLT IF_ICMPGT IF_ICMPLE IF_ICMPGE].each {|opcode| OpcodeStackDeltas[opcode] = -2}
34
+ %w[AASTORE BASTORE CASTORE SASTORE IASTORE LCMP FASTORE DCMPL DCMPG].each {|opcode| OpcodeStackDeltas[opcode] = -3}
35
+ %w[LASTORE DASTORE].each {|opcode| OpcodeStackDeltas[opcode] = -4}
36
+ %w[RETURN SWAP NOP ARRAYLENGTH IINC INEG IUSHR ISHL ISHR I2S I2F I2B I2C
37
+ LALOAD LSHL LSHR LUSHR LNEG L2D FNEG F2I DALOAD D2L DNEG RET
38
+ CHECKCAST ANEWARRAY NEWARRAY GOTO INSTANCEOF].each {|opcode| OpcodeStackDeltas[opcode] = 0}
39
+
40
+ OpcodeInstructions = {}
41
+
42
+ Opcodes.constants.map(&:to_s).each do |const_name|
43
+ const_down = const_name.downcase
44
+
45
+ case const_name
46
+ when "ALOAD", "ASTORE",
47
+ "ISTORE", "ILOAD",
48
+ "LSTORE", "LLOAD",
49
+ "FSTORE", "FLOAD",
50
+ "DSTORE", "DLOAD",
51
+ "RET"
52
+ # variable instructions
53
+ line = __LINE__; eval "
54
+ def #{const_down}(var)
55
+ method_visitor.visit_var_insn(Opcodes::#{const_name}, var)
56
+ #{OpcodeStackDeltas[const_name]}
57
+ end
58
+ ", b, __FILE__, line
59
+ OpcodeInstructions[const_name] = const_down
60
+
61
+ when "LDC"
62
+ # constant loading is tricky because overloaded invocation is pretty bad in JRuby
63
+ def ldc_int(value); method_visitor.visit_ldc_insn(java.lang.Integer.new(value)); 1; end
64
+ def ldc_long(value); method_visitor.visit_ldc_insn(java.lang.Long.new(value)); 2; end
65
+ def ldc_float(value); method_visitor.visit_ldc_insn(java.lang.Float.new(value)); 1; end
66
+ def ldc_double(value); method_visitor.visit_ldc_insn(java.lang.Double.new(value)); 2; end
67
+ def ldc_class(value)
68
+ method_visitor.visit_ldc_insn(ASM::Type.get_type(ci(value)))
69
+ end
70
+ line = __LINE__; eval "
71
+ def #{const_down}(value)
72
+ size = 1
73
+
74
+ case value
75
+ when Symbol
76
+ method_visitor.visit_ldc_insn value.to_s
77
+ when Fixnum
78
+ size = push_int value
79
+ when Float
80
+ ldc_double(value)
81
+ when Module, ClassBuilder
82
+ ldc_class(value)
83
+ else
84
+ method_visitor.visit_ldc_insn(value)
85
+ end
86
+
87
+ size
88
+ end
89
+ ", b, __FILE__, line
90
+ OpcodeInstructions[const_name] = const_down
91
+
92
+ when "BIPUSH", "SIPUSH"
93
+ line = __LINE__; eval "
94
+ def #{const_down}(value)
95
+ method_visitor.visit_int_insn(Opcodes::#{const_name}, value)
96
+ 1
97
+ end
98
+ ", b, __FILE__, line
99
+ OpcodeInstructions[const_name] = const_down
100
+
101
+ when "INVOKESTATIC", "INVOKEVIRTUAL", "INVOKEINTERFACE", "INVOKESPECIAL"
102
+ # method instructions
103
+ line = __LINE__; eval "
104
+ def #{const_down}(type, name, call_sig)
105
+ method_visitor.visit_method_insn(Opcodes::#{const_name}, path(type), name.to_s, sig(*call_sig))
106
+
107
+ sig_stack_net(call_sig, #{const_name == 'INVOKESTATIC' ? 0 : 1})
108
+ end
109
+ ", b, __FILE__, line
110
+ OpcodeInstructions[const_name] = const_down
111
+
112
+ when "INVOKEDYNAMIC"
113
+ # invokedynamic instruction
114
+ line = __LINE__; eval "
115
+ def #{const_down}(name, call_sig, handle, *args)
116
+ method_visitor.visit_invoke_dynamic_insn(name.to_s, sig(*call_sig), handle, args.to_java)
117
+
118
+ sig_stack_net(call_sig, 0)
119
+ end
120
+ ", b, __FILE__, line
121
+ OpcodeInstructions[const_name] = const_down
122
+
123
+ when "RETURN"
124
+ # special case for void return, since return is a reserved word
125
+ def returnvoid()
126
+ method_visitor.visit_insn(Opcodes::RETURN)
127
+ 0
128
+ end
129
+ OpcodeInstructions['RETURN'] = 'returnvoid'
130
+
131
+ when "DUP", "SWAP", "POP", "POP2", "DUP_X1", "DUP_X2", "DUP2", "DUP2_X1", "DUP2_X2",
132
+ "NOP",
133
+ "ARRAYLENGTH",
134
+ "ARETURN", "ATHROW", "ACONST_NULL", "AALOAD", "AASTORE",
135
+ "BALOAD", "BASTORE",
136
+ "CALOAD", "CASTORE",
137
+ "SALOAD", "SASTORE",
138
+ "ICONST_M1", "ICONST_0", "ICONST_1", "ICONST_2", "ICONST_3", "ICONST_4", "ICONST_5", "IRETURN", "IALOAD",
139
+ "IADD", "ISUB", "IDIV", "IMUL", "INEG", "IAND", "IOR", "IXOR", "IASTORE",
140
+ "IUSHR", "ISHL", "ISHR", "I2L", "I2S", "I2F", "I2D", "I2B", "IREM", "I2C",
141
+ "LCONST_0", "LCONST_1", "LRETURN", "LALOAD", "LASTORE", "LCMP", "LSHL", "LSHR", "LREM", "LUSHR",
142
+ "LADD", "LINC", "LSUB", "LDIV", "LMUL", "LNEG", "LAND", "LOR", "LXOR", "L2I", "L2F", "L2D",
143
+ "FCONST_0", "FCONST_1", "FCONST_2", "FRETURN", "FALOAD", "F2D", "F2I", "FASTORE",
144
+ "FADD", "FSUB", "FDIV", "FMUL", "FNEG", "FREM", "FCMPG", "F2L", "FCMPL",
145
+ "DCONST_0", "DCONST_1", "DRETURN", "DALOAD", "DASTORE", "D2I", "D2F", "D2L",
146
+ "DADD", "DINC", "DSUB", "DDIV", "DMUL", "DNEG", "DCMPL", "DCMPG", "DREM",
147
+ "MONITORENTER", "MONITOREXIT"
148
+ # bare instructions
149
+ line = __LINE__; eval "
150
+ def #{const_down}
151
+ method_visitor.visit_insn(Opcodes::#{const_name})
152
+ #{OpcodeStackDeltas[const_name]}
153
+ end
154
+ ", b, __FILE__, line
155
+ OpcodeInstructions[const_name] = const_down
156
+
157
+ when "IINC"
158
+ def iinc(index, value)
159
+ method_visitor.visit_iinc_insn(index, value)
160
+ 0
161
+ end
162
+ OpcodeInstructions[const_name] = 'iinc'
163
+
164
+ when "NEW", "ANEWARRAY", "INSTANCEOF", "CHECKCAST"
165
+ # type instructions
166
+ line = __LINE__; eval "
167
+ def #{const_down}(type)
168
+ method_visitor.visit_type_insn(Opcodes::#{const_name}, tipath(type))
169
+ #{OpcodeStackDeltas[const_name]}
170
+ end
171
+ ", b, __FILE__, line
172
+ OpcodeInstructions[const_name] = const_down
173
+
174
+ when "NEWARRAY"
175
+ # newaray has its own peculiarities
176
+ NEWARRAY_TYPES = {
177
+ :boolean => Opcodes::T_BOOLEAN,
178
+ :byte => Opcodes::T_BYTE,
179
+ :short => Opcodes::T_SHORT,
180
+ :char => Opcodes::T_CHAR,
181
+ :int => Opcodes::T_INT,
182
+ :long => Opcodes::T_LONG,
183
+ :float => Opcodes::T_FLOAT,
184
+ :double => Opcodes::T_DOUBLE
185
+ }
186
+ NEWARRAY_TYPES.each do |type, op_type|
187
+ line = __LINE__; eval "
188
+ def new#{type}array()
189
+ method_visitor.visit_int_insn(Opcodes::#{const_name}, #{op_type})
190
+ #{OpcodeStackDeltas[const_name]}
191
+ end
192
+ ", b, __FILE__, line
193
+ end
194
+ def newarray(type)
195
+ t_type = NEWARRAY_TYPES[path(type).intern]
196
+ method_visitor.visit_int_insn(Opcodes::NEWARRAY, t_type)
197
+ end
198
+ OpcodeInstructions[const_name] = const_down
199
+
200
+ when "GETFIELD", "PUTFIELD", "GETSTATIC", "PUTSTATIC"
201
+ # field instructions
202
+ line = __LINE__; eval "
203
+ def #{const_down}(type, name, field_type)
204
+ method_visitor.visit_field_insn(Opcodes::#{const_name}, path(type), name.to_s, ci(field_type))
205
+
206
+ case field_type
207
+ when Java::boolean, Java::short, Java::char, Java::int, Java::float
208
+ delta = 1
209
+ when Java::long, Java::double
210
+ delta = 2
211
+ else
212
+ delta = 1
213
+ end
214
+
215
+ this_subtracted = #{const_name[3..7] == 'STATI' ? 0 : 1}
216
+
217
+ delta *= #{const_name[0..2] == 'PUT' ? -1 : 1}
218
+ delta -= this_subtracted
219
+ delta
220
+ end
221
+ ", b, __FILE__, line
222
+ OpcodeInstructions[const_name] = const_down
223
+
224
+ when "GOTO", "IFEQ", "IFNE", "IF_ACMPEQ", "IF_ACMPNE", "IF_ICMPEQ", "IF_ICMPNE", "IF_ICMPLT",
225
+ "IF_ICMPGT", "IF_ICMPLE", "IF_ICMPGE", "IFNULL", "IFNONNULL", "JSR",
226
+ "IFLE", "IFGE", "IFLT", "IFGT"
227
+ # jump instructions
228
+ line = __LINE__; eval "
229
+ def #{const_down}(target)
230
+ target = sym_to_label(target) if Symbol === target
231
+ method_visitor.visit_jump_insn(Opcodes::#{const_name}, target.label)
232
+ #{OpcodeStackDeltas[const_name]}
233
+ end
234
+ ", b, __FILE__, line
235
+ OpcodeInstructions[const_name] = const_down
236
+
237
+ when "MULTIANEWARRAY"
238
+ # multi-dim array
239
+ line = __LINE__; eval "
240
+ def #{const_down}(type, dims)
241
+ method_visitor.visit_multi_anew_array_insn(ci(type), dims)
242
+ -dims
243
+ end
244
+ ", b, __FILE__, line
245
+ OpcodeInstructions[const_name] = const_down
246
+
247
+ when "LOOKUPSWITCH"
248
+ def lookupswitch(default, ints, cases = [])
249
+ cases = cases.to_ary
250
+ if cases.size > 0
251
+ # symbol labels, map them to actual
252
+ case_labels = cases.map do |lbl|
253
+ lbl = sym_to_label(lbl) if Symbol === lbl
254
+ lbl.label
255
+ end
256
+ end
257
+ raise "Default case must be provided" unless default
258
+ if default && Symbol === default
259
+ default = sym_to_label(default)
260
+ end
261
+ method_visitor.visit_lookup_switch_insn(default.label, ints.to_java(:int), case_labels.to_java(ASM::Label))
262
+ -1
263
+ end
264
+ OpcodeInstructions['LOOKUPSWITCH'] = 'lookupswitch'
265
+
266
+ when "TABLESWITCH"
267
+ def tableswitch(min, max, default, cases = [])
268
+ cases = cases.to_ary
269
+ if cases.size > 0
270
+ # symbol labels, map them to actual
271
+ case_labels = cases.map do |lbl|
272
+ lbl = sym_to_label(lbl) if Symbol === lbl
273
+ lbl.label
274
+ end
275
+ end
276
+ raise "Default case must be provided" unless default
277
+ if default && Symbol === default
278
+ default = sym_to_label(default)
279
+ end
280
+ method_visitor.visit_table_switch_insn(min, max, default.label, case_labels.to_java(ASM::Label))
281
+ -1
282
+ end
283
+ OpcodeInstructions['TABLESWITCH'] = 'tableswitch'
284
+
285
+ when "MH_INVOKESPECIAL", "MH_INVOKESTATIC", "MH_PUTSTATIC", "MH_GETSTATIC", "MH_PUTFIELD",
286
+ "MH_GETFIELD", "MH_INVOKEVIRTUAL", "MH_INVOKEINTERFACE",
287
+ "MH_NEWINVOKESPECIAL"
288
+ line = __LINE__; eval "
289
+ def #{const_down}(cls, name, *call_sig)
290
+ MethodHandle.new(Opcodes::#{const_name}, path(cls), name, sig(*call_sig))
291
+ end
292
+ ", b, __FILE__, line
293
+ OpcodeInstructions[const_name] = const_down
294
+ when "F_FULL", "ACC_ENUM", "ACC_SYNTHETIC", "ACC_INTERFACE", "ACC_PUBLIC",
295
+ "ACC_PRIVATE", "ACC_PROTECTED", "ACC_DEPRECATED", "ACC_BRIDGE",
296
+ "ACC_VARARGS", "ACC_SUPER", "F_CHOP", "F_APPEND", "FLOAT", "F_SAME",
297
+ "T_LONG", "INTEGER", "T_BYTE", "ACC_STATIC", "ACC_SYNCHRONIZED",
298
+ "T_BOOLEAN", "ACC_ANNOTATION", "ACC_ABSTRACT", "LONG", "ACC_TRANSIENT",
299
+ "T_DOUBLE", "DOUBLE", "ACC_STRICT", "NULL", "T_FLOAT", "ACC_FINAL",
300
+ "F_SAME1", "ACC_NATIVE", "F_NEW", "T_CHAR", "T_INT", "ACC_VOLATILE",
301
+ "V1_6", "V1_5", "V1_4", "V1_3", "V1_2", "V1_1", "UNINITIALIZED_THIS",
302
+ "TOP", "T_SHORT", "INVOKEDYNAMIC_OWNER", "V1_7"
303
+
304
+ # non-instructions
305
+
306
+ else
307
+ raise "Unknown opcode: " + const_name
308
+
309
+ end
310
+ end
311
+
312
+ def start
313
+ method_visitor.visit_code
314
+ @start_label.set!
315
+ end
316
+
317
+ def stop
318
+ @end_label.set!
319
+ @locals.each do |name, locals|
320
+ local_debug_info(name, locals[-1], @end_label)
321
+ end
322
+ method_visitor.visit_maxs(1,1)
323
+ method_visitor.visit_end
324
+ end
325
+
326
+ def trycatch(from, to, target, type)
327
+ from = sym_to_label(from) if Symbol === from
328
+ to = sym_to_label(to) if Symbol === to
329
+ target = sym_to_label(target) if Symbol === target
330
+
331
+ method_visitor.visit_try_catch_block(from.label, to.label, target.label, type ? path(type) : nil)
332
+ end
333
+
334
+ class SmartLabel
335
+ attr_reader :label
336
+
337
+ def initialize(method_visitor)
338
+ @method_visitor = method_visitor
339
+ @label = ASM::Label.new
340
+ @set = false
341
+ end
342
+
343
+ def set!
344
+ raise "label set twice" if @set
345
+ @set = true
346
+ @method_visitor.visit_label(@label)
347
+ self
348
+ end
349
+
350
+ def set?
351
+ @set
352
+ end
353
+ end
354
+
355
+ def labels
356
+ @labels ||= {}
357
+ end
358
+
359
+ def sym_to_label(id)
360
+ lbl = labels[id]
361
+ unless lbl
362
+ lbl = labels[id] = label
363
+ end
364
+ lbl
365
+ end
366
+
367
+ def label(id = nil)
368
+ if id
369
+ lbl = sym_to_label(id)
370
+ lbl.set!
371
+ lbl
372
+ else
373
+ return SmartLabel.new(method_visitor)
374
+ end
375
+ end
376
+
377
+ def aprintln
378
+ println
379
+ end
380
+
381
+ def println(type = JObject)
382
+ getstatic JSystem, "out", JPrintStream
383
+ swap
384
+ invokevirtual JPrintStream, "println", [JVoid::TYPE, type]
385
+ end
386
+
387
+ def swap2
388
+ dup2_x2
389
+ pop2
390
+ end
391
+
392
+ def line(num)
393
+ if num && num != @line_num
394
+ method_visitor.visit_line_number(num, label.set!.label)
395
+ end
396
+ @line_num = num
397
+ end
398
+
399
+ def push_int(num)
400
+ if (num <= Java::java.lang.Byte::MAX_VALUE && num >= Java::java.lang.Byte::MIN_VALUE)
401
+ case num
402
+ when -1
403
+ iconst_m1
404
+ when 0
405
+ iconst_0
406
+ when 1
407
+ iconst_1
408
+ when 2
409
+ iconst_2
410
+ when 3
411
+ iconst_3
412
+ when 4
413
+ iconst_4
414
+ when 5
415
+ iconst_5
416
+ else
417
+ bipush(num)
418
+ end
419
+ elsif (num <= Java::java.lang.Short::MAX_VALUE && num >= Java::java.lang.Short::MIN_VALUE)
420
+ sipush(num)
421
+ elsif (num <= Java::java.lang.Integer::MAX_VALUE && num >= Java::java.lang.Integer::MIN_VALUE)
422
+ ldc_int(num)
423
+ else
424
+ ldc_long(num)
425
+ end
426
+ end
427
+
428
+ def sig_stack_net(call_sig, this_subtracted)
429
+ case call_sig[0]
430
+ when nil, Java::void, java.lang.Void
431
+ added = 0
432
+ when Java::boolean, Java::short, Java::char, Java::int, Java::float
433
+ added = 1
434
+ when Java::long, Java::double
435
+ added = 2
436
+ else
437
+ added = 1
438
+ end
439
+
440
+ args_subtracted = 0
441
+ [*call_sig][1..-1].each do |param|
442
+ case param
443
+ when nil, Java::void, java.lang.Void
444
+ args_subtracted += 0
445
+ when Java::boolean, Java::short, Java::char, Java::int, Java::float
446
+ args_subtracted += 1
447
+ when Java::long, Java::double
448
+ args_subtracted += 2
449
+ else
450
+ args_subtracted += 1
451
+ end
452
+ end
453
+
454
+ added - (this_subtracted + args_subtracted)
455
+ end
456
+ private :sig_stack_net
457
+ end
458
+ end