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)


7824
7825
7826
7827
7828
7829
7830
7831
7832
# File 'lib/idlc/ast.rb', line 7824

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



7987
7988
7989
7990
7991
7992
7993
7994
7995
7996
7997
# File 'lib/idlc/ast.rb', line 7987

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



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

def arg_nodes
  @children
end

#const_eval?(symtab) ⇒ Boolean

Returns:

  • (Boolean)


7819
7820
7821
7822
# File 'lib/idlc/ast.rb', line 7819

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

#execute(symtab) ⇒ Object



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

def execute(symtab) = value(symtab)

#func_type(symtab) ⇒ Object



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

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



7966
7967
7968
# File 'lib/idlc/ast.rb', line 7966

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



7977
7978
7979
7980
7981
7982
7983
7984
# File 'lib/idlc/ast.rb', line 7977

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

#to_idlObject



7972
7973
7974
# File 'lib/idlc/ast.rb', line 7972

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:



7887
7888
7889
7890
7891
7892
7893
# File 'lib/idlc/ast.rb', line 7887

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:



7854
7855
7856
7857
7858
7859
7860
7861
7862
7863
7864
7865
7866
7867
7868
7869
7870
7871
7872
7873
7874
7875
7876
7877
7878
7879
7880
7881
7882
7883
7884
# File 'lib/idlc/ast.rb', line 7854

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



7896
7897
7898
7899
7900
7901
7902
7903
7904
7905
7906
7907
7908
7909
7910
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
# File 'lib/idlc/ast.rb', line 7896

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