diff --git a/tools/ldgen/fragments.py b/tools/ldgen/fragments.py index f2d60f161d..dec049ec09 100644 --- a/tools/ldgen/fragments.py +++ b/tools/ldgen/fragments.py @@ -14,6 +14,7 @@ # limitations under the License. # import os +import re from sdkconfig import SDKConfig from pyparsing import OneOrMore @@ -176,7 +177,9 @@ class FragmentFile(): fragment.setParseAction(fragment_parse_action) fragment.ignore("#" + restOfLine) - fragment_stmt << (Group(fragment) | Group(fragment_conditional)) + deprecated_mapping = DeprecatedMapping.get_fragment_grammar(sdkconfig, fragment_file.name).setResultsName("value") + + fragment_stmt << (Group(deprecated_mapping) | Group(fragment) | Group(fragment_conditional)) def fragment_stmt_parsed(pstr, loc, toks): stmts = list() @@ -324,6 +327,93 @@ class Mapping(Fragment): return grammars +class DeprecatedMapping(): + """ + Encapsulates a mapping fragment, which defines what targets the input sections of mappable entties are placed under. + """ + + # Name of the default condition entry + DEFAULT_CONDITION = "default" + MAPPING_ALL_OBJECTS = "*" + + @staticmethod + def get_fragment_grammar(sdkconfig, fragment_file): + + # Match header [mapping] + header = Suppress("[") + Suppress("mapping") + Suppress("]") + + # There are three possible patterns for mapping entries: + # obj:symbol (scheme) + # obj (scheme) + # * (scheme) + obj = Fragment.ENTITY.setResultsName("object") + symbol = Suppress(":") + Fragment.IDENTIFIER.setResultsName("symbol") + scheme = Suppress("(") + Fragment.IDENTIFIER.setResultsName("scheme") + Suppress(")") + + pattern1 = Group(obj + symbol + scheme) + pattern2 = Group(obj + scheme) + pattern3 = Group(Literal(Mapping.MAPPING_ALL_OBJECTS).setResultsName("object") + scheme) + + mapping_entry = pattern1 | pattern2 | pattern3 + + # To simplify parsing, classify groups of condition-mapping entry into two types: normal and default + # A normal grouping is one with a non-default condition. The default grouping is one which contains the + # default condition + mapping_entries = Group(ZeroOrMore(mapping_entry)).setResultsName("mappings") + + normal_condition = Suppress(":") + originalTextFor(SDKConfig.get_expression_grammar()) + default_condition = Optional(Suppress(":") + Literal(DeprecatedMapping.DEFAULT_CONDITION)) + + normal_group = Group(normal_condition.setResultsName("condition") + mapping_entries) + default_group = Group(default_condition + mapping_entries).setResultsName("default_group") + + normal_groups = Group(ZeroOrMore(normal_group)).setResultsName("normal_groups") + + # Any mapping fragment definition can have zero or more normal group and only one default group as a last entry. + archive = Suppress("archive") + Suppress(":") + Fragment.ENTITY.setResultsName("archive") + entries = Suppress("entries") + Suppress(":") + (normal_groups + default_group).setResultsName("entries") + + mapping = Group(header + archive + entries) + mapping.ignore("#" + restOfLine) + + def parsed_deprecated_mapping(pstr, loc, toks): + fragment = Mapping() + fragment.archive = toks[0].archive + fragment.name = re.sub(r"[^0-9a-zA-Z]+", "_", fragment.archive) + + fragment.entries = set() + condition_true = False + for entries in toks[0].entries[0]: + condition = next(iter(entries.condition.asList())).strip() + condition_val = sdkconfig.evaluate_expression(condition) + + if condition_val: + for entry in entries[1]: + fragment.entries.add((entry.object, None if entry.symbol == '' else entry.symbol, entry.scheme)) + condition_true = True + break + + if not fragment.entries and not condition_true: + try: + entries = toks[0].entries[1][1] + except IndexError: + entries = toks[0].entries[1][0] + for entry in entries: + fragment.entries.add((entry.object, None if entry.symbol == '' else entry.symbol, entry.scheme)) + + if not fragment.entries: + fragment.entries.add(("*", None, "default")) + + dep_warning = str(ParseFatalException(pstr, loc, + "Warning: Deprecated old-style mapping fragment parsed in file %s." % fragment_file)) + + print(dep_warning) + return fragment + + mapping.setParseAction(parsed_deprecated_mapping) + return mapping + + FRAGMENT_TYPES = { "sections": Sections, "scheme": Scheme, diff --git a/tools/ldgen/test/test_fragments.py b/tools/ldgen/test/test_fragments.py index 1d2efbe309..e2c4790185 100755 --- a/tools/ldgen/test/test_fragments.py +++ b/tools/ldgen/test/test_fragments.py @@ -735,5 +735,246 @@ entries: FragmentFile(test_fragment, self.sdkconfig) +class DeprecatedMappingTest(FragmentTest): + + def test_valid_grammar(self): + test_fragment = self.create_fragment_file(u""" +[mapping] +archive: lib.a +entries: + obj:symbol (noflash) + # Comments should not matter + obj (noflash) + # Nor should whitespace + obj : symbol_2 ( noflash ) + obj_2 ( noflash ) + * (noflash) +""") + fragment_file = FragmentFile(test_fragment, self.sdkconfig) + self.assertEqual("lib.a", fragment_file.fragments[0].archive) + self.assertEqual("lib_a", fragment_file.fragments[0].name) + + expected = {("obj", "symbol", "noflash"), + ("obj", None, "noflash"), + ("obj", "symbol_2", "noflash"), + ("obj_2", None, "noflash"), + ("*", None, "noflash") + } + + self.assertEqual(expected, fragment_file.fragments[0].entries) + + def test_explicit_blank_default_w_others(self): + test_fragment = self.create_fragment_file(u""" +[mapping] +archive: lib.a +entries: + : A = n + obj_a (noflash) + : default +""") + fragment_file = FragmentFile(test_fragment, self.sdkconfig) + expected = {("*", None, "default")} + + self.assertEqual(expected, fragment_file.fragments[0].entries) + + def test_implicit_blank_default_w_others(self): + test_fragment = self.create_fragment_file(u""" +[mapping] +archive: lib.a +entries: + : A = n + obj_a (noflash) +""") + + fragment_file = FragmentFile(test_fragment, self.sdkconfig) + expected = {("*", None, "default")} + + self.assertEqual(expected, fragment_file.fragments[0].entries) + + def test_explicit_blank_default(self): + test_fragment = self.create_fragment_file(u""" +[mapping] +archive: lib.a +entries: + : default +""") + fragment_file = FragmentFile(test_fragment, self.sdkconfig) + expected = {("*", None, "default")} + + self.assertEqual(expected, fragment_file.fragments[0].entries) + + def test_implicit_blank_default(self): + test_fragment = self.create_fragment_file(u""" +[mapping] +archive: lib.a +entries: + : default +""") + fragment_file = FragmentFile(test_fragment, self.sdkconfig) + expected = {("*", None, "default")} + + self.assertEqual(expected, fragment_file.fragments[0].entries) + + def test_multiple_entries(self): + test_fragment = self.create_fragment_file(u""" +[mapping] +archive: lib.a +entries: + : A = n + obj_a1 (noflash) + obj_a2 (noflash) + : B = n + obj_b1 (noflash) + obj_b2 (noflash) + obj_b3 (noflash) + : C = n + obj_c1 (noflash) +""") + + fragment_file = FragmentFile(test_fragment, self.sdkconfig) + expected = {("obj_b1", None, "noflash"), + ("obj_b2", None, "noflash"), + ("obj_b3", None, "noflash")} + self.assertEqual(expected, fragment_file.fragments[0].entries) + + def test_blank_entries(self): + test_fragment = self.create_fragment_file(u""" +[mapping] +archive: lib.a +entries: + : A = n + obj_a (noflash) + : B = n + : C = n + obj_c (noflash) + : default + obj (noflash) +""") + fragment_file = FragmentFile(test_fragment, self.sdkconfig) + expected = {("*", None, "default")} + self.assertEqual(expected, fragment_file.fragments[0].entries) + + def test_blank_first_condition(self): + test_fragment = self.create_fragment_file(u""" +[mapping] +archive: lib.a +entries: + obj_a (noflash) + : CONFIG_B = y + obj_b (noflash) +""") + + with self.assertRaises(ParseException): + FragmentFile(test_fragment, self.sdkconfig) + + def test_nonlast_default_1(self): + test_fragment = self.create_fragment_file(u""" +[mapping] +archive: lib.a +entries: + : default + obj_a (noflash) + : CONFIG_A = y + obj_A (noflash) +""") + + with self.assertRaises(ParseException): + FragmentFile(test_fragment, self.sdkconfig) + + def test_nonlast_default_2(self): + test_fragment = self.create_fragment_file(u""" +[mapping] +archive: lib.a +entries: + : A = y + obj_A (noflash) + : default + obj_a (noflash) + : B = y + obj_B (noflash +""") + + with self.assertRaises(ParseException): + FragmentFile(test_fragment, self.sdkconfig) + + def test_nonlast_default_3(self): + test_fragment = self.create_fragment_file(u""" +[mapping] +archive: lib.a +entries: + : A = y + obj_A (noflash) + : + obj_a (noflash) + : B = y + obj_B (noflash +""") + + with self.assertRaises(ParseException): + FragmentFile(test_fragment, self.sdkconfig) + + def test_duplicate_default_1(self): + test_fragment = self.create_fragment_file(u""" +[mapping] +archive: lib.a +entries: + : CONFIG_A = y + obj_A (noflash) + : default + obj_a (noflash) + : CONFIG_B = y + obj_B (noflash) + : default + obj_a (noflash) +""") + + with self.assertRaises(ParseException): + FragmentFile(test_fragment, self.sdkconfig) + + def test_duplicate_default_2(self): + test_fragment = self.create_fragment_file(u""" +[mapping] +archive: lib.a +entries: + : CONFIG_A = y + obj_A (noflash) + : CONFIG_B = y + obj_a (noflash) + : default + obj_B (noflash) + : + obj_a (noflash) +""") + + with self.assertRaises(ParseException): + FragmentFile(test_fragment, self.sdkconfig) + + def test_mixed_deprecated_mapping(self): + test_fragment = self.create_fragment_file(u""" +[mapping] +archive: lib.a +entries: + : A = n + obj_A (noflash) + : default + obj_B (noflash) + + +[mapping:test] +archive: lib.a +entries: + if A = n: + obj_A (noflash) + else: + obj_B (noflash) +""") + + fragment_file = FragmentFile(test_fragment, self.sdkconfig) + self.assertEqual(2, len(fragment_file.fragments)) + + self.assertEqual(fragment_file.fragments[0].entries, + fragment_file.fragments[1].entries) + + if __name__ == "__main__": unittest.main() diff --git a/tools/ldgen/test/test_generation.py b/tools/ldgen/test/test_generation.py index a09701576b..c62fa8fcd9 100755 --- a/tools/ldgen/test/test_generation.py +++ b/tools/ldgen/test/test_generation.py @@ -1190,6 +1190,48 @@ entries: self.compare_rules(expected, actual) + def test_rule_generation_condition_with_deprecated_mapping(self): + generation_with_condition = u""" +[mapping] +archive: lib.a +entries: + : PERFORMANCE_LEVEL = 0 + : PERFORMANCE_LEVEL = 1 + obj1 (noflash) + : PERFORMANCE_LEVEL = 2 + obj1 (noflash) + obj2 (noflash) + : PERFORMANCE_LEVEL = 3 + obj1 (noflash) + obj2 (noflash) + obj3 (noflash) +""" + + for perf_level in range(0, 4): + self.sdkconfig.config.syms["PERFORMANCE_LEVEL"].set_value(str(perf_level)) + + self.model.mappings = {} + self.add_fragments(generation_with_condition) + + actual = self.model.generate_rules(self.sections_info) + expected = self.generate_default_rules() + + if perf_level < 4: + for append_no in range(1, perf_level + 1): + flash_text_default = self.get_default("flash_text", expected) + flash_rodata_default = self.get_default("flash_rodata", expected) + + iram_rule = PlacementRule("lib.a", "obj" + str(append_no), None, self.model.sections["text"].entries, "iram0_text") + dram_rule = PlacementRule("lib.a", "obj" + str(append_no), None, self.model.sections["rodata"].entries, "dram0_data") + + flash_text_default.add_exclusion(iram_rule) + flash_rodata_default.add_exclusion(dram_rule) + + expected["iram0_text"].append(iram_rule) + expected["dram0_data"].append(dram_rule) + + self.compare_rules(expected, actual) + if __name__ == "__main__": unittest.main()