Class: Idl::FunctionCallExpressionAst

Inherits:
AstNode
  • Object
show all
Includes:
Executable, Rvalue
Defined in:
lib/idlc/ast.rb,
lib/idlc/passes/prune.rb,
lib/idlc/passes/gen_adoc.rb,
lib/idlc/passes/gen_option_adoc.rb,
lib/idlc/passes/reachable_functions.rb,
lib/idlc/passes/reachable_exceptions.rb

Constant Summary

Constants inherited from AstNode

AstNode::Bits1Type, AstNode::Bits32Type, AstNode::Bits64Type, AstNode::BoolType, AstNode::ConstBoolType, AstNode::PossiblyUnknownBits1Type, AstNode::PossiblyUnknownBits32Type, AstNode::PossiblyUnknownBits64Type, AstNode::ReachableFunctionCacheType, AstNode::StringType, AstNode::VoidType

Instance Attribute Summary

Attributes inherited from AstNode

#children, #input, #interval, #parent

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Executable

#executable?

Methods included from Rvalue

#max_value, #min_value, #truncate, #values

Methods inherited from AstNode

#always_terminates?, #declaration?, #executable?, extract_base_var_name, #find_ancestor, #find_dst_registers, #find_referenced_csrs, #find_src_registers, #freeze_tree, #input_file, input_from_source_yaml, #inspect, #internal_error, interval_from_source_yaml, #lineno, #lines_around, #nullify_assignments, #pass_find_return_values, #path, #print_ast, #set_input_file, #set_input_file_unless_already_set, #source_line_file_offsets, #source_starting_offset, #source_yaml, #starting_line, #text_value, #truncation_warn, #type_error, #unindent, value_else, #value_else, value_error, #value_error, value_try, #value_try, write_back_nested

Constructor Details

#initialize(input, interval, function_name, args) ⇒ FunctionCallExpressionAst

Returns a new instance of FunctionCallExpressionAst.

Raises:

  • (ArgumentError)


7839
7840
7841
7842
7843
7844
7845
7846
7847
# File 'lib/idlc/ast.rb', line 7839

def initialize(input, interval, function_name, args)
  raise ArgumentError, "args should be an array" unless args.is_a?(Array)

  super(input, interval, args)

  @name = function_name
  @reachable_exceptions_func_call_cache = {}
  @func_def_type_cache = {}
end

Class Method Details

.from_h(yaml, source_mapper) ⇒ Object



8002
8003
8004
8005
8006
8007
8008
8009
8010
8011
8012
# File 'lib/idlc/ast.rb', line 8002

def self.from_h(yaml, source_mapper)
  raise "Bad YAML" unless yaml.key?("kind") && yaml.fetch("kind") == "funcall_expr"

  input = input_from_source_yaml(yaml.fetch("source"), source_mapper)
  interval = interval_from_source_yaml(yaml.fetch("source"))
  FunctionCallExpressionAst.new(
    input, interval,
    yaml.fetch("func"),
    yaml.fetch("args").map { |a| AstNode.from_h(a, source_mapper) }
  )
end

Instance Method Details

#arg_nodesArray<AstNode>

Returns Function argument nodes.

Returns:

  • (Array<AstNode>)

    Function argument nodes



7850
7851
7852
# File 'lib/idlc/ast.rb', line 7850

def arg_nodes
  @children
end

#const_eval?(symtab) ⇒ Boolean

Returns:

  • (Boolean)


7834
7835
7836
7837
# File 'lib/idlc/ast.rb', line 7834

def const_eval?(symtab)
  @children.all? { |arg| arg.const_eval?(symtab) } && \
    func_type(symtab).func_def_ast.const_eval?(symtab)
end

#execute(symtab) ⇒ Object



7979
# File 'lib/idlc/ast.rb', line 7979

def execute(symtab) = value(symtab)

#func_type(symtab) ⇒ Object



