diff --git a/Gemfile b/Gemfile index 3916f9ac..8ad68501 100644 --- a/Gemfile +++ b/Gemfile @@ -23,4 +23,5 @@ group :development do gem "solargraph" gem 'rubocop-minitest' gem 'ruby-prof' + gem "ruby-prof-flamegraph" end diff --git a/Gemfile.lock b/Gemfile.lock index 5ebc2cea..f2664dee 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -107,7 +107,9 @@ GEM rubocop-minitest (0.35.1) rubocop (>= 1.61, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) - ruby-prof (1.7.0) + ruby-prof (0.18.0) + ruby-prof-flamegraph (0.3.0) + ruby-prof (~> 0.13) ruby-progressbar (1.13.0) ruby-rc4 (0.1.5) simpleidn (0.2.3) @@ -152,6 +154,7 @@ DEPENDENCIES rouge rubocop-minitest ruby-prof + ruby-prof-flamegraph ruby-progressbar (~> 1.13) solargraph treetop (= 1.6.12) diff --git a/Rakefile b/Rakefile index 4636aee0..d960a3fc 100644 --- a/Rakefile +++ b/Rakefile @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "etc" + $root = Pathname.new(__FILE__).dirname.realpath $lib = $root / "lib" @@ -82,45 +84,84 @@ namespace :validate do end puts "All files validate against their schema" end - task idl: "gen:arch" do - puts "Type checking IDL code..." - arch_def = arch_def_for("_") - progressbar = ProgressBar.create(title: "Instructions", total: arch_def.instructions.size) - arch_def.instructions.each do |inst| - progressbar.increment - inst.type_checked_operation_ast(arch_def.idl_compiler, arch_def.sym_table_32, 32) if inst.rv32? - inst.type_checked_operation_ast(arch_def.idl_compiler, arch_def.sym_table_64, 64) if inst.rv64? - # also need to check for an RV64 machine running with effective XLEN of 32 - inst.type_checked_operation_ast(arch_def.idl_compiler, arch_def.sym_table_64, 32) if inst.rv64? && inst.rv32? - end - progressbar = ProgressBar.create(title: "CSRs", total: arch_def.csrs.size) - arch_def.csrs.each do |csr| - progressbar.increment - if csr.has_custom_sw_read? - csr.type_checked_sw_read_ast(arch_def.sym_table_32) if csr.defined_in_base32? - csr.type_checked_sw_read_ast(arch_def.sym_table_64) if csr.defined_in_base64? - end - csr.fields.each do |field| - unless field.type_ast(arch_def.idl_compiler).nil? - field.type_checked_type_ast(arch_def.sym_table_32) if csr.defined_in_base32? && field.defined_in_base32? - field.type_checked_type_ast(arch_def.sym_table_64) if csr.defined_in_base64? && field.defined_in_base64? - end - unless field.reset_value_ast(arch_def.idl_compiler).nil? - field.type_checked_reset_value_ast(arch_def.sym_table_32) if csr.defined_in_base32? && field.defined_in_base32? - field.type_checked_reset_value_ast(arch_def.sym_table_64) if csr.defined_in_base64? && field.defined_in_base64? - end - unless field.sw_write_ast(arch_def.idl_compiler).nil? - field.type_checked_sw_write_ast(arch_def.sym_table_32, 32) if csr.defined_in_base32? && field.defined_in_base32? - field.type_checked_sw_write_ast(arch_def.sym_table_64, 64) if csr.defined_in_base64? && field.defined_in_base64? - end - end - end - progressbar = ProgressBar.create(title: "Functions", total: arch_def.functions.size) - arch_def.functions.each do |func| - progressbar.increment - func.type_check_body(arch_def.sym_table_32) - func.type_check_body(arch_def.sym_table_64) - end + task idl: ["gen:arch", "#{$root}/.stamps/arch-gen-_32.stamp", "#{$root}/.stamps/arch-gen-_64.stamp"] do + print "Parsing IDL code for RV32..." + arch_def_32 = arch_def_for("_32") + puts "done" + + arch_def_32.type_check + + print "Parsing IDL code for RV64..." + arch_def_64 = arch_def_for("_64") + puts "done" + + arch_def_64.type_check + + # arch_def_64 = arch_def_for("_64") + # arch_def_64.type_check + + # puts "Type checking IDL code..." + # progressbar = ProgressBar.create(title: "Instructions", total: arch_def_32.instructions.size + arch_def_64.instructions.size) + # arch_def_32.instructions.each do |inst| + # progressbar.increment + # inst.type_checked_operation_ast(arch_def_32.idl_compiler, arch_def_32.symtab, 32) if inst.rv32? + # end + # arch_def_64.instructions.each do |inst| + # progressbar.increment + # inst.type_checked_operation_ast(arch_def_64.idl_compiler, arch_def_64.symtab, 64) if inst.rv64? + # # also need to check for an RV64 machine running with effective XLEN of 32 + # inst.type_checked_operation_ast(arch_def_64.idl_compiler, arch_def_64.symtab, 32) if inst.rv64? && inst.rv32? + # end + + # progressbar = ProgressBar.create(title: "CSRs", total: arch_def_32.csrs.size + arch_def_64.csrs.size) + # arch_def_32.csrs.each do |csr| + # progressbar.increment + # profile = RubyProf::Profile.new + # result = profile.profile do + # if csr.has_custom_sw_read? + # csr.type_checked_sw_read_ast(arch_def_32.symtab) if csr.defined_in_base32? + # end + # csr.fields.each do |field| + # unless field.type_ast(arch_def_32.symtab).nil? + # field.type_checked_type_ast(arch_def_32.symtab) if csr.defined_in_base32? && field.defined_in_base32? + # end + # unless field.reset_value_ast(arch_def_32.symtab).nil? + # field.type_checked_reset_value_ast(arch_def_32.symtab) if csr.defined_in_base32? && field.defined_in_base32? + # end + # unless field.sw_write_ast(arch_def_32.symtab).nil? + # field.type_checked_sw_write_ast(arch_def_32.symtab, 32) if csr.defined_in_base32? && field.defined_in_base32? + # end + # end + # end + # RubyProf::GraphHtmlPrinter.new(result).print(File.open("#{csr.name}-prof.html", "w+"), {}) + # end + # arch_def_64.csrs.each do |csr| + # progressbar.increment + # if csr.has_custom_sw_read? + # csr.type_checked_sw_read_ast(arch_def_64.symtab) if csr.defined_in_base64? + # end + # csr.fields.each do |field| + # unless field.type_ast(arch_def_64.symtab).nil? + # field.type_checked_type_ast(arch_def_64.symtab) if csr.defined_in_base64? && field.defined_in_base64? + # end + # unless field.reset_value_ast(arch_def_64.symtab).nil? + # field.type_checked_reset_value_ast(arch_def_64.symtab) if csr.defined_in_base64? && field.defined_in_base64? + # end + # unless field.sw_write_ast(arch_def_64.symtab).nil? + # field.type_checked_sw_write_ast(arch_def_64.symtab, 32) if csr.defined_in_base32? && field.defined_in_base32? + # field.type_checked_sw_write_ast(arch_def_64.symtab, 64) if csr.defined_in_base64? && field.defined_in_base64? + # end + # end + # end + # progressbar = ProgressBar.create(title: "Functions", total: arch_def_32.functions.size + arch_def_64.functions.size) + # arch_def_32.functions.each do |func| + # progressbar.increment + # func.type_check(arch_def_32.symtab) + # end + # arch_def_64.functions.each do |func| + # progressbar.increment + # func.type_check(arch_def_64.symtab) + # end puts "All IDL passed type checking" end end diff --git a/arch/csr/H/htinst.yaml b/arch/csr/H/htinst.yaml new file mode 100644 index 00000000..1f8752aa --- /dev/null +++ b/arch/csr/H/htinst.yaml @@ -0,0 +1,42 @@ +# yaml-language-server: $schema=../../../schemas/csr_schema.json + +htinst: + address: 0x64a + long_name: Hypervisor Trap Instruction Register + description: | + When a trap is taken into HS-mode, mtinst is written with a value that, if nonzero, + provides information about the instruction that trapped, to assist software in handling the trap. + The values that may be written to mtinst on a trap are documented in TODO. + + htinst is a WARL register that need only be able to hold the values that the implementation may automatically write to it on a trap. + priv_mode: S + length: SXLEN + definedBy: H + fields: + VALUE: + location_rv64: 63-0 + location_rv32: 31-0 + type(): | + if ( (TINST_VALUE_ON_FINAL_LOAD_GUEST_PAGE_FAULT != "always zero") + || (TINST_VALUE_ON_FINAL_STORE_AMO_GUEST_PAGE_FAULT != "always zero") + || (TINST_VALUE_ON_FINAL_INSTRUCTION_GUEST_PAGE_FAULT != "always zero") + || (TINST_VALUE_ON_INSTRUCTION_ADDRESS_MISALIGNED != "always zero") + || (TINST_VALUE_ON_BREAKPOINT != "always zero") + || (TINST_VALUE_ON_VIRTUAL_INSTRUCTION != "always zero") + || (TINST_VALUE_ON_LOAD_ADDRESS_MISALIGNED != "always zero") + || (TINST_VALUE_ON_LOAD_ACCESS_FAULT != "always zero") + || (TINST_VALUE_ON_STORE_AMO_ADDRESS_MISALIGNED != "always zero") + || (TINST_VALUE_ON_STORE_AMO_ACCESS_FAULT != "always_zero") + || (TINST_VALUE_ON_UCALL != "always zero") + || (TINST_VALUE_ON_SCALL != "always zero") + || (TINST_VALUE_ON_MCALL != "always zero") + || (TINST_VALUE_ON_VSCALL != "always zero") + || (TINST_VALUE_ON_LOAD_PAGE_FAULT != "always zero") + || (TINST_VALUE_ON_STORE_AMO_PAGE_FAULT != "always zero")) { + return CsrFieldType::RWH; + } else { + return CsrFieldType::RO; + } + description: | + Exception-speicific information for a trap into HS-mode. + reset_value: UNDEFINED_LEGAL diff --git a/arch/csr/H/htval.yaml b/arch/csr/H/htval.yaml new file mode 100644 index 00000000..c2fcc250 --- /dev/null +++ b/arch/csr/H/htval.yaml @@ -0,0 +1,34 @@ +# yaml-language-server: $schema=../../../schemas/csr_schema.json + +htval: + address: 0x643 + long_name: Hypervisor Trap Value Register + description: | + When a trap is taken into HS-mode, htval is written with additional exception-specific information, alongside stval, to assist software in handling the trap. + + When a guest-page-fault trap is taken into HS-mode, htval is written with either zero or the guest physical address that faulted, shifted right by 2 bits. For other traps, htval is set to zero, but a future standard or extension may redefine htval's setting for other traps. + + A guest-page fault may arise due to an implicit memory access during first-stage (VS-stage) address translation, in which case a guest physical address written to htval is that of the implicit memory access that faulted-for example, the address of a VS-level page table entry that could not be read. (The guest physical address corresponding to the original virtual address is unknown when VS-stage translation fails to complete.) Additional information is provided in CSR htinst to disambiguate such situations. + + Otherwise, for misaligned loads and stores that cause guest-page faults, a nonzero guest physical address in htval corresponds to the faulting portion of the access as indicated by the virtual address in stval. For instruction guest-page faults on systems with variable-length instructions, a nonzero htval corresponds to the faulting portion of the instruction as indicated by the virtual address in stval. + + htval is a WARL register that must be able to hold zero and may be capable of holding only an arbitrary subset of other 2-bit-shifted guest physical addresses, if any. + priv_mode: M + length: MXLEN + definedBy: H + fields: + VALUE: + location_rv64: 63-0 + location_rv32: 31-0 + type(): | + if (REPORT_GPA_IN_TVAL_ON_LOAD_GUEST_PAGE_FAULT + || REPORT_GPA_IN_TVAL_ON_STORE_AMO_GUEST_PAGE_FAULT + || REPORT_GPA_IN_TVAL_ON_INSTRUCTION_GUEST_PAGE_FAULT + || REPORT_GPA_IN_TVAL_ON_INTERMEDIATE_GUEST_PAGE_FAULT) { + return CsrFieldType::RWH; + } else { + return CsrFieldType::RO; + } + description: | + Exception-speicific information for a trap into M-mode. + reset_value: UNDEFINED_LEGAL diff --git a/arch/csr/H/mtinst.yaml b/arch/csr/H/mtinst.yaml new file mode 100644 index 00000000..ff3f34ed --- /dev/null +++ b/arch/csr/H/mtinst.yaml @@ -0,0 +1,42 @@ +# yaml-language-server: $schema=../../../schemas/csr_schema.json + +mtinst: + address: 0x34a + long_name: Machine Trap Instruction Register + description: | + When a trap is taken into M-mode, mtinst is written with a value that, if nonzero, + provides information about the instruction that trapped, to assist software in handling the trap. + The values that may be written to mtinst on a trap are documented in TODO. + + mtinst is a WARL register that need only be able to hold the values that the implementation may automatically write to it on a trap. + priv_mode: M + length: MXLEN + definedBy: H + fields: + VALUE: + location_rv64: 63-0 + location_rv32: 31-0 + type(): | + if ( (TINST_VALUE_ON_FINAL_LOAD_GUEST_PAGE_FAULT != "always zero") + || (TINST_VALUE_ON_FINAL_STORE_AMO_GUEST_PAGE_FAULT != "always zero") + || (TINST_VALUE_ON_FINAL_INSTRUCTION_GUEST_PAGE_FAULT != "always zero") + || (TINST_VALUE_ON_INSTRUCTION_ADDRESS_MISALIGNED != "always zero") + || (TINST_VALUE_ON_BREAKPOINT != "always zero") + || (TINST_VALUE_ON_VIRTUAL_INSTRUCTION != "always zero") + || (TINST_VALUE_ON_LOAD_ADDRESS_MISALIGNED != "always zero") + || (TINST_VALUE_ON_LOAD_ACCESS_FAULT != "always zero") + || (TINST_VALUE_ON_STORE_AMO_ADDRESS_MISALIGNED != "always zero") + || (TINST_VALUE_ON_STORE_AMO_ACCESS_FAULT != "always_zero") + || (TINST_VALUE_ON_UCALL != "always zero") + || (TINST_VALUE_ON_SCALL != "always zero") + || (TINST_VALUE_ON_MCALL != "always zero") + || (TINST_VALUE_ON_VSCALL != "always zero") + || (TINST_VALUE_ON_LOAD_PAGE_FAULT != "always zero") + || (TINST_VALUE_ON_STORE_AMO_PAGE_FAULT != "always zero")) { + return CsrFieldType::RWH; + } else { + return CsrFieldType::RO; + } + description: | + Exception-speicific information for a trap into M-mode. + reset_value: UNDEFINED_LEGAL diff --git a/arch/csr/H/mtval2.yaml b/arch/csr/H/mtval2.yaml new file mode 100644 index 00000000..a8022852 --- /dev/null +++ b/arch/csr/H/mtval2.yaml @@ -0,0 +1,35 @@ +# yaml-language-server: $schema=../../../schemas/csr_schema.json + +mtval2: + address: 0x34b + long_name: Machine Second Trap Value Register + description: | + When a trap is taken into M-mode from a virtual mode, mtval2 is written with additional exception-specific information, + alongside mtval, to assist software in handling the trap. + + When a guest-page-fault trap is taken into M-mode, mtval2 is written with either zero or the guest physical address that faulted, shifted right by 2 bits. For other traps, mtval2 is set to zero, but a future standard or extension may redefine mtval2's setting for other traps. + + If a guest-page fault is due to an implicit memory access during first-stage (VS-stage) address translation, a guest physical address written to mtval2 is that of the implicit memory access that faulted. Additional information is provided in CSR mtinst to disambiguate such situations. + + Otherwise, for misaligned loads and stores that cause guest-page faults, a nonzero guest physical address in mtval2 corresponds to the faulting portion of the access as indicated by the virtual address in mtval. For instruction guest-page faults on systems with variable-length instructions, a nonzero mtval2 corresponds to the faulting portion of the instruction as indicated by the virtual address in mtval. + + mtval2 is a WARL register that must be able to hold zero and may be capable of holding only an arbitrary subset of other 2-bit-shifted guest physical addresses, if any. + priv_mode: M + length: MXLEN + definedBy: H + fields: + VALUE: + location_rv64: 63-0 + location_rv32: 31-0 + type(): | + if (REPORT_GPA_IN_TVAL_ON_LOAD_GUEST_PAGE_FAULT + || REPORT_GPA_IN_TVAL_ON_STORE_AMO_GUEST_PAGE_FAULT + || REPORT_GPA_IN_TVAL_ON_INSTRUCTION_GUEST_PAGE_FAULT + || REPORT_GPA_IN_TVAL_ON_INTERMEDIATE_GUEST_PAGE_FAULT) { + return CsrFieldType::RWH; + } else { + return CsrFieldType::RO; + } + description: | + Exception-speicific information for a trap into M-mode. + reset_value: UNDEFINED_LEGAL diff --git a/arch/csr/H/vsatp.yaml b/arch/csr/H/vsatp.yaml index 20be306f..e70d506d 100644 --- a/arch/csr/H/vsatp.yaml +++ b/arch/csr/H/vsatp.yaml @@ -1,4 +1,4 @@ -# yaml-language-server: $schema=../../schemas/csr_schema.json +# yaml-language-server: $schema=../../../schemas/csr_schema.json vsatp: address: 0x280 @@ -94,7 +94,7 @@ vsatp: return UNDEFINED_LEGAL_DETERMINISTIC; } } else { - XReg shamt = (CSR[mstatus].SXL == $bits(XRegWidth::XLEN64)) ? 16 : 9; + XReg shamt = ((XLEN == 32) || (CSR[mstatus].SXL == $bits(XRegWidth::XLEN32))) ? 9 : 16; XReg all_ones = ((1 << shamt) - 1); XReg largest_allowed_asid = (1 << shamt) - 1; diff --git a/arch/csr/cycleh.yaml b/arch/csr/cycleh.yaml index cdca452d..9bdc6793 100644 --- a/arch/csr/cycleh.yaml +++ b/arch/csr/cycleh.yaml @@ -27,7 +27,7 @@ cycleh: fields: COUNT: location: 31-0 - alias: mcycleh.COUNT + alias: mcycleh.COUNT[63:32] description: Alias of `mcycleh.COUNT`. type: RO-H reset_value: UNDEFINED_LEGAL diff --git a/arch/csr/misa.yaml b/arch/csr/misa.yaml index e578bc28..d82e3975 100644 --- a/arch/csr/misa.yaml +++ b/arch/csr/misa.yaml @@ -90,7 +90,7 @@ misa: G: location: 6 description: | - Indicates support for all of the following extensions: `I`, `A`, `M`, `F`, 'D'. + Indicates support for all of the following extensions: `I`, `A`, `M`, `F`, `D`. type(): | if ((implemented?(ExtensionName::A) && MUTABLE_MISA_A) || (implemented?(ExtensionName::M) && MUTABLE_MISA_M) || diff --git a/arch/csr/satp.yaml b/arch/csr/satp.yaml index 4b0b43d8..28906d04 100644 --- a/arch/csr/satp.yaml +++ b/arch/csr/satp.yaml @@ -119,7 +119,7 @@ satp: return UNDEFINED_LEGAL_DETERMINISTIC; } } else { - XReg shamt = (CSR[mstatus].SXL == $bits(XRegWidth::XLEN64)) ? 16 : 9; + XReg shamt = (XLEN == 32 || (CSR[mstatus].SXL == $bits(XRegWidth::XLEN32))) ? 9 : 16; XReg all_ones = ((1 << shamt) - 1); XReg largest_allowed_asid = (1 << shamt) - 1; diff --git a/arch/ext/Sm.yaml b/arch/ext/Sm.yaml index c6c00b64..1925b799 100644 --- a/arch/ext/Sm.yaml +++ b/arch/ext/Sm.yaml @@ -41,11 +41,11 @@ Sm: page-based virtual memory, even if VM is not currently enabled. - Removed the N extension. - Defined the mandatory RV32-only CSR `mstatush`, which contains most of - the same fields as the upper 32 bits of RV64’s `mstatus`. + the same fields as the upper 32 bits of RV64's `mstatus`. - Defined the mandatory CSR `mconfigptr`, which if nonzero contains the address of a configuration data structure. - Defined optional `mseccfg` and `mseccfgh` CSRs, which control the - machine’s security configuration. + machine's security configuration. - Defined `menvcfg` CSR (and RV32-only `menvcfgh`), which control various characteristics of the execution environment. - Designated part of SYSTEM major opcode for custom use. diff --git a/arch/ext/Svnapot.yaml b/arch/ext/Svnapot.yaml new file mode 100644 index 00000000..14cf6cd6 --- /dev/null +++ b/arch/ext/Svnapot.yaml @@ -0,0 +1,173 @@ +# yaml-language-server: $schema=../../schemas/ext_schema.json + +Svnapot: + long_name: Naturally-aligned Power of Two Translation Contiguity + description: | + In Sv39, Sv48, and Sv57, when a PTE has N=1, the PTE represents a + translation that is part of a range of contiguous virtual-to-physical + translations with the same values for PTE bits 5-0. Such ranges must be + of a naturally aligned power-of-2 (NAPOT) granularity larger than the + base page size. + + The Svnapot extension depends on Sv39. + + [[ptenapot]] + .Page table entry encodings when __pte__.N=1 + [%autowidth,float="center",align="center",cols="^,^,<,^",options="header"] + |=== + |i |_pte_._ppn_[_i_] |Description |_pte_.__napot_bits__ + |0 + + 0 + + 0 + + 0 + + 0 + + ≥1 + |`x xxxx xxx1` + + `x xxxx xx1x` + + `x xxxx x1xx` + + `x xxxx 1000` + + `x xxxx 0xxx` + + `x xxxx xxxx` + |_Reserved_ + + _Reserved_ + + _Reserved_ + + 64 KiB contiguous region + + _Reserved_ + + _Reserved_ + | - + + - + + - + + 4 + + - + + - + |=== + + NAPOT PTEs behave identically to non-NAPOT PTEs within the + address-translation algorithm in <>, + except that: + + * If the encoding in _pte_ is valid according to + <>, then instead of returning the original + value of _pte_, implicit reads of a NAPOT PTE return a copy + of _pte_ in which __pte__.__ppn__[__i__][__pte__.__napot_bits__-1:0] is replaced by + __vpn__[__i__][__pte__.__napot_bits__-1:0]. If the encoding in _pte_ is reserved according to + <>, then a page-fault exception must be raised. + * Implicit reads of NAPOT page table entries may create + address-translation cache entries mapping + _a_ + _j_*PTESIZE to a copy of _pte_ in which _pte_._ppn_[_i_][_pte_.__napot_bits__-1:0] + is replaced by _vpn[i][pte.napot_bits_-1:0], for any or all _j_ such that + __j__ >> __napot_bits__ = __vpn__[__i__] >> __napot_bits__, all for the address space identified in _satp_ as loaded by step 1. + + [NOTE] + ==== + The motivation for a NAPOT PTE is that it can be cached in a TLB as one + or more entries representing the contiguous region as if it were a + single (large) page covered by a single translation. This compaction can + help relieve TLB pressure in some scenarios. The encoding is designed to + fit within the pre-existing Sv39, Sv48, and Sv57 PTE formats so as not + to disrupt existing implementations or designs that choose not to + implement the scheme. It is also designed so as not to complicate the + definition of the address-translation algorithm. + + The address translation cache abstraction captures the behavior that + would result from the creation of a single TLB entry covering the entire + NAPOT region. It is also designed to be consistent with implementations + that support NAPOT PTEs by splitting the NAPOT region into TLB entries + covering any smaller power-of-two region sizes. For example, a 64 KiB + NAPOT PTE might trigger the creation of 16 standard 4 KiB TLB entries, + all with contents generated from the NAPOT PTE (even if the PTEs for the + other 4 KiB regions have different contents). + + In typical usage scenarios, NAPOT PTEs in the same region will have the + same attributes, same PPNs, and same values for bits 5-0. RSW remains + reserved for supervisor software control. It is the responsibility of + the OS and/or hypervisor to configure the page tables in such a way that + there are no inconsistencies between NAPOT PTEs and other NAPOT or + non-NAPOT PTEs that overlap the same address range. If an update needs + to be made, the OS generally should first mark all of the PTEs invalid, + then issue SFENCE.VMA instruction(s) covering all 4 KiB regions within + the range (either via a single SFENCE.VMA with _rs1_=`x0`, or with + multiple SFENCE.VMA instructions with _rs1_≠`x0`), then update the PTE(s), as described in <>, unless any inconsistencies are known to be benign. If any inconsistencies do exist, then the effect is the same as when SFENCE.VMA + is used incorrectly: one of the translations will be chosen, but the + choice is unpredictable. + + If an implementation chooses to use a NAPOT PTE (or cached version + thereof), it might not consult the PTE directly specified by the + algorithm in <> at all. Therefore, the D + and A bits may not be identical across all mappings of the same address + range even in typical use cases The operating system must query all + NAPOT aliases of a page to determine whether that page has been accessed + and/or is dirty. If the OS manually sets the A and/or D bits for a page, + it is recommended that the OS also set the A and/or D bits for other + NAPOT aliases as appropriate in order to avoid unnecessary traps. + + Just as with normal PTEs, TLBs are permitted to cache NAPOT PTEs whose V + (Valid) bit is clear. + + Depending on need, the NAPOT scheme may be extended to other + intermediate page sizes and/or to other levels of the page table in the + future. The encoding is designed to accommodate other NAPOT sizes should + that need arise. For example: + + __ + + [%autowidth,float="center",align="center",cols="^,^,<,^",options="header"] + |=== + |i |_pte_._ppn_[_i_] |Description |_pte_.__napot_bits__ + |0 + + 0 + + 0 + + 0 + + 0 + + ... + + 1 + + 1 + + ... + |`x xxxx xxx1` + + `x xxxx xx10` + + `x xxxx x100` + + `x xxxx 1000` + + `x xxx1 0000` + + ... + + `x xxxx xxx1` + + `x xxxx xx10` + + ... + |8 KiB contiguous region + + 16 KiB contiguous region + + 32 KiB contiguous region + + 64 KiB contiguous region + + 128 KiB contiguous region + + ... + + 4 MiB contiguous region + + 8 MiB contiguous region + + ... + | 1 + + 2 + + 3 + + 4 + + 5 + + ... + + 1 + + 2 + + ... + |=== + + In such a case, an implementation may or may not support all options. + The discoverability mechanism for this extension would be extended to + allow system software to determine which sizes are supported. + + Other sizes may remain deliberately excluded, so that PPN bits not being + used to indicate a valid NAPOT region size (e.g., the least-significant + bit of _pte_._ppn_[_i_]) may be repurposed for other uses in the + future. + + However, in case finer-grained intermediate page size support proves not + to be useful, we have chosen to standardize only 64 KiB support as a + first step. + ==== + versions: + - version: "1.0.0" + state: ratified + ratification_date: 2021-11 + requires: + name: Sv39 diff --git a/arch/isa/builtin_functions.idl b/arch/isa/builtin_functions.idl index cf65a6bc..1af75556 100644 --- a/arch/isa/builtin_functions.idl +++ b/arch/isa/builtin_functions.idl @@ -245,9 +245,9 @@ function nan_box { of smaller size by adding all 1's to the upper bits. } body { - assert(FROM_SIZE > TO_SIZE, "Bad template arugments; FROM_SIZE must be less than TO_SIZE"); + assert(FROM_SIZE < TO_SIZE, "Bad template arugments; FROM_SIZE must be less than TO_SIZE"); - return {{FROM_SIZE - TO_SIZE{1'b1}}, sp_value}; + return {{TO_SIZE - FROM_SIZE{1'b1}}, from_value}; } } Bits<32> SP_POS_INF = 32'b0_11111111_00000000000000000000000; diff --git a/arch/isa/globals.isa b/arch/isa/globals.isa index deab65bd..fb08d280 100644 --- a/arch/isa/globals.isa +++ b/arch/isa/globals.isa @@ -277,15 +277,15 @@ function exception_handling_mode { # mode is M, the value of medeleg is irrelevant return PrivilegeMode::M; } else if (implemented?(ExtensionName::S) && ((mode() == PrivilegeMode::HS) || (mode() == PrivilegeMode::U))) { - if ((CSR[medeleg] & (1 << $bits(exception_code))) != 0) { + if (($bits(CSR[medeleg]) & (1 << $bits(exception_code))) != 0) { return PrivilegeMode::HS; } else { return PrivilegeMode::M; } } else { assert(implemented?(ExtensionName::H) && ((mode() == PrivilegeMode::VS) || (mode() == PrivilegeMode::VU)), "Unexpected mode"); - if ((CSR[medeleg] & (1 << $bits(exception_code))) != 0) { - if ((CSR[hedeleg] & (1 << $bits(exception_code))) != 0) { + if (($bits(CSR[medeleg]) & (1 << $bits(exception_code))) != 0) { + if (($bits(CSR[hedeleg]) & (1 << $bits(exception_code))) != 0) { return PrivilegeMode::VS; } else { return PrivilegeMode::HS; @@ -306,7 +306,7 @@ function unimplemented_csr { } body { if (TRAP_ON_UNIMPLEMENTED_CSR) { - raise(ExceptionCode::IllegalInstruction, encoding); + raise(ExceptionCode::IllegalInstruction, mode(), encoding); } else { unpredictable("Accessing an unimplmented CSR"); } @@ -322,21 +322,18 @@ function mtval_readonly? { return !( REPORT_VA_IN_MTVAL_ON_BREAKPOINT || REPORT_VA_IN_MTVAL_ON_LOAD_MISALIGNED || - REPORT_VA_IN_MTVAL_ON_STORE_MISALIGNED || - (implemented?(ExtensionName::Zaamo) && REPORT_VA_IN_MTVAL_ON_AMO_MISALIGNED) || + REPORT_VA_IN_MTVAL_ON_STORE_AMO_MISALIGNED || REPORT_VA_IN_MTVAL_ON_INSTRUCTION_MISALIGNED || REPORT_VA_IN_MTVAL_ON_LOAD_ACCESS_FAULT || - REPORT_VA_IN_MTVAL_ON_STORE_ACCESS_FAULT || - (implemented?(ExtensionName::Zaamo) && REPORT_VA_IN_MTVAL_ON_AMO_ACCESS_FAULT) || + REPORT_VA_IN_MTVAL_ON_STORE_AMO_ACCESS_FAULT || REPORT_VA_IN_MTVAL_ON_INSTRUCTION_ACCESS_FAULT || REPORT_VA_IN_MTVAL_ON_LOAD_PAGE_FAULT || - REPORT_VA_IN_MTVAL_ON_STORE_PAGE_FAULT || - (implemented?(ExtensionName::Zaamo) && REPORT_VA_IN_MTVAL_ON_AMO_PAGE_FAULT) || + REPORT_VA_IN_MTVAL_ON_STORE_AMO_PAGE_FAULT || REPORT_VA_IN_MTVAL_ON_INSTRUCTION_PAGE_FAULT || REPORT_ENCODING_IN_MTVAL_ON_ILLEGAL_INSTRUCTION || REPORT_CAUSE_IN_MTVAL_ON_SHADOW_STACK_SOFTWARE_CHECK || - REPORT_CAUSE_IN_MTVAL_ON_LANDING_PAD_SOFTWARE_CHECK || - implemented?(ExtensionName::Sdext) + REPORT_CAUSE_IN_MTVAL_ON_LANDING_PAD_SOFTWARE_CHECK + # || implemented?(ExtensionName::Sdext) ); } } @@ -361,8 +358,8 @@ function stval_readonly? { REPORT_VA_IN_STVAL_ON_INSTRUCTION_PAGE_FAULT || REPORT_ENCODING_IN_STVAL_ON_ILLEGAL_INSTRUCTION || REPORT_CAUSE_IN_STVAL_ON_SHADOW_STACK_SOFTWARE_CHECK || - REPORT_CAUSE_IN_STVAL_ON_LANDING_PAD_SOFTWARE_CHECK || - implemented?(ExtensionName::Sdext) + REPORT_CAUSE_IN_STVAL_ON_LANDING_PAD_SOFTWARE_CHECK + # || implemented?(ExtensionName::Sdext) ); } else { return true; @@ -390,8 +387,8 @@ function vstval_readonly? { REPORT_VA_IN_VSTVAL_ON_INSTRUCTION_PAGE_FAULT || REPORT_ENCODING_IN_VSTVAL_ON_ILLEGAL_INSTRUCTION || REPORT_CAUSE_IN_VSTVAL_ON_SHADOW_STACK_SOFTWARE_CHECK || - REPORT_CAUSE_IN_VSTVAL_ON_LANDING_PAD_SOFTWARE_CHECK || - implemented?(ExtensionName::Sdext) + REPORT_CAUSE_IN_VSTVAL_ON_LANDING_PAD_SOFTWARE_CHECK + # || implemented?(ExtensionName::Sdext) ); } else { return true; @@ -524,7 +521,9 @@ function raise_guest_page_fault { arguments MemoryOperation op, # op type XReg gpa, # guest physical address - XReg gva # guest virtual address + XReg gva, # guest virtual address + XReg tinst_value, # value for *tinst + PrivilegeMode from_mode # effective privilege mode for reporting description { Raise a guest page fault exception. } @@ -539,7 +538,7 @@ function raise_guest_page_fault { code = ExceptionCode::StoreAmoGuestPageFault; write_gpa_in_tval = REPORT_GPA_IN_TVAL_ON_STORE_AMO_GUEST_PAGE_FAULT; } else { - assert(op == MemoryOperation::Fetch); + assert(op == MemoryOperation::Fetch, "unexpected memory operation"); code = ExceptionCode::InstructionGuestPageFault; write_gpa_in_tval = REPORT_GPA_IN_TVAL_ON_INSTRUCTION_GUEST_PAGE_FAULT; } @@ -548,7 +547,7 @@ function raise_guest_page_fault { if (handling_mode == PrivilegeMode::S) { CSR[htval].VALUE = write_gpa_in_tval ? (gpa >> 2) : 0; - CSR[htinst].VALUE = tinst_value(code); + CSR[htinst].VALUE = tinst_value; CSR[sepc].PC = $pc; if (!stval_readonly?()) { CSR[stval].VALUE = stval_for(code, gva); @@ -558,14 +557,18 @@ function raise_guest_page_fault { CSR[scause].CODE = $bits(code); CSR[hstatus].GVA = 1; CSR[hstatus].SPV = 1; # guest page faults always come from a virtual mode - CSR[hstatus].SPVP = $bits(mode()); + CSR[hstatus].SPVP = $bits(from_mode)[0]; CSR[mstatus].SPP = $bits(from_mode)[0]; } else { - assert(handling_mode == PrivilegeMode::M); + assert(handling_mode == PrivilegeMode::M, "unexpected privilege mode"); CSR[mtval2].VALUE = write_gpa_in_tval ? (gpa >> 2) : 0; - CSR[mtinst].VALUE = tinst_value(code); + CSR[mtinst].VALUE = tinst_value; CSR[mstatus].MPP = $bits(from_mode)[1:0]; - CSR[mstatus].MPV = 1; + if (XLEN == 64) { + CSR[mstatus].MPV = 1; + } else { + CSR[mstatush].MPV = 1; + } } # abort the current instruction, and start to refetch from PC @@ -614,16 +617,24 @@ function raise_precise { } $pc = {CSR[mtvec].BASE, 2'b00}; CSR[mcause].INT = 1'b0; - CSR[mcause].CAUSE = $bits(exception_code); + CSR[mcause].CODE = $bits(exception_code); if (CSR[misa].H == 1) { # write zero into mtval2 and minst # (when these are non-zero values, raise_guest_page_fault should be callecd) CSR[mtval2].VALUE = 0; - CSR[minst].VALUE = 0; + CSR[mtinst].VALUE = 0; if (from_mode == PrivilegeMode::VU || from_mode == PrivilegeMode::VS) { - CSR[mstatus].MPV = 1; + if (XLEN == 32) { + CSR[mstatush].MPV = 1; + } else { + CSR[mstatus].MPV = 1; + } } else { - CSR[mstatus].MPV = 0; + if (XLEN == 32) { + CSR[mstatush].MPV = 0; + } else { + CSR[mstatus].MPV = 0; + } } } CSR[mstatus].MPP = $bits(from_mode); @@ -640,11 +651,11 @@ function raise_precise { # write zero into htval and hinst # (when these are non-zero values, raise_guest_page_fault should be callecd) CSR[htval].VALUE = 0; - CSR[hinst].VALUE = 0; - CSR[mstatus].SPV = $bits(from_mode)[2]; + CSR[htinst].VALUE = 0; + CSR[hstatus].SPV = $bits(from_mode)[2]; if (from_mode == PrivilegeMode::VU || from_mode == PrivilegeMode::VS) { CSR[hstatus].SPV = 1; - if (((exception_code == ExceptionCode::Breakpoint) && (REPORT_VA_IN_STVAL_ON_BREAKPOINT)) + if ( ((exception_code == ExceptionCode::Breakpoint) && (REPORT_VA_IN_STVAL_ON_BREAKPOINT)) || ((exception_code == ExceptionCode::LoadAddressMisaligned) && (REPORT_VA_IN_STVAL_ON_LOAD_MISALIGNED)) || ((exception_code == ExceptionCode::StoreAmoAddressMisaligned) && (REPORT_VA_IN_STVAL_ON_STORE_AMO_MISALIGNED)) || ((exception_code == ExceptionCode::InstructionAddressMisaligned) && (REPORT_VA_IN_STVAL_ON_INSTRUCTION_MISALIGNED)) @@ -659,7 +670,7 @@ function raise_precise { } else { CSR[hstatus].GVA = 0; } - CSR[hstatus].SPVP = from_mode; + CSR[hstatus].SPVP = $bits(from_mode)[0]; } else { CSR[hstatus].SPV = 0; CSR[hstatus].GVA = 0; @@ -785,37 +796,37 @@ function xlen { body { if (XLEN == 32) { return 32; - } - - if (mode() == PrivilegeMode::M) { - if (CSR[misa].MXL == $bits(XRegWidth::XLEN32)) { - return 32; - } else if (CSR[misa].MXL == $bits(XRegWidth::XLEN64)) { - return 64; - } - } else if (implemented?(ExtensionName::S) && mode() == PrivilegeMode::S) { - if (CSR[mstatus].SXL == $bits(XRegWidth::XLEN32)) { - return 32; - } else if (CSR[mstatus].SXL == $bits(XRegWidth::XLEN64)) { - return 64; - } - } else if (implemented?(ExtensionName::U) && mode() == PrivilegeMode::U) { - if (CSR[mstatus].UXL == $bits(XRegWidth::XLEN32)) { - return 32; - } else if (CSR[mstatus].UXL == $bits(XRegWidth::XLEN64)) { - return 64; - } - } else if (implemented?(ExtensionName::H) && mode() == PrivilegeMode::VS) { - if (CSR[hstatus].VSXL == $bits(XRegWidth::XLEN32)) { - return 32; - } else if (CSR[hstatus].VSXL == $bits(XRegWidth::XLEN64)) { - return 64; - } - } else if (implemented?(ExtensionName::H) && mode() == PrivilegeMode::VU) { - if (CSR[vsstatus].UXL == $bits(XRegWidth::XLEN32)) { - return 32; - } else if (CSR[vsstatus].UXL == $bits(XRegWidth::XLEN64)) { - return 64; + } else { + if (mode() == PrivilegeMode::M) { + if (CSR[misa].MXL == $bits(XRegWidth::XLEN32)) { + return 32; + } else if (CSR[misa].MXL == $bits(XRegWidth::XLEN64)) { + return 64; + } + } else if (implemented?(ExtensionName::S) && mode() == PrivilegeMode::S) { + if (CSR[mstatus].SXL == $bits(XRegWidth::XLEN32)) { + return 32; + } else if (CSR[mstatus].SXL == $bits(XRegWidth::XLEN64)) { + return 64; + } + } else if (implemented?(ExtensionName::U) && mode() == PrivilegeMode::U) { + if (CSR[mstatus].UXL == $bits(XRegWidth::XLEN32)) { + return 32; + } else if (CSR[mstatus].UXL == $bits(XRegWidth::XLEN64)) { + return 64; + } + } else if (implemented?(ExtensionName::H) && mode() == PrivilegeMode::VS) { + if (CSR[hstatus].VSXL == $bits(XRegWidth::XLEN32)) { + return 32; + } else if (CSR[hstatus].VSXL == $bits(XRegWidth::XLEN64)) { + return 64; + } + } else if (implemented?(ExtensionName::H) && mode() == PrivilegeMode::VU) { + if (CSR[vsstatus].UXL == $bits(XRegWidth::XLEN32)) { + return 32; + } else if (CSR[vsstatus].UXL == $bits(XRegWidth::XLEN64)) { + return 64; + } } } } @@ -1167,18 +1178,22 @@ function base32? { return True iff current effective XLEN == 32 } body { - XRegWidth xlen32 = XRegWidth::XLEN32; - if (mode() == PrivilegeMode::M) { - return CSR[misa].MXL == $bits(xlen32); - } else if (implemented?(ExtensionName::S) && mode() == PrivilegeMode::S) { - return CSR[mstatus].SXL == $bits(xlen32); - } else if (implemented?(ExtensionName::U) && mode() == PrivilegeMode::U) { - return CSR[mstatus].UXL == $bits(xlen32); - } else if (implemented?(ExtensionName::H) && mode() == PrivilegeMode::VS) { - return CSR[hstatus].VSXL == $bits(xlen32); + if (XLEN == 32) { + return true; } else { - assert(implemented?(ExtensionName::H) && mode() == PrivilegeMode::VU, "Unexpected mode"); - return CSR[vsstatus].UXL == $bits(xlen32); + XRegWidth xlen32 = XRegWidth::XLEN32; + if (mode() == PrivilegeMode::M) { + return CSR[misa].MXL == $bits(xlen32); + } else if (implemented?(ExtensionName::S) && mode() == PrivilegeMode::S) { + return CSR[mstatus].SXL == $bits(xlen32); + } else if (implemented?(ExtensionName::U) && mode() == PrivilegeMode::U) { + return CSR[mstatus].UXL == $bits(xlen32); + } else if (implemented?(ExtensionName::H) && mode() == PrivilegeMode::VS) { + return CSR[hstatus].VSXL == $bits(xlen32); + } else { + assert(implemented?(ExtensionName::H) && mode() == PrivilegeMode::VU, "Unexpected mode"); + return CSR[vsstatus].UXL == $bits(xlen32); + } } } } @@ -1215,13 +1230,15 @@ function current_translation_mode { Bits<4> mode_val = CSR[vsatp].MODE; if (mode_val == $bits(SatpMode::Sv32)) { # Sv32 is only defined when XLEN == 32 - if (effective_mode == PrivilegeMode::VS && CSR[hstatus].VSXL != $bits(XRegWidth::XLEN32)) { - # not supported in this XLEN - return SatpMode::Reserved; - } - if (effective_mode == PrivilegeMode::VU && CSR[vsstatus].UXL != $bits(XRegWidth::XLEN32)) { - # not supported in this XLEN - return SatpMode::Reserved; + if (XLEN == 64) { + if ((effective_mode == PrivilegeMode::VS) && (CSR[hstatus].VSXL != $bits(XRegWidth::XLEN32))) { + # not supported in this XLEN + return SatpMode::Reserved; + } + if ((effective_mode == PrivilegeMode::VU) && (CSR[vsstatus].UXL != $bits(XRegWidth::XLEN32))) { + # not supported in this XLEN + return SatpMode::Reserved; + } } if (!SV32_VSMODE_TRANSLATION) { # not supported in this configuration @@ -1230,7 +1247,7 @@ function current_translation_mode { # OK return SatpMode::Sv32; - } else if (mode_val == $bits(SatpMode::Sv39)) { + } else if ((XLEN == 64) && (mode_val == $bits(SatpMode::Sv39))) { # Sv39 is only defined when XLEN == 64 if (effective_mode == PrivilegeMode::VS && CSR[hstatus].VSXL != $bits(XRegWidth::XLEN64)) { # not supported in this XLEN @@ -1247,7 +1264,7 @@ function current_translation_mode { # OK return SatpMode::Sv39; - } else if (mode_val == $bits(SatpMode::Sv48)) { + } else if ((XLEN == 64) && (mode_val == $bits(SatpMode::Sv48))) { # Sv48 is only defined when XLEN == 64 if (effective_mode == PrivilegeMode::VS && CSR[hstatus].VSXL != $bits(XRegWidth::XLEN64)) { # not supported in this XLEN @@ -1264,7 +1281,7 @@ function current_translation_mode { # OK return SatpMode::Sv48; - } else if (mode_val == $bits(SatpMode::Sv57)) { + } else if ((XLEN == 64) && (mode_val == $bits(SatpMode::Sv57))) { # Sv57 is only defined when XLEN == 64 if (effective_mode == PrivilegeMode::VS && CSR[hstatus].VSXL != $bits(XRegWidth::XLEN64)) { # not supported in this XLEN @@ -1288,23 +1305,25 @@ function current_translation_mode { } # if we reach here, then the effective mode is S or U - assert(effective_mode == PrivilegeMode::S || effective_mode == PrivilegeMode::U); + assert(effective_mode == PrivilegeMode::S || effective_mode == PrivilegeMode::U, "unexpected priv mode"); Bits<4> mode_val = CSR[vsatp].MODE; if (mode_val == $bits(SatpMode::Sv32)) { # Sv32 is only defined when XLEN == 32 - if (effective_mode == PrivilegeMode::S && CSR[mstatus].SXL != $bits(XRegWidth::XLEN32)) { - # not supported in this XLEN - return SatpMode::Reserved; - } - if (effective_mode == PrivilegeMode::U && CSR[sstatus].UXL != $bits(XRegWidth::XLEN32)) { - # not supported in this XLEN - return SatpMode::Reserved; + if (XLEN == 64) { + if (effective_mode == PrivilegeMode::S && CSR[mstatus].SXL != $bits(XRegWidth::XLEN32)) { + # not supported in this XLEN + return SatpMode::Reserved; + } + if (effective_mode == PrivilegeMode::U && CSR[sstatus].UXL != $bits(XRegWidth::XLEN32)) { + # not supported in this XLEN + return SatpMode::Reserved; + } } if (!implemented?(ExtensionName::Sv32)) { # not supported in this configuration return SatpMode::Reserved; } - } else if (mode_val == $bits(SatpMode::Sv39)) { + } else if ((XLEN == 64) && (mode_val == $bits(SatpMode::Sv39))) { # Sv39 is only defined when XLEN == 64 if (effective_mode == PrivilegeMode::S && CSR[mstatus].SXL != $bits(XRegWidth::XLEN64)) { # not supported in this XLEN @@ -1320,7 +1339,7 @@ function current_translation_mode { } # OK return SatpMode::Sv39; - } else if (mode_val == $bits(SatpMode::Sv48)) { + } else if ((XLEN == 64) && (mode_val == $bits(SatpMode::Sv48))) { # Sv48 is only defined when XLEN == 64 if (effective_mode == PrivilegeMode::S && CSR[mstatus].SXL != $bits(XRegWidth::XLEN64)) { # not supported in this XLEN @@ -1336,7 +1355,7 @@ function current_translation_mode { } # OK return SatpMode::Sv48; - } else if (mode_val == $bits(SatpMode::Sv57)) { + } else if ((XLEN == 64) && (mode_val == $bits(SatpMode::Sv57))) { # Sv57 is only defined when XLEN == 64 if (effective_mode == PrivilegeMode::S && CSR[mstatus].SXL != $bits(XRegWidth::XLEN64)) { # not supported in this XLEN @@ -1390,7 +1409,7 @@ function translate_gstage { } # mstatus.MXR affects G-stage XR, but not hstatus.MXR - Boolean mxr = CSR[mstatus].MXR; + Boolean mxr = CSR[mstatus].MXR == 1; if (GSTAGE_MODE_BARE && CSR[hgatp].MODE == $bits(HgatpMode::Bare)) { # bare mode @@ -1411,12 +1430,12 @@ function translate_gstage { } else { # Invalid mode if (op == MemoryOperation::Read) { - raise_guest_page_fault(op, gpaddr, vaddr, tinst_value_for_guest_page_fault(op, encoding, true)); + raise_guest_page_fault(op, gpaddr, vaddr, tinst_value_for_guest_page_fault(op, encoding, true), effective_mode); } else if (op == MemoryOperation::Write || op == MemoryOperation::ReadModifyWrite) { - raise_guest_page_fault(op, gpaddr, vaddr, tinst_value_for_guest_page_fault(op, encoding, true)); + raise_guest_page_fault(op, gpaddr, vaddr, tinst_value_for_guest_page_fault(op, encoding, true), effective_mode); } else { - assert(op == MemoryOperation::Fetch); - raise_guest_page_fault(op, gpaddr, vaddr, tinst_value_for_guest_page_fault(op, encoding, true)); + assert(op == MemoryOperation::Fetch, "unexpected memory op"); + raise_guest_page_fault(op, gpaddr, vaddr, tinst_value_for_guest_page_fault(op, encoding, true), effective_mode); } } } @@ -1446,7 +1465,7 @@ function tinst_value_for_guest_page_fault { if (TINST_VALUE_ON_FINAL_LOAD_GUEST_PAGE_FAULT == "always zero") { return 0; } else if (TINST_VALUE_ON_FINAL_LOAD_GUEST_PAGE_FAULT == "always pseudoinstruction") { - if ((VSXLEN == 32) || (CSR[hstatus].VSXL == $bits(XRegWidth::XLEN32))) { + if ((VSXLEN == 32) || ((XLEN == 64) && (CSR[hstatus].VSXL == $bits(XRegWidth::XLEN32)))) { return 0x00002000; } else { return 0x00003000; @@ -1460,7 +1479,7 @@ function tinst_value_for_guest_page_fault { if (TINST_VALUE_ON_FINAL_STORE_AMO_GUEST_PAGE_FAULT == "always zero") { return 0; } else if (TINST_VALUE_ON_FINAL_STORE_AMO_GUEST_PAGE_FAULT == "always pseudoinstruction") { - if ((VSXLEN == 32) || (CSR[hstatus].VSXL == $bits(XRegWidth::XLEN32))) { + if ((VSXLEN == 32) || ((XLEN == 64) && (CSR[hstatus].VSXL == $bits(XRegWidth::XLEN32)))) { return 0x00002020; } else { return 0x00003020; @@ -1474,9 +1493,9 @@ function tinst_value_for_guest_page_fault { } else { if (REPORT_GPA_IN_TVAL_ON_INTERMEDIATE_GUEST_PAGE_FAULT) { # spec states hardware must write the pseduo-instruction values to *tinst - if ((VSXLEN == 32) || (CSR[hstatus].VSXL == $bits(XRegWidth::XLEN32))) { + if ((VSXLEN == 32) || ((XLEN == 64) && (CSR[hstatus].VSXL == $bits(XRegWidth::XLEN32)))) { return 0x00002000; - } else if ((VSXLEN == 64) || (CSR[hstatus].VSXL == $bits(XRegWidth::XLEN64))) { + } else if ((VSXLEN == 64) || ((XLEN == 64) && (CSR[hstatus].VSXL == $bits(XRegWidth::XLEN64)))) { return 0x00003000; } } @@ -1515,11 +1534,26 @@ function tinst_transform { } } +function transformed_standard_instruction_for_tinst { + returns + Bits # transformed instruction encoding + arguments + Bits original # original instruction encoding + description { + Transforms an instruction encoding for htinst. + } + body { + assert(false, "TODO"); + return 0; + } +} + function tinst_value { returns XReg # tinst value arguments - ExceptionCode code # expect type + ExceptionCode code, # expect type + Bits encoding # instruction encoding, needed when tinst value might be the encoding description { Returns the value of htinst/mtinst for the given exception code. } @@ -1580,25 +1614,25 @@ function tinst_value { } else { unpredictable("An unpredictable value is written into tinst in response to a StoreAmoAccessFault exception"); } - } else if (code == ExceptionCode::UCall) { + } else if (code == ExceptionCode::Ucall) { if (TINST_VALUE_ON_UCALL == "always zero") { return 0; } else { unpredictable("An unpredictable value is written into tinst in response to a UCall exception"); } - } else if (code == ExceptionCode::SCall) { + } else if (code == ExceptionCode::Scall) { if (TINST_VALUE_ON_SCALL == "always zero") { return 0; } else { unpredictable("An unpredictable value is written into tinst in response to a SCall exception"); } - } else if (code == ExceptionCode::MCall) { + } else if (code == ExceptionCode::Mcall) { if (TINST_VALUE_ON_MCALL == "always zero") { return 0; } else { unpredictable("An unpredictable value is written into tinst in response to a MCall exception"); } - } else if (code == ExceptionCode::VSCall) { + } else if (code == ExceptionCode::VScall) { if (TINST_VALUE_ON_VSCALL == "always zero") { return 0; } else { @@ -1632,7 +1666,7 @@ function gstage_page_walk { template U32 VA_SIZE, # virtual address size (Sv32 = 32, Sv39 = 39, Sv48 = 48, Sv57 = 57) U32 PA_SIZE, # physical address size (Sv32 = 34, Sv39 = 56, Sv48 = 56, Sv57 = 56) - U32 PTESIZE, # length, in bits, of a Page Table Entry (Sv32 = 4, others = 8) + U32 PTESIZE, # length, in bits, of a Page Table Entry (Sv32 = 32, others = 64) U32 LEVELS # levels in the page table (Sv32 = 2, Sv39 = 3, Sv48 = 4, Sv57 = 5) returns TranslationResult # the translated address and attributes @@ -1677,7 +1711,7 @@ function gstage_page_walk { ExceptionCode::InstructionGuestPageFault : ExceptionCode::StoreAmoGuestPageFault ); - Boolean mxr = for_final_vs_pte && (CSR[menvcfg].MXR == 1); + Boolean mxr = for_final_vs_pte && (CSR[mstatus].MXR == 1); Boolean pbmte = CSR[menvcfg].PBMTE == 1; Boolean adue = CSR[menvcfg].ADUE == 1; @@ -1687,7 +1721,7 @@ function gstage_page_walk { U32 max_gpa_width = LEVELS * VPN_SIZE + 2 + 12; if (gpaddr >> max_gpa_width != 0) { # Guest physical address is too large for the page table - raise_guest_page_fault(op, gpaddr, vaddr, tinst); + raise_guest_page_fault(op, gpaddr, vaddr, tinst, effective_mode); } ppn = CSR[hgatp].PPN; @@ -1711,31 +1745,31 @@ function gstage_page_walk { # check if any reserved bits are set # Sv32 has no reserved bits, and Sv39/48/57 all have reserved bits at 60:54 if ((VA_SIZE != 32) && (pte[60:54] != 0)) { - raise_guest_page_fault(op, gpaddr, vaddr, tinst); + raise_guest_page_fault(op, gpaddr, vaddr, tinst, effective_mode); } if (!implemented?(ExtensionName::Svnapot)) { - if (pte[63] != 0) { + if ((PTESIZE >= 64) && pte[63] != 0) { # N is reserved if Svnapot is not supported - raise_guest_page_fault(op, gpaddr, vaddr, tinst); + raise_guest_page_fault(op, gpaddr, vaddr, tinst, effective_mode); } } - if (!pbmte && (pte[62:61] != 0)) { + if ((PTESIZE >= 64) && !pbmte && (pte[62:61] != 0)) { # PBMTE is reserved when Svpbmt is not enabled - raise_guest_page_fault(op, gpaddr, vaddr, tinst); + raise_guest_page_fault(op, gpaddr, vaddr, tinst, effective_mode); } - if (pbmte && (pte[62:61] == 3)) { + if ((PTESIZE >= 64) && pbmte && (pte[62:61] == 3)) { # PBMTE == 3 is reserved - raise_guest_page_fault(op, gpaddr, vaddr, tinst); + raise_guest_page_fault(op, gpaddr, vaddr, tinst, effective_mode); } if (pte_flags.V == 0) { # page table entry is not valid - raise_guest_page_fault(op, gpaddr, vaddr, tinst); + raise_guest_page_fault(op, gpaddr, vaddr, tinst, effective_mode); } if (pte_flags.R == 0 && pte_flags.W == 1) { # Writable pages must also be readable - raise_guest_page_fault(op, gpaddr, vaddr, tinst); + raise_guest_page_fault(op, gpaddr, vaddr, tinst, effective_mode); } if (pte_flags.R == 1 || pte_flags.X == 1) { @@ -1743,28 +1777,28 @@ function gstage_page_walk { if (pte_flags.U == 0) { # all g-stage tables *must* be user mode accessible # since all g-stage accesses appear like U-mode accesses - raise_guest_page_fault(op, gpaddr, vaddr, tinst); + raise_guest_page_fault(op, gpaddr, vaddr, tinst, effective_mode); } if (((op == MemoryOperation::Write) || (op == MemoryOperation::ReadModifyWrite)) && (pte_flags.W == 0)) { # not write permission for store - raise_guest_page_fault(op, gpaddr, vaddr, tinst); + raise_guest_page_fault(op, gpaddr, vaddr, tinst, effective_mode); } else if ((op == MemoryOperation::Fetch) && (pte_flags.X == 0)) { # no execute permission - raise_guest_page_fault(op, gpaddr, vaddr, tinst); + raise_guest_page_fault(op, gpaddr, vaddr, tinst, effective_mode); } else if ((op == MemoryOperation::Read) || (op == MemoryOperation::ReadModifyWrite)) { if (((!mxr) && (pte_flags.R == 0)) || ((mxr) && (pte_flags.X == 0 && pte_flags.R == 0))) { # no read permision - raise_guest_page_fault(op, gpaddr, vaddr, tinst); + raise_guest_page_fault(op, gpaddr, vaddr, tinst, effective_mode); } } # ensure remaining PPN bits are zero, otherwise there is a misaligned super page if ((i > 0) && (pte[(i-1)*VPN_SIZE:10] != 0)) { - raise_guest_page_fault(op, gpaddr, vaddr, tinst); + raise_guest_page_fault(op, gpaddr, vaddr, tinst, effective_mode); } # check access and dirty bits @@ -1812,13 +1846,15 @@ function gstage_page_walk { } else { # successful translation and update result.paddr = pte_paddr; - result.pbmt = pte[62:61]; + if (PTESIZE >= 64) { + result.pbmt = pte[62:61]; + } result.pte_flags = pte_flags; return result; } } else { # A or D bit needs updated - raise_guest_page_fault(op, gpaddr, vaddr, tinst); + raise_guest_page_fault(op, gpaddr, vaddr, tinst, effective_mode); } } @@ -1826,22 +1862,22 @@ function gstage_page_walk { # pointer to next level if (i == 0) { # a pointer can't exist on the last level - raise_guest_page_fault(op, gpaddr, vaddr, tinst); + raise_guest_page_fault(op, gpaddr, vaddr, tinst, effective_mode); } if (pte_flags.D == 1 || pte_flags.A == 1 || pte_flags.U == 1) { # D, A, and U are reserved in non-leaf PTEs - raise_guest_page_fault(op, gpaddr, vaddr, tinst); + raise_guest_page_fault(op, gpaddr, vaddr, tinst, effective_mode); } if ((VA_SIZE != 32) && (pte[62:61] != 0)) { # PBMT must be zero in a pointer PTE - raise_guest_page_fault(op, gpaddr, vaddr, tinst); + raise_guest_page_fault(op, gpaddr, vaddr, tinst, effective_mode); } if ((VA_SIZE != 32) && pte[63] != 0) { # N must be zero in a pointer PTE - raise_guest_page_fault(op, gpaddr, vaddr, tinst); + raise_guest_page_fault(op, gpaddr, vaddr, tinst, effective_mode); } # fall through to next level @@ -1900,19 +1936,19 @@ function stage1_page_walk { ExceptionCode::StoreAmoPageFault ); # shadow stacks enabled? - Boolean sse; - if (CSR[misa].H == 1 && effective_mode == PrivilegeMode::VS) { - sse = CSR[henvcfg].SSE == 1; - } else if (CSR[misa].H == 1 && effective_mode == PrivilegeMode::VU) { - sse = CSR[senvcfg].SSE == 1; - } else if (CSR[misa].U == 1 && effective_mode == PrivilegeMode::U) { - sse = CSR[senvcfg].SSE == 1; - } else if (CSR[misa].S == 1 && effective_mode == PrivilegeMode::S) { - sse = CSR[menvcfg].SSE == 1; - } else { - # M-mode - sse = false; - } + Boolean sse = false; + # if (CSR[misa].H == 1 && effective_mode == PrivilegeMode::VS) { + # sse = CSR[henvcfg].SSE == 1; + # } else if (CSR[misa].H == 1 && effective_mode == PrivilegeMode::VU) { + # sse = CSR[senvcfg].SSE == 1; + # } else if (CSR[misa].U == 1 && effective_mode == PrivilegeMode::U) { + # sse = CSR[senvcfg].SSE == 1; + # } else if (CSR[misa].S == 1 && effective_mode == PrivilegeMode::S) { + # sse = CSR[menvcfg].SSE == 1; + # } else { + # # M-mode + # sse = false; + # } # access/dirty bit hardware update enable? Boolean adue; @@ -1940,9 +1976,9 @@ function stage1_page_walk { if (CSR[misa].H == 1 && (effective_mode == PrivilegeMode::VS || effective_mode == PrivilegeMode::VU)) { # HS-level sstatus.MXR makes execute-only pages readable for both stages of address translation # (VS-stage and G-stage), whereas vsstatus.MXR affects only the first translation stage (VS-stage) - mxr = (CSR[menvcfg].MXR == 1) || (CSR[henvcfg].MXR == 1); + mxr = (CSR[mstatus].MXR == 1) || (CSR[vsstatus].MXR == 1); } else { - mxr = CSR[menvcfg].MXR == 1; + mxr = CSR[mstatus].MXR == 1; } # Supervisor access user page? @@ -1955,7 +1991,7 @@ function stage1_page_walk { ppn = CSR[vsatp].PPN; - if (vaddr[xlen()-1:VA_SIZE] != {xlen()-VA_SIZE{vaddr[VA_SIZE - 1]}}) { + if ((VA_SIZE < xlen()) && (vaddr[xlen()-1:VA_SIZE] != {xlen()-VA_SIZE{vaddr[VA_SIZE - 1]}})) { # non-canonical virtual address raises a page fault # note that if pointer masking is enabled, # vaddr has already been transformed before reaching here @@ -2015,13 +2051,13 @@ function stage1_page_walk { } } else { # PBMT is reserved if Svpbmt is not supported) - if ((pte[62:61] != 0)) { + if ((PTESIZE >= 64) && (pte[62:61] != 0)) { raise (page_fault_code, effective_mode, vaddr); } } if (!implemented?(ExtensionName::Svnapot)) { - if (pte[63] != 0) { + if ((PTESIZE >= 64) && (pte[63] != 0)) { # N is reserved if Svnapot is not supported raise (page_fault_code, effective_mode, vaddr); } @@ -2150,7 +2186,9 @@ function stage1_page_walk { encoding ); result.paddr = pte_phys.paddr; - result.pbmt = pte_phys.pbmt == 0 ? pte[62:61] : pte_phys.pbmt; + if (PTESIZE >= 64) { + result.pbmt = pte_phys.pbmt == Pbmt::PMA ? $enum(Pbmt, pte[62:61]) : pte_phys.pbmt; + } result.pte_flags = pte_flags; return result; } else { @@ -2238,7 +2276,7 @@ function translate { } else if (op == MemoryOperation::Write || op == MemoryOperation::ReadModifyWrite) { raise (ExceptionCode::StoreAmoPageFault, effective_mode, vaddr); } else { - assert(op == MemoryOperation::Fetch); + assert(op == MemoryOperation::Fetch, "Unexpected memory operation"); raise (ExceptionCode::InstructionPageFault, effective_mode, vaddr); } } @@ -2321,11 +2359,11 @@ function canonical_gpaddr? { } else if (satp_mode == SatpMode::Sv32) { # Sv32 uses all 32 bits of the VA return true; - } else if (satp_mode == SatpMode::Sv39) { + } else if ((XLEN > 32) && (satp_mode == SatpMode::Sv39)) { return gpaddr[63:39] == {25{gpaddr[38]}}; - } else if (satp_mode == SatpMode::Sv48) { + } else if ((XLEN > 32) && (satp_mode == SatpMode::Sv48)) { return gpaddr[63:48] == {16{gpaddr[47]}}; - } else if (satp_mode == SatpMode::Sv57) { + } else if ((XLEN > 32) && (satp_mode == SatpMode::Sv57)) { return gpaddr[63:57] == {6{gpaddr[56]}}; } } @@ -2414,10 +2452,10 @@ function read_memory { if (MAX_MISALIGNED_ATOMICITY_GRANULE_SIZE > 0) { # sanity check that the implementation isn't expecting a Misaligned exception # before an access/page fault exception (that would be an invalid config) - assert(MISALIGNED_LDST_EXCEPTION_PRIORITY == "low"); + assert(MISALIGNED_LDST_EXCEPTION_PRIORITY == "low", "Invalid config: can't mix low-priority misaligned exceptions with large atomicity granule"); physical_address = (CSR[misa].S == 1) - ? translate(virtual_address, MemoryOperation::Read, effective_ldst_mode(), encoding) + ? translate(virtual_address, MemoryOperation::Read, effective_ldst_mode(), encoding).paddr : virtual_address; if (misaligned_is_atomic?(physical_address)) { @@ -2433,7 +2471,7 @@ function read_memory { if (MISALIGNED_LDST_EXCEPTION_PRIORITY == "low") { # do translation to trigger any access/page faults before raising misaligned physical_address = (CSR[misa].S == 1) - ? translate(virtual_address, MemoryOperation::Read, effective_ldst_mode(), encoding) + ? translate(virtual_address, MemoryOperation::Read, effective_ldst_mode(), encoding).paddr : virtual_address; access_check(physical_address, LEN, virtual_address, MemoryOperation::Read, ExceptionCode::LoadAccessFault, effective_ldst_mode()); } @@ -2530,7 +2568,7 @@ function load_reserved { body { Bits physical_address = (CSR[misa].S == 1) - ? translate(virtual_address, MemoryOperation::Read, effective_ldst_mode(), encoding) + ? translate(virtual_address, MemoryOperation::Read, effective_ldst_mode(), encoding).paddr : virtual_address; if (pma_applies?(PmaAttribute::RsrvNone, physical_address, N)) { @@ -2577,7 +2615,7 @@ function store_conditional { body { Bits physical_address = (CSR[misa].S == 1) - ? translate(virtual_address, MemoryOperation::Write, effective_ldst_mode(), encoding) + ? translate(virtual_address, MemoryOperation::Write, effective_ldst_mode(), encoding).paddr : virtual_address; if (pma_applies?(PmaAttribute::RsrvNone, physical_address, N)) { @@ -2655,7 +2693,7 @@ function amo { Bits physical_address = (CSR[misa].S == 1) - ? translate(virtual_address, MemoryOperation::ReadModifyWrite, effective_ldst_mode(), encoding) + ? translate(virtual_address, MemoryOperation::ReadModifyWrite, effective_ldst_mode(), encoding).paddr : virtual_address; # PMA Atomicity checks @@ -2670,7 +2708,8 @@ function amo { } else { assert( pma_applies?(PmaAttribute::AmoSwap, physical_address, N) && - op == AmoOperation::Swap + op == AmoOperation::Swap, + "Bad AMO operation" ); } @@ -2704,7 +2743,7 @@ function write_memory_aligned { XReg physical_address; physical_address = (CSR[misa].S == 1) - ? translate(virtual_address, MemoryOperation::Write, effective_ldst_mode(), encoding) + ? translate(virtual_address, MemoryOperation::Write, effective_ldst_mode(), encoding).paddr : virtual_address; # may raise an exception @@ -2737,10 +2776,10 @@ function write_memory { if (MAX_MISALIGNED_ATOMICITY_GRANULE_SIZE > 0) { # sanity check that the implementation isn't expecting a Misaligned exception # before an access/page fault exception (that would be an invalid config) - assert(MISALIGNED_LDST_EXCEPTION_PRIORITY == "low"); + assert(MISALIGNED_LDST_EXCEPTION_PRIORITY == "low", "Invalid config: can't mix low-priority misaligned exceptions with large atomicity granule"); physical_address = (CSR[misa].S == 1) - ? translate(virtual_address, MemoryOperation::Write, effective_ldst_mode(), encoding) + ? translate(virtual_address, MemoryOperation::Write, effective_ldst_mode(), encoding).paddr : virtual_address; if (misaligned_is_atomic?(physical_address)) { @@ -2756,7 +2795,7 @@ function write_memory { if (MISALIGNED_LDST_EXCEPTION_PRIORITY == "low") { # do translation to trigger any access/page faults before raising misaligned physical_address = (CSR[misa].S == 1) - ? translate(virtual_address, MemoryOperation::Write, effective_ldst_mode(), encoding) + ? translate(virtual_address, MemoryOperation::Write, effective_ldst_mode(), encoding).paddr : virtual_address; access_check(physical_address, LEN, virtual_address, MemoryOperation::Write, ExceptionCode::StoreAmoAccessFault, effective_ldst_mode()); } diff --git a/arch/isa/util.idl b/arch/isa/util.idl index ba031a18..3b8370c8 100644 --- a/arch/isa/util.idl +++ b/arch/isa/util.idl @@ -82,6 +82,28 @@ function lowest_set_bit { } } +function count_leading_zeros { + template U32 N + returns + Bits # Number of leading zeros in +vlaue+ + arguments + Bits value # value to count zero in + description { + Returns the number of leading 0 bits before the most-significant 1 bit of +value+, + or N if value is zero. + } + body { + for (U32 i=0; i < N; i++) { + if (value[N - 1 - i] == 1) { + return i; + } + } + + # fall-through; value must be zero + return N; + } +} + function sext { returns XReg arguments XReg value, XReg first_extended_bit diff --git a/arch/manual/isa/20240411/contents.yaml b/arch/manual/isa/20240411/contents.yaml index 7a6a7e6c..41819afb 100644 --- a/arch/manual/isa/20240411/contents.yaml +++ b/arch/manual/isa/20240411/contents.yaml @@ -203,10 +203,13 @@ volumes: - [Smrnmi, "0.5.0"] - [Smcdeleg, "1.0.0"] - [S, "1.12.0"] - - [Sv32, "1.13.0"] - - [Sv39, "1.13.0"] - - [Sv48, "1.13.0"] - - [Sv57, "1.13.0"] + - [Sm, "1.12.0"] + - [Smhpm, "1.12.0"] + - [Smpmp, "1.12.0"] + - [Sv32, "1.12.0"] + - [Sv39, "1.12.0"] + - [Sv48, "1.12.0"] + - [Sv57, "1.12.0"] - [Svnapot, "1.0.0"] - [Svpbmt, "1.0.0"] - [Svinval, "1.0.0"] diff --git a/backends/arch_gen/lib/arch_gen.rb b/backends/arch_gen/lib/arch_gen.rb index 1e974a3a..db038abe 100644 --- a/backends/arch_gen/lib/arch_gen.rb +++ b/backends/arch_gen/lib/arch_gen.rb @@ -42,7 +42,8 @@ def gen_params_schema "type" => "object", "required" => ["NAME"], "properties" => { - "NAME" => { "type" => "string", "enum" => [@name] } + "NAME" => { "type" => "string", "enum" => [@name] }, + "XLEN" => { "type" => "intger", "enum" => [32, 64] } }, "additionalProperties" => false } @@ -90,6 +91,12 @@ def initialize(config_name) @cfg_impl_ext = @validator.validate(cfg_impl_ext_path)["implemented_extensions"] raise "Validation failed" if @cfg_impl_ext.nil? + cfg_opts_path = @cfg_dir / "cfg.yaml" + @cfg_opts = YAML.load_file(cfg_opts_path) + raise "Validation failed" if @cfg_opts.nil? + raise "Validation failed: bad type" unless ["partially configured", "fully configured"].include?(@cfg_opts["type"]) + + @params_schema_path = @gen_dir / "schemas" / "params_schema.json" @ext_gen_complete = false @@ -276,16 +283,69 @@ def gen_arch_def ext_name = ext_obj.keys[0] [ext_name, ext_obj[ext_name]] end.to_h + profile_family_hash = Dir.glob($root / "arch" / "profile_family" / "**" / "*.yaml").map do |f| + profile_obj = YAML.load_file(f) + profile_name = profile_obj.keys[0] + profile_obj[profile_name]["name"] = profile_name + profile_obj[profile_name]["__source"] = f + [profile_name, profile_obj[profile_name]] + end.to_h + profile_hash = Dir.glob($root / "arch" / "profile" / "**" / "*.yaml").map do |f| + profile_obj = YAML.load_file(f) + profile_name = profile_obj.keys[0] + profile_obj[profile_name]["name"] = profile_name + profile_obj[profile_name]["__source"] = f + [profile_name, profile_obj[profile_name]] + end.to_h + manual_hash = {} + Dir.glob($root / "arch" / "manual" / "**" / "contents.yaml").map do |f| + manual_version = YAML.load_file(f) + manual_id = manual_version["manual"] + unless manual_hash.key?(manual_id) + manual_info_files = Dir.glob($root / "arch" / "manual" / "**" / "#{manual_id}.yaml") + raise "Could not find manual info '#{manual_id}'.yaml, needed by #{f}" if manual_info_files.empty? + raise "Found multiple manual infos '#{manual_id}'.yaml, needed by #{f}" if manual_info_files.size > 1 + + manual_info_file = manual_info_files.first + manual_hash[manual_id] = YAML.load_file(manual_info_file) + manual_hash[manual_id]["__source"] = manual_info_file + # TODO: schema validation + end + + manual_hash[manual_id]["versions"] ||= [] + manual_hash[manual_id]["versions"] << YAML.load_file(f) + # TODO: schema validation + manual_hash[manual_id]["versions"].last["__source"] = f + end + csc_crd_family_hash = Dir.glob($root / "arch" / "csc_crd_family" / "**" / "*.yaml").map do |f| + family_obj = YAML.load_file(f, permitted_classes: [Date]) + family_name = family_obj.keys[0] + family_obj[family_name]["name"] = family_name + family_obj[family_name]["__source"] = f + [family_name, family_obj[family_name]] + end.to_h + csc_crd_hash = Dir.glob($root / "arch" / "csc_crd" / "**" / "*.yaml").map do |f| + crd_obj = YAML.load_file(f, permitted_classes: [Date]) + crd_name = crd_obj.keys[0] + crd_obj[crd_name]["name"] = crd_name + crd_obj[crd_name]["__source"] = f + [crd_name, crd_obj[crd_name]] + end.to_h arch_def = { - "type" => "fully configured", + "type" => @cfg_opts["type"], "params" => params, "instructions" => inst_hash, "implemented_instructions" => @implemented_instructions, "extensions" => ext_hash, "implemented_extensions" => @implemented_extensions, "csrs" => csr_hash, - "implemented_csrs" => @implemented_csrs + "implemented_csrs" => @implemented_csrs, + "profile_families" => profile_family_hash, + "profiles" => profile_hash, + "manuals" => manual_hash, + "csc_crd_families" => csc_crd_family_hash, + "csc_crds" => csc_crd_hash } yaml = YAML.dump(arch_def) diff --git a/backends/cfg_html_doc/adoc_gen.rake b/backends/cfg_html_doc/adoc_gen.rake index 36cbd5d0..2e83a6ef 100644 --- a/backends/cfg_html_doc/adoc_gen.rake +++ b/backends/cfg_html_doc/adoc_gen.rake @@ -50,7 +50,7 @@ require "ruby-prof" File.write(path, arch_def.find_replace_links(erb.result(binding))) end when "func" - global_symtab = arch_def.sym_table + global_symtab = arch_def.symtab path = dir_path / "funcs.adoc" puts " Generating #{path}" File.write(path, arch_def.find_replace_links(erb.result(binding))) diff --git a/backends/cfg_html_doc/templates/csr.adoc.erb b/backends/cfg_html_doc/templates/csr.adoc.erb index 27cd27e7..671ddb68 100644 --- a/backends/cfg_html_doc/templates/csr.adoc.erb +++ b/backends/cfg_html_doc/templates/csr.adoc.erb @@ -60,7 +60,7 @@ This CSR format changes dynamically. <%- csr.implemented_fields(arch_def).each do |field| -%> | `xref:<%=csr.name%>-<%=field.name%>-def[<%= field.name %>]` | <%= field.location_pretty(arch_def) %> -| <%= field.type(arch_def) %> +| <%= field.type(arch_def.symtab) %> | <%= field.reset_value(arch_def) %> <%- end -%> @@ -88,7 +88,7 @@ Type:: [%autowidth] |=== -| <%= field.type(arch_def) %> | <%= field.type_desc(arch_def) %> +| <%= field.type(arch_def.symtab) %> | <%= field.type_desc(arch_def) %> |=== Reset value:: @@ -148,7 +148,7 @@ Original:: + [source,idl,subs="specialchars,macros"] ---- -<%= csr.type_checked_sw_read_ast(arch_def.sym_table).gen_adoc %> +<%= csr.type_checked_sw_read_ast(arch_def.symtab).gen_adoc %> ---- ==== <%- end -%> diff --git a/backends/cfg_html_doc/templates/inst.adoc.erb b/backends/cfg_html_doc/templates/inst.adoc.erb index b5525725..7159fdce 100644 --- a/backends/cfg_html_doc/templates/inst.adoc.erb +++ b/backends/cfg_html_doc/templates/inst.adoc.erb @@ -108,7 +108,7 @@ Pruned, XLEN == <%= effective_xlen %>:: + [source,idl,subs="specialchars,macros"] ---- -<%= inst.pruned_operation_ast(arch_def.sym_table, effective_xlen).gen_adoc %> +<%= inst.pruned_operation_ast(arch_def.symtab, effective_xlen).gen_adoc %> ---- <%- end -%> @@ -121,7 +121,7 @@ Original:: ==== <%- end -%> -<%- exception_list = inst.reachable_exceptions_str(arch_def.sym_table) -%> +<%- exception_list = inst.reachable_exceptions_str(arch_def.symtab) -%> <%- unless exception_list.empty? -%> == Exceptions diff --git a/backends/crd_doc/templates/crd.adoc.erb b/backends/crd_doc/templates/crd.adoc.erb index 2d1a6194..ddb27de2 100644 --- a/backends/crd_doc/templates/crd.adoc.erb +++ b/backends/crd_doc/templates/crd.adoc.erb @@ -454,7 +454,7 @@ RV64:: // TODO: add back after sym table update for generic arch def is merged in profiles branch <%# -<%- exception_list = inst.reachable_exceptions_str(crd.arch_def.sym_table) -% > +<%- exception_list = inst.reachable_exceptions_str(crd.arch_def.symtab) -% > <%- if exception_list.empty? -% > This instruction does not generate synchronous exceptions. <%- else -% > diff --git a/backends/ext_pdf_doc/tasks.rake b/backends/ext_pdf_doc/tasks.rake index e93880a2..b0a4852b 100644 --- a/backends/ext_pdf_doc/tasks.rake +++ b/backends/ext_pdf_doc/tasks.rake @@ -133,7 +133,7 @@ rule %r{#{$root}/gen/ext_pdf_doc/.*/adoc/.*_extension\.adoc} => proc { |tname| arch_def = if config_name == "_" - arch_def_for(nil) + arch_def_for("_64") else arch_def_for(config_name) end diff --git a/backends/manual/tasks.rake b/backends/manual/tasks.rake index 5da921eb..8d18cd1b 100644 --- a/backends/manual/tasks.rake +++ b/backends/manual/tasks.rake @@ -30,50 +30,63 @@ directory MANUAL_GEN_DIR / "adoc" directory MANUAL_GEN_DIR / "antora" directory MANUAL_GEN_DIR / "html" -["inst", "csr", "ext"].each do |type| - directory MANUAL_GEN_DIR / "antora" / "modules" / "#{type}s" / "pages" - - Dir.glob($root / "arch" / type / "**" / "*.yaml") do |fn| - file MANUAL_GEN_DIR / "adoc" / "#{File.basename(fn, '.yaml')}.adoc" => [ - "gen:arch", - (MANUAL_GEN_DIR / "adoc").to_s, - ($root / "backends" / "manual" / "templates" / "#{type}.adoc.erb").to_s, - __FILE__ - ] do |t| - name = File.basename(t.name, ".adoc") - - arch_def = arch_def_for("_") - erb = case type - when "inst" - inst = arch_def.instruction(name) - raise "Could not find inst '#{name}'" if inst.nil? - - ERB.new(File.read($root / "backends" / "manual" / "templates" / "inst.adoc.erb"), trim_mode: "-") - when "csr" - csr = arch_def.csr(name) - raise "Could not find inst '#{name}'" if csr.nil? - - ERB.new(File.read($root / "backends" / "manual" / "templates" / "csr.adoc.erb"), trim_mode: "-") - when "ext" - ext = arch_def.extension(name) - raise "Could not find ext '#{name}'" if ext.nil? - - ERB.new(File.read($root / "backends" / "manual" / "templates" / "ext.adoc.erb"), trim_mode: "-") - else - raise "Unhandled type '#{type}'" - end - - File.write(t.name, erb.result(binding)) - end - - file MANUAL_GEN_DIR / "antora" / "modules" / "#{type}s" / "pages" => [ - (MANUAL_GEN_DIR / "adoc" / "#{File.basename(fn, '.yaml')}.adoc").to_s, - (MANUAL_GEN_DIR / "antora" / "modules" / "#{type}s" / "pages").to_s - ] do |t| - FileUtils.cp t.prerequisites.first, t.name - end - end -end +# ["inst", "csr", "ext"].each do |type| +# directory MANUAL_GEN_DIR / "antora" / "modules" / "#{type}s" / "pages" + +# Dir.glob($root / "arch" / type / "**" / "*.yaml") do |fn| +# file MANUAL_GEN_DIR / "adoc" / "#{File.basename(fn, '.yaml')}.adoc" => [ +# "gen:arch", +# (MANUAL_GEN_DIR / "adoc").to_s, +# ($root / "backends" / "manual" / "templates" / "#{type}.adoc.erb").to_s, +# __FILE__ +# ] do |t| +# name = File.basename(t.name, ".adoc") + +# arch_def_32 = arch_def_for("_32") +# arch_def_64 = arch_def_for("_64") +# erb = case type +# when "inst" +# inst_32 = arch_def_32.instruction(name) +# raise "Could not find inst '#{name}' in RV32" if inst_32.nil? + +# inst_64 = arch_def_64.instruction(name) +# raise "Could not find inst '#{name}' in RV64" if inst_64.nil? + +# inst = inst_32 || inst_64 + +# ERB.new(File.read($root / "backends" / "manual" / "templates" / "inst.adoc.erb"), trim_mode: "-") +# when "csr" +# csr_32 = arch_def_32.csr(name) +# raise "Could not find csr '#{name}' in RV32" if csr_32.nil? + +# csr_64 = arch_def_64.csr(name) +# raise "Could not find csr '#{name}' in RV32" if csr_64.nil? + +# csr = csr_32 || csr_64 + +# ERB.new(File.read($root / "backends" / "manual" / "templates" / "csr.adoc.erb"), trim_mode: "-") +# when "ext" +# ext = arch_def_64.extension(name) +# raise "Could not find ext '#{name}'" if ext.nil? + +# arch_def = arch_def_64 + +# ERB.new(File.read($root / "backends" / "manual" / "templates" / "ext.adoc.erb"), trim_mode: "-") +# else +# raise "Unhandled type '#{type}'" +# end + +# File.write(t.name, erb.result(binding)) +# end + +# file MANUAL_GEN_DIR / "antora" / "modules" / "#{type}s" / "pages" => [ +# (MANUAL_GEN_DIR / "adoc" / "#{File.basename(fn, '.yaml')}.adoc").to_s, +# (MANUAL_GEN_DIR / "antora" / "modules" / "#{type}s" / "pages").to_s +# ] do |t| +# FileUtils.cp t.prerequisites.first, t.name +# end +# end +# end file MANUAL_GEN_DIR / "antora" / "antora.yml" => (MANUAL_GEN_DIR / "antora").to_s do |t| File.write t.name, <<~ANTORA @@ -88,7 +101,7 @@ end # Rule to create a chapter page in antora hierarchy rule %r{#{MANUAL_GEN_DIR}/.*/.*/antora/modules/chapters/pages/.*\.adoc} do |t| parts = t.name.sub("#{MANUAL_GEN_DIR}/", "").split("/") - manual_version = arch_def_for("_").manual(parts[0]).version(parts[1]) + manual_version = arch_def_for("_64").manual(parts[0]).version(parts[1]) chapter_name = File.basename(t.name, ".adoc") volume = manual_version.volumes.find { |v| !v.chapter(chapter_name).nil? } @@ -119,7 +132,7 @@ rule %r{#{MANUAL_GEN_DIR}/.*/.*/antora/antora.yml} => proc { |tname| ] } do |t| parts = t.name.sub("#{MANUAL_GEN_DIR}/", "").split("/") - manual_version = arch_def_for("_").manual(parts[0])&.version(parts[1]) + manual_version = arch_def_for("_64").manual(parts[0])&.version(parts[1]) raise "Can't find any manual version for '#{parts[0]}' '#{parts[1]}'" if manual_version.nil? @@ -154,7 +167,7 @@ rule %r{#{MANUAL_GEN_DIR}/.*/.*/antora/nav.adoc} => proc { |tname| ] } do |t| parts = t.name.sub("#{MANUAL_GEN_DIR}/", "").split("/") - manual_version = arch_def_for("_").manual(parts[0])&.version(parts[1]) + manual_version = arch_def_for("_64").manual(parts[0])&.version(parts[1]) raise "Can't find any manual version for '#{parts[0]}' '#{parts[1]}'" if manual_version.nil? @@ -193,7 +206,7 @@ rule %r{#{MANUAL_GEN_DIR}/.*/.*/antora/modules/ROOT/pages/index.adoc} => proc { ] } do |t| parts = t.name.sub("#{MANUAL_GEN_DIR}/", "").split("/") - manual_version = arch_def_for("_").manual(parts[0])&.version(parts[1]) + manual_version = arch_def_for("_64").manual(parts[0])&.version(parts[1]) raise "Can't find any manual version for '#{parts[0]}' '#{parts[1]}'" if manual_version.nil? @@ -218,7 +231,7 @@ rule %r{#{MANUAL_GEN_DIR}/.*/.*/antora/modules/insts/pages/.*.adoc} => [ ] do |t| inst_name = File.basename(t.name, ".adoc") - arch_def = arch_def_for("_") + arch_def = arch_def_for("_64") inst = arch_def.instruction(inst_name) raise "Can't find instruction '#{inst_name}'" if inst.nil? @@ -238,7 +251,7 @@ rule %r{#{MANUAL_GEN_DIR}/.*/.*/antora/modules/csrs/pages/.*.adoc} => [ ] do |t| csr_name = File.basename(t.name, ".adoc") - arch_def = arch_def_for("_") + arch_def = arch_def_for("_64") csr = arch_def.csr(csr_name) raise "Can't find csr '#{csr_name}'" if csr.nil? @@ -256,7 +269,7 @@ rule %r{#{MANUAL_GEN_DIR}/.*/.*/antora/modules/funcs/pages/funcs.adoc} => [ "gen:arch", ($root / "backends" / "manual" / "templates" / "func.adoc.erb").to_s ] do |t| - arch_def = arch_def_for("_") + arch_def = arch_def_for("_64") funcs_template_path = $root / "backends" / "manual" / "templates" / "func.adoc.erb" erb = ERB.new(funcs_template_path.read, trim_mode: "-") @@ -272,7 +285,7 @@ rule %r{#{MANUAL_GEN_DIR}/.*/top/.*/antora/landing/antora.yml} => [ parts = t.name.sub("#{MANUAL_GEN_DIR}/", "").split("/") manual_name = parts[0] - arch_def = arch_def_for("_") + arch_def = arch_def_for("_64") manual = arch_def.manual(manual_name) raise "Can't find any manual version for '#{manual_name}'" if manual.nil? @@ -297,7 +310,7 @@ rule %r{#{MANUAL_GEN_DIR}/.*/top/.*/antora/landing/modules/ROOT/pages/index.adoc parts = t.name.sub("#{MANUAL_GEN_DIR}/", "").split("/") manual_name = parts[0] - arch_def = arch_def_for("_") + arch_def = arch_def_for("_64") manual = arch_def.manual(manual_name) raise "Can't find any manual version for '#{manual_name}'" if manual.nil? @@ -325,7 +338,7 @@ rule %r{#{MANUAL_GEN_DIR}/.*/top/.*/antora/playbook/playbook.yml} => proc { |tna parts = t.name.sub("#{MANUAL_GEN_DIR}/", "").split("/") manual_name = parts[0] - arch_def = arch_def_for("_") + arch_def = arch_def_for("_64") manual = arch_def.manual(manual_name) raise "Can't find any manual version for '#{manual_name}'" if manual.nil? @@ -379,11 +392,11 @@ namespace :gen do A static HTML website will be written into gen/manual/MANUAL_NAME//html DESC desc html_manual_desc - task :html_manual => "gen:arch" do + task :html_manual => "#{$root}/.stamps/arch-gen-_64.stamp" do raise ArgumentError, "Missing required environment variable MANUAL_NAME\n\n#{html_manual_desc}" if ENV["MANUAL_NAME"].nil? raise ArgumentError, "Missing required environment variable VERSIONS\n\n#{html_manual_desc}" if ENV["VERSIONS"].nil? - arch_def = arch_def_for("_") + arch_def = arch_def_for("_64") manual = arch_def.manuals.find { |m| m.name == ENV["MANUAL_NAME"] } raise "No manual '#{ENV['MANUAL_NAME']}'" if manual.nil? @@ -448,7 +461,7 @@ namespace :serve do port = ENV.key?("PORT") ? ENV["PORT"] : 8000 - arch_def = arch_def_for("_") + arch_def = arch_def_for("_64") manual = arch_def.manuals.find { |m| m.name == ENV["MANUAL_NAME"] } raise "No manual '#{ENV['MANUAL_NAME']}'" if manual.nil? diff --git a/backends/manual/templates/csr.adoc.erb b/backends/manual/templates/csr.adoc.erb index 25abcf2a..38fa8324 100644 --- a/backends/manual/templates/csr.adoc.erb +++ b/backends/manual/templates/csr.adoc.erb @@ -93,7 +93,7 @@ a@ a@ -- -<%= field.type_pretty(arch_def) %> +<%= field.type_pretty(arch_def.symtab) %> -- a@ @@ -127,7 +127,7 @@ Description:: <%= arch_def.render_erb(field.description, "#{csr.name}.#{field.name}.description").gsub("\n", " +\n") %> Type:: -<%= field.type_pretty(arch_def) %> +<%= field.type_pretty(arch_def.symtab) %> Reset value:: <%= field.reset_value_pretty(arch_def) %> @@ -164,7 +164,7 @@ This CSR may return a value that is different from what is stored in hardware. [source,idl,subs="specialchars,macros"] ---- -<%= csr.sw_read_ast(arch_def.idl_compiler).gen_adoc %> +<%= csr.sw_read_ast(arch_def.symtab).gen_adoc %> ---- <%- end -%> diff --git a/backends/manual/templates/instruction.adoc.erb b/backends/manual/templates/instruction.adoc.erb index e233a7d0..030b1a60 100644 --- a/backends/manual/templates/instruction.adoc.erb +++ b/backends/manual/templates/instruction.adoc.erb @@ -104,7 +104,7 @@ RV64:: <%- if inst.key?("operation()") -%> [source,idl,subs="specialchars,macros"] ---- -<%= inst.operation_ast(inst.arch_def.idl_compiler).gen_adoc %> +<%= inst.operation_ast(inst.arch_def.symtab).gen_adoc %> ---- <%- end -%> diff --git a/backends/profile_doc/templates/profile_pdf.adoc.erb b/backends/profile_doc/templates/profile_pdf.adoc.erb index 9bbb32a5..0745dca9 100644 --- a/backends/profile_doc/templates/profile_pdf.adoc.erb +++ b/backends/profile_doc/templates/profile_pdf.adoc.erb @@ -620,7 +620,7 @@ RV64:: // TODO: add back after sym table update for generic arch def is merged in profiles branch <%# -<%- exception_list = inst.reachable_exceptions_str(arch_def.sym_table) -% > +<%- exception_list = inst.reachable_exceptions_str(arch_def.symtab) -% > <%- if exception_list.empty? -% > This instruction does not generate synchronous exceptions. <%- else -% > diff --git a/cfgs/_32/cfg.yaml b/cfgs/_32/cfg.yaml new file mode 100644 index 00000000..ca0f2d33 --- /dev/null +++ b/cfgs/_32/cfg.yaml @@ -0,0 +1,2 @@ +--- +type: partially configured diff --git a/cfgs/_32/implemented_exts.yaml b/cfgs/_32/implemented_exts.yaml new file mode 100644 index 00000000..11151c24 --- /dev/null +++ b/cfgs/_32/implemented_exts.yaml @@ -0,0 +1 @@ +implemented_extensions: [] \ No newline at end of file diff --git a/cfgs/_32/params.yaml b/cfgs/_32/params.yaml new file mode 100644 index 00000000..b4f8fb3d --- /dev/null +++ b/cfgs/_32/params.yaml @@ -0,0 +1,5 @@ +--- +params: + NAME: _32 + + XLEN: 32 diff --git a/cfgs/_64/cfg.yaml b/cfgs/_64/cfg.yaml new file mode 100644 index 00000000..ca0f2d33 --- /dev/null +++ b/cfgs/_64/cfg.yaml @@ -0,0 +1,2 @@ +--- +type: partially configured diff --git a/cfgs/_64/implemented_exts.yaml b/cfgs/_64/implemented_exts.yaml new file mode 100644 index 00000000..11151c24 --- /dev/null +++ b/cfgs/_64/implemented_exts.yaml @@ -0,0 +1 @@ +implemented_extensions: [] \ No newline at end of file diff --git a/cfgs/_64/params.yaml b/cfgs/_64/params.yaml new file mode 100644 index 00000000..b4ff65b8 --- /dev/null +++ b/cfgs/_64/params.yaml @@ -0,0 +1,4 @@ +params: + NAME: _64 + + XLEN: 64 diff --git a/cfgs/generic_rv64/cfg.yaml b/cfgs/generic_rv64/cfg.yaml new file mode 100644 index 00000000..c9fde921 --- /dev/null +++ b/cfgs/generic_rv64/cfg.yaml @@ -0,0 +1,2 @@ +--- +type: fully configured diff --git a/lib/arch_def.rb b/lib/arch_def.rb index 713a8d51..2346e3fc 100644 --- a/lib/arch_def.rb +++ b/lib/arch_def.rb @@ -26,7 +26,7 @@ class ArchDef # @return [Idl::Compiler] The IDL compiler attr_reader :idl_compiler - # @return [Idl::AstNode] Abstract syntax tree of global scope + # @return [Idl::IsaAst] Abstract syntax tree of global scope attr_reader :global_ast # @return [String] Name of this definition. Special names are: @@ -43,14 +43,14 @@ class ArchDef attr_reader :mxlen # hash for Hash lookup - def hash = @name.hash + def hash = @name_sym.hash # @return [Idl::SymbolTable] Symbol table with global scope - # @return [nil] if the architecture is not configured (use sym_table_32 or sym_table_64) - def sym_table - raise NotImplementedError, "Un-configured ArchDefs have no symbol table" if @sym_table.nil? + # @return [nil] if the architecture is not configured (use symtab_32 or symtab_64) + def symtab + raise NotImplementedError, "Un-configured ArchDefs have no symbol table" if @symtab.nil? - @sym_table + @symtab end def fully_configured? = @arch_def["type"] == "fully configured" @@ -64,7 +64,9 @@ def type = @arch_def["type"] # @param config_name [#to_s] The name of a configuration, which must correspond # to a folder under $root/cfgs def initialize(config_name, arch_def_path, overlay_path: nil) - @name = config_name.to_s + @name = config_name.to_s.freeze + @name_sym = @name.to_sym.freeze + @idl_compiler = Idl::Compiler.new(self) validator = Validator.instance @@ -75,8 +77,8 @@ def initialize(config_name, arch_def_path, overlay_path: nil) raise e end - @arch_def = YAML.load_file(arch_def_path, permitted_classes: [Date]) - @param_values = @arch_def.key?("params") ? @arch_def["params"] : {} + @arch_def = YAML.load_file(arch_def_path, permitted_classes: [Date]).freeze + @param_values = (@arch_def.key?("params") ? @arch_def["params"] : {}).freeze @mxlen = @arch_def.dig("params", "XLEN") # might be nil unless @mxlen.nil? @@ -84,55 +86,100 @@ def initialize(config_name, arch_def_path, overlay_path: nil) # to populate the symbol table. # # if this is the fully generic config ("_"), then you need to use - # either sym_table_32 or sym_table_64 - @sym_table = Idl::SymbolTable.new(self) + # either symtab_32 or symtab_64 + @symtab = Idl::SymbolTable.new(self) custom_globals_path = overlay_path.nil? ? Pathname.new("/does/not/exist") : overlay_path / "isa" / "globals.isa" idl_path = File.exist?(custom_globals_path) ? custom_globals_path : $root / "arch" / "isa" / "globals.isa" @global_ast = @idl_compiler.compile_file( - idl_path, - symtab: @sym_table + idl_path ) - - @sym_table.deep_freeze + @global_ast.add_global_symbols(@symtab) + @symtab.deep_freeze + @global_ast.freeze_tree(@symtab) + @mxlen.freeze else # parse globals @global_ast = @idl_compiler.compile_file( - $root / "arch" / "isa" / "globals.isa", - symtab: sym_table_32 + $root / "arch" / "isa" / "globals.isa" ) - sym_table_32.deep_freeze + @global_ast.add_global_symbols(symtab_32) + symtab_32.deep_freeze + @global_ast.freeze_tree(symtab_32) # do it again for rv64, but we don't need the ast this time - @idl_compiler.compile_file( - $root / "arch" / "isa" / "globals.isa", - symtab: sym_table_64 + global_ast_64 = @idl_compiler.compile_file( + $root / "arch" / "isa" / "globals.isa" ) - sym_table_64.deep_freeze + global_ast_64.add_global_symbols(symtab_64) + symtab_64.deep_freeze + global_ast_64.freeze_tree(symtab_64) end end - # Get a symbol table with globals defined for a generic (config-independent) RV32 architecture defintion - # Being config-independent, parameters in this symbol table will not have values assigned + # type check all IDL, including globals, instruction ops, and CSR functions # - # @return [Idl::SymbolTable] Symbol table with config-independent global symbols populated for RV32 - def sym_table_32 - raise NotImplementedError, "Use sym_table for configured def" unless @sym_table.nil? + # @param show_progress [Boolean] whether to show progress bars + # @param io [IO] where to write progress bars + # @return [void] + def type_check(show_progress: true, io: $stdout) + io.puts "Type checking IDL code for #{name}..." + progressbar = + if show_progress + ProgressBar.create(title: "Instructions", total: instructions.size) + end - return @sym_table_32 unless @sym_table_32.nil? + instructions.each do |inst| + progressbar.increment if show_progress + if @mxlen == 32 + inst.type_checked_operation_ast(@idl_compiler, @symtab, 32) if inst.rv32? + elsif @mxlen == 64 + inst.type_checked_operation_ast(@idl_compiler, @symtab, 64) if inst.rv64? + inst.type_checked_operation_ast(@idl_compiler, @symtab, 32) if possible_xlens.include?(32) && inst.rv32? + end + end - @sym_table_32 = Idl::SymbolTable.new(self, 32) - end + progressbar = + if show_progress + ProgressBar.create(title: "CSRs", total: csrs.size) + end - # Get a symbol table with globals defined for a generic (config-independent) RV64 architecture defintion - # Being config-independent, parameters in this symbol table will not have values assigned - # - # @return [Idl::SymbolTable] Symbol table with config-independent global symbols populated for RV64 - def sym_table_64 - raise NotImplementedError, "Use sym_table for configured def" unless @sym_table.nil? + csrs.each do |csr| + progressbar.increment if show_progress + if csr.has_custom_sw_read? + if (possible_xlens.include?(32) && csr.defined_in_base32?) || (possible_xlens.include?(64) && csr.defined_in_base64?) + csr.type_checked_sw_read_ast(@symtab) + end + end + csr.fields.each do |field| + unless field.type_ast(@symtab).nil? + if ((possible_xlens.include?(32) && csr.defined_in_base32? && field.defined_in_base32?) || + (possible_xlens.include?(64) && csr.defined_in_base64? && field.defined_in_base64?)) + field.type_checked_type_ast(@symtab) + end + end + unless field.reset_value_ast(@symtab).nil? + if ((possible_xlens.include?(32) && csr.defined_in_base32? && field.defined_in_base32?) || + (possible_xlens.include?(64) && csr.defined_in_base64? && field.defined_in_base64?)) + field.type_checked_reset_value_ast(@symtab) if csr.defined_in_base32? && field.defined_in_base32? + end + end + unless field.sw_write_ast(@symtab).nil? + field.type_checked_sw_write_ast(@symtab, 32) if possible_xlens.include?(32) && csr.defined_in_base32? && field.defined_in_base32? + field.type_checked_sw_write_ast(@symtab, 64) if possible_xlens.include?(64) && csr.defined_in_base64? && field.defined_in_base64? + end + end + end - return @sym_table_64 unless @sym_table_64.nil? + progressbar = + if show_progress + ProgressBar.create(title: "Functions", total: functions.size) + end + functions.each do |func| + progressbar.increment if show_progress + func.type_check(@symtab) + end - @sym_table_64 = Idl::SymbolTable.new(self, 64) + puts "done" if show_progress end # Returns whether or not it may be possible to switch XLEN given this definition. @@ -164,6 +211,8 @@ def multi_xlen? # @return [Boolean] true if this configuration might execute in multiple xlen environments in +mode+ # (e.g., that in some mode the effective xlen can be either 32 or 64, depending on CSR values) def multi_xlen_in_mode?(mode) + return false if mxlen == 32 + case mode when "M" mxlen.nil? @@ -766,16 +815,18 @@ def implemented_functions @implemented_functions << if inst.base.nil? if multi_xlen? - (inst.reachable_functions(sym_table, 32) + - inst.reachable_functions(sym_table, 64)) + (inst.reachable_functions(symtab, 32) + + inst.reachable_functions(symtab, 64)) else - inst.reachable_functions(sym_table, mxlen) + inst.reachable_functions(symtab, mxlen) end else - inst.reachable_functions(sym_table, inst.base) + inst.reachable_functions(symtab, inst.base) end end - @implemented_functions.flatten!.uniq!(&:name) + raise "?" unless @implemented_functions.is_a?(Array) + @implemented_functions = @implemented_functions.flatten + @implemented_functions.uniq!(&:name) puts " Finding all reachable functions from CSR operations" @@ -924,6 +975,7 @@ class ExceptionCode def initialize(name, var, number, ext) @name = name + @name.freeze @var = var @num = number @ext = ext diff --git a/lib/arch_obj_models/csr.rb b/lib/arch_obj_models/csr.rb index 823b63b4..fb546170 100644 --- a/lib/arch_obj_models/csr.rb +++ b/lib/arch_obj_models/csr.rb @@ -65,8 +65,8 @@ def reachable_functions(arch_def) if has_custom_sw_read? ast = pruned_sw_read_ast(arch_def) - symtab = arch_def.sym_table.deep_clone - symtab.push + symtab = arch_def.symtab.deep_clone + symtab.push(ast) fns.concat(ast.reachable_functions(symtab)) end @@ -94,7 +94,7 @@ def reachable_functions_unevaluated(arch_def) fns = [] if has_custom_sw_read? - ast = sw_read_ast(arch_def.idl_compiler) + ast = sw_read_ast(arch_def) fns.concat(ast.reachable_functions_unevaluated(arch_def)) end @@ -415,8 +415,8 @@ def type_checked_sw_read_ast(symtab) return ast unless ast.nil? symtab_hash = symtab.hash - symtab = symtab.deep_clone - symtab.push + symtab = symtab.global_clone + symtab.push(ast) # all CSR instructions are 32-bit symtab.add( "__instruction_encoding_size", @@ -427,28 +427,33 @@ def type_checked_sw_read_ast(symtab) Idl::Type.new(:bits, width: 128) ) - ast = sw_read_ast(symtab.archdef.idl_compiler) + ast = sw_read_ast(symtab) symtab.archdef.idl_compiler.type_check( ast, symtab, "CSR[#{name}].sw_read()" ) + symtab.pop + symtab.release @type_checked_sw_read_asts[symtab_hash] = ast end # @return [FunctionBodyAst] The abstract syntax tree of the sw_read() function - # @param idl_compiler [Idl::Compiler] A compiler - def sw_read_ast(idl_compiler) + # @param archdef [ArchDef] A configuration + def sw_read_ast(symtab) + raise ArgumentError, "Argument should be a symtab" unless symtab.is_a?(Idl::SymbolTable) + return @sw_read_ast unless @sw_read_ast.nil? return nil if @data["sw_read()"].nil? # now, parse the function - @sw_read_ast = idl_compiler.compile_func_body( + @sw_read_ast = symtab.archdef.idl_compiler.compile_func_body( @data["sw_read()"], return_type: Idl::Type.new(:bits, width: 128), # big int to hold special return values name: "CSR[#{name}].sw_read()", input_file: __source, input_line: source_line("sw_read()"), + symtab:, type_check: false ) @@ -464,10 +469,10 @@ def pruned_sw_read_ast(arch_def) ast = @pruned_sw_read_asts[arch_def.name] return ast unless ast.nil? - ast = type_checked_sw_read_ast(arch_def.sym_table).prune(arch_def.sym_table.deep_clone) + ast = type_checked_sw_read_ast(arch_def.symtab) - symtab = arch_def.sym_table.deep_clone - symtab.push + symtab = arch_def.symtab.global_clone + symtab.push(ast) # all CSR instructions are 32-bit symtab.add( "__instruction_encoding_size", @@ -478,11 +483,18 @@ def pruned_sw_read_ast(arch_def) Idl::Type.new(:bits, width: 128) ) + ast = ast.prune(symtab) + ast.freeze_tree(arch_def.symtab) + arch_def.idl_compiler.type_check( ast, symtab, "CSR[#{name}].sw_read()" ) + + symtab.pop + symtab.release + @pruned_sw_read_asts[arch_def.name] = ast end diff --git a/lib/arch_obj_models/csr_field.rb b/lib/arch_obj_models/csr_field.rb index ebfa93a3..0e6740f9 100644 --- a/lib/arch_obj_models/csr_field.rb +++ b/lib/arch_obj_models/csr_field.rb @@ -39,16 +39,17 @@ def exists_in_cfg?(possible_xlens, extensions) # @return [Idl::FunctionBodyAst] Abstract syntax tree of the type() function # @return [nil] if the type property is not a function - # @param idl_compiler [Idl::Compiler] A compiler - def type_ast(idl_compiler) + # @param symtab [SymbolTable] Symbol table with execution context + def type_ast(symtab) return @type_ast unless @type_ast.nil? return nil if @data["type()"].nil? - @type_ast = idl_compiler.compile_func_body( + @type_ast = symtab.archdef.idl_compiler.compile_func_body( @data["type()"], name: "CSR[#{csr.name}].#{name}.type()", input_file: csr.__source, input_line: csr.source_line("fields", name, "type()"), + symtab:, type_check: false ) @@ -66,20 +67,25 @@ def type_checked_type_ast(symtab) return ast unless ast.nil? symtab_hash = symtab.hash - symtab = symtab.deep_clone - symtab.push + + symtab = symtab.global_clone + + symtab.push(ast) # all CSR instructions are 32-bit symtab.add( "__expected_return_type", Idl::Type.new(:enum_ref, enum_class: symtab.get("CsrFieldType")) ) - ast = type_ast(symtab.archdef.idl_compiler) + ast = type_ast(symtab) symtab.archdef.idl_compiler.type_check( ast, symtab, "CSR[#{name}].type()" ) + symtab.pop + symtab.release + @type_checked_type_asts[symtab_hash] = ast end @@ -94,24 +100,29 @@ def pruned_type_ast(symtab) ast = type_checked_type_ast(symtab).prune(symtab.deep_clone) symtab_hash = symtab.hash - symtab.push + symtab = symtab.global_clone + symtab.push(ast) # all CSR instructions are 32-bit symtab.add( "__expected_return_type", Idl::Type.new(:enum_ref, enum_class: symtab.get("CsrFieldType")) ) + ast.freeze_tree(symtab) + symtab.archdef.idl_compiler.type_check( ast, symtab, "CSR[#{name}].type()" ) + symtab.pop + symtab.release @pruned_type_asts[symtab_hash] = ast end # returns the definitive type for a configuration # - # @param arch_def [ArchDef] A config + # @param symtab [SymbolTable] Symbol table # @return [String] # The type of the field. One of: # 'RO' => Read-only @@ -120,12 +131,14 @@ def pruned_type_ast(symtab) # 'RW-R' => Read-write, with a restricted set of legal values # 'RW-H' => Read-write, with a hardware update # 'RW-RH' => Read-write, with a hardware update and a restricted set of legal values - def type(arch_def) - if !@type_cache.nil? && @type_cache.key?(arch_def) - return @type_cache[arch_def] - end + def type(symtab) + raise ArgumentError, "Argument 1 should be a symtab" unless symtab.is_a?(Idl::SymbolTable) - @type_cache ||= {} + unless @type_cache.nil? + raise "Different archdef for type #{@type_cache.keys}, #{symtab.archdef}" unless @type_cache.key?(symtab.archdef) + + return @type_cache[symtab.archdef] + end type = if @data.key?("type") @@ -135,46 +148,58 @@ def type(arch_def) idl = @data["type()"] raise "type() is nil for #{csr.name}.#{name} #{@data}?" if idl.nil? - raise Idl::AstNode::ValueError.new(0, "", ""), "ArchDef is not configured" if arch_def.unconfigured? - - # grab global symtab - sym_table = arch_def.sym_table + + # value_result = Idl::AstNode.value_try do + ast = type_checked_type_ast(symtab) begin - case type_checked_type_ast(sym_table.deep_clone).return_value(sym_table.deep_clone.push) - when 0 - "RO" - when 1 - "RO-H" - when 2 - "RW" - when 3 - "RW-R" - when 4 - "RW-H" - when 5 - "RW-RH" - else - raise "Unhandled CsrFieldType value" - end - rescue Idl::AstNode::ValueError => e - warn "In parsing #{csr.name}.#{name}::type()" - warn " Return of type() function cannot be evaluated at compile time" - raise e + symtab = symtab.global_clone + + symtab.push(ast) + type = case ast.return_value(symtab) + when 0 + "RO" + when 1 + "RO-H" + when 2 + "RW" + when 3 + "RW-R" + when 4 + "RW-H" + when 5 + "RW-RH" + else + raise "Unhandled CsrFieldType value" + end + ensure + symtab.pop + symtab.release end + type + # end + # Idl::AstNode.value_else(value_result) do + # warn "In parsing #{csr.name}.#{name}::type()" + # raise " Return of type() function cannot be evaluated at compile time" + # Idl::AstNode.value_error "" + # end end - @type_cache[arch_def] = type + @type_cache ||= {} + @type_cache[symtab.archdef] = type end # @return [String] A pretty-printed type string - def type_pretty(arch_def) - begin - type(arch_def) - rescue Idl::AstNode::ValueError => e - ast = type_ast(arch_def.idl_compiler) - ast.gen_option_adoc + def type_pretty(symtab) + str = nil + value_result = Idl::AstNode.value_try do + str = type(symtab) end + Idl::AstNode.value_else(value_result) do + ast = type_ast(symtab) + str = ast.gen_option_adoc + end + str end # @return [Alias,nil] The aliased field, or nil if there is no alias @@ -212,14 +237,14 @@ def reachable_functions(archdef, effective_xlen) symtab = if (archdef.configured?) - archdef.sym_table + archdef.symtab else raise ArgumentError, "Must supply effective_xlen for generic ArchDef" if effective_xlen.nil? if effective_xlen == 32 - archdef.sym_table_32 + archdef.symtab_32 else - archdef.sym_table_64 + archdef.symtab_64 end end @@ -228,7 +253,7 @@ def reachable_functions(archdef, effective_xlen) ast = pruned_sw_write_ast(archdef, effective_xlen) unless ast.nil? sw_write_symtab = symtab.deep_clone - sw_write_symtab.push + sw_write_symtab.push(ast) sw_write_symtab.add("csr_value", Idl::Var.new("csr_value", csr.bitfield_type(symtab.archdef, effective_xlen))) fns.concat ast.reachable_functions(sw_write_symtab) end @@ -236,13 +261,13 @@ def reachable_functions(archdef, effective_xlen) if @data.key?("type()") ast = pruned_type_ast(symtab.deep_clone) unless ast.nil? - fns.concat ast.reachable_functions(symtab.deep_clone.push) + fns.concat ast.reachable_functions(symtab.deep_clone.push(ast)) end end if @data.key?("reset_value()") ast = pruned_reset_value_ast(symtab.deep_clone) unless ast.nil? - fns.concat ast.reachable_functions(symtab.deep_clone.push) + fns.concat ast.reachable_functions(symtab.deep_clone.push(ast)) end end @@ -250,27 +275,29 @@ def reachable_functions(archdef, effective_xlen) end # @return [Array] List of functions called thorugh this field, irrespective of context - # @param archdef [ArchDef] Architecture definition - def reachable_functions_unevaluated(archdef) + # @param symtab [SymbolTable] + def reachable_functions_unevaluated(symtab) + raise ArgumentError, "Argument should be a symtab" unless symtab.is_a?(Idl::SymbolTable) + return @reachable_functions_unevaluated unless @reachable_functions_unevaluated.nil? fns = [] if has_custom_sw_write? - ast = sw_write_ast(archdef.idl_compiler) + ast = sw_write_ast(symtab) unless ast.nil? - fns.concat ast.reachable_functions_unevaluated(archdef) + fns.concat ast.reachable_functions_unevaluated(symtab) end end if @data.key?("type()") - ast = type_ast(archdef.idl_compiler) + ast = type_ast(symtab) unless ast.nil? - fns.concat ast.reachable_functions_unevaluated(archdef) + fns.concat ast.reachable_functions_unevaluated(symtab) end end if @data.key?("reset_value()") - ast = reset_value_ast(archdef.idl_compiler) + ast = reset_value_ast(symtab) unless ast.nil? - fns.concat ast.reachable_functions_unevalutated(archdef) + fns.concat ast.reachable_functions_unevalutated(symtab) end end @@ -294,16 +321,19 @@ def dynamic_location?(arch_def) # @param arch_def [IdL::Compiler] A compiler # @return [Idl::FunctionBodyAst] Abstract syntax tree of the reset_value function # @return [nil] If the reset_value is not a function - def reset_value_ast(idl_compiler) + def reset_value_ast(symtab) + raise ArgumentError, "Argument should be a symtab (is a #{symtab.class.name})" unless symtab.is_a?(Idl::SymbolTable) + return @reset_value_ast unless @reset_value_ast.nil? return nil unless @data.key?("reset_value()") - @reset_value_ast = idl_compiler.compile_func_body( + @reset_value_ast = symtab.archdef.idl_compiler.compile_func_body( @data["reset_value()"], return_type: Idl::Type.new(:bits, width: 64), name: "CSR[#{parent.name}].#{name}.reset_value()", input_file: csr.__source, input_line: csr.source_line("fields", name, "reset_value()"), + symtab:, type_check: false ) end @@ -320,11 +350,11 @@ def type_checked_reset_value_ast(symtab) return nil unless @data.key?("reset_value()") - ast = reset_value_ast(symtab.archdef.idl_compiler) + ast = reset_value_ast(symtab) symtab_hash = symtab.hash symtab = symtab.deep_clone - symtab.push + symtab.push(ast) symtab.add("__expected_return_type", Idl::Type.new(:bits, width: 64)) symtab.archdef.idl_compiler.type_check( ast, @@ -348,18 +378,21 @@ def pruned_reset_value_ast(symtab) symtab_hash = symtab.hash symtab = symtab.deep_clone - symtab.push + symtab.push(ast) symtab.add("__expected_return_type", Idl::Type.new(:bits, width: 64)) ast = ast.prune(symtab) symtab.pop - symtab.push + + ast.freeze_tree(symtab) + + symtab.push(ast) symtab.add("__expected_return_type", Idl::Type.new(:bits, width: 64)) symtab.archdef.idl_compiler.type_check( ast, symtab, - "CSR[#{csr.name}].reset_value()" + "CSR[#{csr.name}].#{name}.reset_value()" ) @type_checked_reset_value_asts[symtab_hash] = ast @@ -369,9 +402,8 @@ def pruned_reset_value_ast(symtab) # @return [Integer] The reset value of this field # @return [String] The string 'UNDEFINED_LEGAL' if, for this config, there is no defined reset value def reset_value(arch_def, effective_xlen = nil) - if !@reset_value_cache.nil? && @reset_value_cache.key?(arch_def) - return @reset_value_cache[arch_def] - end + cached_value = @reset_value_cache.nil? ? nil : @reset_value_cache[arch_def] + return cached_value if cached_value @reset_value_cache ||= {} @@ -381,13 +413,14 @@ def reset_value(arch_def, effective_xlen = nil) else symtab = if !arch_def.mxlen.nil? - arch_def.sym_table + arch_def.symtab else raise ArgumentError, "effective_xlen is required when using generic arch_def" if effective_xlen.nil? - effective_xlen == 32 ? arch_def.sym_table_32 : arch_def.sym_table_64 + effective_xlen == 32 ? arch_def.symtab_32 : arch_def.symtab_64 end - val = pruned_reset_value_ast(symtab.deep_clone).return_value(symtab.deep_clone.push) + ast = pruned_reset_value_ast(symtab.deep_clone) + val = ast.return_value(symtab.deep_clone.push(ast)) val = "UNDEFINED_LEGAL" if val == 0x1_0000_0000_0000_0000 val end @@ -396,36 +429,39 @@ def reset_value(arch_def, effective_xlen = nil) def dynamic_reset_value?(arch_def) return false unless @data["reset_value"].nil? - begin + value_result = value_try do if arch_def.mxlen.nil? - # need to try with generic sym_table_32/sym_table_64 + # need to try with generic symtab_32/symtab_64 reset_value_32 = reset_value(arch_def, 32) reset_value_64 = reset_value(arch_def, 64) reset_value_32 != reset_value_64 else # just call the function, see if we get a value error reset_value(arch_def) + false end - rescue Idl::AstNode::ValueError - true - end + end || true end def reset_value_pretty(arch_def) - begin - if arch_def.mxlen.nil? - if dynamic_reset_value?(arch_def) - raise Idl::AstNode::ValueError.new(0, "", "") + str = nil + value_result = Idl::AstNode.value_try do + str = + if arch_def.mxlen.nil? + if dynamic_reset_value?(arch_def) + Idl::AstNode.value_error "" + else + reset_value(arch_def, 32) # 32 or 64, doesn't matter + end else - reset_value(arch_def, 32) # 32 or 64, doesn't matter + reset_value(arch_def) end - else - reset_value(arch_def) - end - rescue Idl::AstNode::ValueError - ast = reset_value_ast(arch_def.idl_compiler) - ast.gen_option_adoc end + Idl::AstNode.value_else(value_result) do + ast = reset_value_ast(arch_def.symtab) + str = ast.gen_option_adoc + end + str end # @return [Boolean] true if the CSR field has a custom sw_write function @@ -444,8 +480,8 @@ def type_checked_sw_write_ast(symtab, effective_xlen) return nil unless @data.key?("sw_write(csr_value)") symtab_hash = symtab.hash - symtab = symtab.deep_clone - symtab.push + symtab = symtab.global_clone + symtab.push(ast) # all CSR instructions are 32-bit symtab.add( "__instruction_encoding_size", @@ -460,39 +496,43 @@ def type_checked_sw_write_ast(symtab, effective_xlen) Idl::Var.new("csr_value", csr.bitfield_type(symtab.archdef, effective_xlen)) ) - ast = sw_write_ast(symtab.archdef.idl_compiler) + ast = sw_write_ast(symtab) symtab.archdef.idl_compiler.type_check( ast, symtab, "CSR[#{csr.name}].#{name}.sw_write()" ) + symtab.pop + symtab.release @type_checked_sw_write_asts[symtab_hash] = ast end # @return [Idl::FunctionBodyAst] The abstract syntax tree of the sw_write() function # @return [nil] If there is no sw_write() function - # @param idl_compiler [Idl::Compiler] A compiler - def sw_write_ast(idl_compiler) + # @param archdef [ArchDef] An architecture definition + def sw_write_ast(symtab) + raise ArgumentError, "Argument should be a symtab" unless symtab.is_a?(Idl::SymbolTable) + return @sw_write_ast unless @sw_write_ast.nil? return nil if @data["sw_write(csr_value)"].nil? # now, parse the function - @sw_write_ast = idl_compiler.compile_func_body( + @sw_write_ast = symtab.archdef.idl_compiler.compile_func_body( @data["sw_write(csr_value)"], return_type: Idl::Type.new(:bits, width: 128), # big int to hold special return values name: "CSR[#{csr.name}].#{name}.sw_write(csr_value)", - input_file: csr.source_line("fields", name, "sw_write(csr_value)"), + input_file: csr.__source, + input_line: csr.source_line("fields", name, "sw_write(csr_value)"), + symtab:, type_check: false ) raise "unexpected #{@sw_write_ast.class}" unless @sw_write_ast.is_a?(Idl::FunctionBodyAst) - @sw_write_ast.set_input_file(csr.__source, csr.source_line("fields", name, "sw_write(csr_value)")) - @sw_write_ast end - # @return [Idl::FunctionBodyAst] The abstract syntax tree of the sw_write() function, type checked + # @return [Idl::FunctionBodyAst] The abstract syntax tree of the sw_write() function, type checked and pruned # @return [nil] if there is no sw_write() function # @param effective_xlen [Integer] effective xlen, needed because fields can change in different bases # @param arch_def [ArchDef] A configuration @@ -505,8 +545,8 @@ def pruned_sw_write_ast(arch_def, effective_xlen) raise ArgumentError, "arch_def must be configured to prune" if arch_def.unconfigured? - symtab = arch_def.sym_table.deep_clone - symtab.push + symtab = arch_def.symtab.global_clone + symtab.push(ast) # all CSR instructions are 32-bit symtab.add( "__instruction_encoding_size", @@ -521,13 +561,22 @@ def pruned_sw_write_ast(arch_def, effective_xlen) Idl::Var.new("csr_value", csr.bitfield_type(arch_def, effective_xlen)) ) - ast = type_checked_sw_write_ast(arch_def.sym_table.deep_clone, effective_xlen).prune(symtab.deep_clone) + ast = type_checked_sw_write_ast(arch_def.symtab, effective_xlen) + ast = ast.prune(symtab) + raise "Symbol table didn't come back at global + 1" unless symtab.levels == 2 + + ast.freeze_tree(arch_def.symtab) + arch_def.idl_compiler.type_check( ast, symtab, "CSR[#{name}].sw_write(csr_value)" ) + + symtab.pop + symtab.release + @pruned_sw_write_asts[arch_def.name] = ast end @@ -706,6 +755,6 @@ def location_pretty(arch_def, effective_xlen = nil) # @return [String] Long description of the field type def type_desc(arch_def) - TYPE_DESC_MAP[type(arch_def)] + TYPE_DESC_MAP[type(arch_def.symtab)] end end \ No newline at end of file diff --git a/lib/arch_obj_models/instruction.rb b/lib/arch_obj_models/instruction.rb index bfbece61..9b9fa007 100644 --- a/lib/arch_obj_models/instruction.rb +++ b/lib/arch_obj_models/instruction.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'ruby-prof-flamegraph' + require_relative "obj" @@ -54,9 +56,9 @@ def assembly @data["assembly"] end - def fill_symtab(global_symtab, effective_xlen) + def fill_symtab(global_symtab, effective_xlen, ast) symtab = global_symtab.deep_clone - symtab.push + symtab.push(ast) symtab.add( "__instruction_encoding_size", Idl::Var.new("__instruction_encoding_size", Idl::Type.new(:bits, width: encoding_width.bit_length), encoding_width) @@ -91,10 +93,13 @@ def pruned_operation_ast(global_symtab, effective_xlen) return nil unless @data.key?("operation()") type_checked_ast = type_checked_operation_ast(arch_def.idl_compiler, global_symtab, effective_xlen) - pruned_ast = type_checked_ast.prune(fill_symtab(global_symtab, effective_xlen)) + print "Pruning #{name} operation()..." + pruned_ast = type_checked_ast.prune(fill_symtab(global_symtab, effective_xlen, type_checked_ast)) + puts "done" + pruned_ast.freeze_tree(global_symtab) arch_def.idl_compiler.type_check( pruned_ast, - fill_symtab(global_symtab, effective_xlen), + fill_symtab(global_symtab, effective_xlen, pruned_ast), "#{name}.operation() (pruned)" ) @@ -109,24 +114,45 @@ def reachable_functions(symtab, effective_xlen) [] else # RubyProf.start - pruned_operation_ast(symtab, effective_xlen).reachable_functions(fill_symtab(symtab, effective_xlen)) + ast = type_checked_operation_ast(symtab.archdef.idl_compiler, symtab, effective_xlen) + print "Determining reachable funcs from #{name}..." + fns = ast.reachable_functions(fill_symtab(symtab, effective_xlen, ast)) + puts "done" # result = RubyProf.stop # RubyProf::FlatPrinter.new(result).print($stdout) # exit + fns end end # @param symtab [Idl::SymbolTable] Symbol table with global scope populated # @param effective_xlen [Integer] Effective XLEN to evaluate against - # @return [Array] List of all exceptions that can be reached from operation() + # @return [Integer] Mask of all exceptions that can be reached from operation() def reachable_exceptions(symtab, effective_xlen) if @data["operation()"].nil? [] else - pruned_operation_ast(symtab).reachable_exceptions(fill_symtab(symtab, effective_xlen)).uniq + # pruned_ast = pruned_operation_ast(symtab) + # type_checked_operation_ast() + type_checked_ast = type_checked_operation_ast(symtab.arch_def.idl_compiler, symtab, effective_xlen) + symtab = fill_symtab(symtab, effective_xlen, pruned_ast) + type_checked_ast.reachable_exceptions(symtab) end end + def mask_to_array(int) + elems = [] + idx = 0 + while int != 0 + if (int & (1 << idx)) != 0 + elems << idx + end + int &= ~(1 << idx) + idx += 1 + end + elems + end + # @param symtab [Idl::SymbolTable] Symbol table with global scope populated # @param effective_xlen [Integer] Effective XLEN to evaluate against. If nil, evaluate against all valid XLENs # @return [Array] List of all exceptions that can be reached from operation() @@ -140,28 +166,54 @@ def reachable_exceptions_str(symtab, effective_xlen=nil) if symtab.archdef.multi_xlen? if base.nil? ( - pruned_operation_ast(symtab, 32).reachable_exceptions(fill_symtab(symtab, 32)).uniq.map { |code| + pruned_ast = pruned_operation_ast(symtab, 32) + print "Determining reachable exceptions from #{name}#RV32..." + e32 = mask_to_array(pruned_ast.reachable_exceptions(fill_symtab(symtab, 32, pruned_ast))).map { |code| etype.element_name(code) - } + - pruned_operation_ast(symtab, 64).reachable_exceptions(fill_symtab(symtab, 64)).uniq.map { |code| + } + puts "done" + pruned_ast = pruned_operation_ast(symtab, 64) + print "Determining reachable exceptions from #{name}#RV64..." + e64 = mask_to_array(prunted_ast.reachable_exceptions(fill_symtab(symtab, 64, pruned_ast))).map { |code| etype.element_name(code) } + puts done + e32 + e64 ).uniq else - pruned_operation_ast(symtab, base).reachable_exceptions(fill_symtab(symtab, base)).uniq.map { |code| - etype.element_name(code) - } + pruned_ast = pruned_operation_ast(symtab, base) + print "Determining reachable exceptions from #{name}..." + result = RubyProf.profile do + e = mask_to_array(pruned_ast.reachable_exceptions(fill_symtab(symtab, base, pruned_ast))).map { |code| + etype.element_name(code) + } + end + RubyProf::CallStackPrinter.new(result).print(File.open("#{name}-profile.html", "w+"), {}) + puts "done" + e end else effective_xlen = symtab.archdef.mxlen - pruned_operation_ast(symtab, effective_xlen).reachable_exceptions(fill_symtab(symtab, effective_xlen)).uniq.map { |code| - etype.element_name(code) - } + pruned_ast = pruned_operation_ast(symtab, effective_xlen) + print "Determining reachable exceptions from #{name}..." + # result = RubyProf.profile do + e = mask_to_array(pruned_ast.reachable_exceptions(fill_symtab(symtab, effective_xlen, pruned_ast))).map { |code| + etype.element_name(code) + } + # end + # RubyProf::FlameGraphPrinter.new(result).print(File.open("#{name}-profile.html", "w+"), {}) + puts "done" + e end else - pruned_operation_ast(symtab, effective_xlen).reachable_exceptions(fill_symtab(symtab, effective_xlen)).uniq.map { |code| + pruned_ast = pruned_operation_ast(symtab, effective_xlen) + + print "Determining reachable exceptions from #{name}..." + e = mask_to_array(prunted_ast.reachable_exceptions(fill_symtab(symtab, effective_xlen, pruned_ast))).map { |code| etype.element_name(code) } + puts "done" + e end # result = RubyProf.stop # RubyProf::FlatPrinter.new(result).print(STDOUT) @@ -543,21 +595,23 @@ def type_checked_operation_ast(idl_compiler, symtab, effective_xlen) return nil unless @data.key?("operation()") - ast = operation_ast(idl_compiler) + ast = operation_ast(symtab) - idl_compiler.type_check(ast, fill_symtab(symtab, effective_xlen), "#{name}.operation()") + idl_compiler.type_check(ast, fill_symtab(symtab, effective_xlen, ast), "#{name}.operation()") @type_checked_operation_ast[symtab.hash] = ast end + # @param symtab [SymbolTable] Symbol table with compilation context # @return [FunctionBodyAst] The abstract syntax tree of the instruction operation - def operation_ast(idl_compiler) + def operation_ast(symtab) return @operation_ast unless @operation_ast.nil? return nil if @data["operation()"].nil? # now, parse the operation - @operation_ast = idl_compiler.compile_inst_operation( + @operation_ast = symtab.archdef.idl_compiler.compile_inst_operation( self, + symtab:, input_file: @data["__source"], input_line: source_line("operation()") ) diff --git a/lib/asciidoc_extensions.js b/lib/asciidoc_extensions.js new file mode 100644 index 00000000..7da2f377 --- /dev/null +++ b/lib/asciidoc_extensions.js @@ -0,0 +1,3 @@ +const asciidoctor = require('asciidoctor')() +const registry = asciidoctor.Extensions.create() +require('./asciidoc_when_extension.js')(registry) \ No newline at end of file diff --git a/lib/idl.rb b/lib/idl.rb index fc094a16..47250ddb 100644 --- a/lib/idl.rb +++ b/lib/idl.rb @@ -41,7 +41,7 @@ def initialize(arch_def) @arch_def = arch_def end - def compile_file(path, symtab: nil, type_check: true) + def compile_file(path) @parser.set_input_file(path.to_s) m = @parser.parse path.read @@ -91,24 +91,13 @@ def compile_file(path, symtab: nil, type_check: true) MSG end - include_ast = compile_file(include_path, symtab: nil, type_check: false) + include_ast = compile_file(include_path) include_ast.set_input_file_unless_already_set(include_path) ast.replace_include!(child, include_ast) end # we may have already set an input file from an include, so only set it if it's not already set ast.set_input_file_unless_already_set(path.to_s) - if type_check - begin - ast.type_check(symtab) - rescue AstNode::TypeError, AstNode::InternalError, AstNode::ValueError => e - warn "\n" - warn e.what - warn e.bt - exit 1 - end - ast.freeze_tree - end ast end @@ -138,12 +127,13 @@ def compile_func_body(body, return_type: nil, symtab: nil, name: nil, input_file # fix up left recursion ast = m.to_ast ast.set_input_file(input_file, input_line) + ast.freeze_tree(symtab) # type check unless type_check == false cloned_symtab = symtab.deep_clone - cloned_symtab.push + cloned_symtab.push(ast) cloned_symtab.add("__expected_return_type", return_type) unless return_type.nil? extra_syms.each { |k, v| @@ -179,7 +169,6 @@ def compile_func_body(body, return_type: nil, symtab: nil, name: nil, input_file cloned_symtab.pop end - ast.freeze_tree end ast @@ -188,11 +177,11 @@ def compile_func_body(body, return_type: nil, symtab: nil, name: nil, input_file # compile an instruction operation, and return the abstract syntax tree # # @param inst [Instruction] Instruction object - # @param symtab [SymbolTable] Symbol table to use for type checking + # @param symtab [SymbolTable] Symbol table # @param input_file [Pathname] Path to the input file this source comes from # @param input_line [Integer] Starting line in the input file that this source comes from # @return [Ast] The root of the abstract syntax tree - def compile_inst_operation(inst, input_file: nil, input_line: 0) + def compile_inst_operation(inst, symtab:, input_file: nil, input_line: 0) operation = inst["operation()"] @parser.set_input_file(input_file, input_line) @@ -208,7 +197,7 @@ def compile_inst_operation(inst, input_file: nil, input_line: 0) # fix up left recursion ast = m.to_ast ast.set_input_file(input_file, input_line) - ast.freeze_tree + ast.freeze_tree(symtab) ast end @@ -221,13 +210,22 @@ def compile_inst_operation(inst, input_file: nil, input_line: 0) # @raise AstNode::TypeError if a type error is found def type_check(ast, symtab, what) # type check + raise "Tree should be frozen" unless ast.frozen? + begin - ast.type_check(symtab) - rescue AstNode::TypeError, AstNode::ValueError => e - warn "While type checking #{what}:" - warn e.what - warn e.backtrace - exit 1 + value_result = AstNode.value_try do + ast.type_check(symtab) + end + AstNode.value_else(value_result) do + warn "While type checking #{what}, got a value error on:" + warn ast.text_value + warn AstNode.value_error_reason + warn symtab.callstack + unless AstNode.value_error_ast.nil? + warn "At #{AstNode.value_error_ast.input_file}:#{AstNode.value_error_ast.lineno}" + end + exit 1 + end rescue AstNode::InternalError => e warn "While type checking #{what}:" warn e.what @@ -235,8 +233,6 @@ def type_check(ast, symtab, what) exit 1 end - ast.freeze_tree - ast end @@ -252,6 +248,7 @@ def compile_expression(expression, symtab, pass_error: false) ast = m.to_ast ast.set_input_file("[EXPRESSION]", 0) + ast.freeze_tree(symtab) begin ast.type_check(symtab) rescue AstNode::TypeError => e @@ -270,7 +267,6 @@ def compile_expression(expression, symtab, pass_error: false) exit 1 end - ast.freeze_tree ast end diff --git a/lib/idl/ast.rb b/lib/idl/ast.rb index dc3c653b..1a435b05 100644 --- a/lib/idl/ast.rb +++ b/lib/idl/ast.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require 'benchmark' @@ -39,6 +39,8 @@ def space? = false end module Idl + EMPTY_ARRAY = [].freeze + # base class for all nodes considered part of the Ast # @abstract class AstNode @@ -62,6 +64,9 @@ class AstNode # @retrun [Range] Range within the input for this node attr_reader :interval + # @return [String] The IDL source of this node + attr_reader :text_value + # @retrun [AstNode] The parent node # @retrun [nil] if this is the root of the tree attr_reader :parent @@ -142,6 +147,20 @@ def message end end + def self.value_try(&block) + catch(:value_error) do + yield block + end + end + def value_try(&block) = return self.class.value_try(&block) + + def self.value_else(value_result, &block) + return unless value_result == :unknown_value + + yield block + end + def value_else(value_result, &block) = self.class.value_else(value_result, &block) + # @param input [String] The source being compiled # @param interval [Range] The range in the source corresponding to this AstNode # @param children [Array] Children of this node @@ -150,6 +169,7 @@ def initialize(input, interval, children) @input_file = nil @starting_line = 0 @interval = interval + @text_value = input[interval] children.each { |child| raise ArgumentError, "Children of #{self.class.name} must be AstNodes (found a #{child.class.name})" unless child.is_a?(AstNode)} @children = children @parent = nil # will be set later unless this is the root @@ -203,10 +223,6 @@ def find_ancestor(klass) end end - def text_value - input[interval] - end - # @return [String] returns +-2 lines around the current interval def lines_around cnt = 0 @@ -293,13 +309,25 @@ def internal_error(reason) raise AstNode::InternalError, msg end + @value_error_reason = nil + @value_error_ast = nil + class << self + attr_accessor :value_error_reason, :value_error_ast + end + # raise a value error, indicating that the value is not known at compile time # # @param reason [String] Error message # @raise [AstNode::ValueError] always - def value_error(reason) - raise AstNode::ValueError.new(lineno, input_file, reason), reason, [] + def self.value_error(reason, ast = nil) + AstNode.value_error_reason = reason + AstNode.value_error_ast = ast + # warn reason + # warn "At #{ast.input_file}:#{ast.lineno}" unless ast.nil? + throw(:value_error, :unknown_value) + #raise AstNode::ValueError.new(lineno, input_file, reason), reason, [] end + def value_error(reason) = self.class.value_error(reason, self) # unindent a multiline string, getting rid of all common leading whitespace (like <<~ heredocs) # @@ -323,9 +351,17 @@ def print_ast(indent = 0, indent_size: 2, io: $stdout) end end - # freeze the entire tree from further modification - def freeze_tree - @children.each { |child| child.freeze_tree } + # @!macro [new] freeze_tree + # + # freeze the entire tree from further modification + # This is also an opportunity to pre-calculate anything that only needs global symbols + # + # @param global_symtab [SymbolTable] Symbol table with global scope populated + + + # @!macro freeze_tree + def freeze_tree(global_symtab) + @children.each { |child| child.freeze_tree(global_symtab) } freeze end @@ -514,7 +550,9 @@ class IdAst < AstNode def name = text_value def initialize(input, interval) - super(input, interval, []) + super(input, interval, EMPTY_ARRAY) + @const = (text_value[0] == text_value[0].upcase) + @vars = {} end # @!macro type_check @@ -524,20 +562,41 @@ def type_check(symtab) # @!macro type_no_archdef def type(symtab) + return @type unless @type.nil? + internal_error "Symbol '#{name}' not found" if symtab.get(name).nil? sym = symtab.get(name) - if sym.is_a?(Type) - sym - elsif sym.is_a?(Var) - sym.type - else - internal_error "Unexpected object on the symbol table" - end + # @type = + if sym.is_a?(Type) + sym + elsif sym.is_a?(Var) + sym.type + else + internal_error "Unexpected object on the symbol table" + end end - # @!macro value_no_archdef + # @return [Boolean] whether or not the Id represents a const + def const? = @const + + # @!macro value def value(symtab) + # can't do this.... a const might be in a template function, with different values at call time + # if @const + # # consts never change, so we can look them up by arch_def + # var = @vars[symtab.archdef] + # if var.nil? + # var = symtab.get(name) + # @vars[symtab.archdef] = var + # end + # type_error "Variable '#{name}' was not found" if var.nil? + # value_error "Value of '#{name}' not known" if var.value.nil? + # value_error "Value of #{name} is unknown" if var.value == :unknown + + # return var.value + # end + var = symtab.get(name) type_error "Variable '#{name}' was not found" if var.nil? @@ -570,6 +629,7 @@ def to_ast # 0, 0, 0, 0, 0, 0, 0, 0]; class GlobalWithInitializationAst < AstNode include Executable + include Declaration # @return [VariableDeclationWithInitializationAst] The initializer def var_decl_with_init @@ -596,6 +656,13 @@ def value(symtab) var_decl_with_init.value(symtab) end + def add_symbol(symtab) + raise "Symtab should be at global scope" unless symtab.levels == 1 + + # globals never have a compile-time value + var_decl_with_init.add_symbol(symtab) + end + # @1macro to_idl def to_idl var_decl_with_init.to_idl @@ -630,8 +697,11 @@ def type(symtab) end def add_symbol(symtab) + internal_error "Should be at global scope" unless symtab.levels == 1 + declaration.add_symbol(symtab) end + end # @api private @@ -666,6 +736,19 @@ def structs = definitions.select { |e| e.is_a?(StructDefinitionAst) } # @return {Array] List of all function definitions def functions = definitions.select { |e| e.is_a?(FunctionDefAst) } + # Add all the global symbols to symtab + # + # @param symtab [SymbolTable] symbol table + def add_global_symbols(symtab) + raise "Symtab is not at global scope" unless symtab.levels == 1 + + enums.each { |g| g.add_symbol(symtab) } + bitfields.each { |g| g.add_symbol(symtab) } + globals.each { |g| g.add_symbol(symtab) } + structs.each { |g| g.add_symbol(symtab) } + functions.each { |g| g.add_symbol(symtab) } + end + # replaces an include statement with the ast in that file, making # it a direct child of this IsaAst # @@ -793,6 +876,53 @@ def value(symtab) def to_idl = "$enum_element_size(#{enum_class.to_idl})" end + class EnumCastSyntaxNode < Treetop::Runtime::SyntaxNode + def to_ast + EnumCastAst.new(input, interval, user_type_name.to_ast, expression.to_ast) + end + end + + class EnumCastAst < AstNode + include Rvalue + + # @return [UserTypeAst] Enum name + def enum_name = @children[0] + + # @return [Rvalue] Value expression + def expression = @children[1] + + def initialize(input, interval, user_type_name, expression) + super(input, interval, [user_type_name, expression]) + end + + def type_check(symtab) + enum_name.type_check(symtab) + expression.type_check(symtab) + + if expression.type(symtab).kind != :bits + type_error "Can only cast from Bits to enum" + end + + enum_def_type = symtab.get(enum_name.text_value) + type_error "No enum named #{enum_name.text_value}" if enum_def_type.nil? + + value_try do + unless enum_def_type.element_values.include?(expression.value(symtab)) + type_error "#{expression.value(symtab)} is not a value in enum #{enum_name.text_value}" + end + end + end + + def type(symtab) + enum_def_type = symtab.get(enum_name.text_value) + Type.new(:enum_ref, enum_class: enum_def_type) + end + + def value(symtab) = expression.value(symtab) + + def to_idl = "$enum(#{enum_name.to_idl}, #{expression.to_idl})" + end + class EnumArrayCastSyntaxNode < Treetop::Runtime::SyntaxNode def to_ast EnumArrayCastAst.new(input, interval, user_type_name.to_ast) @@ -817,7 +947,7 @@ def type(symtab) Type.new( :array, width: enum_class.type(symtab).element_values.size, - sub_type: Type.new(:bits, width: enum_class.type(symtab).width), + sub_type: Type.new(:bits, width: enum_class.type(symtab).width, qualifiers: [:const]), qualifiers: [:const] ) end @@ -863,6 +993,7 @@ def to_ast # VU 0b100 # } class EnumDefinitionAst < AstNode + include Declaration def initialize(input, interval, user_type, element_names, element_values) super(input, interval, [user_type] + element_names + element_values.reject{ |e| e.nil? }) @@ -916,6 +1047,9 @@ def type_check(symtab) # @!macro add_symbol def add_symbol(symtab) + internal_error "All enums should be declared in global scope" unless symtab.levels == 1 + + internal_error "Type is nil?" if type(symtab).nil? symtab.add!(name, type(symtab)) end @@ -953,11 +1087,19 @@ def to_ast # builtin enum ExtensionName # class BuiltinEnumDefinitionAst < AstNode + include Declaration + def initialize(input, interval, user_type) super(input, interval, [user_type]) @user_type = user_type end + def freeze_tree(global_symtab) + # call type to get it set before we freeze the object + type(global_symtab) + freeze + end + # @!macro type_check_no_args def type_check(_symtab) case @user_type.text_value @@ -970,12 +1112,12 @@ def type_check(_symtab) def element_names(symtab) case name - when 'ExtensionName' + when "ExtensionName" symtab.archdef.extensions.map(&:name) - when 'ExceptionCode' - symtab.archdef.exception_codes(&:var) - when 'InterruptCode' - symtab.archdef.interrupt_codes(&:var) + when "ExceptionCode" + symtab.archdef.exception_codes.map(&:var) + when "InterruptCode" + symtab.archdef.interrupt_codes.map(&:var) else type_error "Unknown builtin enum type '#{name}'" end @@ -983,19 +1125,30 @@ def element_names(symtab) def element_values(symtab) case name - when 'ExtensionName' + when "ExtensionName" (0...symtab.archdef.extensions.size).to_a - when 'ExceptionCode' - symtab.archdef.exception_codes(&:num) - when 'InterruptCode' - symtab.archdef.interrupt_codes(&:num) + when "ExceptionCode" + symtab.archdef.exception_codes.map(&:num) + when "InterruptCode" + symtab.archdef.interrupt_codes.map(&:num) else type_error "Unknown builtin enum type '#{name}'" end end # @!macro type_no_archdef - def type(symtab) = symtab.get(@user_type.text_value) + def type(symtab) + return @type unless @type.nil? + + @type = EnumerationType.new(name, element_names(symtab), element_values(symtab)) + end + + # @!macro add_symbol + def add_symbol(symtab) + internal_error "All enums should be declared in global scope" unless symtab.levels == 1 + + symtab.add!(name, type(symtab)) + end # @return [String] name of the enum class def name = @user_type.text_value @@ -1023,18 +1176,22 @@ def initialize(input, interval, name, msb, lsb) # @!macro type_check def type_check(symtab) @msb.type_check(symtab) - begin + + value_result = value_try do @msb.value(symtab) - rescue ValueError + end + value_else(value_result) do @msb.type_error "Bitfield position must be compile-time-known" end - unless @lsb.nil? - @lsb.type_check(symtab) - begin - @lsb.value(symtab) - rescue ValueError - @lsb.type_error "Bitfield position must be compile-time-known" - end + + return if @lsb.nil? + + @lsb.type_check(symtab) + value_result = value_try do + @lsb.value(symtab) + end + value_else(value_result) do + @lsb.type_error "Bitfield position must be compile-time-known" end end @@ -1088,6 +1245,8 @@ def to_ast # V 0 # } class BitfieldDefinitionAst < AstNode + include Declaration + def initialize(input, interval, name, size, fields) super(input, interval, [name, size] + fields) @@ -1095,7 +1254,14 @@ def initialize(input, interval, name, size, fields) @size = size @fields = fields end + + # @!macro freeze_tree + def freeze_tree(global_symtab) + type(global_symtab) + freeze + end + # @return [Integer] The number of bits in the Bitfield def size(symtab) @size.value(symtab) end @@ -1130,12 +1296,25 @@ def type_check(symtab) # @!macro add_symbol def add_symbol(symtab) + internal_error "All Bitfields should be declared at global scope" unless symtab.levels == 1 + t = type(symtab) + internal_error "Type is nil" if t.nil? + symtab.add!(name, t) end # @!macro type_no_args - def type(symtab) = BitfieldType.new(name, @size.value(symtab), element_names, element_ranges(symtab)) + def type(symtab) + return @type unless @type.nil? + + @type = BitfieldType.new( + name, + @size.value(symtab), + element_names, + element_ranges(symtab) + ) + end # @return [String] bitfield name def name = @name.text_value @@ -1207,6 +1386,8 @@ def type_check(symtab) def type(symtab) @type = StructType.new(@name, @member_types.map do |t| member_type = t.type(symtab) + type_error "Type #{t.text_value} is not known" if member_type.nil? + member_type = Type.new(:enum_ref, enum_class: member_type) if member_type.kind == :enum member_type end, @member_names) @@ -1214,6 +1395,8 @@ def type(symtab) # @!macro add_symbol def add_symbol(symtab) + internal_error "Structs should be declared at global scope" unless symtab.levels == 1 + t = type(symtab) symtab.add!(name, t) end @@ -1273,12 +1456,13 @@ class AryAccessSyntaxNode < Treetop::Runtime::SyntaxNode def to_ast var = a.to_ast brackets.elements.each do |bracket| - var = if bracket.msb.empty? - AryElementAccessAst.new(input, interval, var, bracket.lsb.to_ast) - else - AryRangeAccessAst.new(input, interval, var, - bracket.msb.expression.to_ast, bracket.lsb.to_ast) - end + var = + if bracket.msb.empty? + AryElementAccessAst.new(input, interval, var, bracket.lsb.to_ast) + else + AryRangeAccessAst.new(input, interval, var, + bracket.msb.expression.to_ast, bracket.lsb.to_ast) + end end var @@ -1312,23 +1496,19 @@ def type_check(symtab) type_error "Array index must be integral" unless index.type(symtab).integral? if var.type(symtab).kind == :array - begin + value_result = value_try do index_value = index.value(symtab) type_error "Array index out of range" if index_value >= var.type(symtab).width - rescue ValueError - # Ok, doesn't need to be known - end + end # Ok, doesn't need to be known elsif var.type(symtab).integral? if var.type(symtab).kind == :bits - begin + value_result = value_try do index_value = index.value(symtab) if index_value >= var.type(symtab).width type_error "Bits element index (#{index_value}) out of range (max #{var.type(symtab).width - 1}) in access '#{text_value}'" end - rescue ValueError - # OK, doesn need to be known - end + end # OK, doesn need to be known end else @@ -1390,32 +1570,29 @@ def type_check(symtab) type_error "Range MSB must be an integral type" unless msb.type(symtab).integral? type_error "Range LSB must be an integral type" unless lsb.type(symtab).integral? - begin + value_result = value_try do msb_value = msb.value(symtab) lsb_value = lsb.value(symtab) if var.type(symtab).kind == :bits && msb_value >= var.type(symtab).width type_error "Range too large for bits (msb = #{msb_value}, range size = #{var.type(symtab).width})" - end + end range_size = msb_value - lsb_value + 1 type_error "zero/negative range (#{msb_value}:#{lsb_value})" if range_size <= 0 - rescue ValueError - # OK, don't have to know - end + end # OK, don't have to know end # @!macro type def type(symtab) - begin + value_result = value_try do msb_value = msb.value(symtab) lsb_value = lsb.value(symtab) range_size = msb_value - lsb_value + 1 - Type.new(:bits, width: range_size) - rescue ValueError - # don't know the width at compile time....assume the worst - var.type(symtab) + return Type.new(:bits, width: range_size) end + # don't know the width at compile time....assume the worst + value_else(value_result) { var.type(symtab) } end # @!macro value @@ -1484,31 +1661,44 @@ def rhs = @children[1] def initialize(input, interval, lhs_ast, rhs_ast) super(input, interval, [lhs_ast, rhs_ast]) + @vars = {} end # @!macro type_check def type_check(symtab) lhs.type_check(symtab) + type_error "Cannot assign to a const" if lhs.type(symtab).const? + rhs.type_check(symtab) unless rhs.type(symtab).convertable_to?(lhs.type(symtab)) type_error "Incompatible type in assignment (#{lhs.type(symtab)}, #{rhs.type(symtab)})" end end + def var(symtab) + variable = @vars[symtab.archdef] + if variable.nil? + variable = symtab.get(lhs.text_value) + @vars[symtab.archdef] = variable + end + variable + end + # @!macro execute def execute(symtab) if lhs.is_a?(CsrWriteAst) value_error "CSR writes are never compile-time-known" else - variable = symtab.get(lhs.text_value) + variable = var(symtab) internal_error "No variable #{lhs.text_value}" if variable.nil? - begin + value_result = value_try do variable.value = rhs.value(symtab) - rescue ValueError + end + value_else(value_result) do variable.value = nil - raise + value_error "" end end end @@ -1518,7 +1708,7 @@ def execute_unknown(symtab) if lhs.is_a?(CsrWriteAst) value_error "CSR writes are never compile-time-known" else - variable = symtab.get(lhs.text_value) + variable = var(symtab) internal_error "No variable #{lhs.text_value}" if variable.nil? @@ -1563,12 +1753,11 @@ def type_check(symtab) type_error "Index must be integral" unless idx.type(symtab).integral? - begin + value_result = value_try do idx_value = idx.value(symtab) type_error "Array index (#{idx.text_value} = #{idx_value}) out of range (< #{var.type(symtab).width})" if idx_value >= lhs.type(symtab).width - rescue ValueError - # OK, doesn't need to be known end + # OK, doesn't need to be known rhs.type_check(symtab) @@ -1595,18 +1784,20 @@ def execute(symtab) when :array idx_value = idx.value(symtab) lhs_value = lhs.value(symtab) - begin + value_result = value_try do lhs_value[idx_value] = rhs.value(symtab) - rescue ValueError + end + value_else(value_result) do lhs_value[idx_value] = nil - raise + value_error "right-hand side of array element assignment is unknown" end when :bits var = symtab.get(lhs.text_value) - begin + value_result = value_try do v = rhs.value(symtab) var.value = (lhs.value & ~0) | ((v & 1) << idx.value(symtab)) - rescue ValueError + end + value_else(value_result) do var.value = nil end else @@ -1619,24 +1810,27 @@ def execute_unknown(symtab) case lhs.type(symtab).kind when :array lhs_value = lhs.value(symtab) - begin + value_result = value_try do idx_value = idx.value(symtab) - begin + value_result = value_try do lhs_value[idx_value] = rhs.value(symtab) - rescue ValueError + end + value_else(value_result) do lhs_value[idx_value] = nil - raise + value_error "right-hand side of array element assignment is unknown" end - rescue ValueError + end + value_else(value_result) do # the idx isn't known; the entire array must become unknown - lhs_value.map! { |v| nil } + lhs_value.map! { |_v| nil } end when :bits var = symtab.get(lhs.text_value) - begin + value_result = value_try do v = rhs.value(symtab) var.value = (lhs.value & ~0) | ((v & 1) << idx.value(symtab)) - rescue ValueError + end + value_else(value_result) do var.value = nil end else @@ -1682,15 +1876,14 @@ def type_check(symtab) type_error "MSB must be integral" unless msb.type(symtab).integral? type_error "LSB must be integral" unless lsb.type(symtab).integral? - begin + value_result = value_try do msb_value = msb.value(symtab) lsb_value = lsb.value(symtab) type_error "MSB must be > LSB" unless msb_value > lsb_value type_error "MSB is out of range" if msb_value >= variable.type(symtab).width - rescue ValueError - # OK, don't have to know the value end + # OK, don't have to know the value write_value.type_check(symtab) @@ -1707,7 +1900,7 @@ def rhs def execute(symtab) return if variable.type(symtab).global? - begin + value_result = value_try do var_val = variable.value(symtab) msb_val = msb.value(symtab) @@ -1723,9 +1916,11 @@ def execute(symtab) var_val | ((rval_val << lsb_val) & mask) symtab.add(variable.name, Var.new(variable.name, variable.type(symtab), var_val)) - rescue ValueError + :ok + end + value_else(value_result) do symtab.add(variable.name, Var.new(variable.name, variable.type(symtab))) - raise + value_error "Either the range or right-hand side of an array range assignemnt is unknown" end end @@ -1815,7 +2010,11 @@ def initialize(input, interval, csr_field, write_value) def type(symtab) if field(symtab).defined_in_all_bases? - Type.new(:bits, width: [field(symtab).location(symtab.archdef, 32).size, field(symtab).location(symtab.archdef, 64).size].max) + if symtab.archdef.mxlen == 64 && symtab.archdef.multi_xlen? + Type.new(:bits, width: [field(symtab).location(symtab.archdef, 32).size, field(symtab).location(symtab.archdef, 64).size].max) + else + Type.new(:bits, width: field(symtab).location(symtab.archdef, symtab.archdef.mxlen).size) + end elsif field(symtab).base64_only? Type.new(:bits, width: field(symtab).location(symtab.archdef, 64).size) elsif field(symtab).base32_only? @@ -1831,13 +2030,12 @@ def field(symtab) def type_check(symtab) csr_field.type_check(symtab) - begin - if ["RO", "RO-H"].any?(csr_field.field_def(symtab).type(symtab.archdef)) + value_try do + if ["RO", "RO-H"].any?(csr_field.field_def(symtab).type(symtab)) type_error "Cannot write to read-only CSR field" end - rescue ValueError - # ok, we don't know the type because the archdef isn't configured end + # ok, we don't know the type because the archdef isn't configured write_value.type_check(symtab) type_error "Incompatible type in assignment" unless write_value.type(symtab).convertable_to?(type(symtab)) @@ -1916,7 +2114,7 @@ def type_check(symtab) # @!macro execute def execute(symtab) - begin + value_result = value_try do values = function_call.execute(symtab) i = 0 @@ -1929,11 +2127,12 @@ def execute(symtab) var.value = values[i] i += 1 end - rescue ValueError + end + value_else(value_result) do variables.each do |v| symtab.get(v.text_value).value = nil end - raise + value_error "value of right-hand side of multi-variable assignment is unknown" end end @@ -2054,11 +2253,12 @@ def decl_type(symtab) # dtype = dtype.clone.qualify(q.text_value.to_sym) unless q.empty? unless ary_size.nil? - begin - dtype = Type.new(:array, width: ary_size.value(symtab), sub_type: dtype.clone, qualifiers:) - rescue ValueError + value_result = value_try do + dtype = Type.new(:array, width: ary_size.value(symtab), sub_type: dtype, qualifiers:) + end + value_else(value_result) do type_error "Array size must be known at compile time" if symtab.archdef.fully_configured? - dtype = Type.new(:array, width: :unknown, sub_type: dtype.clone, qualifiers:) + dtype = Type.new(:array, width: :unknown, sub_type: dtype, qualifiers:) end end @@ -2068,7 +2268,7 @@ def decl_type(symtab) def type(symtab) = decl_type(symtab) # @!macro type_check - def type_check(symtab) + def type_check(symtab, add_sym = true) type_name.type_check(symtab) dtype = type_name.type(symtab) @@ -2078,9 +2278,10 @@ def type_check(symtab) unless ary_size.nil? ary_size.type_check(symtab) - begin + value_result = value_try do ary_size.value(symtab) - rescue ValueError + end + value_else(value_result) do # if this is a fully configured ArchDef, this is an error because all constants are supposed to be known if symtab.archdef.fully_configured? type_error "Array size (#{ary_size.text_value}) must be known at compile time" @@ -2091,7 +2292,7 @@ def type_check(symtab) end end - add_symbol(symtab) + add_symbol(symtab) if add_sym id.type_check(symtab) end @@ -2100,7 +2301,7 @@ def type_check(symtab) def add_symbol(symtab) if @global # fill global with nil to prevent its use in compile-time evaluation - symtab.add(id.text_value, Var.new(id.text_value, decl_type(symtab), nil)) + symtab.add!(id.text_value, Var.new(id.text_value, decl_type(symtab), nil)) else type_error "No Type '#{type_name.text_value}'" if decl_type(symtab).nil? symtab.add(id.text_value, Var.new(id.text_value, decl_type(symtab), decl_type(symtab).default)) @@ -2169,9 +2370,10 @@ def lhs_type(symtab) end unless ary_size.nil? - begin + value_result = value_try do decl_type = Type.new(:array, sub_type: decl_type, width: ary_size.value(symtab), qualifiers:) - rescue ValueError + end + value_else(value_result) do type_error "Array size must be known at compile time" end end @@ -2192,9 +2394,10 @@ def type_check(symtab) if decl_type.const? # this is a constant; ensure we are assigning a constant value - begin + value_result = value_try do symtab.add(lhs.text_value, Var.new(lhs.text_value, decl_type.clone, rhs.value(symtab))) - rescue ValueError => e + end + value_else(value_result) do unless rhs.type(symtab).const? type_error "Declaring constant with a non-constant value (#{e})" end @@ -2214,18 +2417,41 @@ def type_check(symtab) # @!macro add_symbol def add_symbol(symtab) - symtab.add(lhs.text_value, Var.new(lhs.text_value, lhs_type(symtab))) + if @global + if lhs.text_value[0] == lhs.text_value[0].upcase + # const, add the value if it's known + value_result = value_try do + symtab.add(lhs.text_value, Var.new(lhs.text_value, lhs_type(symtab), rhs.value(symtab))) + end + value_else(value_result) do + symtab.add(lhs.text_value, Var.new(lhs.text_value, lhs_type(symtab))) + end + else + # mutable globals never have a compile-time value + symtab.add!(lhs.text_value, Var.new(lhs.text_value, lhs_type(symtab))) + end + else + value_result = value_try do + symtab.add(lhs.text_value, Var.new(lhs.text_value, lhs_type(symtab), rhs.value(symtab))) + end + value_else(value_result) do + symtab.add(lhs.text_value, Var.new(lhs.text_value, lhs_type(symtab))) + end + end end # @!macro execute def execute(symtab) value_error "TODO: Array declaration" unless ary_size.nil? rhs_value = nil - begin - rhs_value = rhs.value(symtab) unless @global - rescue ValueError + return if @global # never executed at compile time + + value_result = value_try do + rhs_value = rhs.value(symtab) + end + value_else(value_result) do symtab.add(lhs.text_value, Var.new(lhs.text_value, lhs_type(symtab), nil)) - raise + value_error "value of right-hand side of variable initialization is unknown" end symtab.add(lhs.text_value, Var.new(lhs.text_value, lhs_type(symtab), rhs_value)) end @@ -2439,14 +2665,15 @@ def to_idl def type(symtab) lhs_type = lhs.type(symtab) short_circuit = false - begin + + value_result = value_try do lhs_value = lhs.value(symtab) if (lhs_value == true && op == "||") || (lhs_value == false && op == "&&") short_circuit = true end - rescue ValueError - short_circuit = false end + value_else(value_result) { short_circuit = false } + rhs_type = rhs.type(symtab) unless short_circuit qualifiers = [] @@ -2459,11 +2686,13 @@ def type(symtab) BoolType end elsif op == "<<" - begin + value_result = value_try do # if shift amount is known, then the result width is increased by the shift # otherwise, the result is the width of the left hand side - Type.new(:bits, width: lhs_type.width + rhs.value(symtab), qualifiers:) - rescue ValueError + value_error "lhs width unknown" if lhs_type.width == :unknown + return Type.new(:bits, width: lhs_type.width + rhs.value(symtab), qualifiers:) + end + value_else(value_result) do Type.new(:bits, width: lhs_type.width, qualifiers:) end #elsif ["+", "-", "*", "/", "%"].include?(op) @@ -2514,15 +2743,16 @@ def type_check(symtab) lhs.type_check(symtab) short_circuit = false - # begin - # lhs_value = lhs.value(symtab) - # if (lhs_value == true && op == "||") || (lhs_value == false && op == "&&") - # short_circuit = true - # end - # rescue ValueError - # short_circuit = false - # end - rhs.type_check(symtab) #unless short_circuit + value_result = value_try do + lhs_value = lhs.value(symtab) + if (lhs_value == true && op == "||") || (lhs_value == false && op == "&&") + short_circuit = true + end + end + value_else(value_result) do + short_circuit = false + end + rhs.type_check(symtab) unless short_circuit if ["<=", ">=", "<", ">", "!=", "=="].include?(op) rhs_type = rhs.type(symtab) @@ -2559,19 +2789,19 @@ def type_check(symtab) rhs_type = rhs.type(symtab) lhs_type = lhs.type(symtab) unless lhs_type.integral? && rhs_type.integral? - type_error "Multiplication/division is only defined for integral types" + type_error "Multiplication/division is only defined for integral types. Maybe you forgot a $bits cast?" end elsif ["+", "-"].include?(op) rhs_type = rhs.type(symtab) lhs_type = lhs.type(symtab) unless lhs_type.integral? && rhs_type.integral? - type_error "Addition/subtraction is only defined for integral types" + type_error "Addition/subtraction is only defined for integral types. Maybe you forgot a $bits cast?" end elsif ["&", "|", "^"].include?(op) rhs_type = rhs.type(symtab) lhs_type = lhs.type(symtab) unless lhs_type.integral? && rhs_type.integral? - type_error "Bitwise operations is only defined for integral types" + type_error "Bitwise operation is only defined for integral types. Maybe you forgot a $bits cast?" end else internal_error "Unhandled op '#{op}'" @@ -2612,21 +2842,24 @@ def value(symtab) end end elsif op == "==" - begin - lhs.value(symtab) == rhs.value(symtab) - rescue ValueError + value_result = value_try do + return lhs.value(symtab) == rhs.value(symtab) + end + value_else(value_result) do # even if we don't know the exact value of @lhs and @rhs, we can still # know that == is false if the possible values of each do not overlap - if lhs.values(symtab).intersection(rhs.value(symtab)).empty? + if lhs.values(symtab).intersection(rhs.values(symtab)).empty? false else + value_error "There is overlap in the lhs/rhs return values" end end elsif op == "!=" - begin - lhs.value(symtab) != rhs.value(symtab) - rescue ValueError + value_result = value_try do + return lhs.value(symtab) != rhs.value(symtab) + end + value_else(value_result) do # even if we don't know the exact value of @lhs and @rhs, we can still # know that != is true if the possible values of each do not overlap if lhs.values(symtab).intersection(rhs.values(symtab)).empty? @@ -2636,9 +2869,10 @@ def value(symtab) end end elsif op == "<=" - begin - lhs.value(symtab) <= rhs.value(symtab) - rescue ValueError + value_result = value_try do + return lhs.value(symtab) <= rhs.value(symtab) + end + value_else(value_result) do # even if we don't know the exact value of @lhs and @rhs, we can still # know that != is true if the possible values of lhs are all <= the possible values of rhs rhs_values = rhs.values(symtab) @@ -2649,9 +2883,10 @@ def value(symtab) end end elsif op == ">=" - begin - lhs.value(symtab) >= rhs.value(symtab) - rescue ValueError + value_result = value_try do + return lhs.value(symtab) >= rhs.value(symtab) + end + value_else(value_result) do # even if we don't know the exact value of @lhs and @rhs, we can still # know that != is true if the possible values of lhs are all >= the possible values of rhs rhs_values = rhs.values(symtab) @@ -2662,9 +2897,10 @@ def value(symtab) end end elsif op == "<" - begin - lhs.value(symtab) < rhs.value(symtab) - rescue ValueError + value_result = value_try do + return lhs.value(symtab) < rhs.value(symtab) + end + value_else(value_result) do # even if we don't know the exact value of @lhs and @rhs, we can still # know that != is true if the possible values of lhs are all < the possible values of rhs rhs_values = rhs.values(symtab) @@ -2675,9 +2911,10 @@ def value(symtab) end end elsif op == ">" - begin - lhs.value(symtab) > rhs.value(symtab) - rescue ValueError + value_result = value_try do + return lhs.value(symtab) > rhs.value(symtab) + end + value_else(value_result) do # even if we don't know the exact value of @lhs and @rhs, we can still # know that != is true if the possible values of lhs are all > the possible values of rhs rhs_values = rhs.values(symtab) @@ -2689,11 +2926,10 @@ def value(symtab) end elsif op == "&" # if one side is zero, we don't need to know the other side - begin + value_result = value_try do return 0 if lhs.value(symtab).zero? - rescue ValueError - # ok, trye rhs end + # ok, trye rhs return 0 if rhs.value(symtab).zero? @@ -2701,12 +2937,11 @@ def value(symtab) elsif op == "|" # if one side is all ones, we don't need to know the other side - begin + value_result = value_try do rhs_mask = ((1 << rhs.type(symtab).width) - 1) return rhs_mask if (rhs.value(symtab) == rhs_mask) && (lhs.type(symtab).width <= rhs.type(symtab).width) - rescue ValueError - # ok, trye rhs end + # ok, trye rhs lhs_mask = ((1 << lhs.type(symtab).width) - 1) return lhs_mask if (lhs.value(symtab) == lhs_mask) && (rhs.type(symtab).width <= lhs.type(symtab).width) @@ -2739,6 +2974,7 @@ def value(symtab) else internal_error "Unhandled binary op #{op}" end + v_trunc = if !lhs.type(symtab).const? || !rhs.type(symtab).const? # when both sides are constant, the value is not truncated @@ -2746,6 +2982,7 @@ def value(symtab) else v end + warn "WARNING: The value of '#{text_value}' (#{lhs.type(symtab).const?}, #{rhs.type(symtab).const?}) is truncated from #{v} to #{v_trunc} because the result is only #{type(symtab).width} bits" if v != v_trunc v_trunc end @@ -2901,11 +3138,10 @@ def type_check(symtab) v.type_check(symtab) type_error "value of replication must be a Bits type" unless v.type(symtab).kind == :bits - begin + value_try do type_error "replication amount must be positive (#{n.value(symtab)})" unless n.value(symtab).positive? - rescue ValueError - # type_error "replication amount must be known at compile time" end + # type_error "replication amount must be known at compile time" end # @!macro value @@ -2919,10 +3155,11 @@ def value(symtab) # @!macro type def type(symtab) - begin + value_result = value_try do width = (n.value(symtab) * v.type(symtab).width) - Type.new(:bits, width:) - rescue ValueError + return Type.new(:bits, width:) + end + value_else(value_result) do Type.new(:bits, width: :unknown) end end @@ -2962,15 +3199,16 @@ def type(symtab) # @!macro execute def execute(symtab) var = symtab.get(rval.text_value) - begin + value_result = value_try do internal_error "No symbol #{rval.text_value}" if var.nil? value_error "value of variable '#{rval.text_value}' not know" if var.value.nil? var.value = var.value - 1 - rescue ValueError + end + value_else(value_result) do var.value = nil - raise + value_error "value of variable '#{rval.text_value}' not know" end end @@ -2993,7 +3231,7 @@ class BuiltinVariableAst < AstNode def name = text_value def initialize(input, interval) - super(input, interval, []) + super(input, interval, EMPTY_ARRAY) end def type_check(symtab) @@ -3055,16 +3293,18 @@ def type(symtab) # @!macro execute def execute(symtab) - begin - var = symtab.get(rval.text_value) + var = symtab.get(rval.text_value) + + value_result = value_try do internal_error "No symbol named '#{rval.text_value}'" if var.nil? value_error "#{rval.text_value} is not compile-time-known" if var.value.nil? var.value = var.value + 1 - rescue ValueError + end + value_else(value_result) do var.value = nil - raise + value_error "#{rval.text_value} is not compile-time-known" if var.value.nil? end end @@ -3079,7 +3319,7 @@ def to_idl = "#{rval.to_idl}++" class FieldAccessExpressionSyntaxNode < Treetop::Runtime::SyntaxNode def to_ast - FieldAccessExpressionAst.new(input, interval, rval.to_ast, field_name.text_value) + FieldAccessExpressionAst.new(input, interval, field_access_eligible_expression.to_ast, field_name.text_value) end end @@ -3166,15 +3406,31 @@ def class_name = @enum_class_name def member_name = @member_name def initialize(input, interval, class_name, member_name) - super(input, interval, []) + super(input, interval, EMPTY_ARRAY) @enum_class_name = class_name @member_name = member_name + @enum_def_type = nil + end + + # @!macro freeze_tree + def freeze_tree(global_symtab) + enum_def_ast = global_symtab.archdef.global_ast.enums.find { |e| e.name == @enum_class_name } + + @enum_def_type = + if enum_def_ast.is_a?(BuiltinEnumDefinitionAst) + enum_def_ast&.type(global_symtab) + else + enum_def_ast&.type(nil) + end + + freeze end # @!macro type_check def type_check(symtab) - enum_def_type = symtab.get(@enum_class_name) + enum_def_type = @enum_def_type + type_error "No symbol #{@enum_class_name} has been defined" if enum_def_type.nil? type_error "#{@enum_class_name} is not an enum type" unless enum_def_type.is_a?(EnumerationType) @@ -3183,16 +3439,17 @@ def type_check(symtab) # @!macro type_no_archdef def type(symtab) - internal_error "Must call type_check first" if symtab.get(@enum_class_name).nil? + internal_error "Not frozen?" unless frozen? + type_error "No enum named #{@enum_class_name}" if @enum_def_type.nil? - symtab.get(@enum_class_name).ref_type + @enum_def_type.ref_type end # @!macro value_no_archdef def value(symtab) - internal_error "Must call type_check first" if symtab.get(@enum_class_name).nil? + internal_error "Must call type_check first" if @enum_def_type.nil? - symtab.get(@enum_class_name).value(@member_name) + @enum_def_type.value(@member_name) end # @!macro to_idl @@ -3342,15 +3599,12 @@ def type_check(symtab) condition.type_check(symtab) type_error "ternary selector must be bool" unless condition.type(symtab).kind == :boolean - begin + value_result = value_try do cond = condition.value(symtab) # if the condition is compile-time-known, only check the used field - if (cond) - true_expression.type_check(symtab) - else - false_expression.type_check(symtab) - end - rescue ValueError + cond ? true_expression.type_check(symtab) : false_expression.type_check(symtab) + end + value_else(value_result) do true_expression.type_check(symtab) false_expression.type_check(symtab) @@ -3366,23 +3620,32 @@ def type_check(symtab) # @!macro type def type(symtab) condition.type_check(symtab) - begin + value_result = value_try do cond = condition.value(symtab) # if the condition is compile-time-known, only check the used field - if (cond) - true_expression.type(symtab) + if cond + return true_expression.type(symtab) else - false_expression.type(symtab) + return false_expression.type(symtab) end - rescue ValueError - t = + end + value_else(value_result) do + t = if true_expression.type(symtab).kind == :bits && false_expression.type(symtab).kind == :bits - Type.new(:bits, width: [true_expression.type(symtab).width, false_expression.type(symtab).width].max) + true_width = true_expression.type(symtab).width + false_width = false_expression.type(symtab).width + if true_width == :unknown || false_width == :unknown + Type.new(:bits, width: :unknown) + else + Type.new(:bits, width: [true_width, false_width].max) + end else true_expression.type(symtab).clone end - t.make_const if condition.type(symtab).const? && true_expression.type(symtab).const? && false_expression.type(symtab).const? - t + if condition.type(symtab).const? && true_expression.type(symtab).const? && false_expression.type(symtab).const? + t.make_const + end + return t end end @@ -3393,9 +3656,12 @@ def value(symtab) # @!macro values def values(symtab) - condition.value(symtab) ? true_expression.values(symtab) : false_expression.values(symtab) - rescue ValueError - (true_expression.values(symtab) + false_expression.values(symtab)).uniq + value_result = value_try do + return condition.value(symtab) ? true_expression.values(symtab) : false_expression.values(symtab) + end + value_else(value_result) do + (true_expression.values(symtab) + false_expression.values(symtab)).uniq + end end # @!macro to_idl @@ -3410,7 +3676,7 @@ def to_ast class NoopAst < AstNode def initialize - super("", 0...0, []) + super("", 0...0, EMPTY_ARRAY) end # @!macro type_check @@ -3497,16 +3763,17 @@ def type_check(symtab) # @!macro execute def execute(symtab) - begin + value_result = value_try do cond = condition.value(symtab) if (cond) action.execute(symtab) end - rescue ValueError + end + value_else(value_result) do # force action to set any values to nil action.execute_unknown(symtab) - raise + value_error "" end end @@ -3535,7 +3802,7 @@ class DontCareReturnAst < AstNode include Rvalue def initialize(input, interval) - super(input, interval, []) + super(input, interval, EMPTY_ARRAY) end # @!macro type_check_no_args @@ -3576,7 +3843,7 @@ def to_ast = DontCareLvalueAst.new(input, interval) class DontCareLvalueAst < AstNode include Rvalue - def initialize(input, interval) = super(input, interval, []) + def initialize(input, interval) = super(input, interval, EMPTY_ARRAY) # @!macro type_check_no_args def type_check(_symtab) @@ -3667,6 +3934,11 @@ def to_ast class ReturnExpressionAst < AstNode def return_value_nodes = @children + def initialize(input, interval, return_nodes) + super(input, interval, return_nodes) + @func_type_cache = {} + end + # @return [Array] List of actual return types def return_types(symtab) if return_value_nodes[0].type(symtab).kind == :tuple @@ -3697,6 +3969,9 @@ def expected_return_type(symtab) symtab.get("__expected_return_type") else # need to find the type to get the right symbol table + func_type = @func_type_cache[symtab.archdef] + return func_type.return_type(EMPTY_ARRAY, self) unless func_type.nil? + 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? @@ -3704,13 +3979,18 @@ def expected_return_type(symtab) # 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}" + if func_type.templated? + 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), self) + else + @func_type_cache[symtab.archdef]= func_type + func_type.return_type(EMPTY_ARRAY, self) end - func_type.return_type(template_values.sort { |a, b| a.template_index <=> b.template_index }.map(&:value)) end end @@ -3802,13 +4082,16 @@ def return_value(symtab) # @!macro return_values def return_values(symtab) - cond = condition.value(symtab) + value_result = value_try do + cond = condition.value(symtab) - cond ? return_expression.return_values(symtab) : [] + return cond ? return_expression.return_values(symtab) : EMPTY_ARRAY - rescue ValueError - # condition isn't known, so the return value is always possible - return_expression.return_values(symtab) + end + value_else(value_result) do + # condition isn't known, so the return value is always possible + return_expression.return_values(symtab) + end end def to_idl = "#{return_expression.to_idl} if (#{condition.to_idl});" @@ -3822,7 +4105,7 @@ def to_ast = CommentAst(input, interval) # represents a comment class CommentAst < AstNode def initialize(input, interval) - super(input, interval, []) + super(input, interval, EMPTY_ARRAY) end # @!macro type_check @@ -3862,7 +4145,7 @@ def bits_expression = @children[0] def initialize(input, interval, type_name, bits_expression) if bits_expression.nil? - super(input, interval, []) + super(input, interval, EMPTY_ARRAY) else super(input, interval, [bits_expression]) end @@ -3873,9 +4156,12 @@ def initialize(input, interval, type_name, bits_expression) def type_check(symtab) if @type_name == "Bits" bits_expression.type_check(symtab) - begin - type_error "Bits width (#{bits_expression.value(symtab)}) must be positive" unless bits_expression.value(symtab).positive? - rescue ValueError + value_result = value_try do + unless bits_expression.value(symtab).positive? + type_error "Bits width (#{bits_expression.value(symtab)}) must be positive" + end + end + value_else(value_result) do type_error "Bit width must be known at compile time" if symtab.archdef.fully_configured? end end @@ -3884,6 +4170,21 @@ def type_check(symtab) end end + def freeze_tree(symtab) + if @type_name == "Bits" + # precalculate size if possible + begin + value_try do + @bits_type = Type.new(:bits, width: bits_expression.value(symtab)) + end + rescue TypeError + # ok, probably in a function template + end + bits_expression.freeze_tree(symtab) + end + freeze + end + # @!macro type def type(symtab) case @type_name @@ -3902,10 +4203,13 @@ def type(symtab) when "String" StringType when "Bits" - begin - Type.new(:bits, width: bits_expression.value(symtab)) - rescue ValueError - Type.new(:bits, width: :unknown) + return @bits_type unless @bits_type.nil? + + value_result = value_try do + return Type.new(:bits, width: bits_expression.value(symtab)) + end + value_else(value_result) do + return Type.new(:bits, width: :unknown) end else internal_error "TODO: #{text_value}" @@ -3937,7 +4241,7 @@ class StringLiteralAst < AstNode include Rvalue def initialize(input, interval) - super(input, interval, []) + super(input, interval, EMPTY_ARRAY) @type = Type.new(:string, width: value(nil).length, qualifiers: [:const]) end @@ -3967,7 +4271,7 @@ class IntLiteralAst < AstNode include Rvalue def initialize(input, interval) - super(input, interval, []) + super(input, interval, EMPTY_ARRAY) @types = [nil, nil] end @@ -4156,7 +4460,7 @@ def to_idl = text_value class FunctionCallExpressionSyntaxNode < Treetop::Runtime::SyntaxNode def to_ast - targs = t.empty? ? [] : [t.targs.first.to_ast] + t.targs.rest.elements.map { |e| e.arg.to_ast } + targs = t.empty? ? EMPTY_ARRAY : [t.targs.first.to_ast] + t.targs.rest.elements.map { |e| e.arg.to_ast } args = [] args << function_arg_list.first.to_ast unless function_arg_list.first.empty? args += function_arg_list.rest.elements.map { |e| e.expression.to_ast } @@ -4180,6 +4484,8 @@ def initialize(input, interval, function_name, targs, args) @num_targs = targs.size @name = function_name + @reachable_exceptions_func_call_cache = {} + @func_def_type_cache = {} end # @return [Boolean] whether or not the function call has a template argument @@ -4192,10 +4498,23 @@ def template_arg_nodes targs end - def template_values(symtab) - return [] unless template? + def template_values(symtab, unknown_ok: false) + return EMPTY_ARRAY unless template? - template_arg_nodes.map { |e| e.value(symtab) } + if unknown_ok + template_arg_nodes.map do |e| + val = nil + value_result = value_try do + val = e.value(symtab) + end + value_else(value_result) do + val = :unknown + end + val + end + else + template_arg_nodes.map { |e| e.value(symtab) } + end end # @return [Array] Function argument nodes @@ -4204,6 +4523,9 @@ def arg_nodes end def func_type(symtab) + func_def_type = @func_def_type_cache[symtab.archdef] + 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? @@ -4211,13 +4533,16 @@ def func_type(symtab) type_error "#{@name} is not a function (it's a #{func_def_type.class.name})" end - func_def_type + @func_def_type_cache[symtab.archdef] = func_def_type end # @!macro type_check def type_check(symtab) level = symtab.levels + unknown_ok = symtab.archdef.partially_configured? + tvals = template_values(symtab, unknown_ok:) + func_def_type = func_type(symtab) type_error "Missing template arguments in call to #{@name}" if template? && func_def_type.template_names.empty? @@ -4237,9 +4562,10 @@ def type_check(symtab) end end - func_def_type.type_check_call(template_values(symtab), self) + func_def_type.type_check_call(tvals, arg_nodes, symtab, self) else - func_def_type.type_check_call([], self) + # no need to type check this function; it will be done on its own + # func_def_type.type_check_call([], arg_nodes, symtab, self) end num_args = arg_nodes.size @@ -4250,12 +4576,12 @@ def type_check(symtab) a.type_check(symtab) end arg_nodes.each_with_index do |a, idx| - unless a.type(symtab).convertable_to?(func_def_type.argument_type(idx, template_values(symtab), arg_nodes, symtab, self)) - type_error "Wrong type for argument number #{idx + 1}. Expecting #{func_def_type.argument_type(idx, template_values(symtab), self)}, got #{a.type(symtab)}" + unless a.type(symtab).convertable_to?(func_def_type.argument_type(idx, tvals, arg_nodes, symtab, self)) + type_error "Wrong type for argument number #{idx + 1}. Expecting #{func_def_type.argument_type(idx, tvals, arg_nodes, symtab, self)}, got #{a.type(symtab)}" end end - if func_def_type.return_type(template_values(symtab), self).nil? + if func_def_type.return_type(tvals, self).nil? internal_error "No type determined for function" end @@ -4266,8 +4592,7 @@ def type_check(symtab) def type(symtab) return ConstBoolType if name == "implemented?" - func_def_type = symtab.get(name) - func_def_type.return_type(template_values(symtab), self) + func_type(symtab).return_type(template_values(symtab, unknown_ok: symtab.archdef.partially_configured?), self) end # @!macro value @@ -4277,7 +4602,7 @@ def value(symtab) return symtab.get("__effective_xlen").value end - func_def_type = symtab.get(name) + func_def_type = func_type(symtab) type_error "#{name} is not a function" unless func_def_type.is_a?(FunctionType) if func_def_type.builtin? if name == "implemented?" @@ -4286,16 +4611,24 @@ def value(symtab) return symtab.archdef.ext?(arg_nodes[0].member_name) if symtab.archdef.fully_configured? + if symtab.archdef.ext?(arg_nodes[0].member_name) + # we can know if it is implemented, but not if it's not implemented for a partially configured + return true + end value_error "implemented? is only known when evaluating in the context of a fully-configured arch def" else value_error "value of builtin function cannot be known" end end - template_values = [] - template_arg_nodes.each do |targ| - template_values << targ.value(symtab) - end + template_values = + if !template? + EMPTY_ARRAY + else + template_arg_nodes.map do |targ| + targ.value(symtab) + end + end func_def_type.return_value(template_values, arg_nodes, symtab, self) end @@ -4328,19 +4661,23 @@ def to_ast class UserTypeNameAst < AstNode def initialize(input, interval) - super(input, interval, []) + super(input, interval, EMPTY_ARRAY) + @type_cache = {} end # @!macro type_check def type_check(symtab) - type = symtab.get(text_value) + type = type(symtab) type_error "#{text_value} is not a type" unless type.is_a?(Type) end # @!macro type_no_archdef def type(symtab) - symtab.get(text_value) + typ = @type_cache[symtab.archdef] + return typ unless typ.nil? + + @type_cache[symtab.archdef] = symtab.get(text_value) end # @!macro to_idl @@ -4349,7 +4686,7 @@ def to_idl = text_value class InstructionOperationSyntaxNode < Treetop::Runtime::SyntaxNode def to_ast - FunctionBodyAst.new(input, interval, op_stmt_list.elements.map(&:choice).map(&:to_ast) ) + FunctionBodyAst.new(input, interval, op_stmt_list.elements.map(&:choice).map(&:to_ast)) end end @@ -4421,18 +4758,20 @@ def return_values(symtab) internal_error "Function bodies should be at global + 1 scope" unless symtab.levels == 2 values = [] - begin + value_result = value_try do # if there is a definate return value, then just return that return [return_value(symtab)] - rescue ValueError + end + value_else(value_result) do # go through the statements, and collect return values # we can stop if we encounter a statement with a known return value stmts.each do |s| if s.is_a?(Returns) - begin + value_result = value_try do v = s.return_value(symtab) return values.push(v).uniq unless v.nil? - rescue ValueError + end + value_else(value_result) do values += s.return_values(symtab) end else @@ -4470,6 +4809,16 @@ def to_ast end class FunctionDefAst < AstNode + include Declaration + + # @param input [String] The source code + # @param interval [Range] The range in the source code for this function definition + # @param name [String] The name of the function + # @param targs [Array] Template arguments + # @params return_types [Array] Return types + # @param arguments [Array] Arguments + # @param desc [String] Description + # @param body [AstNode,nil] Body, unless the function is builtin def initialize(input, interval, name, targs, return_types, arguments, desc, body) if body.nil? super(input, interval, targs + return_types + arguments) @@ -4484,33 +4833,50 @@ def initialize(input, interval, name, targs, return_types, arguments, desc, body @desc = desc @body = body + @cached_return_type = {} @reachable_functions_cache ||= {} end attr_reader :reachable_functions_cache + # @!macro freeze_tree + def freeze_tree(global_symtab) + unless templated? + arguments(global_symtab) + end + + @children.each { |child| child.freeze_tree(global_symtab) } + freeze + end + + # @return [String] Asciidoc formatted function description def description unindent(@desc) end + # @return [Boolean] whether or not the function is templated def templated? !@targs.empty? end + # @return [Integer] The number of arguments to the function def num_args @argument_nodes.size end - # @return [Array] containing the argument types, in order + # @return [Array] containing the argument types and names, in order def arguments(symtab) + return @arglist unless @arglist.nil? + if templated? template_names.each do |tname| - internal_error "Template values missing" unless symtab.get(tname) + internal_error "Template values missing in symtab" unless symtab.get(tname) end end + return EMPTY_ARRAY if @argument_nodes.empty? + arglist = [] - return arglist if @argument_nodes.empty? @argument_nodes.each do |a| atype = a.type(symtab) @@ -4519,6 +4885,10 @@ def arguments(symtab) arglist << [atype, a.name] end + arglist.freeze + unless templated? + @arglist = arglist + end arglist end @@ -4530,19 +4900,47 @@ def arguments_list_str # return the return type, which may be a tuple of multiple types def return_type(symtab) + cached = @cached_return_type[symtab.archdef] + return cached unless cached.nil? + unless symtab.levels == 2 internal_error "Function bodies should be at global + 1 scope (at global + #{symtab.levels - 1})" end + if @return_type_nodes.empty? + @cached_return_type[symtab.archdef] = VoidType + return VoidType + end + + unless templated? + # with no templates, the return type does not change for a given arch_def + rtype = + if @return_type_nodes.size == 1 + rtype = @return_type_nodes[0].type(symtab) + rtype = rtype.ref_type if rtype.kind == :enum + rtype + else + tuple_types = @return_type_nodes.map do |r| + rtype = r.type(symtab) + rtype = rtype.ref_type if rtype.kind == :enum + rtype + end + + Type.new(:tuple, tuple_types:) + end + + raise "??????" if rtype.nil? + + return @cached_return_type[symtab.archdef] = rtype + end + if templated? template_names.each do |tname| internal_error "Template values missing" unless symtab.get(tname) end end - if @return_type_nodes.empty? - return VoidType - end + if @return_type_nodes.size == 1 rtype = @return_type_nodes[0].type(symtab) @@ -4586,10 +4984,8 @@ def type_check_template_instance(symtab) type_check_return(symtab) type_check_args(symtab) - symtab.push @argument_nodes.each { |a| symtab.add(a.name, Var.new(a.name, a.type(symtab))) } type_check_body(symtab) - symtab.pop end # we do lazy type checking of the function body so that we never check @@ -4600,10 +4996,15 @@ def type_check_from_call(symtab) type_check_return(symtab) type_check_args(symtab) - symtab.push # push function scope - @argument_nodes.each { |a| symtab.add(a.name, Var.new(a.name, a.type(symtab))) } + # @argument_nodes.each do |a| + # value_result = value_try do + # symtab.add(a.name, Var.new(a.name, a.type(symtab), a.value(symtab))) + # end + # value_else(value_result) do + # symtab.add(a.name, Var.new(a.name, a.type(symtab))) + # end + # end type_check_body(symtab) - symtab.pop end # @!macro type_check @@ -4612,12 +5013,31 @@ def type_check(symtab) type_check_targs(symtab) - # recursion isn't supported (doesn't map well to hardware), so we can add the function after type checking the body - add_symbol(symtab) + symtab = symtab.deep_clone + symtab.push(self) + template_names.each_with_index do |tname, index| + symtab.add(tname, Var.new(tname, template_types(symtab)[index])) + end + + type_check_return(symtab) + + arguments(symtab).each do |arg_type, arg_name| + symtab.add(arg_name, Var.new(arg_name, arg_type)) + end + type_check_args(symtab) + + + # template functions are checked as they are called + unless templated? + type_check_body(symtab) + end + symtab.pop end # @!macro add_symbol def add_symbol(symtab) + internal_error "Functions should be declared at global scope" unless symtab.levels == 1 + # now add the function in global scope def_type = FunctionType.new( name, @@ -4636,7 +5056,7 @@ def template_names # @param symtab [SymbolTable] The context for evaluation # @return [Array] Template argument types, in order def template_types(symtab) - return [] unless templated? + return EMPTY_ARRAY unless templated? ttypes = [] @targs.each do |a| @@ -4657,13 +5077,13 @@ def type_check_return(symtab) end def type_check_args(symtab) - @argument_nodes.each { |a| a.type_check(symtab) } + @argument_nodes.each { |a| a.type_check(symtab, false) } end def type_check_body(symtab) - if respond_to?(:body_block) - @body.type_check(symtab) - end + return if @body.nil? + + @body.type_check(symtab) end def body @@ -4704,7 +5124,7 @@ def initialize(input, interval, init, condition, update, stmts) # @!macro type_check def type_check(symtab) - symtab.push + symtab.push(self) init.type_check(symtab) condition.type_check(symtab) update.type_check(symtab) @@ -4717,66 +5137,76 @@ def type_check(symtab) # @!macro return_value def return_value(symtab) - symtab.push + symtab.push(self) begin - init.execute(symtab) + value_result = value_try do + init.execute(symtab) - while condition.value(symtab) - stmts.each do |s| - if s.is_a?(Returns) - v = s.return_value(symtab) - unless v.nil? - symtab.pop - return v + while condition.value(symtab) + stmts.each do |s| + if s.is_a?(Returns) + v = s.return_value(symtab) + unless v.nil? + return v + end + else + s.execute(symtab) end - else - s.execute(symtab) end + update.execute(symtab) end - update.execute(symtab) end - rescue ValueError => e + value_else(value_result) do + value_error "" + end + ensure symtab.pop - raise e end - - symtab.pop nil end # @!macro return_values def return_values(symtab) - # if there is a known return value, then we are done - [return_value(symtab)] - rescue ValueError - # see if we can collect a list - values = [] - symtab.push - - begin - init.execute(symtab) + value_result = value_try do + # if there is a known return value, then we are done + return [return_value(symtab)] + end + value_else(value_result) do + # see if we can collect a list + values = [] + symtab.push(self) - while condition.value(symtab) - stmts.each do |s| - if s.is_a?(Returns) - begin - v = s.return_value(symtab) - return values.push(v).uniq unless v.nil? - rescue ValueError - values += s.return_values(symtab) + begin + value_result = value_try do + init.execute(symtab) + + while condition.value(symtab) + stmts.each do |s| + if s.is_a?(Returns) + value_result = value_try do + v = s.return_value(symtab) + unless v.nil? + return values.push(v).uniq + end + end + value_else(value_result) do + values += s.return_values(symtab) + end + else + s.execute(symtab) + end end - else - s.execute(symtab) + update.execute(symtab) end + :ok end - update.execute(symtab) + ensure + symtab.pop end - ensure - symtab.pop - end - values.uniq + values.uniq + end end # @!macro execute @@ -4801,7 +5231,7 @@ def stmts = @children def initialize(input, interval, body_stmts) if body_stmts.empty? - super("", 0...0, []) + super("", 0...0, EMPTY_ARRAY) else super(input, interval, body_stmts) end @@ -4809,18 +5239,20 @@ def initialize(input, interval, body_stmts) # @!macro type_check def type_check(symtab) - symtab.push + symtab.push(self) - stmts.each do |s| - s.type_check(symtab) + begin + stmts.each do |s| + s.type_check(symtab) + end + ensure + symtab.pop end - - symtab.pop end # @!macro return_value def return_value(symtab) - symtab.push + symtab.push(self) begin stmts.each do |s| if s.is_a?(Returns) @@ -4842,18 +5274,23 @@ def return_value(symtab) # @!macro return_values def return_values(symtab) values = [] - symtab.push + symtab.push(self) begin - stmts.each do |s| - if s.is_a?(Returns) - begin - v = s.return_value(symtab) - return values.push(v).uniq unless v.nil? - rescue ValueError - values += s.return_values(symtab) + value_result = value_try do + stmts.each do |s| + if s.is_a?(Returns) + value_result = value_try do + v = s.return_value(symtab) + unless v.nil? + return values.push(v).uniq + end + end + value_else(value_result) do + values += s.return_values(symtab) + end + else + s.execute(symtab) end - else - s.execute(symtab) end end ensure @@ -4867,24 +5304,27 @@ def return_values(symtab) def execute(symtab) err = nil stmts.each do |s| - begin + value_result = value_try do if s.is_a?(Returns) - begin + value_result = value_try do v = s.return_value(symtab) break unless v.nil? # nil means this is a conditional return and the condition is false - rescue ValueError => e + + end + value_else(value_result) do # not known, keep going - err = e if err.nil? + err = :value_error end else s.execute(symtab) end - rescue ValueError => e + end + value_else(value_result) do # keep going so that we invalidate everything - err = e if err.nil? # remember the first error + err = :value_error end end - raise err unless err.nil? + throw err unless err.nil? end # @!macro execute_unknown @@ -4914,23 +5354,28 @@ def initialize(input, interval, body_interval, cond, body_stmts) def type_check(symtab) cond.type_check(symtab) + + cond_value = nil + value_try do + cond_value = cond.value(symtab) + end + unless cond.type(symtab).convertable_to?(:boolean) type_error "'#{cond.text_value}' is not boolean" end - body.type_check(symtab) + body.type_check(symtab) unless cond_value == false end # @!macro return_values def return_values(symtab) - if cond.value(symtab) + value_result = value_try do + return cond.value(symtab) ? body.return_values(symtab) : EMPTY_ARRAY + end + value_else(value_result) do + # might be taken, so add the possible return values body.return_values(symtab) - else - [] end - rescue ValueError - # might be taken, so add the possible return values - body.return_values(symtab) end # @!macro to_idl @@ -4997,11 +5442,17 @@ def type_check(symtab) type_error "'#{if_cond.text_value}' is not boolean" unless if_cond.type(symtab).convertable_to?(:boolean) - if_body.type_check(symtab) + if_cond_value = nil + value_try do + if_cond_value = if_cond.value(symtab) + end + + # short-circuit the if body if we can + if_body.type_check(symtab) unless if_cond_value == false internal_error "not at same level #{level} #{symtab.levels}" unless level == symtab.levels - unless elseifs.empty? + unless (if_cond_value == true) || elseifs.empty? elseifs.each do |eif| eif.type_check(symtab) end @@ -5009,7 +5460,7 @@ def type_check(symtab) internal_error "not at same level #{level} #{symtab.levels}" unless level == symtab.levels - final_else_body.type_check(symtab) + final_else_body.type_check(symtab) unless if_cond_value == true internal_error "not at same level #{level} #{symtab.levels}" unless level == symtab.levels end @@ -5045,15 +5496,16 @@ def return_values_after_if(symtab) unless elseifs.empty? elseifs.each do |eif| values += eif.return_values(symtab) - begin + value_result = value_try do elseif_cond_value = eif.value(symtab) if elseif_cond_value # this else if is defintately taken, so we are done return (values + eif.return_values(symtab)).uniq else - next # we know the else if isn't taken, so we can just go to the next + next :ok # we know the else if isn't taken, so we can just go to the next end - rescue ValueError + end + value_else(value_result) do # else if path not known; body return paths are possible values += eif.return_values(symtab) end @@ -5071,17 +5523,20 @@ def return_values_after_if(symtab) # @return [Array] List of all possible return values # @raise ValueError if it is not possible to determine all return values at compile time def return_values(symtab) - if_cond_value = if_cond.value(symtab) - if if_cond_value - # if is taken, so the only possible return values are those in the if body - if_body.return_values(symtab) - else - # if cond not taken; check else ifs and possibly final else - return_values_after_if(symtab) + value_result = value_try do + if_cond_value = if_cond.value(symtab) + if if_cond_value + # if is taken, so the only possible return values are those in the if body + return if_body.return_values(symtab) + else + # if cond not taken; check else ifs and possibly final else + return return_values_after_if(symtab) + end + end + value_else(value_result) do + # if condition not known; both paths are possible + (if_body.return_values(symtab) + return_values_after_if(symtab)).uniq end - rescue ValueError - # if condition not known; both paths are possible - (if_body.return_values(symtab) + return_values_after_if(symtab)).uniq end # return values starting at the first else if @@ -5089,66 +5544,75 @@ def execute_after_if(symtab) err = nil unless elseifs.empty? elseifs.each do |eif| - begin + value_result = value_try do elseif_cond_value = eif.cond.value(symtab) if elseif_cond_value # this else if is defintately taken, so we are done eif.body.execute(symtab) return else - next # we know the else if isn't taken, so we can just go to the next + next :ok # we know the else if isn't taken, so we can just go to the next end - rescue ValueError + end + value_else(value_result) do # else if path not known; body return paths are possible - begin + value_result = value_try do eif.body.execute(symtab) - rescue ValueError => e - err = e if err.nil? + end + value_else(value_result) do + err = :value_error if err.nil? end end end end # now do the final else - begin + value_result = value_try do final_else_body.execute(symtab) unless final_else_body.nil? - rescue ValueError => e - err = e if err.nil? + end + value_else(value_result) do + err = :value_error if err.nil? end - raise err unless err.nil? + value_error "" unless err.nil? end private :execute_after_if # @!macro execute def execute(symtab) err = nil - if_cond_value = if_cond.value(symtab) - if if_cond_value - # if is taken, so only the taken body is executable - begin + value_result = value_try do + if_cond_value = if_cond.value(symtab) + if if_cond_value + # if is taken, so only the taken body is executable + value_result = value_try do + if_body.execute(symtab) + end + value_else(value_result) do + err = :value_error if err.nil? + end + else + execute_after_if(symtab) + end + end + value_else(value_result) do + # condition not known; both paths can execute + value_result = value_try do if_body.execute(symtab) - rescue ValueError => e + end + value_else(value_result) do + err = :value_error if err.nil? + end + + value_result = value_try do + execute_after_if(symtab) + end + value_else(value_result) do err = e if err.nil? end - else - execute_after_if(symtab) - end - rescue ValueError - # condition not known; both paths can execute - begin - if_body.execute(symtab) - rescue ValueError => e - err = e if err.nil? end - begin - execute_after_if(symtab) - rescue ValueError => e - err = e if err.nil? - end - ensure - raise err unless err.nil? + value_error "" unless err.nil? end # return values starting at the first else if @@ -5190,13 +5654,24 @@ def initialize(input, interval, idx, field_name) if idx.is_a?(AstNode) super(input, interval, [idx]) else - super(input, interval, []) + super(input, interval, EMPTY_ARRAY) end @idx = idx @field_name = field_name end + def freeze_tree(symtab) + value_result = value_try do + @value = calc_value(symtab) + end + value_else(value_result) do + @value = nil + end + @type = calc_type(symtab) + freeze + end + # @!macro type_check def type_check(symtab) if @idx.is_a?(IntLiteralAst) @@ -5207,6 +5682,8 @@ def type_check(symtab) type_error "No CSR named #{csr_name}" if csr_def(symtab).nil? end type_error "CSR[#{csr_name(symtab)}] has no field named #{@field_name}" if field_def(symtab).nil? + type_error "CSR[#{csr_name(symtab)}].#{@field_name} is not defined in RV32" if symtab.archdef.mxlen == 32 && !field_def(symtab).defined_in_base32? + type_error "CSR[#{csr_name(symtab)}].#{@field_name} is not defined in RV64" if symtab.archdef.mxlen == 64 && !field_def(symtab).defined_in_base64? end def csr_def(symtab) @@ -5242,6 +5719,10 @@ def to_idl # @!macro type def type(symtab) + @type + end + + def calc_type(symtab) fd = field_def(symtab) if fd.nil? if @idx.is_a?(IntLiteralAst) @@ -5253,9 +5734,13 @@ def type(symtab) if fd.defined_in_all_bases? Type.new(:bits, width: symtab.archdef.possible_xlens.map{ |xlen| fd.width(symtab.archdef, xlen) }.max) elsif fd.base64_only? - Type.new(:bits, width: fd.width(symtab.archdef, 64)) + if symtab.archdef.possible_xlens.include?(64) + Type.new(:bits, width: fd.width(symtab.archdef, 64)) + end elsif fd.base32_only? - Type.new(:bits, width: fd.width(symtab.archdef, 32)) + if symtab.archdef.possible_xlens.include?(32) + Type.new(:bits, width: fd.width(symtab.archdef, 32)) + end else internal_error "unexpected field base" end @@ -5263,10 +5748,18 @@ def type(symtab) # @!macro value def value(symtab) + if @value.nil? + value_error "'#{csr_name(symtab)}.#{field_name(symtab)}' is not RO" + else + @value + end + end + + def calc_value(symtab) # field isn't implemented, so it must be zero return 0 if field_def(symtab).nil? - unless field_def(symtab).type(symtab.archdef) == "RO" + unless field_def(symtab).type(symtab) == "RO" value_error "'#{csr_name(symtab)}.#{field_name(symtab)}' is not RO" end @@ -5301,7 +5794,7 @@ def initialize(input, interval, idx) if idx.is_a?(AstNode) super(input, interval, [idx]) else - super(input, interval, []) + super(input, interval, EMPTY_ARRAY) end @idx = idx @@ -5339,13 +5832,13 @@ def type_check(symtab) @idx.type_check(symtab) type_error "Csr index must be integral" unless @idx.type(symtab).integral? - begin + value_result = value_try do idx_value = @idx.value(symtab) csr_index = archdef.csrs.index { |csr| csr.address == idx_value } type_error "No csr number '#{idx_value}' was found" if csr_index.nil? - rescue ValueError - # OK, index doesn't have to be known + :ok end + # OK, index doesn't have to be known end end @@ -5358,13 +5851,12 @@ def csr_def(symtab) csr else # this is an expression - begin + value_result = value_try do idx_value = @idx.value(symtab) - csr_index = archdef.csrs.find { |csr| csr.address == idx_value } - rescue ValueError - # we don't know at compile time which CSR this is... - nil + return archdef.csrs.find { |csr| csr.address == idx_value } end + # || we don't know at compile time which CSR this is... + nil end end @@ -5387,7 +5879,7 @@ def value(symtab) else value_error "CSR is not defined" unless symtab.archdef.csrs.any? { |icsr| icsr.name == cd.name } end - cd.fields.each { |f| value_error "#{csr_name(symtab)}.#{f.name} not RO" unless f.type(symtab.archdef) == "RO" } + cd.fields.each { |f| value_error "#{csr_name(symtab)}.#{f.name} not RO" unless f.type(symtab) == "RO" } csr_def(symtab).fields.reduce(0) { |val, f| val | (f.value << f.location.begin) } end diff --git a/lib/idl/idl.treetop b/lib/idl/idl.treetop index ceae364d..a054cb2d 100644 --- a/lib/idl/idl.treetop +++ b/lib/idl/idl.treetop @@ -279,8 +279,16 @@ grammar Idl 'CSR' space* '[' space* idx:expression space* ']' end + rule field_access_eligible_expression + paren_expression + / + function_call # Ast is assigned in function_call rule + / + rval # must come last! + end + rule field_access_expression - rval space* '.' space* field_name + field_access_eligible_expression space* '.' space* field_name end rule ary_eligible_expression @@ -290,14 +298,14 @@ grammar Idl / concatenation_expression / + field_access_expression + / function_call # Ast is assigned in function_call rule / csr_field_access_expression / csr_register_access_expression / - field_access_expression - / bits_cast / rval # must come last! @@ -334,6 +342,8 @@ grammar Idl / '$enum_to_a' space* '(' space* user_type_name ')' / + '$enum' space* '(' space* user_type_name space* ',' space* expression space* ')' + / '$array_size' space* '(' space* expression ')' / paren_expression @@ -348,14 +358,14 @@ grammar Idl / concatenation_expression / + field_access_expression + / function_call # Ast is assigned in function_call rule / csr_field_access_expression / csr_register_access_expression / - field_access_expression - / enum_ref / rval # must come last diff --git a/lib/idl/passes/gen_adoc.rb b/lib/idl/passes/gen_adoc.rb index a4bc52fe..fbaa8172 100644 --- a/lib/idl/passes/gen_adoc.rb +++ b/lib/idl/passes/gen_adoc.rb @@ -96,6 +96,11 @@ def gen_adoc(indent, indent_spaces: 2) "#{' '*indent}$bits(#{expression.gen_adoc(0, indent_spaces: )})" end end + class EnumCastAst + def gen_adoc(indent, indent_spaces: 2) + "#{' '*indent}$enum(#{enum_name.gen_adoc(0, indent_spaces:)}, #{expression.gen_adoc(0, indent_spaces: )})" + end + end class CsrFieldAssignmentAst def gen_adoc(indent, indent_spaces: 2) "#{' '*indent}#{csr_field.gen_adoc(indent, indent_spaces:)} = #{write_value.gen_adoc(0, indent_spaces:)}" diff --git a/lib/idl/passes/prune.rb b/lib/idl/passes/prune.rb index 768d073f..3cd76e6a 100644 --- a/lib/idl/passes/prune.rb +++ b/lib/idl/passes/prune.rb @@ -21,7 +21,7 @@ def create_literal(value) elsif value.is_a?(TrueClass) || value.is_a?(FalseClass) create_bool_literal(value) else - raise "TODO" + raise "TODO: #{value.class.name}" end end @@ -35,9 +35,10 @@ def prune(symtab) new_node.instance_variable_set(:@children, new_children) if is_a?(Executable) - begin + value_result = value_try do execute(symtab) - rescue ValueError + end + value_else(value_result) do execute_unknown(symtab) end end @@ -48,10 +49,11 @@ def prune(symtab) end class FunctionCallExpressionAst def prune(symtab) - begin + value_result = value_try do v = value(symtab) - create_literal(v) - rescue ValueError + return create_literal(v) + end + value_else(value_result) do FunctionCallExpressionAst.new(input, interval, name, targs.map { |t| t.prune(symtab) }, args.map { |a| a.prune(symtab)} ) end end @@ -69,79 +71,92 @@ def prune(symtab) end class ForLoopAst def prune(symtab) - symtab.push + symtab.push(self) symtab.add(init.lhs.name, Var.new(init.lhs.name, init.lhs_type(symtab))) - new_loop = - ForLoopAst.new( - input, interval, - init.prune(symtab), - condition.prune(symtab), - update.prune(symtab), - stmts.map { |s| s.prune(symtab) } - ) - symtab.pop + begin + new_loop = + ForLoopAst.new( + input, interval, + init.prune(symtab), + condition.prune(symtab), + update.prune(symtab), + stmts.map { |s| s.prune(symtab) } + ) + ensure + symtab.pop + end new_loop end end class FunctionBodyAst def prune(symtab, args_already_applied: false) - symtab.push + symtab.push(self) - func_def = find_ancestor(FunctionDefAst) - unless args_already_applied || func_def.nil? - if func_def.templated? # can't prune a template because we don't have all types - return dup - end + begin + func_def = find_ancestor(FunctionDefAst) + unless args_already_applied || func_def.nil? + if func_def.templated? # can't prune a template because we don't have all types + return dup + end - # push template values - func_def.template_names.each_with_index do |tname, idx| - symtab.add(tname, Var.new(tname, func_def.template_types(symtab)[idx])) - end + # push template values + func_def.template_names.each_with_index do |tname, idx| + symtab.add(tname, Var.new(tname, func_def.template_types(symtab)[idx])) + end - # push args - func_def.arguments(symtab).each do |arg_type, arg_name| - symtab.add(arg_name, Var.new(arg_name, arg_type)) + # push args + func_def.arguments(symtab).each do |arg_type, arg_name| + symtab.add(arg_name, Var.new(arg_name, arg_type)) + end end - end - begin - # go through the statements, and stop if we find one that retuns or raises an exception - statements.each_with_index do |s, idx| - if s.is_a?(ReturnStatementAst) - return FunctionBodyAst.new(input, interval, statements[0..idx].map { |s| s.prune(symtab) }) - elsif s.is_a?(ConditionalReturnStatementAst) - begin - v = s.return_value(symtab) + pruned_body = nil - # conditional return, condition not taken if v.nil? - return FunctionBodyAst.new(input, interval, statements[0..idx].map { |s| s.prune(symtab) }) unless v.nil? - rescue ValueError - # conditional return, condition not known; keep going + value_result = value_try do + # go through the statements, and stop if we find one that retuns or raises an exception + statements.each_with_index do |s, idx| + if s.is_a?(ReturnStatementAst) + pruned_body = FunctionBodyAst.new(input, interval, statements[0..idx].map { |s| s.prune(symtab) }) + return pruned_body + elsif s.is_a?(ConditionalReturnStatementAst) + value_try do + v = s.return_value(symtab) + + # conditional return, condition not taken if v.nil? + unless v.nil? + pruned_body = FunctionBodyAst.new(input, interval, statements[0..idx].map { |s| s.prune(symtab) }) + return pruned_body + end + end + # || conditional return, condition not known; keep going + elsif s.is_a?(StatementAst) && s.action.is_a?(FunctionCallExpressionAst) && s.action.name == "raise" + pruned_body = FunctionBodyAst.new(input, interval, statements[0..idx].map { |s| s.prune(symtab) }) + return pruned_body + else + s.execute(symtab) end - elsif s.is_a?(StatementAst) && s.action.is_a?(FunctionCallExpressionAst) && s.action.name == "raise" - return FunctionBodyAst.new(input, interval, statements[0..idx].map { |s| s.prune(symtab) }) - else - s.execute(symtab) end - end - FunctionBodyAst.new(input, interval, statements.map { |s| s.prune(symtab) }) - rescue ValueError - FunctionBodyAst.new(input, interval, statements.map { |s| s.prune(symtab) }) + pruned_body = FunctionBodyAst.new(input, interval, statements.map { |s| s.prune(symtab) }) + end + value_else(value_result) do + pruned_body = FunctionBodyAst.new(input, interval, statements.map { |s| s.prune(symtab) }) + end ensure symtab.pop end + + pruned_body end end class StatementAst def prune(symtab) pruned_action = action.prune(symtab) pruned_action.add_symbol(symtab) if pruned_action.is_a?(Declaration) - begin + value_try do pruned_action.execute(symtab) if pruned_action.is_a?(Executable) - rescue ValueError - # ok end + # || ok StatementAst.new(input, interval, pruned_action) end @@ -149,22 +164,21 @@ def prune(symtab) class BinaryExpressionAst # @!macro prune def prune(symtab) - begin + value_try do val = value(symtab) return create_literal(val) - rescue ValueError - # fall through end + # fall through - begin + lhs_value = nil + rhs_value = nil + + value_try do lhs_value = lhs.value(symtab) - rescue ValueError - lhs_value = nil end - begin + + value_try do rhs_value = rhs.value(symtab) - rescue ValueError - rhs_value = nil end if op == "&&" @@ -245,67 +259,72 @@ def prune(symtab) class IfAst # @!macro prune def prune(symtab) - if if_cond.value(symtab) - if_body.prune(symtab) - elsif !elseifs.empty? - # we know that the if condition is false, so now we treat the else if - # as the starting point and try again - IfAst.new( - input, interval, - elseifs[0].cond.dup, - elseifs[0].body.dup, - elseifs[1..].map(&:dup), - final_else_body.dup).prune(symtab) - elsif !final_else_body.stmts.empty? - # the if is false, and there are no else ifs, so the result of the prune is just the pruned else body - final_else_body.prune(symtab) - else - # the if is false, and there are no else ifs or elses. This is just a no-op - NoopAst.new + value_result = value_try do + if if_cond.value(symtab) + return if_body.prune(symtab) + elsif !elseifs.empty? + # we know that the if condition is false, so now we treat the else if + # as the starting point and try again + return IfAst.new( + input, interval, + elseifs[0].cond.dup, + elseifs[0].body.dup, + elseifs[1..].map(&:dup), + final_else_body.dup).prune(symtab) + elsif !final_else_body.stmts.empty? + # the if is false, and there are no else ifs, so the result of the prune is just the pruned else body + return final_else_body.prune(symtab) + else + # the if is false, and there are no else ifs or elses. This is just a no-op + return NoopAst.new + end end - rescue ValueError - # we don't know the value of the if condition - # we still might know the value of an else if - unknown_elsifs = [] - elseifs.each do |eif| - begin - if eif.cond.value(symtab) - # this elseif is true, so turn it into an else and then we are done - return IfAst.new( - input, interval, - if_cond.dup, - if_body.dup, - unknown_elsifs.map(&:dup), - eif.body.dup - ).prune(symtab) - else - # this elseif is false, so we can remove it - next + value_else(value_result) do + # we don't know the value of the if condition + # we still might know the value of an else if + unknown_elsifs = [] + elseifs.each do |eif| + value_result = value_try do + if eif.cond.value(symtab) + # this elseif is true, so turn it into an else and then we are done + return IfAst.new( + input, interval, + if_cond.dup, + if_body.dup, + unknown_elsifs.map(&:dup), + eif.body.dup + ).prune(symtab) + else + # this elseif is false, so we can remove it + next :ok + end + end + value_else(value_result) do + unknown_elsifs << eif end - rescue ValueError - unknown_elsifs << eif end + # we get here, then we don't know the value of anything. just return this if with everything pruned + IfAst.new( + input, interval, + if_cond.prune(symtab), + if_body.prune(symtab), + unknown_elsifs.map { |eif| eif.prune(symtab) }, + final_else_body.prune(symtab) + ) end - # we get here, then we don't know the value of anything. just return this if with everything pruned - IfAst.new( - input, interval, - if_cond.prune(symtab), - if_body.prune(symtab), - elseifs.map { |eif| eif.prune(symtab) }, - final_else_body.prune(symtab) - ) end end class ConditionalReturnStatementAst def prune(symtab) - begin + value_result = value_try do if condition.value(symtab) return return_expression.prune(symtab) else return NoopAst.new end - rescue ValueError + end + value_else(value_result) do ConditionalReturnStatementAst.new(input, interval, return_expression.prune(symtab), condition.prune(symtab)) end end @@ -313,40 +332,42 @@ def prune(symtab) class ConditionalStatementAst def prune(symtab) - if condition.value(symtab) + value_result = value_try do + if condition.value(symtab) + pruned_action = action.prune(symtab) + pruned_action.add_symbol(symtab) if pruned_action.is_a?(Declaration) + value_result = value_try do + pruned_action.execute(symtab) if pruned_action.is_a?(Executable) + end + + return StatementAst.new(input, interval, pruned_action) + else + return NoopAst.new + end + end + value_else(value_result) do + # condition not known pruned_action = action.prune(symtab) pruned_action.add_symbol(symtab) if pruned_action.is_a?(Declaration) - begin + value_result = value_try do pruned_action.execute(symtab) if pruned_action.is_a?(Executable) - rescue ValueError - # ok end - StatementAst.new(input, interval, pruned_action) - else - NoopAst.new() - end - rescue ValueError - # condition not known - pruned_action = action.prune(symtab) - pruned_action.add_symbol(symtab) if pruned_action.is_a?(Declaration) - begin - pruned_action.execute(symtab) if pruned_action.is_a?(Executable) - rescue ValueError - # ok + # ok + ConditionalStatementAst.new(input, interval, pruned_action, condition.prune(symtab)) end - ConditionalStatementAst.new(input, interval, pruned_action, condition.prune(symtab)) end end class TernaryOperatorExpressionAst def prune(symtab) - begin + value_result = value_try do if condition.value(symtab) - true_expression.prune(symtab) + return true_expression.prune(symtab) else - false_expression.prune(symtab) + return false_expression.prune(symtab) end - rescue ValueError + end + value_else(value_result) do TernaryOperatorExpressionAst.new( input, interval, condition.prune(symtab), diff --git a/lib/idl/passes/reachable_exceptions.rb b/lib/idl/passes/reachable_exceptions.rb index 528ada8d..72f8dc5a 100644 --- a/lib/idl/passes/reachable_exceptions.rb +++ b/lib/idl/passes/reachable_exceptions.rb @@ -8,9 +8,13 @@ module Idl class AstNode # @return [Array] List of all functions that can be reached (via function calls) from this node def reachable_exceptions(symtab) - children.reduce([]) do |list, e| - list.concat e.reachable_exceptions(symtab) - end.uniq + return 0 if @children.empty? + + mask = 0 + @children.size.times do |i| + mask |= @children[i].reachable_exceptions(symtab) + end + mask end end @@ -19,108 +23,178 @@ def reachable_exceptions(symtab) if name == "raise" # first argument is the exception code_ast = arg_nodes[0] - begin + value_result = value_try do code = code_ast.value(symtab) internal_error "Code should be an integer" unless code.is_a?(Integer) - return [code] - rescue ValueError + 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) - fns = [] + mask = 0 if template? template_arg_nodes.each do |t| - fns.concat(t.reachable_exceptions(symtab)) + mask |= t.reachable_exceptions(symtab) if t.is_a?(FunctionCallExpressionAst) end end arg_nodes.each do |a| - fns.concat(a.reachable_exceptions(symtab)) + mask |= a.reachable_exceptions(symtab) if a.is_a?(FunctionCallExpressionAst) end unless func_def_type.builtin? body_symtab = func_def_type.apply_template_values(template_values(symtab), self) func_def_type.apply_arguments(body_symtab, arg_nodes, symtab, self) - fns.concat(func_def_type.body.prune(body_symtab, args_already_applied: true).reachable_exceptions(body_symtab)) + begin + mask |= func_def_type.body.reachable_exceptions(body_symtab) + ensure + body_symtab.pop + body_symtab.release + end end - fns + # @reachable_exceptions_func_call_cache[symtab] = mask + mask end end class StatementAst def reachable_exceptions(symtab) - fns = action.reachable_exceptions(symtab) + mask = + # if action.is_a?(FunctionCallExpressionAst) + action.reachable_exceptions(symtab) + # else + # 0 + # end action.add_symbol(symtab) if action.is_a?(Declaration) - begin - action.execute(symtab) if action.is_a?(Executable) - rescue ValueError + if action.is_a?(Executable) + value_try do + action.execute(symtab) + end + end # ok + mask + end + end + + class IfAst + def reachable_exceptions(symtab) + mask = 0 + value_try do + mask = if_cond.reachable_exceptions(symtab) if if_cond.is_a?(FunctionCallExpressionAst) + value_result = value_try do + if (if_cond.value(symtab)) + mask |= if_body.reachable_exceptions(symtab) + return mask # no need to continue + else + elseifs.each do |eif| + mask |= eif.cond.reachable_exceptions(symtab) if eif.cond.is_a?(FunctionCallExpressionAst) + value_result = value_try do + if (eif.cond.value(symtab)) + mask |= eif.body.reachable_exceptions(symtab) + return mask # no need to keep going + end + end + value_else(value_result) do + # condition isn't known; body is potentially reachable + mask |= eif.body.reachable_exceptions(symtab) + end + end + mask |= final_else_body.reachable_exceptions(symtab) + end + end + value_else(value_result) do + mask |= if_body.reachable_exceptions(symtab) + + elseifs.each do |eif| + mask |= eif.cond.reachable_exceptions(symtab) if eif.cond.is_a?(FunctionCallExpressionAst) + value_result = value_try do + if (eif.cond.value(symtab)) + mask |= eif.body.reachable_exceptions(symtab) + return mask # no need to keep going + end + end + value_else(value_result) do + # condition isn't known; body is potentially reachable + mask |= eif.body.reachable_exceptions(symtab) + end + end + mask |= final_else_body.reachable_exceptions(symtab) + end end - fns + return mask end end class ConditionalReturnStatementAst - def reachable_functions(symtab) - fns = condition.reachable_exceptions(symtab) - if condition.value(symtab) - fns.concat return_expression.reachable_exceptions(symtab) - begin - return_expression.execute(symtab) - rescue ValueError - # ok + def reachable_exceptions(symtab) + mask = condition.is_a?(FunctionCallExpressionAst) ? condition.reachable_exceptions(symtab) : 0 + value_result = value_try do + if condition.value(symtab) + mask |= return_expression.is_a?(FunctionCallExpressionAst) ? return_expression.reachable_exceptions(symtab) : 0 + # ok end - fns - else - [] end + value_else(value_result) do + mask |= return_expression.is_a?(FunctionCallExpressionAst) ? return_expression.reachable_exceptions(symtab) : 0 + end + mask end end class ConditionalStatementAst def reachable_exceptions(symtab) - if condition.value(symtab) - fns = action.reachable_exceptions(symtab) - action.add_symbol(symtab) if action.is_a?(Declaration) - begin - action.execute(symtab) if action.is_a?(Executable) - rescue ValueError - # ok + mask = 0 + value_result = value_try do + mask |= condition.reachable_exceptions(symtab) + if condition.value(symtab) + mask |= action.reachable_exceptions(symtab) + action.add_symbol(symtab) if action.is_a?(Declaration) + if action.is_a?(Executable) + value_result = value_try do + action.execute(symtab) + end + end end - fns - else - [] end - rescue ValueError - # condition not known - fns = action.reachable_exceptions(symtab) - action.add_symbol(symtab) if action.is_a?(Declaration) - begin - action.execute(symtab) if action.is_a?(Executable) - rescue ValueError - # ok + value_else(value_result) do + mask = 0 + # condition not known + mask |= condition.reachable_exceptions(symtab) + mask |= action.reachable_exceptions(symtab) + action.add_symbol(symtab) if action.is_a?(Declaration) + if action.is_a?(Executable) + value_result = value_try do + action.execute(symtab) + end + end end - fns + mask end end class ForLoopAst def reachable_exceptions(symtab) - symtab.push - symtab.add(init.lhs.name, Var.new(init.lhs.name, init.lhs_type(symtab))) - fns = init.reachable_exceptions(symtab) - fns.concat(condition.reachable_exceptions(symtab)) - fns.concat(update.reachable_exceptions(symtab)) - stmts.each do |stmt| - fns.concat(stmt.reachable_exceptions(symtab)) + symtab.push(self) + begin + symtab.add(init.lhs.name, Var.new(init.lhs.name, init.lhs_type(symtab))) + mask = init.is_a?(FunctionCallExpressionAst) ? init.reachable_exceptions(symtab) : 0 + mask |= condition.reachable_exceptions(symtab) if condition.is_a?(FunctionCallExpressionAst) + mask |= update.reachable_exceptions(symtab) if update.is_a?(FunctionCallExpressionAst) + stmts.each do |stmt| + mask |= stmt.reachable_exceptions(symtab) + end + ensure + symtab.pop end - symtab.pop - fns.uniq + mask end end end diff --git a/lib/idl/passes/reachable_functions.rb b/lib/idl/passes/reachable_functions.rb index e7574e18..5ee13366 100644 --- a/lib/idl/passes/reachable_functions.rb +++ b/lib/idl/passes/reachable_functions.rb @@ -7,7 +7,8 @@ class AstNode # @return [Array] List of all functions that can be reached (via function calls) from this node def reachable_functions(symtab) children.reduce([]) do |list, e| - list.concat e.reachable_functions(symtab) + fns = e.reachable_functions(symtab) + list.concat fns end.uniq(&:name) end end @@ -20,40 +21,31 @@ def reachable_functions(symtab) body_symtab = func_def_type.apply_template_values(tvals, self) - # have we seen this exact same call already?? - key = nil + fns = [] + begin - key = [ - name, - tvals, - func_def_type.argument_values(body_symtab, arg_nodes, symtab, self) - ].hash - fns = func_def_type.func_def_ast.reachable_functions_cache[key] - return fns unless fns.nil? - rescue ValueError - # fall through, we need to evaluate - end + if template? + template_arg_nodes.each do |t| + fns.concat(t.reachable_functions(symtab)) if t.is_a?(FunctionCallExpressionAst) + end + end - fns = [] - if template? - template_arg_nodes.each do |t| - fns.concat(t.reachable_functions(symtab)) + arg_nodes.each do |a| + fns.concat(a.reachable_functions(symtab)) if a.is_a?(FunctionCallExpressionAst) end - end - arg_nodes.each do |a| - fns.concat(a.reachable_functions(symtab)) - end + func_def_type.apply_arguments(body_symtab, arg_nodes, symtab, self) - func_def_type.apply_arguments(body_symtab, arg_nodes, symtab, self) + unless func_def_type.builtin? + fns.concat(func_def_type.body.reachable_functions(body_symtab)) + end - unless func_def_type.builtin? - prune_symtab = body_symtab #.deep_clone - fns.concat(func_def_type.body.prune(prune_symtab, args_already_applied: true).reachable_functions(body_symtab)) + fns = fns.push(func_def_type.func_def_ast).uniq(&:name) + ensure + body_symtab.pop + body_symtab.release end - fns = fns.push(func_def_type.func_def_ast).uniq(&:name) - func_def_type.func_def_ast.reachable_functions_cache[key] = fns unless key.nil? fns end end @@ -61,75 +53,69 @@ def reachable_functions(symtab) class StatementAst def reachable_functions(symtab) fns = action.reachable_functions(symtab) + action.add_symbol(symtab) if action.is_a?(Declaration) - begin + value_try do action.execute(symtab) if action.is_a?(Executable) - rescue ValueError - # ok end + # ok + fns end end class ConditionalReturnStatementAst def reachable_functions(symtab) - fns = condition.reachable_functions(symtab) - if condition.value(symtab) - fns.concat return_expression.reachable_functions(symtab) - begin - return_expression.execute(symtab) - rescue ValueError - # ok + fns = condition.is_a?(FunctionCallExpressionAst) ? condition.reachable_functions(symtab) : [] + value_result = value_try do + cv = condition.value(symtab) + if cv + fns.concat return_expression.reachable_functions(symtab) if return_expression.is_a?(FunctionCallExpressionAst) end - fns - else - [] end + value_else(value_result) do + fns.concat return_expression.reachable_functions(symtab) if return_expression.is_a?(FunctionCallExpressionAst) + end + + fns end end class ConditionalStatementAst def reachable_functions(symtab) - fns = condition.reachable_functions(symtab) - if condition.value(symtab) - fns.concat action.reachable_functions(symtab) - action.add_symbol(symtab) if action.is_a?(Declaration) - begin - action.execute(symtab) if action.is_a?(Executable) - rescue ValueError - # ok + + fns = condition.is_a?(FunctionCallExpressionAst) ? condition.reachable_functions(symtab) : [] + + value_result = value_try do + if condition.value(symtab) + fns.concat action.reachable_functions(symtab) if action.is_a?(FunctionCallExpressionAst) + # no need to execute action (return) end - fns - else - [] end - rescue ValueError - # condition not known - fns = action.reachable_functions(symtab) - action.add_symbol(symtab) if action.is_a?(Declaration) - begin - action.execute(symtab) if action.is_a?(Executable) - rescue ValueError - # ok + value_else(value_result) do + # condition not known + fns = fns.concat action.reachable_functions(symtab) if action.is_a?(FunctionCallExpressionAst) end + fns end end class ForLoopAst def reachable_functions(symtab) - # puts path - # puts to_idl - symtab.push - symtab.add(init.lhs.name, Var.new(init.lhs.name, init.lhs_type(symtab))) - fns = init.reachable_functions(symtab) - fns.concat(condition.reachable_functions(symtab)) - fns.concat(update.reachable_functions(symtab)) - stmts.each do |stmt| - fns.concat(stmt.reachable_functions(symtab)) + symtab.push(self) + begin + symtab.add(init.lhs.name, Var.new(init.lhs.name, init.lhs_type(symtab))) + fns = init.is_a?(FunctionCallExpressionAst) ? init.reachable_functions(symtab) : [] + fns.concat(condition.reachable_functions(symtab)) if condition.is_a?(FunctionCallExpressionAst) + fns.concat(update.reachable_functions(symtab)) if update.is_a?(FunctionCallExpressionAst) + stmts.each do |stmt| + fns.concat(stmt.reachable_functions(symtab)) + end + ensure + symtab.pop end - symtab.pop - fns.uniq + fns end end end diff --git a/lib/idl/symbol_table.rb b/lib/idl/symbol_table.rb index 27d61002..ca97b1dc 100644 --- a/lib/idl/symbol_table.rb +++ b/lib/idl/symbol_table.rb @@ -94,9 +94,10 @@ def initialize(arch_def, effective_xlen = nil) if arch_def.fully_configured? raise "effective_xlen should not be set when symbol table is given a fully-configured ArchDef" unless effective_xlen.nil? else - raise "effective_xlen should be set when symbol table is given an ArchDef" if effective_xlen.nil? + raise "effective_xlen should be set when symbol table is given an ArchDef" if effective_xlen.nil? && arch_def.mxlen.nil? end @mxlen = effective_xlen.nil? ? arch_def.mxlen : effective_xlen + @callstack = [nil] @scopes = [{ "X" => Var.new( "X", @@ -155,30 +156,30 @@ def initialize(arch_def, effective_xlen = nil) end # add the builtin extensions - add!( - "ExtensionName", - EnumerationType.new( - "ExtensionName", - arch_def.extensions.map(&:name), - Array.new(arch_def.extensions.size) { |i| i + 1 } - ) - ) - add!( - "ExceptionCode", - EnumerationType.new( - "ExceptionCode", - arch_def.exception_codes.map(&:var), - arch_def.exception_codes.map(&:num) - ) - ) - add!( - "InterruptCode", - EnumerationType.new( - "InterruptCode", - arch_def.interrupt_codes.map(&:var), - arch_def.interrupt_codes.map(&:num) - ) - ) + # add!( + # "ExtensionName", + # EnumerationType.new( + # "ExtensionName", + # arch_def.extensions.map(&:name), + # Array.new(arch_def.extensions.size) { |i| i + 1 } + # ) + # ) + # add!( + # "ExceptionCode", + # EnumerationType.new( + # "ExceptionCode", + # arch_def.exception_codes.map(&:var), + # arch_def.exception_codes.map(&:num) + # ) + # ) + # add!( + # "InterruptCode", + # EnumerationType.new( + # "InterruptCode", + # arch_def.interrupt_codes.map(&:var), + # arch_def.interrupt_codes.map(&:num) + # ) + # ) end # do a deep freeze to protect the sym table and all its entries from modification @@ -192,17 +193,39 @@ def deep_freeze # set frozen_hash so that we can quickly compare symtabs @frozen_hash = [@scopes.hash, @archdef.hash].hash + # set up the global clone that be used as a mutable table + @global_clone_pool = [] + + 5.times do + copy = SymbolTable.allocate + copy.instance_variable_set(:@scopes, [@scopes[0]]) + copy.instance_variable_set(:@callstack, [@callstack[0]]) + copy.instance_variable_set(:@archdef, @archdef) + copy.instance_variable_set(:@mxlen, @mxlen) + copy.instance_variable_set(:@global_clone_pool, @global_clone_pool) + copy.instance_variable_set(:@in_use, false) + @global_clone_pool << copy + end + freeze self end + # @return [String] inspection string + def inspect + "SymbolTable[#{@archdef.name}]#{frozen? ? ' (frozen)' : ''}" + end + # pushes a new scope # @return [SymbolTable] self - def push + def push(ast) # puts "push #{caller[0]}" # @scope_caller ||= [] # @scope_caller.push caller[0] + raise "#{@scopes.size} #{@callstack.size}" unless @scopes.size == @callstack.size @scopes << {} + @callstack << ast + @frozen_hash = nil self end @@ -212,7 +235,13 @@ def pop # puts " from #{@scope_caller.pop}" raise "Error: popping the symbol table would remove global scope" if @scopes.size == 1 + raise "?" unless @scopes.size == @callstack.size @scopes.pop + @callstack.pop + end + + def callstack + @callstack.reverse.map { |ast| ast.nil? ? "" : "#{ast.input_file}:#{ast.lineno}" }.join("\n") end # @return [Boolean] whether or not any symbol 'name' is defined at any level in the symbol table @@ -229,7 +258,8 @@ def keys_pretty # @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) + result = s.fetch(name, nil) + return result unless result.nil? end nil end @@ -323,6 +353,51 @@ def print end end + # @return [Boolean] true if the symbol table is at the global scope + def at_global_scope? + @scopes.size == 1 + end + + # @return [SymbolTable] a mutable clone of the global scope of this SymbolTable + def global_clone + # raise "symtab isn't frozen" if @global_clone.nil? + # raise "global clone isn't at global scope" unless @global_clone.at_global_scope? + + @global_clone_pool.each do |symtab| + unless symtab.in_use? + symtab.instance_variable_set(:@in_use, true) + return symtab + end + end + + # need more! + warn "Allocating more SymbolTables" + 5.times do + copy = SymbolTable.allocate + copy.instance_variable_set(:@scopes, [@scopes[0]]) + copy.instance_variable_set(:@callstack, [@callstack[0]]) + copy.instance_variable_set(:@archdef, @archdef) + copy.instance_variable_set(:@mxlen, @mxlen) + copy.instance_variable_set(:@global_clone_pool, @global_clone_pool) + copy.instance_variable_set(:@in_use, false) + @global_clone_pool << copy + end + + global_clone + end + + def release + pop while levels > 1 + raise "Clone isn't back in global scope" unless at_global_scope? + raise "You are calling release on the frozen SymbolTable" if frozen? + raise "??" if @in_use.nil? + raise "Double release detected" unless @in_use + + @in_use = false + end + + def in_use? = @in_use + # @return [SymbolTable] a deep clone of this SymbolTable def deep_clone(clone_values: false, freeze_global: true) raise "don't do this" unless freeze_global @@ -332,11 +407,13 @@ def deep_clone(clone_values: false, freeze_global: true) if levels == 1 copy = dup copy.instance_variable_set(:@scopes, copy.instance_variable_get(:@scopes).dup) + copy.instance_variable_set(:@callstack, copy.instance_variable_get(:@callstack).dup) return copy end copy = dup # back up the table to global scope + copy.instance_variable_set(:@callstack, @callstack.dup) copy.instance_variable_set(:@scopes, []) c_scopes = copy.instance_variable_get(:@scopes) c_scopes.push(@scopes[0]) diff --git a/lib/idl/tests/helpers.rb b/lib/idl/tests/helpers.rb index 0f293e6b..ca26eb4a 100644 --- a/lib/idl/tests/helpers.rb +++ b/lib/idl/tests/helpers.rb @@ -27,6 +27,10 @@ def interrupt_codes = [OpenStruct.new(var: "CoolInterrupt", num: 1)] def fully_configured? = false def partially_configured? = true def unconfigured? = false + + def name = "mock" + + attr_accessor :global_ast end module TestMixin diff --git a/lib/idl/tests/test_functions.rb b/lib/idl/tests/test_functions.rb index 922ba403..70917a73 100644 --- a/lib/idl/tests/test_functions.rb +++ b/lib/idl/tests/test_functions.rb @@ -18,6 +18,11 @@ def test_that_reachable_raise_analysis_respects_transitive_known_values B 1 } + enum ExceptionCode { + ACode 0 + BCode 1 + } + builtin function raise { arguments ExceptionCode code description { raise an exception} @@ -64,10 +69,16 @@ def test_that_reachable_raise_analysis_respects_transitive_known_values path = Pathname.new(t.path) - ast = @compiler.compile_file(path, symtab: @symtab) + ast = @compiler.compile_file(path) + ast.add_global_symbols(@symtab) + @symtab.deep_freeze + @archdef.global_ast = ast + ast.freeze_tree(@symtab) test_ast = ast.functions.select { |f| f.name == "test" }[0] - assert_equal [1], test_ast.body.prune(@symtab).reachable_exceptions(@symtab) + + # should return (1 << BCode), also known as 2 + assert_equal (1 << 1), test_ast.body.prune(@symtab.deep_clone).reachable_exceptions(@symtab.deep_clone) end def test_that_reachable_raise_analysis_respects_known_paths_down_an_unknown_path @@ -78,6 +89,11 @@ def test_that_reachable_raise_analysis_respects_known_paths_down_an_unknown_path B 1 } + enum ExceptionCode { + ACode 0 + BCode 1 + } + Bits<64> unknown; builtin function raise { @@ -118,10 +134,14 @@ def test_that_reachable_raise_analysis_respects_known_paths_down_an_unknown_path path = Pathname.new(t.path) - ast = @compiler.compile_file(path, symtab: @symtab) + ast = @compiler.compile_file(path) + ast.add_global_symbols(@symtab) + @symtab.deep_freeze + @archdef.global_ast = ast + ast.freeze_tree(@symtab) test_ast = ast.functions.select { |f| f.name == "test" }[0] - pruned_test_ast = test_ast.body.prune(@symtab) - assert_equal [1], pruned_test_ast.reachable_exceptions(@symtab) + pruned_test_ast = test_ast.body.prune(@symtab.deep_clone) + assert_equal (1 << 1), pruned_test_ast.reachable_exceptions(@symtab.deep_clone) end end diff --git a/lib/idl/type.rb b/lib/idl/type.rb index 14dc9dfd..bb0a58ef 100644 --- a/lib/idl/type.rb +++ b/lib/idl/type.rb @@ -113,6 +113,7 @@ def initialize(kind, qualifiers: [], width: nil, sub_type: nil, name: nil, tuple @width = width end end + TYPE_FROM_KIND = [:boolean, :void, :dontcare].map { |k| [k, Type.new(k)] }.to_h.freeze def clone Type.new( @@ -167,7 +168,7 @@ def equal_to?(type) if type.is_a?(Symbol) raise "#{type} is not a kind" unless KINDS.include?(type) - type = Type.new(type) + type = TYPE_FROM_KIND[type] end case @kind @@ -183,6 +184,8 @@ def equal_to?(type) type.kind == :string && type.width == @width when :array type.kind == :array && type.sub_type.equal_to?(@sub_type) + when :struct + type.kind == :struct && (type.type_name == type_name) else raise "unimplemented type '#{@kind}'" end @@ -203,7 +206,7 @@ def convertable_to?(type) if type.is_a?(Symbol) raise "#{type} is not a kind" unless KINDS.include?(type) - type = Type.new(type) + type = TYPE_FROM_KIND[type] end case @kind @@ -521,24 +524,32 @@ def member_type(member_name) end class EnumerationType < Type - attr_reader :element_names, :element_values, :width, :ref_type + # @return [Integer] The bit width of the enumeration elements + attr_reader :width + + # @return [Array] The names of the enumeration elements, in the same order as element_values + attr_reader :element_names + # @return [Array] The values of the enumeration elements, in the same order as element_names + attr_reader :element_values + + # @return [Type] The type of an reference to this Enumeration class + attr_reader :ref_type + + # @param type_name [String] The name of the enum class + # @param element_names [Array] The names of the elements, in the same order as +element_values+ + # @param element_values [Array] The values of the elements, in the same order as +element_names+ def initialize(type_name, element_names, element_values) width = element_values.max.bit_length width = 1 if width.zero? # can happen if only enum member has value 0 - super(:enum, width: width) + super(:enum, width:) @name = type_name @element_names = element_names @element_values = element_values raise "unexpected" unless element_names.is_a?(Array) - # now add the constant values at the same scope - # ...or, enum values are only usable in specific contexts? - # element_names.each_index do |idx| - # syms.add!(element_names[idx], Var.new(element_names[idx], self, element_values[idx])) - # end - @ref_type = Type.new(:enum_ref, enum_class: self) + @ref_type = Type.new(:enum_ref, enum_class: self) end def clone @@ -625,20 +636,27 @@ def builtin? = @func_def_ast.builtin? def num_args = @func_def_ast.num_args - def type_check_call(template_values, func_call_ast) + def type_check_call(template_values, argument_nodes, call_site_symtab, func_call_ast) raise "Missing template values" if templated? && template_values.empty? if templated? symtab = apply_template_values(template_values, func_call_ast) + apply_arguments(symtab, argument_nodes, call_site_symtab, func_call_ast) @func_def_ast.type_check_template_instance(symtab) + + symtab.pop + symtab.release else - symtab = @symtab.deep_clone - symtab.pop while symtab.levels != 1 + symtab = @symtab.global_clone + + symtab.push(func_call_ast) # to keep things consistent with template functions, push a scope - symtab.push # to keep things consistent with template functions, push a scope + apply_arguments(symtab, argument_nodes, call_site_symtab, func_call_ast) @func_def_ast.type_check_from_call(symtab) + symtab.pop + symtab.release end end @@ -648,17 +666,16 @@ def template_types(symtab) = @func_def_ast.template_types(symtab) def templated? = @func_def_ast.templated? - def apply_template_values(template_values = [], func_call_ast) + def apply_template_values(template_values, func_call_ast) func_call_ast.type_error "Missing template values" if templated? && template_values.empty? func_call_ast.type_error "wrong number of template values in call to #{name}" unless template_names.size == template_values.size - symtab = @symtab.deep_clone - symtab.pop while symtab.levels != 1 + symtab = @symtab.global_clone func_call_ast.type_error "Symbol table should be at global scope" unless symtab.levels == 1 - symtab.push + symtab.push(func_call_ast) template_values.each_with_index do |value, idx| func_call_ast.type_error "template value should be an Integer (found #{value.class.name})" unless value == :unknown || value.is_a?(Integer) @@ -674,9 +691,10 @@ def apply_arguments(symtab, argument_nodes, call_site_symtab, func_call_ast) idx = 0 @func_def_ast.arguments(symtab).each do |atype, aname| func_call_ast.type_error "Missing argument #{idx}" if idx >= argument_nodes.size - begin + value_result = Idl::AstNode.value_try do symtab.add(aname, Var.new(aname, atype, argument_nodes[idx].value(call_site_symtab))) - rescue AstNode::ValueError => e + end + Idl::AstNode.value_else(value_result) do symtab.add(aname, Var.new(aname, atype)) end idx += 1 @@ -690,9 +708,10 @@ def argument_values(symtab, argument_nodes, call_site_symtab, func_call_ast) values = [] @func_def_ast.arguments(symtab).each do |atype, aname| func_call_ast.type_error "Missing argument #{idx}" if idx >= argument_nodes.size - begin + value_result = Idl::AstNode.value_try do values << argument_nodes[idx].value(call_site_symtab) - rescue AstNode::ValueError => e + end + Idl::AstNode.value_else(value_result) do return nil end idx += 1 @@ -707,14 +726,26 @@ def return_type(template_values, func_call_ast) symtab = apply_template_values(template_values, func_call_ast) # apply_arguments(symtab, argument_nodes, call_site_symtab) - @func_def_ast.return_type(symtab).clone + begin + type = @func_def_ast.return_type(symtab) + ensure + symtab.pop + symtab.release + end + type end def return_value(template_values, argument_nodes, call_site_symtab, func_call_ast) symtab = apply_template_values(template_values, func_call_ast) apply_arguments(symtab, argument_nodes, call_site_symtab, func_call_ast) - @func_def_ast.body.return_value(symtab) + begin + value = @func_def_ast.body.return_value(symtab) + ensure + symtab.pop + symtab.release + end + value end # @param template_values [Array] Template values to apply, required if {#templated?} @@ -723,17 +754,28 @@ def return_types(template_values, argument_nodes, call_site_symtab, func_call_as symtab = apply_template_values(template_values, func_call_ast) apply_arguments(symtab, argument_nodes, call_site_symtab, func_call_ast) - @func_def_ast.return_types(symtab).map(&:clone) + begin + types = @func_def_ast.return_types(symtab) + ensure + symtab.pop + symtab.release + end + types end def argument_type(index, template_values, argument_nodes, call_site_symtab, func_call_ast) return nil if index >= @func_def_ast.num_args symtab = apply_template_values(template_values, func_call_ast) - apply_arguments(symtab, argument_nodes, call_site_symtab, func_call_ast) + # apply_arguments(symtab, argument_nodes, call_site_symtab, func_call_ast) - arguments = @func_def_ast.arguments(symtab) - arguments[index][0].clone + begin + arguments = @func_def_ast.arguments(symtab) + ensure + symtab.pop + symtab.release + end + arguments[index][0] end def argument_name(index, template_values = [], func_call_ast) @@ -742,7 +784,12 @@ def argument_name(index, template_values = [], func_call_ast) symtab = apply_template_values(template_values, func_call_ast) # apply_arguments(symtab, argument_nodes, call_site_symtab) - arguments = @func_def_ast.arguments(symtab) + begin + arguments = @func_def_ast.arguments(symtab) + ensure + symtab.pop + symtab.relase + end arguments[index][1] end diff --git a/lib/validate.rb b/lib/validate.rb index b5323022..406b817c 100644 --- a/lib/validate.rb +++ b/lib/validate.rb @@ -205,6 +205,7 @@ def validate(path, type: nil) if [:inst, :ext, :csr].include?(type) && obj.keys.first != File.basename(path, ".yaml").to_s raise ValidationError, "In #{path}, top key '#{obj.keys.first}' does not match filename '#{File.basename(path)}'" end + obj rescue Psych::SyntaxError => e warn "While parsing #{path}" raise e