asmjit 0.1.0 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c624450e0bd1c3f5275f6bab3417f3a147f90efb595c26ca58ae93dd6828aa74
4
- data.tar.gz: 36fab728b2acc6bcca18b8fc415f4e7aca02ed919914f162242bba4f397533a3
3
+ metadata.gz: bcda0d9ba5d87e102ebf2fabf636aa07c3c8b24eca8c66c740fcb8700cd769cb
4
+ data.tar.gz: 4ecec0d43302ffce529cc54ed2a0c46d88035a589897b8a2ceaf3b022dc017a9
5
5
  SHA512:
6
- metadata.gz: 0ff819f0ecbc9750048a3ac529275ee49846b074c30097b182553a99733d149aa14096cf191e6ce50856a0d26a242c020c715801fab1442e2fc9666991106091
7
- data.tar.gz: 62e66a1ab2106832491749cef583d5248649eaf8c77bd29649d4afd39f2f00857a9a5e04f3fe192b38580fa57133b92196e733b3d3b077cf59dc61f2b48e2fc3
6
+ metadata.gz: 1e66beaefdfaa62ff6d9b27a3b984192cc3a7c6906f77ea96a3de303c7e342196b0aef7cd1f00596883720b490403bed9bd9e09ce526fca2c11e1700c9ec1fc3
7
+ data.tar.gz: b649c8f79ab822fb77beb840be1a8174fd71ba7e131320d80d87e8ae79539a18095311eb80492a0901182f4a4aa55c4496e3afd6254514049409829222e9291c
data/Gemfile.lock CHANGED
@@ -1,11 +1,12 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- asmjit (0.1.0)
4
+ asmjit (0.2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
+ hatstone (1.0.0)
9
10
  minitest (5.15.0)
10
11
  rake (13.0.6)
11
12
  rake-compiler (1.2.0)
@@ -16,6 +17,7 @@ PLATFORMS
16
17
 
17
18
  DEPENDENCIES
18
19
  asmjit!
20
+ hatstone
19
21
  minitest (~> 5.0)
20
22
  rake (~> 13.0)
21
23
  rake-compiler
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
- # Asmjit
1
+ # AsmJit Ruby
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/asmjit`. To experiment with that code, run `bin/console` for an interactive prompt.
4
-
5
- TODO: Delete this and the text above, and describe your gem
3
+ Ruby bindings for [AsmJit](https://asmjit.com/), a lightweight library for machine code generation.
6
4
 
7
5
  ## Installation
8
6
 
@@ -24,9 +22,12 @@ Or install it yourself as:
24
22
 
25
23
  ```
26
24
  f = AsmJIT.assemble do |a|
27
- a.mov(:eax, 1)
28
- a.ret()
29
- end.to_func
25
+ a.mov :eax, 123
26
+ a.ret
27
+ end.to_fiddle
28
+
29
+ f.call
30
+ # => 123
30
31
  ```
31
32
 
32
33
  ## Development
data/asmjit.gemspec CHANGED
@@ -29,9 +29,5 @@ Gem::Specification.new do |spec|
29
29
  spec.require_paths = ["lib"]
30
30
  spec.extensions = ["ext/asmjit/extconf.rb"]
31
31
 
32
- # Uncomment to register a new dependency of your gem
33
- # spec.add_dependency "example-gem", "~> 1.0"
34
-
35
- # For more information and examples about making a new gem, check out our
36
- # guide at: https://bundler.io/guides/creating_gem.html
32
+ spec.add_development_dependency "hatstone"
37
33
  end
data/ext/asmjit/asmjit.cc CHANGED
@@ -9,6 +9,9 @@ using namespace asmjit;
9
9
 
10
10
  static VALUE rb_mAsmjit;
11
11
  static VALUE rb_eAsmJITError;
12
+ static VALUE rb_cOperand;
13
+ static VALUE cX86Reg;
14
+ static VALUE cX86Mem;
12
15
 
13
16
  static JitRuntime jit_runtime;
14
17
 
@@ -54,17 +57,8 @@ VALUE code_holder_initialize(VALUE self) {
54
57
  TypedData_Get_Struct(self, CodeHolderWrapper, &code_holder_type, wrapper);
55
58
 
56
59
  CodeHolder *code = wrapper->code;
57
-
58
60
  code->init(jit_runtime.environment());
59
61
 
60
- //x86::Assembler a(code);
61
-
62
- //a.mov(x86::eax, 1);
63
- //a.ret();
64
-
65
- //int (*fn)(void);
66
- //Error err = jit_runtime.add(&fn, code);
67
-
68
62
  return self;
69
63
  }
70
64
 
@@ -80,24 +74,162 @@ VALUE code_holder_to_ptr(VALUE self) {
80
74
  return ULL2NUM(uintptr_t(fn));
81
75
  }
82
76
 
83
- struct x86AssemblerWrapper {
84
- x86::Assembler *assembler;
77
+ #define rb_define_method_id_original rb_define_method_id
78
+
79
+ VALUE code_holder_define_method(VALUE self, VALUE mod, VALUE name, VALUE arity_val) {
80
+ CodeHolderWrapper *wrapper;
81
+ TypedData_Get_Struct(self, CodeHolderWrapper, &code_holder_type, wrapper);
82
+
83
+ CodeHolder *code = wrapper->code;
84
+
85
+ int arity = FIX2INT(arity_val);
86
+ ID id = rb_sym2id(name);
87
+
88
+ VALUE (*fn)(ANYARGS);
89
+ jit_runtime.add(&fn, code);
90
+
91
+ // avoid cxxanyargs
92
+ #undef rb_define_method_id
93
+ rb_define_method_id(mod, id, fn, arity);
94
+
95
+ return name;
96
+ }
97
+
98
+ VALUE code_holder_binary(VALUE self) {
99
+ CodeHolderWrapper *wrapper;
100
+ TypedData_Get_Struct(self, CodeHolderWrapper, &code_holder_type, wrapper);
101
+
102
+ CodeHolder *code = wrapper->code;
103
+
104
+ CodeBuffer buffer = code->sectionById(0)->buffer();
105
+ return rb_str_new(
106
+ reinterpret_cast<const char *>(buffer.data()),
107
+ buffer.size()
108
+ );
109
+ }
110
+
111
+ static const rb_data_type_t operand_type = {
112
+ .wrap_struct_name = "AsmJIT::Operand",
113
+ .function = {
114
+ .dmark = NULL,
115
+ .dfree = RUBY_DEFAULT_FREE,
116
+ .dsize = NULL,
117
+ },
118
+ .data = NULL,
119
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
120
+ };
121
+
122
+ struct OperandWrapper {
123
+ Operand opnd;
124
+ };
125
+
126
+ static VALUE build_register(const char *c_name, x86::Reg reg) {
127
+ OperandWrapper *wrapper = static_cast<OperandWrapper *>(xmalloc(sizeof(OperandWrapper)));
128
+ wrapper->opnd = reg;
129
+
130
+ VALUE name = ID2SYM(rb_intern(c_name));
131
+ VALUE obj = TypedData_Wrap_Struct(cX86Reg, &operand_type, wrapper);
132
+ rb_iv_set(obj, "@name", name);
133
+ return obj;
134
+ }
135
+
136
+ static VALUE build_label(Label label) {
137
+ OperandWrapper *wrapper = static_cast<OperandWrapper *>(xmalloc(sizeof(OperandWrapper)));
138
+ wrapper->opnd = label;
139
+
140
+ VALUE obj = TypedData_Wrap_Struct(cX86Reg, &operand_type, wrapper);
141
+ return obj;
142
+ }
143
+
144
+
145
+ static Operand opnd_get(VALUE val) {
146
+ OperandWrapper *wrapper;
147
+ TypedData_Get_Struct(val, OperandWrapper, &operand_type, wrapper);
148
+ return wrapper->opnd;
149
+ }
150
+
151
+ static Label label_get(VALUE val) {
152
+ Operand opnd = opnd_get(val);
153
+ if (!opnd.isLabel()) {
154
+ rb_raise(rb_eTypeError, "operand is not label");
155
+ }
156
+ return opnd.as<Label>();
157
+ }
158
+
159
+ static x86::Reg x86_reg_get(VALUE val) {
160
+ Operand opnd = opnd_get(val);
161
+ if (!opnd.isReg()) {
162
+ rb_raise(rb_eTypeError, "operand is not reg");
163
+ }
164
+ return opnd.as<x86::Reg>();
165
+ }
166
+
167
+ static x86::Mem x86_mem_get(VALUE val) {
168
+ Operand opnd = opnd_get(val);
169
+ if (!opnd.isMem()) {
170
+ rb_raise(rb_eTypeError, "operand is not mem");
171
+ }
172
+ return opnd.as<x86::Mem>();
173
+ }
174
+
175
+ static VALUE x86_ptr(VALUE _self, VALUE regv, VALUE offsetv, VALUE sizev) {
176
+ OperandWrapper *wrapper = static_cast<OperandWrapper *>(xmalloc(sizeof(OperandWrapper)));
177
+
178
+ x86::Reg reg = x86_reg_get(regv);
179
+ if (!reg.isGp()) {
180
+ rb_raise(rb_eAsmJITError, "reg must be Gp");
181
+ }
182
+ int32_t offset = NUM2INT(offsetv);
183
+ uint32_t size = NUM2UINT(sizev);
184
+ x86::Mem mem = x86::ptr(reg.as<x86::Gp>(), offset, size);
185
+ wrapper->opnd = mem;
186
+
187
+ VALUE obj = TypedData_Wrap_Struct(cX86Mem, &operand_type, wrapper);
188
+ return obj;
189
+ }
190
+
191
+ static VALUE build_registers_hash() {
192
+ VALUE hash = rb_hash_new();
193
+
194
+ #define REGISTER(name) rb_hash_aset(hash, ID2SYM(rb_intern(#name)), build_register((#name), x86::name))
195
+
196
+ REGISTER(eax);
197
+ REGISTER(ebx);
198
+ REGISTER(ecx);
199
+ REGISTER(edx);
200
+ REGISTER(edi);
201
+ REGISTER(esi);
202
+
203
+ REGISTER(rax);
204
+ REGISTER(rbx);
205
+ REGISTER(rcx);
206
+ REGISTER(rdx);
207
+ REGISTER(rdi);
208
+ REGISTER(rsi);
209
+
210
+ #undef REGISTER
211
+
212
+ return hash;
213
+ }
214
+
215
+ struct BaseEmitterWrapper {
216
+ BaseEmitter *emitter;
85
217
  VALUE code_holder;
86
218
  };
87
219
 
88
220
  void x86_assembler_free(void *data) {
89
- x86AssemblerWrapper *wrapper = static_cast<x86AssemblerWrapper *>(data);
90
- delete wrapper->assembler;
221
+ BaseEmitterWrapper *wrapper = static_cast<BaseEmitterWrapper *>(data);
222
+ delete wrapper->emitter;
91
223
  xfree(wrapper);
92
224
  }
93
225
 
94
226
  void x86_assembler_mark(void *data) {
95
- x86AssemblerWrapper *wrapper = static_cast<x86AssemblerWrapper *>(data);
227
+ BaseEmitterWrapper *wrapper = static_cast<BaseEmitterWrapper *>(data);
96
228
  rb_gc_mark(wrapper->code_holder);
97
229
  }
98
230
 
99
- static const rb_data_type_t x86_assembler_type = {
100
- .wrap_struct_name = "AsmJIT::Assembler",
231
+ static const rb_data_type_t base_emitter_type = {
232
+ .wrap_struct_name = "AsmJIT::BaseEmitter",
101
233
  .function = {
102
234
  .dmark = NULL,
103
235
  .dfree = x86_assembler_free,
@@ -107,59 +239,73 @@ static const rb_data_type_t x86_assembler_type = {
107
239
  .flags = RUBY_TYPED_FREE_IMMEDIATELY,
108
240
  };
109
241
 
110
- VALUE x86_assembler_alloc(VALUE self) {
111
- x86AssemblerWrapper *wrapper = static_cast<x86AssemblerWrapper *>(xmalloc(sizeof(CodeHolderWrapper)));
112
- wrapper->assembler = NULL;
113
- wrapper->code_holder = Qnil;
114
-
115
- return TypedData_Wrap_Struct(self, &x86_assembler_type, wrapper);
116
- }
117
-
118
- VALUE x86_assembler_initialize(VALUE self, VALUE code_holder) {
119
- x86AssemblerWrapper *wrapper;
120
- TypedData_Get_Struct(self, x86AssemblerWrapper, &x86_assembler_type, wrapper);
242
+ VALUE x86_assembler_new(VALUE self, VALUE code_holder) {
243
+ BaseEmitterWrapper *wrapper = static_cast<BaseEmitterWrapper *>(xmalloc(sizeof(CodeHolderWrapper)));
121
244
 
122
245
  CodeHolderWrapper *code_wrapper;
123
246
  TypedData_Get_Struct(code_holder, CodeHolderWrapper, &code_holder_type, code_wrapper);
124
247
 
248
+ x86::Assembler *assembler = new x86::Assembler(code_wrapper->code);
249
+ assembler->addDiagnosticOptions(DiagnosticOptions::kValidateAssembler);
250
+
125
251
  wrapper->code_holder = code_holder;
126
- wrapper->assembler = new x86::Assembler(code_wrapper->code);
252
+ wrapper->emitter = assembler;
127
253
 
128
- return self;
254
+ return TypedData_Wrap_Struct(self, &base_emitter_type, wrapper);
129
255
  }
130
256
 
131
257
  Operand parse_operand(VALUE val) {
132
258
  if (FIXNUM_P(val)) {
133
259
  return Imm(NUM2LL(val));
134
- } else if (RB_TYPE_P(val, T_SYMBOL)) {
135
- if (val == ID2SYM(rb_intern("eax"))) {
136
- return x86::eax;
137
- } else if (val == ID2SYM(rb_intern("rax"))) {
138
- return x86::rax;
139
- }
260
+ } else if (rb_obj_is_kind_of(val, rb_cOperand)) {
261
+ return opnd_get(val);
140
262
  }
141
263
  rb_raise(rb_eAsmJITError, "bad operand: %" PRIsVALUE, val);
142
264
  }
143
265
 
144
- VALUE x86_assembler_emit(int argc, VALUE* argv, VALUE self) {
145
- x86AssemblerWrapper *wrapper;
146
- TypedData_Get_Struct(self, x86AssemblerWrapper, &x86_assembler_type, wrapper);
266
+ VALUE base_emitter_new_label(VALUE self) {
267
+ BaseEmitterWrapper *wrapper;
268
+ TypedData_Get_Struct(self, BaseEmitterWrapper, &base_emitter_type, wrapper);
269
+ BaseEmitter *emitter = wrapper->emitter;
270
+
271
+ Label label = emitter->newLabel();
272
+ return build_label(label);
273
+ }
274
+
275
+ VALUE base_emitter_bind(VALUE self, VALUE labelv) {
276
+ BaseEmitterWrapper *wrapper;
277
+ TypedData_Get_Struct(self, BaseEmitterWrapper, &base_emitter_type, wrapper);
278
+ BaseEmitter *emitter = wrapper->emitter;
147
279
 
148
- x86::Assembler *assembler = wrapper->assembler;
280
+ Label label = label_get(labelv);
281
+
282
+ int err = emitter->bind(label);
283
+ if (err) {
284
+ rb_raise(rb_eAsmJITError, "error binding label");
285
+ }
286
+
287
+ return labelv;
288
+ }
289
+
290
+ VALUE base_emitter_emit(int argc, VALUE* argv, VALUE self) {
291
+ BaseEmitterWrapper *wrapper;
292
+ TypedData_Get_Struct(self, BaseEmitterWrapper, &base_emitter_type, wrapper);
293
+
294
+ BaseEmitter *emitter = wrapper->emitter;
149
295
 
150
296
  if (argc < 1) return Qnil;
151
297
  if (argc > 7) return Qnil;
152
298
 
153
299
  VALUE insn_name = argv[0];
154
- InstId inst_id = InstAPI::stringToInstId(assembler->arch(), RSTRING_PTR(insn_name), RSTRING_LEN(insn_name));
300
+ Check_Type(insn_name, T_STRING);
301
+ InstId inst_id = InstAPI::stringToInstId(emitter->arch(), RSTRING_PTR(insn_name), RSTRING_LEN(insn_name));
155
302
 
156
303
  Operand operands[6];
157
304
  for (int i = 0; i < argc - 1; i++) {
158
305
  operands[i] = parse_operand(argv[i + 1]);
159
306
  }
160
307
 
161
- //assembler->emit(inst_id);
162
- assembler->emitOpArray(inst_id, &operands[0], argc - 1);
308
+ emitter->emitOpArray(inst_id, &operands[0], argc - 1);
163
309
 
164
310
  return self;
165
311
  }
@@ -177,17 +323,32 @@ Init_asmjit(void)
177
323
 
178
324
  rb_eAsmJITError = rb_define_class_under(rb_mAsmjit, "Error", rb_eStandardError);
179
325
 
180
- VALUE cCodeHolder = rb_define_class_under(rb_mAsmjit, "CodeHolder", rb_cObject);
181
- rb_define_alloc_func(cCodeHolder, code_holder_alloc);
182
- rb_define_method(cCodeHolder, "initialize", code_holder_initialize, 0);
183
- rb_define_method(cCodeHolder, "to_ptr", code_holder_to_ptr, 0);
326
+ VALUE cCodeHolder = rb_define_class_under(rb_mAsmjit, "CodeHolder", rb_cObject);
327
+ rb_define_alloc_func(cCodeHolder, code_holder_alloc);
328
+ rb_define_method(cCodeHolder, "initialize", code_holder_initialize, 0);
329
+ rb_define_method(cCodeHolder, "to_ptr", code_holder_to_ptr, 0);
330
+ rb_define_method(cCodeHolder, "def_method", code_holder_define_method, 3);
331
+ rb_define_method(cCodeHolder, "binary", code_holder_binary, 0);
332
+
333
+ VALUE rb_mX86 = rb_define_module_under(rb_mAsmjit, "X86");
334
+
335
+ VALUE rb_cBaseEmitter = rb_define_class_under(rb_mAsmjit, "BaseEmitter", rb_cObject);
336
+ rb_undef_alloc_func(rb_cBaseEmitter);
337
+ rb_define_method(rb_cBaseEmitter, "_emit", base_emitter_emit, -1);
338
+ rb_define_method(rb_cBaseEmitter, "new_label", base_emitter_new_label, 0);
339
+ rb_define_method(rb_cBaseEmitter, "bind", base_emitter_bind, 1);
340
+
341
+ VALUE cX86Assembler = rb_define_class_under(rb_mX86, "Assembler", rb_cBaseEmitter);
342
+ rb_define_singleton_method(cX86Assembler, "new", x86_assembler_new, 1);
343
+
344
+ rb_cOperand = rb_define_class_under(rb_mAsmjit, "Operand", rb_cObject);
345
+ rb_undef_alloc_func(rb_cOperand);
184
346
 
185
- VALUE rb_mX86 = rb_define_module_under(rb_mAsmjit, "X86");
347
+ cX86Reg = rb_define_class_under(rb_mX86, "Reg", rb_cOperand);
348
+ rb_define_attr(cX86Reg, "name", 1, 0);
186
349
 
187
- VALUE cX86Assembler = rb_define_class_under(rb_mX86, "Assembler", rb_cObject);
188
- rb_define_alloc_func(cX86Assembler, x86_assembler_alloc);
189
- rb_define_method(cX86Assembler, "initialize", x86_assembler_initialize, 1);
190
- rb_define_method(cX86Assembler, "emit", x86_assembler_emit, -1);
350
+ cX86Mem = rb_define_class_under(rb_mX86, "Mem", rb_cOperand);
351
+ rb_define_singleton_method(cX86Mem, "new", x86_ptr, 3);
191
352
 
192
353
  VALUE instructions = rb_ary_new();
193
354
 
@@ -203,4 +364,5 @@ Init_asmjit(void)
203
364
  }
204
365
 
205
366
  rb_define_const(rb_mX86, "INSTRUCTIONS", instructions);
367
+ rb_define_const(rb_mX86, "REGISTERS", build_registers_hash());
206
368
  }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AsmJIT
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/asmjit.rb CHANGED
@@ -3,14 +3,80 @@
3
3
  require_relative "asmjit/version"
4
4
  require_relative "asmjit/asmjit"
5
5
 
6
+ AsmJit = AsmJIT
6
7
  module AsmJIT
8
+ def self.assemble
9
+ code = CodeHolder.new
10
+ yield code.assembler
11
+ code
12
+ end
13
+
14
+ class CodeHolder
15
+ def assembler
16
+ X86::Assembler.new(self)
17
+ end
18
+
19
+ def to_fiddle(inputs: nil, output: nil)
20
+ ptr = to_ptr
21
+
22
+ Fiddle::Function.new(
23
+ ptr,
24
+ inputs || [],
25
+ output || Fiddle::TYPE_INT
26
+ )
27
+ end
28
+
29
+ def def_module(arity, method_name: :call)
30
+ mod = Module.new
31
+ self.def_method(mod, method_name, arity)
32
+ mod
33
+ end
34
+
35
+ def def_class(arity, method_name: :call)
36
+ mod = Class.new
37
+ self.def_method(mod, method_name, arity)
38
+ mod
39
+ end
40
+ end
41
+
7
42
  module X86
43
+ module Helpers
44
+ extend self
45
+
46
+ def parse_operand(arg)
47
+ if Symbol === arg && reg = REGISTERS[arg]
48
+ reg
49
+ else
50
+ arg
51
+ end
52
+ end
53
+ end
54
+
55
+ class << self
56
+ def ptr(base, offset, size)
57
+ X86::Mem.new(Helpers.parse_operand(base), offset, size)
58
+ end
59
+
60
+ def qword_ptr(base, offset)
61
+ ptr(base, offset, 8)
62
+ end
63
+ end
64
+
8
65
  class Assembler
66
+ def emit(*args)
67
+ _emit(*(args.map { |arg| Helpers.parse_operand(arg) }))
68
+ end
69
+
9
70
  INSTRUCTIONS.each do |name|
10
71
  define_method(name) do |*args|
11
72
  emit(name, *args)
12
73
  end
13
74
  end
14
75
  end
76
+ class Reg
77
+ def inspect
78
+ "#<#{self.class} #{name}>"
79
+ end
80
+ end
15
81
  end
16
82
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: asmjit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Hawthorn
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-05-07 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2022-09-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: hatstone
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  description: 'Ruby wrapper for asmjit: a lightweight library for machine code generation'
14
28
  email:
15
29
  - john@hawthorn.email
@@ -53,7 +67,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
53
67
  - !ruby/object:Gem::Version
54
68
  version: '0'
55
69
  requirements: []
56
- rubygems_version: 3.3.3
70
+ rubygems_version: 3.3.7
57
71
  signing_key:
58
72
  specification_version: 4
59
73
  summary: 'Ruby wrapper for asmjit: a lightweight library for machine code generation'