idlc 0.1.2 → 0.1.5
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 +4 -4
- data/lib/idlc/ast.rb +340 -95
- data/lib/idlc/cli.rb +11 -2
- data/lib/idlc/idl.treetop +39 -62
- data/lib/idlc/idl_parser.rb +1810 -2452
- data/lib/idlc/passes/find_src_registers.rb +58 -18
- data/lib/idlc/passes/prune.rb +50 -17
- data/lib/idlc/passes/reachable_exceptions.rb +6 -6
- data/lib/idlc/passes/reachable_functions.rb +2 -2
- data/lib/idlc/symbol_table.rb +50 -9
- data/lib/idlc/syntax_node.rb +2 -8
- data/lib/idlc/type.rb +15 -10
- data/lib/idlc/version.rb +1 -1
- data/lib/idlc.rb +53 -29
- metadata +3 -6
|
@@ -9,7 +9,7 @@ module Idl
|
|
|
9
9
|
|
|
10
10
|
class AstNode
|
|
11
11
|
def find_src_registers(symtab)
|
|
12
|
-
# if
|
|
12
|
+
# if executable?
|
|
13
13
|
# value_result = value_try do
|
|
14
14
|
# execute(symtab)
|
|
15
15
|
# end
|
|
@@ -17,7 +17,7 @@ module Idl
|
|
|
17
17
|
# execute_unknown(symtab)
|
|
18
18
|
# end
|
|
19
19
|
# end
|
|
20
|
-
add_symbol(symtab) if
|
|
20
|
+
add_symbol(symtab) if declaration?
|
|
21
21
|
|
|
22
22
|
srcs = []
|
|
23
23
|
@children.each do |child|
|
|
@@ -27,7 +27,7 @@ module Idl
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def find_dst_registers(symtab)
|
|
30
|
-
# if
|
|
30
|
+
# if executable?
|
|
31
31
|
# value_result = value_try do
|
|
32
32
|
# execute(symtab)
|
|
33
33
|
# end
|
|
@@ -35,7 +35,7 @@ module Idl
|
|
|
35
35
|
# execute_unknown(symtab)
|
|
36
36
|
# end
|
|
37
37
|
# end
|
|
38
|
-
add_symbol(symtab) if
|
|
38
|
+
add_symbol(symtab) if declaration?
|
|
39
39
|
|
|
40
40
|
srcs = []
|
|
41
41
|
@children.each do |child|
|
|
@@ -80,16 +80,20 @@ module Idl
|
|
|
80
80
|
class AryElementAccessAst
|
|
81
81
|
def find_src_registers(symtab)
|
|
82
82
|
value_result = value_try do
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
var_type = var.type(symtab) rescue nil
|
|
84
|
+
if var_type&.kind == :array && var_type.sub_type.is_a?(RegFileElementType) && var_type.qualifiers.include?(:global)
|
|
85
|
+
rf_name = var_type.sub_type.name
|
|
86
|
+
return [[rf_name, index.value(symtab)]]
|
|
85
87
|
else
|
|
86
88
|
return []
|
|
87
89
|
end
|
|
88
90
|
end
|
|
89
91
|
value_else(value_result) do
|
|
90
|
-
|
|
92
|
+
var_type = var.type(symtab) rescue nil
|
|
93
|
+
if var_type&.kind == :array && var_type.sub_type.is_a?(RegFileElementType) && var_type.qualifiers.include?(:global)
|
|
94
|
+
rf_name = var_type.sub_type.name
|
|
91
95
|
if index.type(symtab).const?
|
|
92
|
-
return [index.gen_cpp(symtab, 0)]
|
|
96
|
+
return [[rf_name, index.gen_cpp(symtab, 0)]]
|
|
93
97
|
else
|
|
94
98
|
raise ComplexRegDetermination
|
|
95
99
|
end
|
|
@@ -102,22 +106,58 @@ module Idl
|
|
|
102
106
|
|
|
103
107
|
class AryElementAssignmentAst
|
|
104
108
|
def find_dst_registers(symtab)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
109
|
+
# Identify the base variable and the register index based on assignment shape.
|
|
110
|
+
# F[rd] = v → lhs is IdAst(F), reg_idx = idx
|
|
111
|
+
# F[rd][b] = v → lhs is AryElementAccessAst(F[rd]), reg_idx = lhs.index
|
|
112
|
+
lhs_base, reg_idx =
|
|
113
|
+
if lhs.is_a?(Idl::IdAst)
|
|
114
|
+
[lhs, idx]
|
|
115
|
+
elsif lhs.is_a?(Idl::AryElementAccessAst) && lhs.var.is_a?(Idl::IdAst)
|
|
116
|
+
[lhs.var, lhs.index]
|
|
108
117
|
else
|
|
109
118
|
return []
|
|
110
119
|
end
|
|
120
|
+
|
|
121
|
+
# Only proceed if the base variable is a global array of RegFileElementType.
|
|
122
|
+
var_type = lhs_base.type(symtab) rescue nil
|
|
123
|
+
return [] unless var_type&.kind == :array &&
|
|
124
|
+
var_type.sub_type.is_a?(RegFileElementType) &&
|
|
125
|
+
var_type.qualifiers.include?(:global)
|
|
126
|
+
|
|
127
|
+
rf_name = var_type.sub_type.name
|
|
128
|
+
|
|
129
|
+
value_result = value_try do
|
|
130
|
+
return [[rf_name, reg_idx.value(symtab)]]
|
|
111
131
|
end
|
|
112
132
|
value_else(value_result) do
|
|
113
|
-
if
|
|
114
|
-
|
|
115
|
-
return [idx.gen_cpp(symtab, 0)]
|
|
116
|
-
else
|
|
117
|
-
raise ComplexRegDetermination
|
|
118
|
-
end
|
|
133
|
+
if reg_idx.type(symtab).const?
|
|
134
|
+
return [[rf_name, reg_idx.gen_cpp(symtab, 0)]]
|
|
119
135
|
else
|
|
120
|
-
|
|
136
|
+
raise ComplexRegDetermination
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
class AryRangeAssignmentAst
|
|
143
|
+
def find_dst_registers(symtab)
|
|
144
|
+
return [] unless variable.is_a?(Idl::AryElementAccessAst)
|
|
145
|
+
|
|
146
|
+
var_type = variable.var.type(symtab) rescue nil
|
|
147
|
+
return [] unless var_type&.kind == :array &&
|
|
148
|
+
var_type.sub_type.is_a?(RegFileElementType) &&
|
|
149
|
+
var_type.qualifiers.include?(:global)
|
|
150
|
+
|
|
151
|
+
rf_name = var_type.sub_type.name
|
|
152
|
+
|
|
153
|
+
value_result = value_try do
|
|
154
|
+
return [[rf_name, variable.index.value(symtab)]]
|
|
155
|
+
end
|
|
156
|
+
value_else(value_result) do
|
|
157
|
+
if variable.index.type(symtab).const?
|
|
158
|
+
return [[rf_name, variable.index.gen_cpp(symtab, 0)]]
|
|
159
|
+
else
|
|
160
|
+
raise ComplexRegDetermination
|
|
121
161
|
end
|
|
122
162
|
end
|
|
123
163
|
end
|
data/lib/idlc/passes/prune.rb
CHANGED
|
@@ -97,23 +97,27 @@ end
|
|
|
97
97
|
module Idl
|
|
98
98
|
# set up a default
|
|
99
99
|
class AstNode
|
|
100
|
+
def always_terminates? = false
|
|
101
|
+
|
|
100
102
|
# forced_type, when not nil, is the type that the pruned result must be
|
|
101
103
|
# if is used when pruning expressions to ensure that the prune doesn't change
|
|
102
104
|
# bit width just because a value is known and would fit in something smaller
|
|
103
105
|
def prune(symtab, forced_type: nil)
|
|
104
106
|
new_children = children.map { |child| child.prune(symtab, forced_type:) }
|
|
105
107
|
|
|
106
|
-
|
|
107
|
-
new_node.instance_variable_set(:@children, new_children)
|
|
108
|
-
|
|
109
|
-
if is_a?(Executable)
|
|
108
|
+
if executable?
|
|
110
109
|
value_try do
|
|
111
110
|
execute(symtab)
|
|
112
111
|
end
|
|
113
112
|
# value_else: execute raised ValueError; symtab state is already correct
|
|
114
113
|
end
|
|
115
|
-
add_symbol(symtab) if
|
|
114
|
+
add_symbol(symtab) if declaration?
|
|
116
115
|
|
|
116
|
+
# avoid allocation when nothing changed
|
|
117
|
+
return self if !frozen? && new_children.each_with_index.all? { |c, i| c.equal?(children[i]) }
|
|
118
|
+
|
|
119
|
+
new_node = dup
|
|
120
|
+
new_node.instance_variable_set(:@children, new_children)
|
|
117
121
|
new_node
|
|
118
122
|
end
|
|
119
123
|
|
|
@@ -155,7 +159,9 @@ module Idl
|
|
|
155
159
|
# array var itself is unknown; nothing more to do
|
|
156
160
|
end
|
|
157
161
|
when :bits
|
|
158
|
-
|
|
162
|
+
root = lhs
|
|
163
|
+
root = root.var while root.is_a?(AryElementAccessAst) || root.is_a?(AryRangeAccessAst)
|
|
164
|
+
var = symtab.get(root.name)
|
|
159
165
|
var.value = nil unless var.nil?
|
|
160
166
|
end
|
|
161
167
|
end
|
|
@@ -163,7 +169,9 @@ module Idl
|
|
|
163
169
|
class AryRangeAssignmentAst < AstNode
|
|
164
170
|
def nullify_assignments(symtab)
|
|
165
171
|
return if variable.type(symtab).global?
|
|
166
|
-
|
|
172
|
+
root = variable
|
|
173
|
+
root = root.var while root.is_a?(AryElementAccessAst) || root.is_a?(AryRangeAccessAst)
|
|
174
|
+
var = symtab.get(root.name)
|
|
167
175
|
var.value = nil unless var.nil?
|
|
168
176
|
end
|
|
169
177
|
end
|
|
@@ -317,6 +325,14 @@ module Idl
|
|
|
317
325
|
end
|
|
318
326
|
|
|
319
327
|
pruned_body = nil
|
|
328
|
+
prune_stmts = -> {
|
|
329
|
+
[].tap do |out|
|
|
330
|
+
statements.each do |s|
|
|
331
|
+
out << s.prune(symtab)
|
|
332
|
+
break if out.last.always_terminates?
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
}
|
|
320
336
|
|
|
321
337
|
value_result = value_try do
|
|
322
338
|
# go through the statements, and stop if we find one that returns or raises an exception
|
|
@@ -343,10 +359,10 @@ module Idl
|
|
|
343
359
|
end
|
|
344
360
|
end
|
|
345
361
|
|
|
346
|
-
pruned_body = FunctionBodyAst.new(input, interval,
|
|
362
|
+
pruned_body = FunctionBodyAst.new(input, interval, prune_stmts.())
|
|
347
363
|
end
|
|
348
364
|
value_else(value_result) do
|
|
349
|
-
pruned_body = FunctionBodyAst.new(input, interval,
|
|
365
|
+
pruned_body = FunctionBodyAst.new(input, interval, prune_stmts.())
|
|
350
366
|
end
|
|
351
367
|
ensure
|
|
352
368
|
symtab.pop
|
|
@@ -356,13 +372,17 @@ module Idl
|
|
|
356
372
|
end
|
|
357
373
|
end
|
|
358
374
|
class StatementAst < AstNode
|
|
375
|
+
def always_terminates?
|
|
376
|
+
action.is_a?(FunctionCallExpressionAst) && action.name == "raise"
|
|
377
|
+
end
|
|
378
|
+
|
|
359
379
|
def prune(symtab, forced_type: nil)
|
|
360
380
|
pruned_action = action.prune(symtab)
|
|
361
381
|
|
|
362
382
|
new_stmt = StatementAst.new(input, interval, pruned_action)
|
|
363
383
|
# pruned_action.freeze_tree(symtab) unless pruned_action.frozen?
|
|
364
384
|
|
|
365
|
-
pruned_action.add_symbol(symtab) if pruned_action.
|
|
385
|
+
pruned_action.add_symbol(symtab) if pruned_action.declaration?
|
|
366
386
|
# action#prune already handles symtab update (execute)
|
|
367
387
|
|
|
368
388
|
new_stmt
|
|
@@ -469,6 +489,10 @@ module Idl
|
|
|
469
489
|
end
|
|
470
490
|
|
|
471
491
|
class IfBodyAst < AstNode
|
|
492
|
+
def always_terminates?
|
|
493
|
+
!stmts.empty? && stmts.last.always_terminates?
|
|
494
|
+
end
|
|
495
|
+
|
|
472
496
|
def prune(symtab, restore: true, forced_type: nil)
|
|
473
497
|
pruned_stmts = []
|
|
474
498
|
symtab.push(nil)
|
|
@@ -476,7 +500,7 @@ module Idl
|
|
|
476
500
|
stmts.each do |s|
|
|
477
501
|
pruned_stmts << s.prune(symtab)
|
|
478
502
|
|
|
479
|
-
break if pruned_stmts.last.
|
|
503
|
+
break if pruned_stmts.last.always_terminates?
|
|
480
504
|
end
|
|
481
505
|
if restore
|
|
482
506
|
symtab.restore_values(snapshot)
|
|
@@ -545,16 +569,23 @@ module Idl
|
|
|
545
569
|
end
|
|
546
570
|
end
|
|
547
571
|
# we get here, then we don't know the value of anything. just return this if with everything pruned
|
|
572
|
+
# After pruning, some elseif conditions may resolve to a literal (e.g., `false && <runtime_csr_read>`
|
|
573
|
+
# fails value() due to the CSR read but prune() short-circuits the && to false). Filter those out.
|
|
574
|
+
pruned_elsifs = unknown_elsifs.filter_map do |eif|
|
|
575
|
+
pruned = eif.prune(symtab)
|
|
576
|
+
next nil if pruned.cond.is_a?(FalseExpressionAst)
|
|
577
|
+
pruned
|
|
578
|
+
end
|
|
548
579
|
result = IfAst.new(
|
|
549
580
|
input, interval,
|
|
550
581
|
if_cond.prune(symtab),
|
|
551
582
|
if_body.prune(symtab),
|
|
552
|
-
|
|
583
|
+
pruned_elsifs,
|
|
553
584
|
final_else_body.prune(symtab)
|
|
554
585
|
)
|
|
555
586
|
# Nullify any variable assigned in any branch, since we don't know which ran
|
|
556
587
|
if_body.nullify_assignments(symtab)
|
|
557
|
-
|
|
588
|
+
pruned_elsifs.each { |eif| eif.body.nullify_assignments(symtab) }
|
|
558
589
|
final_else_body.nullify_assignments(symtab)
|
|
559
590
|
result
|
|
560
591
|
end
|
|
@@ -581,9 +612,9 @@ module Idl
|
|
|
581
612
|
value_result = value_try do
|
|
582
613
|
if condition.value(symtab)
|
|
583
614
|
pruned_action = action.prune(symtab)
|
|
584
|
-
pruned_action.add_symbol(symtab) if pruned_action.
|
|
615
|
+
pruned_action.add_symbol(symtab) if pruned_action.declaration?
|
|
585
616
|
value_result = value_try do
|
|
586
|
-
pruned_action.execute(symtab) if pruned_action.
|
|
617
|
+
pruned_action.execute(symtab) if pruned_action.executable?
|
|
587
618
|
end
|
|
588
619
|
|
|
589
620
|
return StatementAst.new(input, interval, pruned_action)
|
|
@@ -594,9 +625,9 @@ module Idl
|
|
|
594
625
|
value_else(value_result) do
|
|
595
626
|
# condition not known
|
|
596
627
|
pruned_action = action.prune(symtab)
|
|
597
|
-
pruned_action.add_symbol(symtab) if pruned_action.
|
|
628
|
+
pruned_action.add_symbol(symtab) if pruned_action.declaration?
|
|
598
629
|
value_result = value_try do
|
|
599
|
-
pruned_action.execute(symtab) if pruned_action.
|
|
630
|
+
pruned_action.execute(symtab) if pruned_action.executable?
|
|
600
631
|
end
|
|
601
632
|
# Condition is unknown, so the assignment may not have run; nullify to prevent leakage
|
|
602
633
|
pruned_action.nullify_assignments(symtab)
|
|
@@ -835,6 +866,8 @@ module Idl
|
|
|
835
866
|
end
|
|
836
867
|
|
|
837
868
|
class ReturnStatementAst < AstNode
|
|
869
|
+
def always_terminates? = true
|
|
870
|
+
|
|
838
871
|
def prune(symtab, forced_type: nil)
|
|
839
872
|
ReturnStatementAst.new(input, interval, return_expression.prune(symtab))
|
|
840
873
|
end
|
|
@@ -79,8 +79,8 @@ module Idl
|
|
|
79
79
|
# else
|
|
80
80
|
# 0
|
|
81
81
|
# end
|
|
82
|
-
action.add_symbol(symtab) if action.
|
|
83
|
-
if action.
|
|
82
|
+
action.add_symbol(symtab) if action.declaration?
|
|
83
|
+
if action.executable?
|
|
84
84
|
value_try do
|
|
85
85
|
action.execute(symtab)
|
|
86
86
|
end
|
|
@@ -162,8 +162,8 @@ module Idl
|
|
|
162
162
|
mask |= condition.reachable_exceptions(symtab, cache)
|
|
163
163
|
if condition.value(symtab)
|
|
164
164
|
mask |= action.reachable_exceptions(symtab, cache)
|
|
165
|
-
action.add_symbol(symtab) if action.
|
|
166
|
-
if action.
|
|
165
|
+
action.add_symbol(symtab) if action.declaration?
|
|
166
|
+
if action.executable?
|
|
167
167
|
value_result = value_try do
|
|
168
168
|
action.execute(symtab)
|
|
169
169
|
end
|
|
@@ -175,8 +175,8 @@ module Idl
|
|
|
175
175
|
# condition not known
|
|
176
176
|
mask |= condition.reachable_exceptions(symtab, cache)
|
|
177
177
|
mask |= action.reachable_exceptions(symtab, cache)
|
|
178
|
-
action.add_symbol(symtab) if action.
|
|
179
|
-
if action.
|
|
178
|
+
action.add_symbol(symtab) if action.declaration?
|
|
179
|
+
if action.executable?
|
|
180
180
|
value_result = value_try do
|
|
181
181
|
action.execute(symtab)
|
|
182
182
|
end
|
|
@@ -82,9 +82,9 @@ module Idl
|
|
|
82
82
|
def reachable_functions(symtab, cache = T.let({}, ReachableFunctionCacheType))
|
|
83
83
|
fns = action.reachable_functions(symtab, cache)
|
|
84
84
|
|
|
85
|
-
action.add_symbol(symtab) if action.
|
|
85
|
+
action.add_symbol(symtab) if action.declaration?
|
|
86
86
|
value_try do
|
|
87
|
-
action.execute(symtab) if action.
|
|
87
|
+
action.execute(symtab) if action.executable?
|
|
88
88
|
rescue SystemStackError
|
|
89
89
|
type_error "Detected unbounded recursion during compile-time constant evaluation at #{input_file}:#{input_line}.. This recursion cannot be represented or validated."
|
|
90
90
|
end
|
data/lib/idlc/symbol_table.rb
CHANGED
|
@@ -208,10 +208,12 @@ module Idl
|
|
|
208
208
|
builtin_funcs: T.nilable(BuiltinFunctionCallbacks),
|
|
209
209
|
csrs: T::Array[Csr],
|
|
210
210
|
params: T::Array[RuntimeParam],
|
|
211
|
-
name: String
|
|
211
|
+
name: String,
|
|
212
|
+
register_files: T::Array[T.untyped],
|
|
213
|
+
register_file_max_widths: T::Hash[String, Integer]
|
|
212
214
|
).void
|
|
213
215
|
}
|
|
214
|
-
def initialize(mxlen: nil, possible_xlens_cb: nil, builtin_global_vars: [], builtin_enums: [], builtin_funcs: nil, csrs: [], params: [], name: "")
|
|
216
|
+
def initialize(mxlen: nil, possible_xlens_cb: nil, builtin_global_vars: [], builtin_enums: [], builtin_funcs: nil, csrs: [], params: [], name: "", register_files: [], register_file_max_widths: {})
|
|
215
217
|
@mutex = Thread::Mutex.new
|
|
216
218
|
@mxlen = mxlen
|
|
217
219
|
@possible_xlens_cb = possible_xlens_cb
|
|
@@ -221,11 +223,6 @@ module Idl
|
|
|
221
223
|
|
|
222
224
|
# builtin types
|
|
223
225
|
@scopes = [{
|
|
224
|
-
"X" => Var.new(
|
|
225
|
-
"X",
|
|
226
|
-
Type.new(:array, sub_type: XregType.new(@mxlen.nil? ? 64 : @mxlen), width: 32, qualifiers: [:global])
|
|
227
|
-
),
|
|
228
|
-
"XReg" => XregType.new(@mxlen.nil? ? 64 : @mxlen),
|
|
229
226
|
"Boolean" => Type.new(:boolean),
|
|
230
227
|
"true" => Var.new(
|
|
231
228
|
"true",
|
|
@@ -237,8 +234,29 @@ module Idl
|
|
|
237
234
|
Type.new(:boolean),
|
|
238
235
|
false
|
|
239
236
|
)
|
|
240
|
-
|
|
241
237
|
}]
|
|
238
|
+
# @params must be set before the register_files loop so that param schema
|
|
239
|
+
# max lookups (used to compute int_width for dynamic-width register files)
|
|
240
|
+
# can access @params without triggering a cfg_arch.symtab circular dependency.
|
|
241
|
+
@params = params
|
|
242
|
+
|
|
243
|
+
# Register file globals (X, F, V, etc.) from YAML-derived register file objects.
|
|
244
|
+
# Each RF provides: .name (String), .register_length (IDL body String), .registers.count (Integer).
|
|
245
|
+
register_files.each do |rf|
|
|
246
|
+
int_width = if register_file_max_widths.key?(rf.name)
|
|
247
|
+
register_file_max_widths[rf.name]
|
|
248
|
+
else
|
|
249
|
+
# Fallback for callers without pre-computed widths (CLI, tests with literal-width RFs).
|
|
250
|
+
# eval_register_length_idl handles literals ("return 64;") and simple MXLEN references.
|
|
251
|
+
w = eval_register_length_idl(rf.register_length)
|
|
252
|
+
w.is_a?(Integer) ? w : raise("Cannot determine max register width for '#{rf.name}'. " \
|
|
253
|
+
"Pass register_file_max_widths: to SymbolTable.new.")
|
|
254
|
+
end
|
|
255
|
+
elem_type = RegFileElementType.new(rf.name, int_width, max_width: int_width)
|
|
256
|
+
array_type = Type.new(:array, sub_type: elem_type, width: rf.registers.count, qualifiers: [:global])
|
|
257
|
+
@scopes[0][rf.name] = Var.new(rf.name, array_type)
|
|
258
|
+
@scopes[0]["#{rf.name}Reg"] = elem_type
|
|
259
|
+
end
|
|
242
260
|
builtin_global_vars.each do |v|
|
|
243
261
|
add!(v.name, v)
|
|
244
262
|
end
|
|
@@ -248,12 +266,35 @@ module Idl
|
|
|
248
266
|
@builtin_funcs = builtin_funcs
|
|
249
267
|
@csrs = csrs
|
|
250
268
|
@csr_hash = @csrs.map { |csr| [csr.name.freeze, csr].freeze }.to_h.freeze
|
|
251
|
-
@params = params
|
|
252
269
|
|
|
253
270
|
# set up the global clone that be used as a mutable table
|
|
254
271
|
@global_clone_pool = T.let([], T::Array[SymbolTable])
|
|
255
272
|
end
|
|
256
273
|
|
|
274
|
+
# Compile and evaluate the IDL function body string that defines register width.
|
|
275
|
+
# Returns an Integer if statically known, or the expression string if dynamic.
|
|
276
|
+
# @param idl_body [String] e.g. "return 64;" or "return MXLEN;"
|
|
277
|
+
#
|
|
278
|
+
# NOTE: The body-stripping logic here (strip 'return', strip ';', match literal/MXLEN)
|
|
279
|
+
# is intentionally duplicated in Udb::RegisterFile#register_length_expr and
|
|
280
|
+
# Udb::RegisterFile#eval_register_length (udb gem). Changes here must be mirrored there.
|
|
281
|
+
# idlc cannot depend on udb (udb depends on idlc), so a shared utility is not possible
|
|
282
|
+
# without an additional gem layer.
|
|
283
|
+
def eval_register_length_idl(idl_body)
|
|
284
|
+
# Strip 'return' and ';'
|
|
285
|
+
expr = idl_body.strip.sub(/\Areturn\s+/, "").sub(/;\z/, "").strip
|
|
286
|
+
case expr
|
|
287
|
+
when /\A\d+\z/
|
|
288
|
+
expr.to_i
|
|
289
|
+
when /\AMXLEN\z/
|
|
290
|
+
@mxlen || 64
|
|
291
|
+
else
|
|
292
|
+
# Dynamic parameter — return the parameter name as a String
|
|
293
|
+
expr
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
private :eval_register_length_idl
|
|
297
|
+
|
|
257
298
|
# @return [String] inspection string
|
|
258
299
|
sig { returns(String) }
|
|
259
300
|
def inspect
|
data/lib/idlc/syntax_node.rb
CHANGED
|
@@ -18,18 +18,12 @@ module Treetop
|
|
|
18
18
|
# @param starting_line [Integer] Starting line in the file
|
|
19
19
|
# @param starting_offset [Integer] Starting byte offset in the file
|
|
20
20
|
# @param line_file_offsets [Array<Integer>, nil] Per-IDL-line file byte offsets
|
|
21
|
-
sig { params(filename: T.nilable(String), starting_line: Integer, starting_offset: Integer, line_file_offsets: T.nilable(T::Array[Integer])).void }
|
|
21
|
+
sig { params(filename: T.nilable(String), starting_line: Integer, starting_offset: Integer, line_file_offsets: T.nilable(T::Array[Integer])).void.checked(:never) }
|
|
22
22
|
def set_input_file(filename, starting_line = 0, starting_offset = 0, line_file_offsets = nil)
|
|
23
23
|
@input_file = filename
|
|
24
24
|
@starting_line = starting_line
|
|
25
25
|
@starting_offset = starting_offset
|
|
26
26
|
@line_file_offsets = line_file_offsets
|
|
27
|
-
elements&.each do |child|
|
|
28
|
-
# Adjust the starting offset for each child based on its position in the input
|
|
29
|
-
child_offset = starting_offset + child.interval.first
|
|
30
|
-
child.set_input_file(filename, starting_line, child_offset, line_file_offsets)
|
|
31
|
-
end
|
|
32
|
-
raise "?" if @starting_line.nil?
|
|
33
27
|
end
|
|
34
28
|
|
|
35
29
|
sig { returns(T::Boolean) }
|
|
@@ -43,7 +37,7 @@ module Treetop
|
|
|
43
37
|
# @param [Integer] starting_line The starting line number in the input file.
|
|
44
38
|
# @param [Integer] starting_offset The starting byte offset in the input file.
|
|
45
39
|
# @param [Array<Integer>, nil] line_file_offsets Per-IDL-line file byte offsets
|
|
46
|
-
sig { params(filename: T.nilable(String), starting_line: Integer, starting_offset: Integer, line_file_offsets: T.nilable(T::Array[Integer])).void }
|
|
40
|
+
sig { params(filename: T.nilable(String), starting_line: Integer, starting_offset: Integer, line_file_offsets: T.nilable(T::Array[Integer])).void.checked(:never) }
|
|
47
41
|
def set_input_file_unless_already_set(filename, starting_line = 0, starting_offset = 0, line_file_offsets = nil)
|
|
48
42
|
if @input_file.nil?
|
|
49
43
|
set_input_file(filename, starting_line, starting_offset, line_file_offsets)
|
data/lib/idlc/type.rb
CHANGED
|
@@ -963,19 +963,24 @@ module Idl
|
|
|
963
963
|
def body = @func_def_ast.body
|
|
964
964
|
end
|
|
965
965
|
|
|
966
|
-
#
|
|
967
|
-
#
|
|
968
|
-
class
|
|
969
|
-
|
|
970
|
-
super(:bits, width: xlen, max_width: 64)
|
|
971
|
-
end
|
|
966
|
+
# General-purpose register file element type.
|
|
967
|
+
# Represents the type of one element in a named register file (e.g. F, V, X).
|
|
968
|
+
class RegFileElementType < Type
|
|
969
|
+
attr_reader :name
|
|
972
970
|
|
|
973
|
-
def
|
|
974
|
-
|
|
971
|
+
def initialize(name, width, max_width: nil)
|
|
972
|
+
super(:bits, width:, max_width: max_width || width)
|
|
973
|
+
@name = name
|
|
975
974
|
end
|
|
976
975
|
|
|
977
|
-
def
|
|
978
|
-
|
|
976
|
+
def to_s = "#{name}Reg"
|
|
977
|
+
def to_cxx = "#{name}Reg"
|
|
978
|
+
end
|
|
979
|
+
|
|
980
|
+
# XReg is really a Bits<> type -- keep as a named alias for backwards compatibility
|
|
981
|
+
class XregType < RegFileElementType
|
|
982
|
+
def initialize(xlen)
|
|
983
|
+
super("X", xlen, max_width: 64)
|
|
979
984
|
end
|
|
980
985
|
end
|
|
981
986
|
|
data/lib/idlc/version.rb
CHANGED
data/lib/idlc.rb
CHANGED
|
@@ -55,6 +55,14 @@ module Idl
|
|
|
55
55
|
|
|
56
56
|
attr_reader :parser
|
|
57
57
|
|
|
58
|
+
# Class-level parse cache: absolute file path (String) → IsaSyntaxNode.
|
|
59
|
+
# Shared across all Compiler instances so each file is parsed only once per
|
|
60
|
+
# process. Safe to share because IsaSyntaxNode#to_ast is non-destructive
|
|
61
|
+
# and returns a fresh, independent IsaAst on every call.
|
|
62
|
+
# Mutex guards writes; under MRI, reads without the lock are safe.
|
|
63
|
+
@@parse_cache = {}
|
|
64
|
+
@@parse_cache_mutex = Mutex.new
|
|
65
|
+
|
|
58
66
|
def initialize
|
|
59
67
|
@parser = ::IdlParser.new
|
|
60
68
|
end
|
|
@@ -71,40 +79,56 @@ module Idl
|
|
|
71
79
|
end
|
|
72
80
|
|
|
73
81
|
def compile_file(path, source_mapper = nil)
|
|
74
|
-
|
|
82
|
+
path_key = path.realpath.to_s
|
|
75
83
|
|
|
76
|
-
|
|
77
|
-
source_mapper[path.to_s] = path.read
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
old_format = @pb.format unless @pb.nil?
|
|
81
|
-
@pb.format = "Parsing #{File.basename(path)} [:bar]" unless @pb.nil?
|
|
82
|
-
pid = unless @pb.nil?
|
|
83
|
-
fork {
|
|
84
|
-
loop do
|
|
85
|
-
sleep 1
|
|
86
|
-
@pb.advance unless @pb.nil?
|
|
87
|
-
end
|
|
88
|
-
}
|
|
89
|
-
end
|
|
90
|
-
m = @parser.parse path.read
|
|
91
|
-
unless @pb.nil?
|
|
92
|
-
Process.kill("TERM", T.must(pid))
|
|
93
|
-
Process.wait(T.must(pid))
|
|
94
|
-
@pb.format = old_format
|
|
95
|
-
end
|
|
84
|
+
m = T.let(@@parse_cache[path_key], T.nilable(IsaSyntaxNode))
|
|
96
85
|
|
|
97
86
|
if m.nil?
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
87
|
+
@@parse_cache_mutex.synchronize do
|
|
88
|
+
# Re-check inside the lock in case another thread just populated the entry.
|
|
89
|
+
unless @@parse_cache.key?(path_key)
|
|
90
|
+
@parser.set_input_file(path_key)
|
|
91
|
+
|
|
92
|
+
content = path.read
|
|
93
|
+
source_mapper[path_key] = content unless source_mapper.nil?
|
|
94
|
+
|
|
95
|
+
old_format = @pb.format unless @pb.nil?
|
|
96
|
+
@pb.format = "Parsing #{File.basename(path)} [:bar]" unless @pb.nil?
|
|
97
|
+
pid = unless @pb.nil?
|
|
98
|
+
fork {
|
|
99
|
+
loop do
|
|
100
|
+
sleep 1
|
|
101
|
+
@pb.advance unless @pb.nil?
|
|
102
|
+
end
|
|
103
|
+
}
|
|
104
|
+
end
|
|
105
|
+
m = @parser.parse(content)
|
|
106
|
+
unless @pb.nil?
|
|
107
|
+
Process.kill("TERM", T.must(pid))
|
|
108
|
+
Process.wait(T.must(pid))
|
|
109
|
+
@pb.format = old_format
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
if m.nil?
|
|
113
|
+
raise SyntaxError, <<~MSG
|
|
114
|
+
While parsing #{@parser.input_file}:#{@parser.failure_line}:#{@parser.failure_column}
|
|
115
|
+
|
|
116
|
+
#{@parser.failure_reason}
|
|
117
|
+
MSG
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
raise "unexpected type #{m.class.name}" unless m.is_a?(IsaSyntaxNode)
|
|
121
|
+
|
|
122
|
+
@@parse_cache[path_key] = m
|
|
123
|
+
end
|
|
124
|
+
m = @@parse_cache[path_key]
|
|
125
|
+
end
|
|
126
|
+
else
|
|
127
|
+
# Cache hit: still populate source_mapper if provided (test-only path).
|
|
128
|
+
source_mapper[path_key] = path.read unless source_mapper.nil?
|
|
103
129
|
end
|
|
104
130
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
ast = m.to_ast
|
|
131
|
+
ast = T.must(m).to_ast
|
|
108
132
|
|
|
109
133
|
ast.children.each do |child|
|
|
110
134
|
next unless child.is_a?(IncludeStatementAst)
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: idlc
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Derek Hower
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
10
|
+
date: 2026-05-19 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: activesupport
|
|
@@ -372,7 +371,6 @@ metadata:
|
|
|
372
371
|
homepage_uri: https://github.com/riscv/riscv-unified-db
|
|
373
372
|
mailing_list_uri: https://lists.riscv.org/g/tech-unifieddb
|
|
374
373
|
bug_tracker_uri: https://github.com/riscv/riscv-unified-db/issues
|
|
375
|
-
post_install_message:
|
|
376
374
|
rdoc_options: []
|
|
377
375
|
require_paths:
|
|
378
376
|
- lib
|
|
@@ -387,8 +385,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
387
385
|
- !ruby/object:Gem::Version
|
|
388
386
|
version: '0'
|
|
389
387
|
requirements: []
|
|
390
|
-
rubygems_version: 3.
|
|
391
|
-
signing_key:
|
|
388
|
+
rubygems_version: 3.6.9
|
|
392
389
|
specification_version: 4
|
|
393
390
|
summary: ISA Description Language Compiler
|
|
394
391
|
test_files: []
|