7854
7855
7856
7857
7858
7859
7860
7861
7862
7863
7864
7865
7866
# File 'lib/idlc/ast.rb', line 7854

def func_type(symtab)
  func_def_type = @func_def_type_cache[symtab.name]
  return func_def_type unless func_def_type.nil?

  func_def_type = symtab.get(@name)
  type_error "No symbol #{@name}" if func_def_type.nil?

  unless func_def_type.is_a?(FunctionType)
    type_error "#{@name} is not a function (it's a #{func_def_type.class.name})"
  end

  @func_def_type_cache[symtab.name] = func_def_type
end

#gen_adoc(indent = 0, indent_spaces: 2) ⇒ Object



294
295
296
297
# File 'lib/idlc/passes/gen_adoc.rb', line 294

def gen_adoc(indent = 0, indent_spaces: 2)
  func_link = "%%UDB_DOC_LINK%func;#{name};#{name}%%"
  "#{' ' * indent}" + func_link + "pass:[(]#{arg_nodes.map { |a| a.gen_adoc(0, indent_spaces:) }.join(', ')})"
end

#gen_option_adocObject



28
29
30
# File 'lib/idlc/passes/gen_option_adoc.rb', line 28

def gen_option_adoc
  gen_adoc(0)
end

#nameObject



7981
7982
7983
# File 'lib/idlc/ast.rb', line 7981

def name
  @name
end

#prune(symtab, forced_type: nil) ⇒ Object



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/idlc/passes/prune.rb', line 205

def prune(symtab, forced_type: nil)
  value_result = value_try do
    v = value(symtab)
    if type(symtab).kind == :bits
      # can only prune if the bit width of the integer is known
      if type(symtab).width == :unknown
        value_error "Unknown width"
      end
    elsif type(symtab).kind == :struct
      value_error <<~MSG
        Literal struct values can't be pruned since a struct can't be initialized with a single expression.
        This would require syntax like { .a = FOO, .b = BAR }
      MSG
    end
    return PruneHelpers.create_literal(symtab, v, type(symtab), forced_type: forced_type || type(symtab))
  end
  value_else(value_result) do
    FunctionCallExpressionAst.new(input, interval, name, @children.map { |a| a.prune(symtab) })
  end
end

#reachable_exceptions(symtab, cache = {}) ⇒ Object



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/idlc/passes/reachable_exceptions.rb', line 25

def reachable_exceptions(symtab, cache = {})
  if name == "raise" || name == "raise_precise"
    # first argument is the exception
    code_ast = arg_nodes[0]
    value_result = value_try do
      code = code_ast.value(symtab)
      internal_error "Code should be an integer" unless code.is_a?(Integer)
      return 1 << code
    end
    value_else(value_result) do
      value_error "Cannot determine value of exception code"
    end
  end

  # return @reachable_exceptions_func_call_cache[symtab] unless @reachable_exceptions_func_call_cache[symtab].nil?

  func_def_type = func_type(symtab)

  mask = 0
  arg_nodes.each do |a|
    mask |= a.reachable_exceptions(symtab, cache) if a.is_a?(FunctionCallExpressionAst)
  end

  unless func_def_type.builtin? || func_def_type.generated?
    body_symtab = symtab.global_clone
    body_symtab.push(func_def_type.func_def_ast)
    avals = func_def_type.apply_arguments(body_symtab, arg_nodes, symtab, self)

    idx = [name, [], avals].hash

    begin
      body_mask =
        if cache.key?(idx)
          cache[idx]
        else
          cache[idx] = func_def_type.body.reachable_exceptions(body_symtab, cache)
        end
      mask |= body_mask
    ensure
      body_symtab.pop
      body_symtab.release
    end
  end

  # @reachable_exceptions_func_call_cache[symtab] = mask
  mask
end

#reachable_functions(symtab, cache = T.let({}, ReachableFunctionCacheType)) ⇒ Object



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/idlc/passes/reachable_functions.rb', line 36

