superinstance-flux-runtime 1.0.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 +7 -0
- data/lib/superinstance/flux-runtime/assembler.rb +305 -0
- data/lib/superinstance/flux-runtime/cli.rb +190 -0
- data/lib/superinstance/flux-runtime/disassembler.rb +197 -0
- data/lib/superinstance/flux-runtime/exceptions.rb +49 -0
- data/lib/superinstance/flux-runtime/flux_vm.rb +1020 -0
- data/lib/superinstance/flux-runtime/loader.rb +55 -0
- data/lib/superinstance/flux-runtime/opcode.rb +141 -0
- data/lib/superinstance/flux-runtime/runtime/agent.rb +128 -0
- data/lib/superinstance/flux-runtime/runtime/opcode.rb +57 -0
- data/lib/superinstance/flux-runtime/version.rb +8 -0
- data/lib/superinstance-flux-runtime.rb +22 -0
- metadata +70 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: ea9ba7c8b41057f91a8ff10c2d6cb643dffd8ce9bc269678cd23ee725381f588
|
|
4
|
+
data.tar.gz: 8b1c353e2a4b3e021f568972240888c87679b087074f39d94f803caad7e5e9e1
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ad2712c1004e3c47645cb40911a5bfc5a3d285fc618517b7a983984a23ed2c485b3e7863a6630df8c36e585418102ea5f6f3b0733cbd8799747f996f97996878
|
|
7
|
+
data.tar.gz: b123e8b9b9a7e356b2ff7a76b5aa09d2a8fb280fb09926f9e7a29be6f1f5f2824374c7ad451b763f5fe6f7ab8e4dcd923d16d09e458d1e199c521de088f37756
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'flux_vm'
|
|
4
|
+
|
|
5
|
+
module Flux
|
|
6
|
+
# Text assembly to bytecode
|
|
7
|
+
class Assembler
|
|
8
|
+
REGISTER_ALIASES = {
|
|
9
|
+
'RV' => 8, 'R8' => 8,
|
|
10
|
+
'A0' => 9, 'R9' => 9,
|
|
11
|
+
'A1' => 10, 'R10' => 10,
|
|
12
|
+
'SP' => 11, 'R11' => 11,
|
|
13
|
+
'FP' => 12, 'R12' => 12,
|
|
14
|
+
'FL' => 13, 'R13' => 13,
|
|
15
|
+
'TP' => 14, 'R14' => 14,
|
|
16
|
+
'LR' => 15, 'R15' => 15
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
OPCODE_MAP = {
|
|
20
|
+
# Control Flow
|
|
21
|
+
'HALT' => 0x00, 'NOP' => 0x01, 'RET' => 0x02,
|
|
22
|
+
'JUMP' => 0x03, 'JUMPIF' => 0x04, 'JUMPIFNOT' => 0x05,
|
|
23
|
+
'CALL' => 0x06, 'CALLINDIRECT' => 0x07,
|
|
24
|
+
'YIELD' => 0x08, 'PANIC' => 0x09, 'UNREACHABLE' => 0x0A,
|
|
25
|
+
|
|
26
|
+
# Stack Operations
|
|
27
|
+
'PUSH' => 0x10, 'POP' => 0x11, 'DUP' => 0x12, 'SWAP' => 0x13,
|
|
28
|
+
|
|
29
|
+
# Integer Arithmetic
|
|
30
|
+
'IMOV' => 0x20, 'IADD' => 0x21, 'ISUB' => 0x22,
|
|
31
|
+
'IMUL' => 0x23, 'IDIV' => 0x24, 'IMOD' => 0x25,
|
|
32
|
+
'INEG' => 0x26, 'IABS' => 0x27,
|
|
33
|
+
'IINC' => 0x28, 'IDEC' => 0x29,
|
|
34
|
+
'IMIN' => 0x2A, 'IMAX' => 0x2B,
|
|
35
|
+
'IAND' => 0x2C, 'IOR' => 0x2D, 'IXOR' => 0x2E,
|
|
36
|
+
'ISHL' => 0x2F, 'ISHR' => 0x30, 'INOT' => 0x31,
|
|
37
|
+
'ICMPEQ' => 0x32, 'ICMPNE' => 0x33,
|
|
38
|
+
'ICMPLT' => 0x34, 'ICMPLE' => 0x35,
|
|
39
|
+
'ICMPGT' => 0x36, 'ICMPGE' => 0x37,
|
|
40
|
+
|
|
41
|
+
# Float Arithmetic
|
|
42
|
+
'FMOV' => 0x40, 'FADD' => 0x41, 'FSUB' => 0x42,
|
|
43
|
+
'FMUL' => 0x43, 'FDIV' => 0x44, 'FMOD' => 0x45,
|
|
44
|
+
'FNEG' => 0x46, 'FABS' => 0x47, 'FSQRT' => 0x48,
|
|
45
|
+
'FFLOOR' => 0x49, 'FCEIL' => 0x4A, 'FROUND' => 0x4B,
|
|
46
|
+
'FMIN' => 0x4C, 'FMAX' => 0x4D,
|
|
47
|
+
'FSIN' => 0x4E, 'FCOS' => 0x4F,
|
|
48
|
+
'FEXP' => 0x50, 'FLOG' => 0x51,
|
|
49
|
+
'FCLAMP' => 0x52, 'FLERP' => 0x53,
|
|
50
|
+
'FCMPEQ' => 0x54, 'FCMPNE' => 0x55,
|
|
51
|
+
'FCMPLT' => 0x56, 'FCMPLE' => 0x57,
|
|
52
|
+
'FCMPGT' => 0x58, 'FCMPGE' => 0x59,
|
|
53
|
+
|
|
54
|
+
# Conversions
|
|
55
|
+
'ITOF' => 0x60, 'FTOI' => 0x61, 'BTOI' => 0x62, 'ITOB' => 0x63,
|
|
56
|
+
|
|
57
|
+
# Memory Operations
|
|
58
|
+
'LOAD8' => 0x70, 'LOAD16' => 0x71, 'LOAD32' => 0x72, 'LOAD64' => 0x73,
|
|
59
|
+
'STORE8' => 0x74, 'STORE16' => 0x75, 'STORE32' => 0x76, 'STORE64' => 0x77,
|
|
60
|
+
'LOADADDR' => 0x78, 'STACKALLOC' => 0x79,
|
|
61
|
+
|
|
62
|
+
# A2A Communication
|
|
63
|
+
'ASEND' => 0x80, 'ARECV' => 0x81, 'AASK' => 0x82,
|
|
64
|
+
'ATELL' => 0x83, 'ADELEGATE' => 0x84, 'ABROADCAST' => 0x85,
|
|
65
|
+
'ASUBSCRIBE' => 0x86, 'AWAIT' => 0x87, 'ATRUST' => 0x88, 'AVERIFY' => 0x89,
|
|
66
|
+
|
|
67
|
+
# Type/Meta
|
|
68
|
+
'CAST' => 0x90, 'SIZEOF' => 0x91, 'TYPEOF' => 0x92,
|
|
69
|
+
|
|
70
|
+
# Bitwise
|
|
71
|
+
'BAND' => 0xA0, 'BOR' => 0xA1, 'BXOR' => 0xA2,
|
|
72
|
+
'BSHL' => 0xA3, 'BSHR' => 0xA4, 'BNOT' => 0xA5,
|
|
73
|
+
|
|
74
|
+
# Vector
|
|
75
|
+
'VLOAD' => 0xB0, 'VSTORE' => 0xB1, 'VADD' => 0xB2,
|
|
76
|
+
'VMUL' => 0xB3, 'VDOT' => 0xB4
|
|
77
|
+
}.freeze
|
|
78
|
+
|
|
79
|
+
def initialize
|
|
80
|
+
@labels = {}
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def assemble(source_code)
|
|
84
|
+
output = []
|
|
85
|
+
lines = source_code.lines.map(&:chomp)
|
|
86
|
+
|
|
87
|
+
# Single pass: collect labels and build bytecode
|
|
88
|
+
@labels = {}
|
|
89
|
+
pc = 0
|
|
90
|
+
|
|
91
|
+
# First pass: find labels
|
|
92
|
+
lines.each do |line|
|
|
93
|
+
line = strip_comment(line)
|
|
94
|
+
next if line.empty?
|
|
95
|
+
|
|
96
|
+
if line.end_with?(':')
|
|
97
|
+
label_name = line.chomp(':').upcase
|
|
98
|
+
@labels[label_name] = pc
|
|
99
|
+
else
|
|
100
|
+
pc += estimate_size(line)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Second pass: generate bytecode
|
|
105
|
+
pc = 0
|
|
106
|
+
lines.each do |line|
|
|
107
|
+
line = strip_comment(line)
|
|
108
|
+
next if line.empty?
|
|
109
|
+
next if line.end_with?(':')
|
|
110
|
+
|
|
111
|
+
bytes = encode_instruction(line, pc)
|
|
112
|
+
output.concat(bytes)
|
|
113
|
+
pc += bytes.length
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
output.pack('C*')
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def strip_comment(line)
|
|
122
|
+
line.split(/\/\/|#/).first.to_s.strip
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def estimate_size(line)
|
|
126
|
+
parts = line.upcase.split(/\s+/)
|
|
127
|
+
op = parts[0]
|
|
128
|
+
opcode = OPCODE_MAP[op]
|
|
129
|
+
return 1 unless opcode
|
|
130
|
+
|
|
131
|
+
case opcode
|
|
132
|
+
when 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A then 1
|
|
133
|
+
when 0x10, 0x11, 0x12, 0x13, 0x20, 0x40 then 3
|
|
134
|
+
when 0x21..0x37, 0x41..0x59, 0x60..0x63, 0x90..0x92, 0xA0..0xA5, 0xB2..0xB4 then 4
|
|
135
|
+
when 0x28, 0x29, 0x79 then 4
|
|
136
|
+
when 0x70..0x78, 0xB0, 0xB1 then 5
|
|
137
|
+
when 0x03, 0x04, 0x05 then 4
|
|
138
|
+
when 0x06 then 4
|
|
139
|
+
when 0x07 then 3
|
|
140
|
+
when 0x80..0x89 then 4
|
|
141
|
+
else 4
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def encode_instruction(line, pc)
|
|
146
|
+
parts = line.upcase.split(/\s+/)
|
|
147
|
+
op = parts[0]
|
|
148
|
+
|
|
149
|
+
# Join remaining parts with space and parse comma-separated operands
|
|
150
|
+
raw_args = parts[1..].join(' ')
|
|
151
|
+
args = raw_args.split(',').map(&:strip).reject(&:empty?)
|
|
152
|
+
|
|
153
|
+
opcode = OPCODE_MAP[op]
|
|
154
|
+
raise ArgumentError, "Unknown opcode: #{op}" unless opcode
|
|
155
|
+
|
|
156
|
+
bytes = [opcode]
|
|
157
|
+
|
|
158
|
+
case opcode
|
|
159
|
+
# Format A: no operands
|
|
160
|
+
when 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A
|
|
161
|
+
bytes
|
|
162
|
+
|
|
163
|
+
# Format B: Rd, Rs
|
|
164
|
+
when 0x10, 0x11, 0x12, 0x20, 0x40
|
|
165
|
+
bytes << parse_register(args[0]) << parse_register(args[1] || 'R0')
|
|
166
|
+
|
|
167
|
+
when 0x13 # Swap: Ra, Rb
|
|
168
|
+
bytes << parse_register(args[0]) << parse_register(args[1] || 'R0')
|
|
169
|
+
|
|
170
|
+
# Format C: Rd, Ra, Rb
|
|
171
|
+
when 0x21..0x37, 0x41..0x59, 0x60..0x63, 0x90..0x92, 0xA0..0xA5, 0xB2..0xB4
|
|
172
|
+
bytes << parse_register(args[0]) << parse_register(args[1] || 'R0') << parse_register(args[2] || 'R0')
|
|
173
|
+
|
|
174
|
+
# Format D: Rd, imm16
|
|
175
|
+
when 0x28, 0x29
|
|
176
|
+
bytes << parse_register(args[0]) << encode_imm16(args[1] || '0')
|
|
177
|
+
|
|
178
|
+
when 0x79 # StackAlloc
|
|
179
|
+
bytes << parse_register(args[0] || 'R0') << encode_imm16(args[1] || '0')
|
|
180
|
+
|
|
181
|
+
# Format E: Rd, Rb, off16
|
|
182
|
+
when 0x70..0x78, 0xB0, 0xB1
|
|
183
|
+
bytes << parse_register(args[0]) << parse_register(args[1] || 'R0') << encode_imm16(args[2] || '0')
|
|
184
|
+
|
|
185
|
+
# Format G: variable
|
|
186
|
+
when 0x03 # Jump
|
|
187
|
+
offset = resolve_label(args[0], pc, 4) || 0
|
|
188
|
+
bytes << 4 << encode_imm16(offset.to_s)
|
|
189
|
+
|
|
190
|
+
when 0x04, 0x05 # JumpIf, JumpIfNot
|
|
191
|
+
rd = parse_register(args[0] || 'R0')
|
|
192
|
+
offset = resolve_label(args[1], pc, 5) || 0
|
|
193
|
+
bytes << 5 << rd << encode_imm16(offset.to_s)
|
|
194
|
+
|
|
195
|
+
when 0x06 # Call
|
|
196
|
+
func_idx = resolve_func_idx(args[0] || '0')
|
|
197
|
+
bytes << 4 << encode_imm16(func_idx.to_s)
|
|
198
|
+
|
|
199
|
+
when 0x07 # CallIndirect
|
|
200
|
+
reg = parse_register(args[0] || 'R0')
|
|
201
|
+
bytes << 3 << reg
|
|
202
|
+
|
|
203
|
+
when 0x80 # ASend
|
|
204
|
+
agent_id = parse_immediate(args[0] || '0')
|
|
205
|
+
reg = parse_register(args[1] || 'R0')
|
|
206
|
+
bytes << 4 << agent_id << reg
|
|
207
|
+
|
|
208
|
+
when 0x81 # ARecv
|
|
209
|
+
agent_id = parse_immediate(args[0] || '0')
|
|
210
|
+
reg = parse_register(args[1] || 'R0')
|
|
211
|
+
bytes << 4 << agent_id << reg
|
|
212
|
+
|
|
213
|
+
when 0x82 # AAsk
|
|
214
|
+
agent_id = parse_immediate(args[0] || '0')
|
|
215
|
+
reg = parse_register(args[1] || 'R0')
|
|
216
|
+
bytes << 4 << agent_id << reg
|
|
217
|
+
|
|
218
|
+
when 0x83 # ATell
|
|
219
|
+
agent_id = parse_immediate(args[0] || '0')
|
|
220
|
+
reg = parse_register(args[1] || 'R0')
|
|
221
|
+
bytes << 4 << agent_id << reg
|
|
222
|
+
|
|
223
|
+
when 0x84 # ADelegate
|
|
224
|
+
agent_id = parse_immediate(args[0] || '0')
|
|
225
|
+
bc_start = resolve_func_idx(args[1] || '0')
|
|
226
|
+
bytes << 4 << agent_id << encode_imm16(bc_start.to_s)
|
|
227
|
+
|
|
228
|
+
when 0x85 # ABroadcast
|
|
229
|
+
reg = parse_register(args[0] || 'R0')
|
|
230
|
+
bytes << 3 << reg
|
|
231
|
+
|
|
232
|
+
when 0x86 # ASubscribe
|
|
233
|
+
channel_id = parse_immediate(args[0] || '0')
|
|
234
|
+
bytes << 3 << channel_id
|
|
235
|
+
|
|
236
|
+
when 0x87 # AWait
|
|
237
|
+
cond_reg = parse_register(args[0] || 'R0')
|
|
238
|
+
bytes << 3 << cond_reg
|
|
239
|
+
|
|
240
|
+
when 0x88 # ATrust
|
|
241
|
+
agent_id = parse_immediate(args[0] || '0')
|
|
242
|
+
level = parse_immediate(args[1] || '0')
|
|
243
|
+
bytes << 4 << agent_id << level
|
|
244
|
+
|
|
245
|
+
when 0x89 # AVerify
|
|
246
|
+
agent_id = parse_immediate(args[0] || '0')
|
|
247
|
+
result_reg = parse_register(args[1] || 'R0')
|
|
248
|
+
bytes << 4 << agent_id << result_reg
|
|
249
|
+
|
|
250
|
+
else
|
|
251
|
+
bytes
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def parse_register(name)
|
|
256
|
+
return 0 unless name
|
|
257
|
+
|
|
258
|
+
name = name.to_s.strip.upcase
|
|
259
|
+
return REGISTER_ALIASES[name] if REGISTER_ALIASES[name]
|
|
260
|
+
|
|
261
|
+
if name.start_with?('R')
|
|
262
|
+
num = name[1..].to_i
|
|
263
|
+
return num if num >= 0 && num < 16
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
raise ArgumentError, "Invalid register: #{name}"
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def parse_immediate(value)
|
|
270
|
+
value = value.to_s.strip
|
|
271
|
+
if value.start_with?('0x', '0X')
|
|
272
|
+
value[2..].to_i(16)
|
|
273
|
+
else
|
|
274
|
+
value.to_i
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def encode_imm16(value)
|
|
279
|
+
val = parse_immediate(value)
|
|
280
|
+
[val & 0xFF, (val >> 8) & 0xFF]
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def resolve_func_idx(value)
|
|
284
|
+
value = value.to_s.strip
|
|
285
|
+
if @labels[value.upcase]
|
|
286
|
+
@labels[value.upcase]
|
|
287
|
+
elsif @labels["FUNC_#{value.upcase}"]
|
|
288
|
+
@labels["FUNC_#{value.upcase}"]
|
|
289
|
+
else
|
|
290
|
+
value.to_i
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def resolve_label(name, current_pc, instruction_size)
|
|
295
|
+
return nil unless name
|
|
296
|
+
|
|
297
|
+
name = name.to_s.strip.upcase
|
|
298
|
+
if @labels[name]
|
|
299
|
+
@labels[name] - current_pc - instruction_size
|
|
300
|
+
else
|
|
301
|
+
nil
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
end
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'flux_vm'
|
|
4
|
+
require_relative 'assembler'
|
|
5
|
+
require_relative 'disassembler'
|
|
6
|
+
require_relative 'loader'
|
|
7
|
+
|
|
8
|
+
module Flux
|
|
9
|
+
# Command-line interface for FLUX VM
|
|
10
|
+
class CLI
|
|
11
|
+
def self.run(args)
|
|
12
|
+
new.run(args)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def run(args)
|
|
16
|
+
command = args[0]
|
|
17
|
+
path = args[1]
|
|
18
|
+
|
|
19
|
+
case command
|
|
20
|
+
when 'run'
|
|
21
|
+
run_bytecode(path)
|
|
22
|
+
when 'asm'
|
|
23
|
+
assemble_file(path)
|
|
24
|
+
when 'dis'
|
|
25
|
+
disassemble_file(path)
|
|
26
|
+
when 'repl'
|
|
27
|
+
start_repl
|
|
28
|
+
when 'help', nil
|
|
29
|
+
print_help
|
|
30
|
+
else
|
|
31
|
+
$stderr.puts "Unknown command: #{command}"
|
|
32
|
+
print_help
|
|
33
|
+
exit 1
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def run_bytecode(path)
|
|
40
|
+
unless path
|
|
41
|
+
$stderr.puts 'Error: No file specified'
|
|
42
|
+
exit 1
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
loader = Loader.new
|
|
46
|
+
bytecode = loader.load_file(path)
|
|
47
|
+
|
|
48
|
+
vm = FluxVM.new
|
|
49
|
+
vm.load(bytecode)
|
|
50
|
+
|
|
51
|
+
trap('INT') do
|
|
52
|
+
$stderr.puts "\nInterrupted"
|
|
53
|
+
exit 1
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
vm.run
|
|
57
|
+
|
|
58
|
+
puts "VM state: #{vm.state}"
|
|
59
|
+
puts "Cycles: #{vm.stats[:cycles]}"
|
|
60
|
+
puts "Instructions: #{vm.stats[:instruction_count]}"
|
|
61
|
+
puts "Registers: #{vm.regs}"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def assemble_file(path)
|
|
65
|
+
unless path
|
|
66
|
+
$stderr.puts 'Error: No file specified'
|
|
67
|
+
exit 1
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
source = File.read(path)
|
|
71
|
+
asm = Assembler.new
|
|
72
|
+
bytecode = asm.assemble(source)
|
|
73
|
+
|
|
74
|
+
output_path = path.sub(/\.asm$/, '.flux')
|
|
75
|
+
output_path = "#{path}.flux" unless path.include?('.')
|
|
76
|
+
|
|
77
|
+
File.binwrite(output_path, bytecode)
|
|
78
|
+
puts "Assembled: #{path} -> #{output_path} (#{bytecode.bytesize} bytes)"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def disassemble_file(path)
|
|
82
|
+
unless path
|
|
83
|
+
$stderr.puts 'Error: No file specified'
|
|
84
|
+
exit 1
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
loader = Loader.new
|
|
88
|
+
bytecode = loader.load_file(path)
|
|
89
|
+
|
|
90
|
+
dis = Disassembler.new
|
|
91
|
+
puts dis.disassemble(bytecode)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def start_repl
|
|
95
|
+
puts 'FLUX REPL v1.0.0'
|
|
96
|
+
puts 'Type "help" for commands, "quit" to exit'
|
|
97
|
+
puts
|
|
98
|
+
|
|
99
|
+
vm = FluxVM.new
|
|
100
|
+
loader = Loader.new
|
|
101
|
+
asm = Assembler.new
|
|
102
|
+
|
|
103
|
+
loop do
|
|
104
|
+
print 'flux> '
|
|
105
|
+
line = gets
|
|
106
|
+
break unless line
|
|
107
|
+
|
|
108
|
+
line = line.strip
|
|
109
|
+
next if line.empty?
|
|
110
|
+
|
|
111
|
+
case line
|
|
112
|
+
when 'quit', 'exit', 'q'
|
|
113
|
+
break
|
|
114
|
+
when 'help', '?'
|
|
115
|
+
puts 'Commands:'
|
|
116
|
+
puts ' run - Run loaded bytecode'
|
|
117
|
+
puts ' step - Step one instruction'
|
|
118
|
+
puts ' regs - Show registers'
|
|
119
|
+
puts ' state - Show VM state'
|
|
120
|
+
puts ' reset - Reset VM'
|
|
121
|
+
puts ' load <file> - Load bytecode file'
|
|
122
|
+
puts ' asm <code> - Assemble and load'
|
|
123
|
+
puts ' dis - Disassemble loaded bytecode'
|
|
124
|
+
puts ' quit - Exit REPL'
|
|
125
|
+
when 'run'
|
|
126
|
+
vm.run
|
|
127
|
+
puts "State: #{vm.state}"
|
|
128
|
+
when 'step'
|
|
129
|
+
vm.step
|
|
130
|
+
puts "State: #{vm.state}"
|
|
131
|
+
when 'regs'
|
|
132
|
+
puts vm.regs.inspect
|
|
133
|
+
when 'state'
|
|
134
|
+
puts "State: #{vm.state}"
|
|
135
|
+
puts "PC: #{vm.pc}"
|
|
136
|
+
puts "SP: #{vm.sp}"
|
|
137
|
+
when 'reset'
|
|
138
|
+
vm.reset
|
|
139
|
+
puts 'VM reset'
|
|
140
|
+
when /^load\s+(.+)$/
|
|
141
|
+
path = $1
|
|
142
|
+
bytecode = loader.load_file(path)
|
|
143
|
+
vm.load(bytecode)
|
|
144
|
+
puts "Loaded #{bytecode.bytesize} bytes"
|
|
145
|
+
when /^asm\s+(.+)$/
|
|
146
|
+
code = $1
|
|
147
|
+
begin
|
|
148
|
+
bytecode = asm.assemble(code)
|
|
149
|
+
vm.load(bytecode)
|
|
150
|
+
puts "Assembled #{bytecode.bytesize} bytes"
|
|
151
|
+
rescue => e
|
|
152
|
+
puts "Error: #{e.message}"
|
|
153
|
+
end
|
|
154
|
+
when 'dis'
|
|
155
|
+
dis = Disassembler.new
|
|
156
|
+
# Disassemble what's in memory
|
|
157
|
+
puts dis.disassemble(vm.instance_variable_get(:memory).pack('C*'))
|
|
158
|
+
else
|
|
159
|
+
puts "Unknown command: #{line}"
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def print_help
|
|
165
|
+
puts <<~HELP
|
|
166
|
+
FLUX Runtime v1.0.0
|
|
167
|
+
Pure Ruby FLUX ISA v3.0 Virtual Machine
|
|
168
|
+
|
|
169
|
+
Usage: flux <command> [options]
|
|
170
|
+
|
|
171
|
+
Commands:
|
|
172
|
+
run <file> Run a FLUX bytecode file
|
|
173
|
+
asm <file> Assemble .asm file to .flux
|
|
174
|
+
dis <file> Disassemble .flux to .asm
|
|
175
|
+
repl Start interactive REPL
|
|
176
|
+
help Show this help
|
|
177
|
+
|
|
178
|
+
Examples:
|
|
179
|
+
flux run program.flux
|
|
180
|
+
flux asm source.asm
|
|
181
|
+
flux dis program.flux
|
|
182
|
+
HELP
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Run CLI if executed directly
|
|
188
|
+
if $PROGRAM_NAME == __FILE__ || File.basename($PROGRAM_NAME) == 'flux'
|
|
189
|
+
Flux::CLI.run(ARGV)
|
|
190
|
+
end
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'opcode'
|
|
4
|
+
|
|
5
|
+
module Flux
|
|
6
|
+
# Bytecode to text disassembly
|
|
7
|
+
class Disassembler
|
|
8
|
+
include OpcodeRegistry
|
|
9
|
+
|
|
10
|
+
MNEMONIC_MAP = OPCODE_CLASSES.transform_keys(&:to_i).freeze
|
|
11
|
+
|
|
12
|
+
def disassemble(bytecode_string)
|
|
13
|
+
bytecode = bytecode_string.dup.force_encoding('BINARY')
|
|
14
|
+
output = []
|
|
15
|
+
pc = 0
|
|
16
|
+
|
|
17
|
+
while pc < bytecode.bytesize
|
|
18
|
+
offset = pc
|
|
19
|
+
opcode = bytecode.getbyte(pc)
|
|
20
|
+
pc += 1
|
|
21
|
+
|
|
22
|
+
mnemonic = OPCODE_NAMES[opcode] || "Unknown_0x#{opcode.to_s(16).upcase}"
|
|
23
|
+
line = "#{mnemonic}"
|
|
24
|
+
|
|
25
|
+
case opcode
|
|
26
|
+
# Format A: 1 byte
|
|
27
|
+
when 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A
|
|
28
|
+
# No operands
|
|
29
|
+
|
|
30
|
+
# Format B: +Rd +Rs (2 bytes)
|
|
31
|
+
when 0x10, 0x11, 0x12, 0x20, 0x40
|
|
32
|
+
rd = bytecode.getbyte(pc)
|
|
33
|
+
rs = bytecode.getbyte(pc + 1)
|
|
34
|
+
line = "#{mnemonic} R#{rd}, R#{rs}"
|
|
35
|
+
pc += 2
|
|
36
|
+
|
|
37
|
+
when 0x13 # Swap: Ra, Rb
|
|
38
|
+
ra = bytecode.getbyte(pc)
|
|
39
|
+
rb = bytecode.getbyte(pc + 1)
|
|
40
|
+
line = "#{mnemonic} R#{ra}, R#{rb}"
|
|
41
|
+
pc += 2
|
|
42
|
+
|
|
43
|
+
# Format C: +Rd +Ra +Rb (3 bytes)
|
|
44
|
+
when 0x21..0x37, 0x41..0x59, 0x60..0x63, 0x90..0x92, 0xA0..0xA5, 0xB2..0xB4
|
|
45
|
+
rd = bytecode.getbyte(pc)
|
|
46
|
+
ra = bytecode.getbyte(pc + 1)
|
|
47
|
+
rb = bytecode.getbyte(pc + 2)
|
|
48
|
+
line = "#{mnemonic} R#{rd}, R#{ra}, R#{rb}"
|
|
49
|
+
pc += 3
|
|
50
|
+
|
|
51
|
+
# Format D: +Rd +imm16 (3 bytes)
|
|
52
|
+
when 0x28, 0x29, 0x79
|
|
53
|
+
rd = bytecode.getbyte(pc)
|
|
54
|
+
imm_lo = bytecode.getbyte(pc + 1)
|
|
55
|
+
imm_hi = bytecode.getbyte(pc + 2)
|
|
56
|
+
imm = imm_lo | (imm_hi << 8)
|
|
57
|
+
# Sign extend
|
|
58
|
+
imm = (imm << 16) >> 16
|
|
59
|
+
line = "#{mnemonic} R#{rd}, #{imm}"
|
|
60
|
+
pc += 3
|
|
61
|
+
|
|
62
|
+
# Format E: +Rd +Rb +off16 (4 bytes)
|
|
63
|
+
when 0x70..0x78
|
|
64
|
+
rd = bytecode.getbyte(pc)
|
|
65
|
+
rb = bytecode.getbyte(pc + 1)
|
|
66
|
+
off_lo = bytecode.getbyte(pc + 2)
|
|
67
|
+
off_hi = bytecode.getbyte(pc + 3)
|
|
68
|
+
off = off_lo | (off_hi << 8)
|
|
69
|
+
line = "#{mnemonic} R#{rd}, R#{rb}, #{off}"
|
|
70
|
+
pc += 4
|
|
71
|
+
|
|
72
|
+
when 0xB0, 0xB1 # VLoad, VStore
|
|
73
|
+
rd = bytecode.getbyte(pc)
|
|
74
|
+
rb = bytecode.getbyte(pc + 1)
|
|
75
|
+
off_lo = bytecode.getbyte(pc + 2)
|
|
76
|
+
off_hi = bytecode.getbyte(pc + 3)
|
|
77
|
+
off = off_lo | (off_hi << 8)
|
|
78
|
+
line = "#{mnemonic} R#{rd}, R#{rb}, #{off}"
|
|
79
|
+
pc += 4
|
|
80
|
+
|
|
81
|
+
# Format G: variable
|
|
82
|
+
when 0x03, 0x04, 0x05 # Jump, JumpIf, JumpIfNot
|
|
83
|
+
length = bytecode.getbyte(pc)
|
|
84
|
+
pc += 1
|
|
85
|
+
off_lo = bytecode.getbyte(pc)
|
|
86
|
+
off_hi = bytecode.getbyte(pc + 1)
|
|
87
|
+
off = off_lo | (off_hi << 8)
|
|
88
|
+
off = (off << 16) >> 16 # Sign extend
|
|
89
|
+
line = "#{mnemonic} #{off}"
|
|
90
|
+
pc += 2
|
|
91
|
+
|
|
92
|
+
when 0x06 # Call
|
|
93
|
+
length = bytecode.getbyte(pc)
|
|
94
|
+
pc += 1
|
|
95
|
+
func_lo = bytecode.getbyte(pc)
|
|
96
|
+
func_hi = bytecode.getbyte(pc + 1)
|
|
97
|
+
func_idx = func_lo | (func_hi << 8)
|
|
98
|
+
line = "#{mnemonic} #{func_idx}"
|
|
99
|
+
pc += 2
|
|
100
|
+
|
|
101
|
+
when 0x07 # CallIndirect
|
|
102
|
+
length = bytecode.getbyte(pc)
|
|
103
|
+
pc += 1
|
|
104
|
+
reg = bytecode.getbyte(pc)
|
|
105
|
+
line = "#{mnemonic} R#{reg}"
|
|
106
|
+
pc += 1
|
|
107
|
+
|
|
108
|
+
when 0x80 # ASend
|
|
109
|
+
length = bytecode.getbyte(pc)
|
|
110
|
+
pc += 1
|
|
111
|
+
agent_id = bytecode.getbyte(pc)
|
|
112
|
+
reg = bytecode.getbyte(pc + 1)
|
|
113
|
+
line = "#{mnemonic} #{agent_id}, R#{reg}"
|
|
114
|
+
pc += 2
|
|
115
|
+
|
|
116
|
+
when 0x81 # ARecv
|
|
117
|
+
length = bytecode.getbyte(pc)
|
|
118
|
+
pc += 1
|
|
119
|
+
agent_id = bytecode.getbyte(pc)
|
|
120
|
+
reg = bytecode.getbyte(pc + 1)
|
|
121
|
+
line = "#{mnemonic} #{agent_id}, R#{reg}"
|
|
122
|
+
pc += 2
|
|
123
|
+
|
|
124
|
+
when 0x82 # AAsk
|
|
125
|
+
length = bytecode.getbyte(pc)
|
|
126
|
+
pc += 1
|
|
127
|
+
agent_id = bytecode.getbyte(pc)
|
|
128
|
+
reg = bytecode.getbyte(pc + 1)
|
|
129
|
+
line = "#{mnemonic} #{agent_id}, R#{reg}"
|
|
130
|
+
pc += 2
|
|
131
|
+
|
|
132
|
+
when 0x83 # ATell
|
|
133
|
+
length = bytecode.getbyte(pc)
|
|
134
|
+
pc += 1
|
|
135
|
+
agent_id = bytecode.getbyte(pc)
|
|
136
|
+
reg = bytecode.getbyte(pc + 1)
|
|
137
|
+
line = "#{mnemonic} #{agent_id}, R#{reg}"
|
|
138
|
+
pc += 2
|
|
139
|
+
|
|
140
|
+
when 0x84 # ADelegate
|
|
141
|
+
length = bytecode.getbyte(pc)
|
|
142
|
+
pc += 1
|
|
143
|
+
agent_id = bytecode.getbyte(pc)
|
|
144
|
+
bc_lo = bytecode.getbyte(pc + 1)
|
|
145
|
+
bc_hi = bytecode.getbyte(pc + 2)
|
|
146
|
+
bc_start = bc_lo | (bc_hi << 8)
|
|
147
|
+
line = "#{mnemonic} #{agent_id}, #{bc_start}"
|
|
148
|
+
pc += 3
|
|
149
|
+
|
|
150
|
+
when 0x85 # ABroadcast
|
|
151
|
+
length = bytecode.getbyte(pc)
|
|
152
|
+
pc += 1
|
|
153
|
+
reg = bytecode.getbyte(pc)
|
|
154
|
+
line = "#{mnemonic} R#{reg}"
|
|
155
|
+
pc += 1
|
|
156
|
+
|
|
157
|
+
when 0x86 # ASubscribe
|
|
158
|
+
length = bytecode.getbyte(pc)
|
|
159
|
+
pc += 1
|
|
160
|
+
channel_id = bytecode.getbyte(pc)
|
|
161
|
+
line = "#{mnemonic} #{channel_id}"
|
|
162
|
+
pc += 1
|
|
163
|
+
|
|
164
|
+
when 0x87 # AWait
|
|
165
|
+
length = bytecode.getbyte(pc)
|
|
166
|
+
pc += 1
|
|
167
|
+
cond_reg = bytecode.getbyte(pc)
|
|
168
|
+
line = "#{mnemonic} R#{cond_reg}"
|
|
169
|
+
pc += 1
|
|
170
|
+
|
|
171
|
+
when 0x88 # ATrust
|
|
172
|
+
length = bytecode.getbyte(pc)
|
|
173
|
+
pc += 1
|
|
174
|
+
agent_id = bytecode.getbyte(pc)
|
|
175
|
+
level = bytecode.getbyte(pc + 1)
|
|
176
|
+
line = "#{mnemonic} #{agent_id}, #{level}"
|
|
177
|
+
pc += 2
|
|
178
|
+
|
|
179
|
+
when 0x89 # AVerify
|
|
180
|
+
length = bytecode.getbyte(pc)
|
|
181
|
+
pc += 1
|
|
182
|
+
agent_id = bytecode.getbyte(pc)
|
|
183
|
+
result_reg = bytecode.getbyte(pc + 1)
|
|
184
|
+
line = "#{mnemonic} #{agent_id}, R#{result_reg}"
|
|
185
|
+
pc += 2
|
|
186
|
+
|
|
187
|
+
else
|
|
188
|
+
pc += 3 # Skip unknown bytes
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
output << " #{offset.to_s(16).rjust(8, '0')}: #{line}"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
output.join("\n")
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|