From f56eef1da184b4304c16994e4690d964c6a9c8f3 Mon Sep 17 00:00:00 2001 From: Derek Hower Date: Mon, 15 Jul 2024 08:21:14 -0700 Subject: [PATCH] Better handling of function call symbol tables --- .../arch_overlay/csr/marchid.yaml | 2 +- lib/arch_def.rb | 31 ++-- lib/idl/ast.rb | 137 +++++++----------- lib/idl/symbol_table.rb | 86 ++++++++++- lib/idl/type.rb | 30 +++- tasks/adoc_gen.rake | 2 +- tasks/arch_gen.rake | 1 + 7 files changed, 180 insertions(+), 109 deletions(-) diff --git a/cfgs/generic_rv64/arch_overlay/csr/marchid.yaml b/cfgs/generic_rv64/arch_overlay/csr/marchid.yaml index 45284efba..84fb5b06e 100644 --- a/cfgs/generic_rv64/arch_overlay/csr/marchid.yaml +++ b/cfgs/generic_rv64/arch_overlay/csr/marchid.yaml @@ -3,4 +3,4 @@ marchid: fields: Architecture: - reset_value: 0xcafebabe + description: My custom description. diff --git a/lib/arch_def.rb b/lib/arch_def.rb index 6b44a31ea..4c002f698 100644 --- a/lib/arch_def.rb +++ b/lib/arch_def.rb @@ -127,6 +127,8 @@ def type parent: "#{csr.name}.#{name}" ) + sym_table.push # for consistency with template functions + begin case ast.return_value(sym_table) when 0 @@ -148,6 +150,8 @@ def type warn "In parsing #{csr.name}.#{name}::type()" warn " Return of type() function cannot be evaluated at compile time" raise e + ensure + sym_table.pop end end end @@ -218,13 +222,20 @@ def reset_value_func def reset_value return @reset_value unless @reset_value.nil? - @reset_value = - if @data.key?("reset_value") - @data["reset_value"] - else - puts "evaluating #{name}" - reset_value_func.return_value(arch_def.sym_table) - end + symtab = arch_def.sym_table + + symtab.push # for consistency with template functions + + begin + @reset_value = + if @data.key?("reset_value") + @data["reset_value"] + else + reset_value_func.return_value(arch_def.sym_table) + end + ensure + symtab.pop + end end # @param effective_xlen [Integer] The effective xlen, needed since some fields change location with XLEN. If the field location is not determined by XLEN, then this parameter can be nil @@ -385,7 +396,6 @@ def length(effective_xlen = nil) effective_xlen else - puts arch_def.implemented_csrs.map { |c| c.name } raise "CSR #{name} is not implemented" if arch_def.implemented_csrs.none? { |c| c.name == name } raise "CSR #{name} is not implemented" if arch_def.config_params["SXLEN"].nil? @@ -1213,6 +1223,9 @@ class ArchDef # @return [Idl::Compiler] The IDL compiler attr_reader :idl_compiler + # @return [Idl::AstNode] Abstract syntax tree of global scope + attr_reader :global_ast + # Initialize a new configured architecture defintiion # # @params config_name [#to_s] The name of a configuration, which must correspond @@ -1237,7 +1250,7 @@ def initialize(config_name) @idl_compiler = Idl::Compiler.new(self) # load the globals into the symbol table - @idl_compiler.compile_file( + @global_ast = @idl_compiler.compile_file( $root / "arch" / "isa" / "globals.isa", @sym_table ) diff --git a/lib/idl/ast.rb b/lib/idl/ast.rb index d42d3eebe..f8f1cec91 100644 --- a/lib/idl/ast.rb +++ b/lib/idl/ast.rb @@ -27,26 +27,6 @@ def has_template_ancestor? # @return [Boolean] whether or not this SyntaxNode represents a function name (overriden in the parser) def is_function_name? = false - # @return [Boolean] whether or not this SyntaxNode is being parsed as a template argument - # without being enclosed in parenthesis - def parsing_in_template? - # since this is called in the middle of parsing, the parent pointer hasn't been set up yet - puts self.class.ancestors - puts "text = #{text_value}" - puts parent.class.name - exit - if parent.nil? - nil - elsif parent.is_a?(ParenExpressionAst) - false # once we hit a paren expression, we are cleared - elsif parent.is_a?(BitsTypeAst) - puts "FOund bits!!" - true # must be the expression on the BitsType - else - parent.parsing_in_template? - end - end - # Fix up left recursion for the PEG # # This is the default for anything that isn't a left-recursive binary op @@ -442,7 +422,6 @@ class GlobalWithInitializationAst < AstNode include Executable def type_check(symtab) - puts "global #{text_value}" single_declaration_with_initialization.type_check(symtab) end @@ -942,11 +921,15 @@ def rhs end def execute(symtab) - var = symtab.get(var.text_value) + if var.is_a?(CsrWriteAst) + value_error "CSR writes are never compile-time-known" + else + variable = symtab.get(var.text_value) - internal_error "Call type check first" if var.nil? + internal_error "No variable #{var.text_value}" if variable.nil? - var.value = rval.value(symtab) + variable.value = rval.value(symtab) + end end # @!macro to_idl @@ -1209,7 +1192,7 @@ def type_check(symtab) end def execute(symtab) - values = functin_call.execute(symtab) + values = function_call.execute(symtab) i = 0 vars.each do |v| @@ -1520,7 +1503,9 @@ def value(symtab) element_name = expression.text_value.split(":")[2] etype.enum_class.value(element_name) when :csr - internal_error "TODO" + expression.value(symtab) + else + internal_error "TODO: Bits cast for #{etype.kind}" end end @@ -2334,7 +2319,21 @@ def expected_return_type(symtab) symtab.get("__expected_return_type") else - func_def.return_type(symtab) + # need to find the type to get the right symbol table + func_type = symtab.get_global(func_def.name) + internal_error "Couldn't find function type for '#{func_def.name}' #{symtab.keys} " if func_type.nil? + + # to get the return type, we need to find the template values in case this is + # a templated function definition + # + # that information should be up the stack in the symbol table + template_values = symtab.find_all(single_scope: true) do |o| + o.is_a?(Var) && o.template_value_for?(func_def.name) + end + unless template_values.size == func_type.template_names.size + internal_error "Did not find correct number of template arguments (found #{template_values.size}, need #{func_type.template_names.size}) #{symtab.keys_pretty}" + end + func_type.return_type(template_values.sort { |a, b| a.template_index <=> b.template_index }.map(&:value)) end end @@ -2622,7 +2621,7 @@ def type_check(symtab) end end - if func_def_type.return_type(template_values(symtab), arg_nodes, symtab).nil? + if func_def_type.return_type(template_values(symtab)).nil? internal_error "No type determined for function" end @@ -2632,12 +2631,13 @@ def type_check(symtab) # @!macro type def type(symtab) func_def_type = symtab.get(name) - func_def_type.return_type(template_values(symtab), arg_nodes, symtab) + func_def_type.return_type(template_values(symtab)) end # @!macro value def value(symtab) func_def_type = symtab.get(name) + type_error "not a function" unless func_def_type.is_a?(FunctionType) if func_def_type.builtin? if name == "implemented?" extname = arg_nodes[0].text_value @@ -2647,38 +2647,20 @@ def value(symtab) end end - symtab.push - - begin - template_arg_nodes.each_with_index do |targ, idx| - targ_name = func_def_type.template_names[idx] - targ_type = func_def_type.template_types[idx] - symtab.add(targ_name, Var.new(targ_name, targ_type, targ.value(symtab))) - end - - arg_nodes.each_with_index do |arg, idx| - arg_name = func_def_type.argument_name(idx, template_values(symtab)) - arg_type = func_def_type.argument_type(idx, template_values(symtab), arg_nodes, symtab) - begin - symtab.add!(arg_name, Var.new(arg_name, arg_type, arg.value(symtab))) - rescue SymbolTable::DuplicateSymError - type_error "Argument '#{arg_name}' shadows another symbol (level = #{symtab.levels})" - end - end - - v = func_def_type.body.return_value(symtab) - ensure - symtab.pop + template_values = [] + template_arg_nodes.each_with_index do |targ, idx| + # targ_name = func_def_type.template_names[idx] + # targ_type = func_def_type.template_types[idx] + template_values << targ.value(symtab) end - v + func_def_type.return_value(template_values, arg_nodes, symtab) end alias execute value def name function_name.text_value end - end # class ExecutionAst < AstNode @@ -2747,11 +2729,13 @@ def statements # @!macro type_check def type_check(symtab) + internal_error "Function bodies should be at global + 1 scope" unless symtab.levels == 2 + return_value_might_be_known = true statements.each do |s| s.type_check(symtab) - next if return_value_might_be_known + next unless return_value_might_be_known begin if s.is_a?(Returns) @@ -2771,6 +2755,8 @@ def type_check(symtab) # # @note arguments and template arguments must be put on the symtab before calling def return_value(symtab) + internal_error "Function bodies should be at global + 1 scope" unless symtab.levels == 2 + # go through the statements, and return the first one that has a return value statements.each do |s| if s.is_a?(Returns) @@ -2781,27 +2767,10 @@ def return_value(symtab) end end - internal_error "No function body statement returned a value" + value_error "No function body statement returned a value" end alias execute return_value - # @return [Array] An array of all possible return values - # - # @note arguments and template arguments must be put on the symtab before calling - def return_values(symtab) - # go through the statements, and find all return values - rts = [] - statements.each do |s| - if s.is_a?(Returns) - rts += s.return_values(symtab) - else - s.execute(symtab) - end - end - - rts - end - def to_idl result = "" # go through the statements, and return the first one that has a return value @@ -2866,21 +2835,12 @@ def arguments_list_str list end - # return an array of all the return types, in order - # function (or template instance) must be resolved - def return_types(symtab) - t = return_type(symtab) - if t.kind == :tuple - t.tuple_types - elsif t.kind == :void - [] - else - [t] - end - end - # return the return type, which may be a tuple of multiple types def return_type(symtab) + unless symtab.levels == 2 + internal_error "Function bodies should be at global + 1 scope (at global + #{symtab.levels - 1})" + end + if templated? template_names.each do |tname| internal_error "Template values missing" unless symtab.get(tname) @@ -2930,6 +2890,8 @@ def name # @param [Array] template values to apply def type_check_template_instance(symtab) + internal_error "Function definitions should be at global + 1 scope" unless symtab.levels == 2 + internal_error "Not a template function" unless templated? template_names.each do |tname| @@ -2945,6 +2907,8 @@ def type_check_template_instance(symtab) # uncalled functions, which avoids dealing with mentions of CSRs that # may not exist in a given implmentation def type_check_from_call(symtab) + internal_error "Function definitions should be at global + 1 scope" unless symtab.levels == 2 + global_scope = symtab.deep_clone global_scope.pop while global_scope.levels != 1 @@ -2965,7 +2929,7 @@ def type_check(symtab) def_type = FunctionType.new( name, self, - symtab.deep_clone + symtab ) # recursion isn't supported (doesn't map well to hardware), so we can add the function after type checking the body @@ -3104,7 +3068,6 @@ def return_value(symtab) v = s.s.return_value(symtab) unless v.nil? symtab.pop - puts "value = #{v}" return v end else diff --git a/lib/idl/symbol_table.rb b/lib/idl/symbol_table.rb index 0e8d272a4..1f6cc03e2 100644 --- a/lib/idl/symbol_table.rb +++ b/lib/idl/symbol_table.rb @@ -7,7 +7,7 @@ module Idl class Var attr_reader :name, :type, :value - def initialize(name, type, value = nil, decode_var: false) + def initialize(name, type, value = nil, decode_var: false, template_index: nil, function_name: nil) @name = name raise 'unexpected' unless type.is_a?(Type) @@ -16,6 +16,8 @@ def initialize(name, type, value = nil, decode_var: false) raise 'unexpected' unless decode_var.is_a?(TrueClass) || decode_var.is_a?(FalseClass) @decode_var = decode_var + @template_index = template_index + @function_name = function_name end def clone @@ -35,6 +37,24 @@ def decode_var? @decode_var end + # @param function_name [#to_s] A function name + # @return [Boolean] whether or not this variable is a function template argument from a call site for the function 'function_name' + def template_value_for?(function_name) + !@template_index.nil? && (function_name.to_s == @function_name) + end + + # @return [Integer] the template value position + # @raise if Var is not a template value + def template_index + raise "Not a template value" if @template_index.nil? + + @template_index + end + + def template_val? + !@template_index.nil? + end + def to_cxx @name end @@ -125,6 +145,7 @@ def initialize(arch_def) add!('ExtensionName', EnumerationType.new('ExtensionName', arch_def.extensions.map(&:name), Array.new(arch_def.extensions.size) { |i| i + 1 })) end + # pushes a new scope def push # puts "push #{caller[0]}" # @scope_caller ||= [] @@ -132,18 +153,27 @@ def push @scopes << {} end + # pops the top of the scope stack def pop # puts "pop #{caller[0]}" # puts " from #{@scope_caller.pop}" - raise 'Error: popping the symbol table would remove global scope' if @scopes.size == 1 + raise "Error: popping the symbol table would remove global scope" if @scopes.size == 1 @scopes.pop end + # @return [Boolean] whether or not any symbol 'name' is defined at any level in the symbol table def key?(name) @scopes.each { |s| return true if s.key?(name) } end + def keys_pretty + @scopes.map { |s| s.map { |k, v| v.is_a?(Var) && v.template_val? ? "#{k} (template)" : k }} + end + + # searches the symbol table scope-by-scope to find 'name' + # + # @return [Object] A symbol named 'name', or nil if not found def get(name) @scopes.reverse_each do |s| return s[name] if s.key?(name) @@ -151,11 +181,57 @@ def get(name) nil end + def get_from(name, level) + raise ArgumentError, "level must be positive" unless level.positive? + + raise "There is no level #{level}" unless level < levels + + @scopes[0..level - 1].reverse_each do |s| + return s[name] if s.key?(name) + end + nil + end + + # @return [Object] the symbol named 'name' from global scope, or nil if not found + def get_global(name) + get_from(name, 1) + end + + # searches the symbol table scope-by-scope to find all entries for which the block returns true + # + # @param single_scope [Boolean] If true, stop searching more scope as soon as there are matches + # @yieldparam obj [Object] A object stored in the symbol table + # @yieldreturn [Boolean] Whether or not the object is the one you are looking for + # @return [Array] All matches + def find_all(single_scope: false, &block) + raise ArgumentError, "Block needed" unless block_given? + + raise ArgumentError, "Find block takes one argument" unless block.arity == 1 + + matches = [] + + @scopes.reverse_each do |s| + s.each_value do |v| + matches << v if yield v + end + break if single_scope && !matches.empty? + end + matches + end + + # add a new symbol at the outermost scope + # + # @param name [#to_s] Symbol name + # @param var [Object] Symbol object (usually a Var or a Type) def add(name, var) @scopes.last[name] = var end - # add, and make sure name is unique + # add a new symbol at the outermost scope, unless that symbol is already defined + # + # @param name [#to_s] Symbol name + # @param var [Object] Symbol object (usually a Var or a Type) + # @raise [DuplicationSymError] if 'name' is already in the symbol table def add!(name, var) raise DuplicateSymError, "Symbol #{name} already defined as #{get(name)}" unless @scopes.select { |h| h.key? name }.empty? @@ -180,10 +256,12 @@ def add_at!(level, name, var) @scopes[level][name] = var end + # @return [Integer] Number of scopes on the symbol table (global at 1) def levels @scopes.size end + # pretty-print the symbol table contents def print @scopes.each do |s| s.each do |name, obj| @@ -192,7 +270,7 @@ def print end end - # return a deep clone of this SymbolTable + # @return [SymbolTable] a deep clone of this SymbolTable def deep_clone(clone_values: false) copy = SymbolTable.new(@archdef) copy.instance_variable_set(:@scopes, []) diff --git a/lib/idl/type.rb b/lib/idl/type.rb index 62adbff94..136d65fa2 100644 --- a/lib/idl/type.rb +++ b/lib/idl/type.rb @@ -421,7 +421,7 @@ class FunctionType < Type def initialize(func_name, func_def_ast, symtab) super(:function, name: func_name) @func_def_ast = func_def_ast - @symtab = symtab.deep_clone + @symtab = symtab raise "symtab should be at level 1" unless symtab.levels == 1 end @@ -435,14 +435,19 @@ def builtin? = @func_def_ast.builtin? def num_args = @func_def_ast.num_args def type_check_call(template_values = []) - raise "Missing template values" if templated? and template_values.empty? + raise "Missing template values" if templated? && template_values.empty? if templated? symtab = apply_template_values(template_values) @func_def_ast.type_check_template_instance(symtab) else - @func_def_ast.type_check_from_call(@symtab) + symtab = @symtab.deep_clone + symtab.pop while symtab.levels != 1 + + symtab.push # to keep things consistent with template functions, push a scope + + @func_def_ast.type_check_from_call(symtab) end end @@ -458,11 +463,16 @@ def apply_template_values(template_values = []) raise "wrong number of template values" unless template_names.size == template_values.size symtab = @symtab.deep_clone + symtab.pop while symtab.levels != 1 + + raise "Symbol table should be at global scope" unless symtab.levels == 1 symtab.push template_values.each_with_index do |value, idx| - symtab.add!(template_names[idx], Var.new(template_names[idx], template_types[idx], value)) + raise "template value should be an Integer" unless value.is_a?(Integer) + + symtab.add!(template_names[idx], Var.new(template_names[idx], template_types[idx], value, template_index: idx, function_name: @func_def_ast.name)) end symtab end @@ -482,16 +492,22 @@ def apply_arguments(symtab, argument_nodes, call_site_symtab) end # @param template_values [Array] Template values for the call, in declaration order - # @param argument_nodes [Array] Arguments at the call site # @param call_site_symtab [SymbolTable] Symbol table at the call site # return [Type] type of the call return - def return_type(template_values, argument_nodes, call_site_symtab) + def return_type(template_values) symtab = apply_template_values(template_values) - apply_arguments(symtab, argument_nodes, call_site_symtab) + # apply_arguments(symtab, argument_nodes, call_site_symtab) @func_def_ast.return_type(symtab).clone end + def return_value(template_values, argument_nodes, call_site_symtab) + symtab = apply_template_values(template_values) + apply_arguments(symtab, argument_nodes, call_site_symtab) + + @func_def_ast.body.return_value(symtab) + end + # @param template_values [Array] Template values to apply, required if {#templated?} # @return [Array] return types def return_types(template_values, argument_nodes, call_site_symtab) diff --git a/tasks/adoc_gen.rake b/tasks/adoc_gen.rake index a7baaff86..4ad6a52f2 100644 --- a/tasks/adoc_gen.rake +++ b/tasks/adoc_gen.rake @@ -42,7 +42,7 @@ require_relative "../lib/arch_def" File.write(path, erb.result(binding)) end when "func" - isa_def = arch_def.isa_def + isa_def = arch_def.global_ast path = dir_path / "funcs.adoc" File.write(path, erb.result(binding)) else diff --git a/tasks/arch_gen.rake b/tasks/arch_gen.rake index f25fba035..eb9c84ea9 100644 --- a/tasks/arch_gen.rake +++ b/tasks/arch_gen.rake @@ -40,6 +40,7 @@ rule %r{#{$root}/\.stamps/arch-gen-.*\.stamp} => proc { |tname| [ "#{$root}/.stamps", "#{$root}/lib/arch_gen.rb", + "#{$root}/lib/idl/ast.rb", "#{$root}/tasks/arch_gen.rake", "ext/riscv-opcodes/instr_dict.yaml" ] + arch_files + config_files