def reachable_functions(symtab, cache = T.let({}, ReachableFunctionCacheType))
  func_def_type = func_type(symtab)

  body_symtab = symtab.global_clone
  body_symtab.push(func_def_type.func_def_ast)

  # Use a hash keyed by name to accumulate unique functions without repeated uniq scans
  fns_by_name = {}

  begin
    arg_nodes.each do |a|
      a.reachable_functions(symtab, cache).each { |fn| fns_by_name[fn.name] ||= fn }
    end

    unless func_def_type.builtin? || func_def_type.generated?
      avals = func_def_type.apply_arguments(body_symtab, arg_nodes, symtab, self)

      idx = [name, avals].hash

      if cache.key?(idx)
        # Use cached results from a prior traversal (e.g., same function called
        # by an earlier instruction). The sentinel [] handles recursion cycles.
        cache[idx].each { |fn| fns_by_name[fn.name] ||= fn }
      else
        cache[idx] = [] # sentinel: breaks recursion cycles before body is traversed
        body_fns = func_def_type.body.reachable_functions(body_symtab, cache)
        cache[idx] = body_fns
        body_fns.each { |fn| fns_by_name[fn.name] ||= fn }
      end
    end

    fns_by_name[func_def_type.func_def_ast.name] ||= func_def_type.func_def_ast
  ensure
    body_symtab.pop
    body_symtab.release
  end

  fns_by_name.values
end

#to_hObject



7992
7993
7994
7995
7996
7997
7998
7999
# File 'lib/idlc/ast.rb', line 7992

def to_h
  {
    "kind" => "funcall_expr",
    "func" => name,
    "args" => arg_nodes.map(&:to_h),
    "source" => source_yaml
  }
end

#to_idlObject



7987
7988
7989
# File 'lib/idlc/ast.rb', line 7987

def to_idl
  "#{name}(#{arg_nodes.map(&:to_idl).join(',')})"
end

#type(symtab) ⇒ Type

Given a specific symbol table, return the type of this node.

Should not be called until #type_check is called with the same arguments

Parameters:

Returns:

  • (Type)

    The type of the node

Raises:



7902
7903
7904
7905
7906
7907
7908
# File 'lib/idlc/ast.rb', line 7902

def type(symtab)
  return ConstBoolType if name == "implemented?" || name == "implemented_version?" || name == "implemented_csr?"

  rtype = func_type(symtab).return_type(arg_nodes, self)
  rtype = rtype.make_const if arg_nodes.all? { |a| a.type(symtab).const? } && func_type(symtab).func_def_ast.const_eval?(symtab)
  rtype
end

#type_check(symtab, strict:) ⇒ void

This method returns an undefined value.

type check this node and all children

Calls to #type and/or #value may depend on type_check being called first with the same symtab. If not, those functions may raise an AstNode::InternalError

Parameters:

Raises:



7869
7870
7871
7872
7873
7874
7875
7876
7877
7878
7879
7880
7881
7882
7883
7884
7885
7886
7887
7888
7889
7890
7891
7892
7893
7894
7895
7896
7897
7898
7899
# File 'lib/idlc/ast.rb', line 7869

def type_check(symtab, strict:)
  type_error "Cannot use reserved word '#{@name}' as function name" if ReservedWords::RESERVED.include?(@name)
  level = symtab.levels

  func_def_type = func_type(symtab)

  num_args = arg_nodes.size
  if func_def_type.num_args != num_args
    type_error "Wrong number of arguments to '#{name}' function call. Expecting #{func_def_type.num_args}, got #{num_args}"
  end
  arg_nodes.each do |a|
    a.type_check(symtab, strict:)
  end
  arg_nodes.each_with_index do |a, idx|
    unless a.type(symtab).convertable_to?(func_def_type.argument_type(idx, arg_nodes, symtab, self))
      type_error "Wrong type for argument number #{idx + 1}. Expecting #{func_def_type.argument_type(idx, [], arg_nodes, symtab, self)}, got #{a.type(symtab)}"
    end
    arg_name = func_def_type.argument_nodes.fetch(idx).name
    if arg_name[0].upcase == arg_name[0]
      unless a.type(symtab).const?
        type_error "You cannot pass a mutable expression to '#{arg_name}' const argument of #{name}"
      end
    end
  end

  if func_def_type.return_type(arg_nodes, self).nil?
    internal_error "No type determined for function"
  end

  internal_error "Function call symtab not at same level post type check (#{symtab.levels} #{level})" unless symtab.levels == level
