idlc 0.1.1

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,549 @@
1
+ # Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
2
+ # SPDX-License-Identifier: BSD-3-Clause-Clear
3
+
4
+ # typed: true
5
+ # frozen_string_literal: true
6
+
7
+ require "sorbet-runtime"
8
+ require "concurrent/atomic/semaphore"
9
+
10
+ require_relative "log"
11
+ require_relative "type"
12
+ require_relative "interfaces"
13
+
14
+ module Idl
15
+
16
+ # Objects to represent variables in the ISA def
17
+ class Var
18
+ extend T::Sig
19
+
20
+ attr_reader :name, :type, :value
21
+
22
+ def initialize(name, type, value = nil, decode_var: false, function_name: nil, param: false, for_loop_iter: false)
23
+ @name = name
24
+ raise ArgumentError, "Expecting a Type, got #{type.class.name}" unless type.is_a?(Type)
25
+
26
+ @type = type
27
+ @type.freeze
28
+ @value = value
29
+ raise "unexpected" unless decode_var.is_a?(TrueClass) || decode_var.is_a?(FalseClass)
30
+
31
+ @decode_var = decode_var
32
+ @function_name = function_name
33
+ @param = param
34
+ @for_loop_iter = for_loop_iter
35
+
36
+ @const_compatible = true # until otherwise known
37
+ end
38
+
39
+ sig { void }
40
+ def const_incompatible!
41
+ @const_compatible = false
42
+ end
43
+
44
+ sig { returns(T::Boolean) }
45
+ def const_eval?
46
+ if @global
47
+ @name[0].upcase == @name[0]
48
+ else
49
+ @const_compatible
50
+ end
51
+ end
52
+
53
+ sig { returns(T::Boolean) }
54
+ def for_loop_iter?
55
+ @for_loop_iter
56
+ end
57
+
58
+ def hash
59
+ [@name, @type, @value, @decode_var, @function_name, @param].hash
60
+ end
61
+
62
+ def to_s
63
+ "VAR: #{type} #{name} #{value.nil? ? 'NO VALUE' : value}"
64
+ end
65
+
66
+ def clone
67
+ Var.new(
68
+ name,
69
+ type.clone,
70
+ value&.clone,
71
+ decode_var: @decode_var,
72
+ function_name: @function_name,
73
+ param: @param
74
+ )
75
+ end
76
+
77
+ def const?
78
+ @type.const?
79
+ end
80
+
81
+ def decode_var?
82
+ @decode_var
83
+ end
84
+
85
+ def param?
86
+ @param
87
+ end
88
+
89
+ def to_cxx
90
+ @name
91
+ end
92
+
93
+ def value=(new_value)
94
+ @value = new_value
95
+ end
96
+ end
97
+
98
+ # scoped symbol table holding known symbols at a current point in parsing
99
+ class SymbolTable
100
+ extend T::Sig
101
+
102
+ # @return [Integer] 32 or 64, the XLEN in M-mode
103
+ # @return [nil] if the XLEN in M-mode is unknown
104
+ sig { returns(T.nilable(Integer)) }
105
+ attr_reader :mxlen
106
+
107
+ sig { returns(String) }
108
+ attr_reader :name
109
+
110
+ class DuplicateSymError < StandardError
111
+ end
112
+
113
+ def hash
114
+ return @frozen_hash unless @frozen_hash.nil?
115
+
116
+ object_id
117
+ end
118
+
119
+ class EnumDef < T::Struct
120
+ extend T::Sig
121
+
122
+ prop :name, String
123
+ prop :element_values, T::Array[Integer]
124
+ prop :element_names, T::Array[String]
125
+
126
+ sig { params(name: String, element_values: T::Array[Integer], element_names: T::Array[String]).void }
127
+ def initialize(name:, element_values:, element_names:)
128
+ super(name:, element_values:, element_names:)
129
+ raise "element_values and element_names are not the same size" unless element_values.size == element_names.size
130
+ end
131
+ end
132
+
133
+ PossibleXlensCallbackType = T.type_alias { T.proc.returns(T::Array[Integer]) }
134
+
135
+ sig { returns(T::Boolean) }
136
+ def multi_xlen? = possible_xlens.size > 1
137
+
138
+ class MemoizedState < T::Struct
139
+ prop :possible_xlens, T.nilable(T::Array[Integer])
140
+ prop :params_hash, T.nilable(T::Hash[String, RuntimeParam])
141
+ end
142
+
143
+ sig { returns(T::Array[Integer]) }
144
+ def possible_xlens
145
+ @memo.possible_xlens ||=
146
+ begin
147
+ if @possible_xlens_cb.nil?
148
+ Idl.logger.error "Symbol table was not initialized with a possible xlens callback, so #possible_xlens is not available"
149
+ raise
150
+ end
151
+
152
+ @possible_xlens_cb.call
153
+ end
154
+ end
155
+
156
+ ImplementedCallbackType = T.type_alias { T.proc.params(arg0: String).returns(T.nilable(T::Boolean)) }
157
+
158
+ # some ugliness to capture proc types
159
+ # @see https://sorbet.org/docs/procs#what-can-i-do-for-better-proc-and-lambda-types
160
+ sig { params(blk: ImplementedCallbackType).returns(ImplementedCallbackType) }
161
+ def self.make_implemented_callback(&blk) = blk
162
+
163
+ ImplementedVersionCallbackType = T.type_alias { T.proc.params(arg0: String, arg1: String).returns(T.nilable(T::Boolean)) }
164
+
165
+ # some ugliness to capture proc types
166
+ # @see https://sorbet.org/docs/procs#what-can-i-do-for-better-proc-and-lambda-types
167
+ sig { params(blk: ImplementedVersionCallbackType).returns(ImplementedVersionCallbackType) }
168
+ def self.make_implemented_version_callback(&blk) = blk
169
+
170
+ ImplementedCsrCallbackType = T.type_alias { T.proc.params(arg0: Integer).returns(T.nilable(T::Boolean)) }
171
+
172
+ # some ugliness to capture proc types
173
+ # @see https://sorbet.org/docs/procs#what-can-i-do-for-better-proc-and-lambda-types
174
+ sig { params(blk: ImplementedCsrCallbackType).returns(ImplementedCsrCallbackType) }
175
+ def self.make_implemented_csr_callback(&blk) = blk
176
+
177
+ class BuiltinFunctionCallbacks < T::Struct
178
+ prop :implemented, ImplementedCallbackType
179
+ prop :implemented_version, ImplementedVersionCallbackType
180
+ prop :implemented_csr, ImplementedCsrCallbackType
181
+ end
182
+
183
+ attr_reader :builtin_funcs
184
+
185
+ sig { params(csr_name: String).returns(T::Boolean) }
186
+ def csr?(csr_name) = csr_hash.key?(csr_name)
187
+
188
+ sig { returns(T::Hash[String, Csr]) }
189
+ attr_reader :csr_hash
190
+
191
+ sig { params(csr_name: String).returns(T.nilable(Csr)) }
192
+ def csr(csr_name) = csr_hash[csr_name]
193
+
194
+ sig { params(param_name: String).returns(T.nilable(RuntimeParam)) }
195
+ def param(param_name) = params_hash[param_name]
196
+
197
+ sig { returns(T::Hash[String, RuntimeParam]) }
198
+ def params_hash
199
+ @memo.params_hash ||= @params.map { |p| [p.name.freeze, p] }.to_h.freeze
200
+ end
201
+
202
+ sig {
203
+ params(
204
+ mxlen: T.nilable(Integer),
205
+ possible_xlens_cb: T.nilable(PossibleXlensCallbackType),
206
+ builtin_global_vars: T::Array[Var],
207
+ builtin_enums: T::Array[EnumDef],
208
+ builtin_funcs: T.nilable(BuiltinFunctionCallbacks),
209
+ csrs: T::Array[Csr],
210
+ params: T::Array[RuntimeParam],
211
+ name: String
212
+ ).void
213
+ }
214
+ def initialize(mxlen: nil, possible_xlens_cb: nil, builtin_global_vars: [], builtin_enums: [], builtin_funcs: nil, csrs: [], params: [], name: "")
215
+ @mutex = Thread::Mutex.new
216
+ @mxlen = mxlen
217
+ @possible_xlens_cb = possible_xlens_cb
218
+ @callstack = [nil]
219
+ @name = name
220
+ @memo = MemoizedState.new
221
+
222
+ # builtin types
223
+ @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
+ "Boolean" => Type.new(:boolean),
230
+ "true" => Var.new(
231
+ "true",
232
+ Type.new(:boolean),
233
+ true
234
+ ),
235
+ "false" => Var.new(
236
+ "false",
237
+ Type.new(:boolean),
238
+ false
239
+ )
240
+
241
+ }]
242
+ builtin_global_vars.each do |v|
243
+ add!(v.name, v)
244
+ end
245
+ builtin_enums.each do |enum_def|
246
+ add!(enum_def.name, EnumerationType.new(enum_def.name, enum_def.element_names, enum_def.element_values))
247
+ end
248
+ @builtin_funcs = builtin_funcs
249
+ @csrs = csrs
250
+ @csr_hash = @csrs.map { |csr| [csr.name.freeze, csr].freeze }.to_h.freeze
251
+ @params = params
252
+
253
+ # set up the global clone that be used as a mutable table
254
+ @global_clone_pool = T.let([], T::Array[SymbolTable])
255
+ end
256
+
257
+ # @return [String] inspection string
258
+ sig { returns(String) }
259
+ def inspect
260
+ "SymbolTable[#{@name}]#{frozen? ? ' (frozen)' : ''}"
261
+ end
262
+
263
+ # do a deep freeze to protect the sym table and all its entries from modification
264
+ def deep_freeze
265
+ @scopes.each do |k, v|
266
+ k.freeze
267
+ v.freeze
268
+ end
269
+ @scopes.freeze
270
+
271
+ # set frozen_hash so that we can quickly compare symtabs
272
+ @frozen_hash = [@scopes.hash, @name.hash].hash
273
+
274
+ 5.times do
275
+ copy = SymbolTable.allocate
276
+ copy.instance_variable_set(:@scopes, [@scopes[0]])
277
+ copy.instance_variable_set(:@callstack, [@callstack[0]])
278
+ copy.instance_variable_set(:@mxlen, @mxlen)
279
+ copy.instance_variable_set(:@mutex, @mutex)
280
+ copy.instance_variable_set(:@name, @name)
281
+ copy.instance_variable_set(:@memo, @memo.dup)
282
+ copy.instance_variable_set(:@possible_xlens_cb, @possible_xlens_cb)
283
+ copy.instance_variable_set(:@params, @params)
284
+ copy.instance_variable_set(:@builtin_funcs, @builtin_funcs)
285
+ copy.instance_variable_set(:@csrs, @csrs)
286
+ copy.instance_variable_set(:@csr_hash, @csr_hash)
287
+ copy.instance_variable_set(:@global_clone_pool, @global_clone_pool)
288
+ copy.instance_variable_set(:@in_use, Concurrent::Semaphore.new(1))
289
+ @global_clone_pool << copy
290
+ end
291
+
292
+ freeze
293
+ self
294
+ end
295
+
296
+ # pushes a new scope
297
+ # @return [SymbolTable] self
298
+ def push(ast)
299
+ # puts "push #{caller[0]}"
300
+ # @scope_caller ||= []
301
+ # @scope_caller.push caller[0]
302
+ raise "#{@scopes.size} #{@callstack.size}" unless @scopes.size == @callstack.size
303
+ @scopes << {}
304
+ @callstack << ast
305
+ @frozen_hash = nil
306
+ self
307
+ end
308
+
309
+ # pops the top of the scope stack
310
+ def pop
311
+ # puts "pop #{caller[0]}"
312
+ # puts " from #{@scope_caller.pop}"
313
+ raise "Error: popping the symbol table would remove global scope" if @scopes.size == 1
314
+
315
+ raise "?" unless @scopes.size == @callstack.size
316
+ @scopes.pop
317
+ @callstack.pop
318
+ end
319
+
320
+ def callstack
321
+ @callstack.reverse.map { |ast| ast.nil? ? "" : "#{ast.input_file}:#{ast.lineno}" }.join("\n")
322
+ end
323
+
324
+ # @return [Boolean] whether or not any symbol 'name' is defined at any level in the symbol table
325
+ def key?(name)
326
+ @scopes.each { |s| return true if s.key?(name) }
327
+ end
328
+
329
+ def keys_pretty
330
+ @scopes.map { |s| s.keys }
331
+ end
332
+
333
+ # searches the symbol table scope-by-scope to find 'name'
334
+ #
335
+ # @return [Object] A symbol named 'name', or nil if not found
336
+ def get(name)
337
+ @scopes.reverse_each do |s|
338
+ result = s.fetch(name, nil)
339
+ return result unless result.nil?
340
+ end
341
+ nil
342
+ end
343
+
344
+ def get_from(name, level)
345
+ raise ArgumentError, "level must be positive" unless level.positive?
346
+
347
+ raise "There is no level #{level}" unless level < levels
348
+
349
+ @scopes[0..level - 1].reverse_each do |s|
350
+ return s[name] if s.key?(name)
351
+ end
352
+ nil
353
+ end
354
+
355
+ # @return [Object] the symbol named 'name' from global scope, or nil if not found
356
+ def get_global(name)
357
+ get_from(name, 1)
358
+ end
359
+
360
+ # searches the symbol table scope-by-scope to find all entries for which the block returns true
361
+ #
362
+ # @param single_scope [Boolean] If true, stop searching more scope as soon as there are matches
363
+ # @yieldparam obj [Object] A object stored in the symbol table
364
+ # @yieldreturn [Boolean] Whether or not the object is the one you are looking for
365
+ # @return [Array<Object>] All matches
366
+ def find_all(single_scope: false, &block)
367
+ raise ArgumentError, "Block needed" unless block_given?
368
+
369
+ raise ArgumentError, "Find block takes one argument" unless block.arity == 1
370
+
371
+ matches = []
372
+
373
+ @scopes.reverse_each do |s|
374
+ s.each_value do |v|
375
+ matches << v if yield v
376
+ end
377
+ break if single_scope && !matches.empty?
378
+ end
379
+ matches
380
+ end
381
+
382
+ # add a new symbol at the outermost scope
383
+ #
384
+ # @param name [#to_s] Symbol name
385
+ # @param var [Object] Symbol object (usually a Var or a Type)
386
+ def add(name, var)
387
+ @scopes.last[name] = var
388
+ end
389
+
390
+ # add a new symbol at the outermost scope, unless that symbol is already defined
391
+ #
392
+ # @param name [#to_s] Symbol name
393
+ # @param var [Object] Symbol object (usually a Var or a Type)
394
+ # @raise [DuplicationSymError] if 'name' is already in the symbol table
395
+ def add!(name, var)
396
+ raise DuplicateSymError, "Symbol #{name} already defined as #{get(name)}" unless @scopes.select { |h| h.key? name }.empty?
397
+
398
+ @scopes.last[name] = var
399
+ end
400
+
401
+ # delete a new symbol at the outermost scopea
402
+ #
403
+ # @param name [#to_s] Symbol name
404
+ # @param var [Object] Symbol object (usually a Var or a Type)
405
+ def del(name)
406
+ raise "No symbol #{name} at outer scope" unless @scopes.last.key?(name)
407
+
408
+ @scopes.last.delete(name)
409
+ end
410
+
411
+ # add to the scope above the tail, and make sure name is unique at that scope
412
+ def add_above!(name, var)
413
+ raise "There is only one scope" if @scopes.size <= 1
414
+
415
+ raise "Symbol #{name} already defined" unless @scopes[0..-2].select { |h| h.key? name }.empty?
416
+
417
+ @scopes[-2][name] = var
418
+ end
419
+
420
+ # add to the scope at level, and make sure name is unique at that scope
421
+ def add_at!(level, name, var)
422
+ raise "Level #{level} is too large #{@scopes.size}" if level >= @scopes.size
423
+
424
+ raise "Symbol #{name} already defined" unless @scopes[0...level].select { |h| h.key? name }.empty?
425
+
426
+ @scopes[level][name] = var
427
+ end
428
+
429
+ # @return [Integer] Number of scopes on the symbol table (global at 1)
430
+ def levels
431
+ @scopes.size
432
+ end
433
+
434
+ # pretty-print the symbol table contents
435
+ sig { void }
436
+ def print
437
+ @scopes.each do |s|
438
+ s.each do |name, obj|
439
+ puts "#{name} #{obj}"
440
+ end
441
+ end
442
+ end
443
+
444
+ # @return [Boolean] true if the symbol table is at the global scope
445
+ def at_global_scope?
446
+ @scopes.size == 1
447
+ end
448
+
449
+ # Returns a Hash mapping each Var in non-global scopes to its current value.
450
+ # Only captures Vars (not Types or other objects).
451
+ # skips frozen vars since they can't be modified
452
+ def snapshot_values
453
+ snapshot = {}
454
+ @scopes[1..].each do |scope|
455
+ scope.each_value do |v|
456
+ if v.is_a?(Var) && !v.frozen?
457
+ snapshot[v] = v.value
458
+ end
459
+ end
460
+ end
461
+ snapshot
462
+ end
463
+
464
+ # Restores Var values from a snapshot produced by snapshot_values.
465
+ def restore_values(snapshot)
466
+ snapshot.each do |var, value|
467
+ var.value = value
468
+ end
469
+ end
470
+
471
+ # @return [SymbolTable] a mutable clone of the global scope of this SymbolTable
472
+ def global_clone
473
+ # raise "symtab isn't frozen" if @global_clone.nil?
474
+ # raise "global clone isn't at global scope" unless @global_clone.at_global_scope?
475
+
476
+ @global_clone_pool.each do |symtab|
477
+ return symtab if symtab.instance_variable_get(:@in_use).try_acquire
478
+ end
479
+
480
+ # need more!
481
+ Idl.logger.debug "Allocating more SymbolTables"
482
+ 5.times do
483
+ copy = SymbolTable.allocate
484
+ copy.instance_variable_set(:@scopes, [@scopes[0]])
485
+ copy.instance_variable_set(:@callstack, [@callstack[0]])
486
+ copy.instance_variable_set(:@mxlen, @mxlen)
487
+ copy.instance_variable_set(:@mutex, @mutex)
488
+ copy.instance_variable_set(:@name, @name)
489
+ copy.instance_variable_set(:@memo, @memo.dup)
490
+ copy.instance_variable_set(:@possible_xlens_cb, @possible_xlens_cb)
491
+ copy.instance_variable_set(:@params, @params)
492
+ copy.instance_variable_set(:@builtin_funcs, @builtin_funcs)
493
+ copy.instance_variable_set(:@csrs, @csrs)
494
+ copy.instance_variable_set(:@csr_hash, @csr_hash)
495
+ copy.instance_variable_set(:@global_clone_pool, @global_clone_pool)
496
+ copy.instance_variable_set(:@in_use, Concurrent::Semaphore.new(1))
497
+ @global_clone_pool << copy
498
+ end
499
+
500
+ global_clone
501
+ end
502
+
503
+ def release
504
+ @mutex.synchronize do
505
+ pop while levels > 1
506
+ raise "Clone isn't back in global scope" unless at_global_scope?
507
+ raise "You are calling release on the frozen SymbolTable" if frozen?
508
+ raise "Double release detected" unless in_use?
509
+
510
+ @in_use.release
511
+ end
512
+ end
513
+
514
+ def in_use? = @in_use.available_permits.zero?
515
+
516
+ # @return [SymbolTable] a deep clone of this SymbolTable
517
+ def deep_clone(clone_values: false, freeze_global: true)
518
+ raise "don't do this" unless freeze_global
519
+
520
+ # globals are frozen, so we can just return a shallow clone
521
+ # if we are in global scope
522
+ if levels == 1
523
+ copy = dup
524
+ copy.instance_variable_set(:@scopes, copy.instance_variable_get(:@scopes).dup)
525
+ copy.instance_variable_set(:@callstack, copy.instance_variable_get(:@callstack).dup)
526
+ return copy
527
+ end
528
+
529
+ copy = dup
530
+ # back up the table to global scope
531
+ copy.instance_variable_set(:@callstack, @callstack.dup)
532
+ copy.instance_variable_set(:@scopes, [])
533
+ c_scopes = copy.instance_variable_get(:@scopes)
534
+ c_scopes.push(@scopes[0])
535
+
536
+ @scopes[1..].each do |scope|
537
+ c_scopes << {}
538
+ scope.each do |k, v|
539
+ if clone_values
540
+ c_scopes.last[k] = v.dup
541
+ else
542
+ c_scopes.last[k] = v
543
+ end
544
+ end
545
+ end
546
+ copy
547
+ end
548
+ end
549
+ end
@@ -0,0 +1,64 @@
1
+
2
+ # Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
3
+ # SPDX-License-Identifier: BSD-3-Clause-Clear
4
+
5
+ # typed: true
6
+ # frozen_string_literal: true
7
+
8
+ require "sorbet-runtime"
9
+
10
+ # adds a few functions to Treetop's syntax node
11
+ module Treetop
12
+ module Runtime
13
+ class SyntaxNode
14
+ extend T::Sig
15
+ # remember where the code comes from
16
+ #
17
+ # @param filename [String] Filename
18
+ # @param starting_line [Integer] Starting line in the file
19
+ # @param starting_offset [Integer] Starting byte offset in the file
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 }
22
+ def set_input_file(filename, starting_line = 0, starting_offset = 0, line_file_offsets = nil)
23
+ @input_file = filename
24
+ @starting_line = starting_line
25
+ @starting_offset = starting_offset
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
+ end
34
+
35
+ sig { returns(T::Boolean) }
36
+ def space? = false
37
+
38
+ # Sets the input file for this syntax node unless it has already been set.
39
+ #
40
+ # If the input file has not been set, it will be set with the given filename and starting line number.
41
+ #
42
+ # @param [String] filename The name of the input file.
43
+ # @param [Integer] starting_line The starting line number in the input file.
44
+ # @param [Integer] starting_offset The starting byte offset in the input file.
45
+ # @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 }
47
+ def set_input_file_unless_already_set(filename, starting_line = 0, starting_offset = 0, line_file_offsets = nil)
48
+ if @input_file.nil?
49
+ set_input_file(filename, starting_line, starting_offset, line_file_offsets)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ module Idl
57
+ class SyntaxNode < Treetop::Runtime::SyntaxNode
58
+ extend T::Sig
59
+ extend T::Helpers
60
+
61
+ sig { overridable.returns(Idl::AstNode) }
62
+ def to_ast = raise "Must override to_ast for #{self.class.name}"
63
+ end
64
+ end