end

#value(symtab) ⇒ Object

Return the compile-time-known value of the node



7911
7912
7913
7914
7915
7916
7917
7918
7919
7920
7921
7922
7923
7924
7925
7926
7927
7928
7929
7930
7931
7932
7933
7934
7935
7936
7937
7938
7939
7940
7941
7942
7943
7944
7945
7946
7947
7948
7949
7950
7951
7952
7953
7954
7955
7956
7957
7958
7959
7960
7961
7962
7963
7964
7965
7966
7967
7968
7969
7970
7971
7972
7973
7974
7975
7976
7977
# File 'lib/idlc/ast.rb', line 7911

def value(symtab)
  # sometimes we want to evaluate for a specific XLEN
  if name == "xlen" && !symtab.get("__effective_xlen").nil?
    return symtab.get("__effective_xlen").value
  end

  func_def_type = func_type(symtab)
  type_error "#{name} is not a function" unless func_def_type.is_a?(FunctionType)
  if func_def_type.generated?
    value_error "builtin functions not provided" if symtab.builtin_funcs.nil?

    if name == "implemented?"
      extname_ref = arg_nodes[0]
      type_error "First argument should be a ExtensionName" unless extname_ref.type(symtab).kind == :enum_ref && extname_ref.class_name == "ExtensionName"

      v = symtab.builtin_funcs.implemented.call(extname_ref.member_name)
      if v.nil?
        value_error "implemented? is only known when evaluating in the context of a fully-configured arch def"
      end
      return v

    elsif name == "implemented_version?"
      extname_ref = arg_nodes[0]
      type_error "First argument should be a ExtensionName" unless extname_ref.type(symtab).kind == :enum_ref && extname_ref.class_name == "ExtensionName"

      ver_req = arg_nodes[1].text_value[1..-2]

      v = symtab.builtin_funcs.implemented_version.call(extname_ref.member_name, ver_req)
      if v.nil?
        value_error "implemented_version? is only known when evaluating in the context of a fully-configured arch def"
      end
      return v

    elsif name == "implemented_csr?"
      csr_addr = arg_nodes[0].value(symtab)
      v = symtab.builtin_funcs.implemented_csr.call(csr_addr)
      if v.nil?
        value_error "implemented_csr? is only known when evaluating in the context of a fully-configured arch def"
      end
      return v

    elsif name == "cached_translation"
      value_error "cached_translation is not compile-time-knowable"
    elsif name == "maybe_cache_translation"
      value_error "maybe_cache_translation is not compile-time-knowable"
    elsif name == "invalidate_translations"
      value_error "invalidate_translations is not compile-time-knowable"
    elsif name == "direct_csr_lookup"
      value_error "direct_csr_lookup is not compile-time-knowable"
    elsif name == "indirect_csr_lookup"
      value_error "indirect_csr_lookup is not compile-time-knowable"
    elsif name == "csr_hw_read"
      value_error "csr_hw_read is not compile-time-knowable"
    elsif name == "csr_sw_read"
      value_error "csr_sw_read is not compile-time-knowable"
    elsif name == "csr_sw_write"
      value_error "csr_sw_write is not compile-time-knowable"
    else
      internal_error "Unimplemented generated: '#{name}'"
    end
  end
  if func_def_type.builtin?
    value_error "value of builtin functions aren't knowable"
  end

  func_def_type.return_value(arg_nodes, symtab, self)
end