From 8a3353cdcc8c2136689141db5933914737c948e9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 12 Oct 2024 09:44:17 +0000 Subject: [PATCH 01/18] Upload pr32-report.txt (53d5f) Pull request: https://github.com/erlingrj/reactor-uc/pull/32 Commit: https://github.com/erlingrj/reactor-uc/commit/53d5f18ae9557d13bd7bd37b42d9a64e79db9364 From a7c1750607d9c3c6c2d417422b0be652b8bca306 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 12 Oct 2024 09:44:18 +0000 Subject: [PATCH 02/18] Upload pr32-report.txt (53d5f) Pull request: https://github.com/erlingrj/reactor-uc/pull/32 Commit: https://github.com/erlingrj/reactor-uc/commit/53d5f18ae9557d13bd7bd37b42d9a64e79db9364 From 56017efd049de4d4c922217eb9940f7c2fbbf5ac Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 12 Oct 2024 09:52:37 +0000 Subject: [PATCH 03/18] Upload pr32-report.txt (0940a) Pull request: https://github.com/erlingrj/reactor-uc/pull/32 Commit: https://github.com/erlingrj/reactor-uc/commit/0940a1b8b2a927160d098297877524140a3fa276 From a114c2a8f1e5620911a8252fc33c2ead47a3649d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 12 Oct 2024 21:36:44 +0000 Subject: [PATCH 04/18] Upload pr32-report.txt (e51c8) Pull request: https://github.com/erlingrj/reactor-uc/pull/32 Commit: https://github.com/erlingrj/reactor-uc/commit/e51c8c89d8b4172c46be027c5eb6b6f5ce930ae3 From eefd6c6fdeeb17829f456c78eb848cba1f94d234 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 12 Oct 2024 21:57:56 +0000 Subject: [PATCH 05/18] Upload pr32-report.txt (c41c2) Pull request: https://github.com/erlingrj/reactor-uc/pull/32 Commit: https://github.com/erlingrj/reactor-uc/commit/c41c2717c484b52d471f63541af9e172e2cc3346 From 76213af08b8540ea8adc00e9ecd91ca9ccf46efa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 12 Oct 2024 22:00:42 +0000 Subject: [PATCH 06/18] Upload pr32-report.txt (55df9) Pull request: https://github.com/erlingrj/reactor-uc/pull/32 Commit: https://github.com/erlingrj/reactor-uc/commit/55df9be31eade4b4d93cd1bd4f1a5ce8a60fd42a From 74b6b96ef3b7cb5b8449fd53dbee471d378002ee Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 12 Oct 2024 22:02:51 +0000 Subject: [PATCH 07/18] Upload pr32-report.txt (2e607) Pull request: https://github.com/erlingrj/reactor-uc/pull/32 Commit: https://github.com/erlingrj/reactor-uc/commit/2e607f129d58c3c5b00a2cba8da30ffd9ff8f29e From 80f2da8a3ddde20f23b1548d0f35256d5b1148a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20M=C3=A6hlum?= Date: Mon, 14 Oct 2024 04:26:28 +0200 Subject: [PATCH 08/18] Memory report ci (#32) * Add custom command to tests POST_BUILD to print some memory stats * First attempt @ memory report workflow * Add checkout step * Add post comment step * Attempt another comment step * Attempt give path * Add permission write on PR * Add permission write to contents * Remove upload artifact step * Add comment * Update workflow to compare with main branch. Python script is stub * Add some vertical lines :) * Change makefile so it can run 'make clean' anytime * Revert * Ad hoc fix for testng * Remove clean * Try make action * Add checkout step * Rename to remove error * Rename to remove error * Add shell * Spelling * Fix branch refs * Try to put back together * Add python script to parse raw report files into more readable format. * Add requirements.txt * Add vertical line * Add three backslashes * Update test values to be more varying * Remove uncommented out and redundant code * Test comment a table from github CI * Try format table again * Skip LF tests for now * Update path * Try comment table from file * Try another table * Update script and test new tables * Update * Delete unused files * Try use real data * Add used data * Maybe fix workflow * Fix multi line variabl * Add debug * Add debug * Add debug * Test * Test * Test * Test * Test * Fix update comment * Fix update comment nowmaybe * Make change to c test * Revert change to c test * Maybe fix now * Test add static data * Minor changes * Remove py dep * Revert C changes --- .github/actions/memory-report/action.yml | 31 ++++++++ .github/workflows/memory.yml | 61 +++++++++++++++ Makefile | 3 +- scripts/ci/reports/parse-reports.py | 97 ++++++++++++++++++++++++ scripts/ci/reports/requirements.txt | 2 + scripts/ci/reports/test/expected.txt | 60 +++++++++++++++ scripts/ci/reports/test/main.txt | 20 +++++ scripts/ci/reports/test/update.txt | 20 +++++ test/unit/CMakeLists.txt | 5 ++ 9 files changed, 298 insertions(+), 1 deletion(-) create mode 100644 .github/actions/memory-report/action.yml create mode 100644 .github/workflows/memory.yml create mode 100644 scripts/ci/reports/parse-reports.py create mode 100644 scripts/ci/reports/requirements.txt create mode 100644 scripts/ci/reports/test/expected.txt create mode 100644 scripts/ci/reports/test/main.txt create mode 100644 scripts/ci/reports/test/update.txt diff --git a/.github/actions/memory-report/action.yml b/.github/actions/memory-report/action.yml new file mode 100644 index 00000000..b313b60e --- /dev/null +++ b/.github/actions/memory-report/action.yml @@ -0,0 +1,31 @@ +name: Fetch memory usage statistics +description: Fetch memory usage statistics from compiled tests for a specific branch + +inputs: + branch: + required: true + +outputs: + report: + description: "Memory usage statistics" + value: ${{ steps.make_report.outputs.report }} + +runs: + using: "composite" + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: recursive + ref: ${{ inputs.branch }} + - name: Report + id: make_report + shell: bash + # For the multi line output + # https://stackoverflow.com/questions/74137120/how-to-fix-or-avoid-error-unable-to-process-file-command-output-successfully + run: | + make test + EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) + echo "report<<$EOF" >> $GITHUB_OUTPUT + echo "$(cat build/test/unit/*.size)" >> $GITHUB_OUTPUT + echo "$EOF" >> $GITHUB_OUTPUT diff --git a/.github/workflows/memory.yml b/.github/workflows/memory.yml new file mode 100644 index 00000000..0716b35a --- /dev/null +++ b/.github/workflows/memory.yml @@ -0,0 +1,61 @@ +name: Memory usage report + +on: + pull_request: + +permissions: + contents: write + pull-requests: write + +jobs: + ci: + name: Report memory usage + runs-on: ubuntu-latest + steps: + # To get the potential changes to CI + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Generate memory usage report (base branch) + id: base + uses: ./.github/actions/memory-report + with: + branch: ${{ github.base_ref }} + - name: Generate memory usage report (head branch) # I.e. the PR branch + id: head + uses: ./.github/actions/memory-report + with: + branch: ${{ github.head_ref }} + + - name: Compare + run: | + echo "$BASE_REPORT" > base_report.txt + echo "$HEAD_REPORT" > head_report.txt + python3 -m pip install -r scripts/ci/reports/requirements.txt + python3 scripts/ci/reports/parse-reports.py \ + head_report.txt \ + base_report.txt \ + report + env: + BASE_REPORT: ${{ steps.base.outputs.report }} + HEAD_REPORT: ${{ steps.head.outputs.report }} + + # This in conjunction with create-or-update-comment allows us to only + # comment once and update it after + - name: Find Comment + uses: peter-evans/find-comment@v3 + id: fc + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: Memory report + + - name: Create or update comment + uses: peter-evans/create-or-update-comment@v4 + with: + comment-id: ${{ steps.fc.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body-path: report.md + edit-mode: replace diff --git a/Makefile b/Makefile index 0c82253d..b3e9453c 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,8 @@ unit-test: # Build and run lf tests lf-test: - make -C test/lf + @echo "Skipping LF tests" +#make -C test/lf # Get coverage data on unit tests coverage: diff --git a/scripts/ci/reports/parse-reports.py b/scripts/ci/reports/parse-reports.py new file mode 100644 index 00000000..a156d1bd --- /dev/null +++ b/scripts/ci/reports/parse-reports.py @@ -0,0 +1,97 @@ +import sys +from pathlib import Path +import pandas as pd + +THIS_DIR = Path(__file__).parent.resolve() +SAMPLE_DIR = THIS_DIR / 'samples' +TEST_DIR = THIS_DIR / 'test' + +def parse_report(path: Path) -> pd.DataFrame: + # Read file and separate on whitespace + # Also skip every other line except for the first one to only get + # the header once + df = pd.read_csv(path, sep='\s+', + skiprows=lambda x: x % 2 == 0 and x > 1 + ) + + # hex is dec just shown in hexadecimal - not useful especially because + # pandas interprets them as strings + df = df.drop('hex', axis=1) + + return df + +def find_percentages(rep1: pd.DataFrame, rep2: pd.DataFrame) -> pd.DataFrame: + # Extract the integer values only + data1 = rep1.iloc[:,0:4] + data2 = rep2.iloc[:,0:4] + + # Calculate percentage increase/decrease + return data1 / data2 * 100 - 100 + +def format_basic_report(update: pd.DataFrame, main: pd.DataFrame, report: pd.DataFrame) -> str: + ret = "" + for idx, row in report.iterrows(): + ret += f"Test {idx}: {row['filename'].replace('_c', '.c')}:\n" + for name in report.head(): + if name != 'filename': + ret += f"% {name : <4}: {row[name] : <8.2f} (from {main.loc[idx][name] : >6} -> {update.loc[idx][name] : >6})\n" + ret += '\n' + return ret + +def format_md_tables(update: pd.DataFrame, main: pd.DataFrame, report: pd.DataFrame) -> str: + format_string = "" + for idx, row in report.iterrows(): + table = f""" +| | from | to | increase (%) | +| -- | ---- | ------ | ------------ | +| text | {main.loc[idx]['text']} | {update.loc[idx]['text']} | {row['text'] : <4.2f} | +| data | {main.loc[idx]['data']} | {update.loc[idx]['data']} | {row['data'] : <4.2f} | +| bss | {main.loc[idx]['bss']} | {update.loc[idx]['bss']} | {row['bss'] : <4.2f} | +| total | {main.loc[idx]['dec']} | {update.loc[idx]['dec']} | {row['dec'] : <4.2f} | +""" + format_string += f"## {row['filename']}\n" + table + "\n\n" + + return format_string + +TESTING = False +if TESTING: + # For testing / development purposes + update = parse_report(TEST_DIR / 'update.txt') + main = parse_report(TEST_DIR / 'main.txt') +else: + if len(sys.argv) != 4: + print(f'Usage: {sys.argv[0]} ') + exit(1) + + # Get reports in raw format + update = parse_report(sys.argv[1]) + main = parse_report(sys.argv[2]) + out = sys.argv[3] + +# Calculate percentage differences +cmp = find_percentages(update, main) +cmp.insert(4, 'filename', update['filename']) + +# Format a human readable report with added statistics +str = format_basic_report(update, main, cmp) + +if TESTING: + # Check that the string is formatted as expected + with open(TEST_DIR / 'expected.txt') as f: + exp = f.read() + assert(str == exp) + print("Test ok.") +else: + # Write the string + with open(out + ".txt", 'w') as f: + f.write(str) + + # Write table to string + with open(out + ".md", 'w') as f: + explanation = \ +"""# Memory report +You will find how your pull request compares to your target branch in terms of memory usage below. +""" + tables = format_md_tables(update, main, cmp) + f.write(explanation + "\n\n" + tables) + print('Write ok.') \ No newline at end of file diff --git a/scripts/ci/reports/requirements.txt b/scripts/ci/reports/requirements.txt new file mode 100644 index 00000000..356df892 --- /dev/null +++ b/scripts/ci/reports/requirements.txt @@ -0,0 +1,2 @@ +pathlib +pandas diff --git a/scripts/ci/reports/test/expected.txt b/scripts/ci/reports/test/expected.txt new file mode 100644 index 00000000..630ef5d9 --- /dev/null +++ b/scripts/ci/reports/test/expected.txt @@ -0,0 +1,60 @@ +Test 0: action_test.c: +% text: 7.20 (from 36104 -> 38702) +% data: -2.47 (from 728 -> 710) +% bss : 56.25 (from 512 -> 800) +% dec : 7.68 (from 37344 -> 40212) + +Test 1: delayed.conn_test.c: +% text: -15.05 (from 39103 -> 33218) +% data: -51.10 (from 728 -> 356) +% bss : 138.67 (from 512 -> 1222) +% dec : -14.00 (from 40343 -> 34696) + +Test 2: event_queue_test.c: +% text: -0.22 (from 22472 -> 22422) +% data: 20.25 (from 632 -> 760) +% bss : 0.00 (from 320 -> 320) +% dec : 0.33 (from 23424 -> 23502) + +Test 3: physical_action_test.c: +% text: 12.55 (from 37566 -> 42280) +% data: 157.72 (from 745 -> 1920) +% bss : -89.75 (from 1952 -> 200) +% dec : 10.27 (from 40263 -> 44400) + +Test 4: port_test.c: +% text: -17.01 (from 39067 -> 32420) +% data: 5.49 (from 728 -> 768) +% bss : -2.34 (from 512 -> 500) +% dec : -16.42 (from 40307 -> 33688) + +Test 5: reaction_queue_test.c: +% text: 35.07 (from 22117 -> 29874) +% data: -36.71 (from 632 -> 400) +% bss : -15.62 (from 320 -> 270) +% dec : 32.40 (from 23069 -> 30544) + +Test 6: shutdown_test.c: +% text: -60.66 (from 32808 -> 12908) +% data: -41.67 (from 720 -> 420) +% bss : 52.34 (from 512 -> 780) +% dec : -58.55 (from 34040 -> 14108) + +Test 7: startup_test.c: +% text: -53.36 (from 31926 -> 14890) +% data: 0.00 (from 720 -> 720) +% bss : 0.00 (from 512 -> 512) +% dec : -51.38 (from 33158 -> 16122) + +Test 8: timer_test.c: +% text: -2.67 (from 32042 -> 31188) +% data: -9.17 (from 720 -> 654) +% bss : 75.78 (from 512 -> 900) +% dec : -1.60 (from 33274 -> 32742) + +Test 9: trigger_value_test.c: +% text: -3.02 (from 18191 -> 17642) +% data: -31.65 (from 632 -> 432) +% bss : -37.50 (from 320 -> 200) +% dec : -4.54 (from 19143 -> 18274) + diff --git a/scripts/ci/reports/test/main.txt b/scripts/ci/reports/test/main.txt new file mode 100644 index 00000000..e475959d --- /dev/null +++ b/scripts/ci/reports/test/main.txt @@ -0,0 +1,20 @@ + text data bss dec hex filename + 36104 728 512 37344 91e0 action_test_c + text data bss dec hex filename + 39103 728 512 40343 9d97 delayed_conn_test_c + text data bss dec hex filename + 22472 632 320 23424 5b80 event_queue_test_c + text data bss dec hex filename + 37566 745 1952 40263 9d47 physical_action_test_c + text data bss dec hex filename + 39067 728 512 40307 9d73 port_test_c + text data bss dec hex filename + 22117 632 320 23069 5a1d reaction_queue_test_c + text data bss dec hex filename + 32808 720 512 34040 84f8 shutdown_test_c + text data bss dec hex filename + 31926 720 512 33158 8186 startup_test_c + text data bss dec hex filename + 32042 720 512 33274 81fa timer_test_c + text data bss dec hex filename + 18191 632 320 19143 4ac7 trigger_value_test_c \ No newline at end of file diff --git a/scripts/ci/reports/test/update.txt b/scripts/ci/reports/test/update.txt new file mode 100644 index 00000000..54ad1d33 --- /dev/null +++ b/scripts/ci/reports/test/update.txt @@ -0,0 +1,20 @@ + text data bss dec hex filename + 38702 710 800 40212 9d14 action_test_c + text data bss dec hex filename + 33218 356 1222 34696 8788 delayed_conn_test_c + text data bss dec hex filename + 22422 760 320 23502 5bce event_queue_test_c + text data bss dec hex filename + 42280 1920 200 44400 ad70 physical_action_test_c + text data bss dec hex filename + 32420 768 500 33688 8398 port_test_c + text data bss dec hex filename + 29874 400 270 30544 7750 reaction_queue_test_c + text data bss dec hex filename + 12908 420 780 14108 371c shutdown_test_c + text data bss dec hex filename + 14890 720 512 16122 3efa startup_test_c + text data bss dec hex filename + 31188 654 900 32742 7fe6 timer_test_c + text data bss dec hex filename + 17642 432 200 18274 4762 trigger_value_test_c \ No newline at end of file diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 1d710e16..cb76fb66 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -19,6 +19,11 @@ foreach(FILE ${TEST_SOURCES}) ${NAME} PRIVATE reactor-uc Unity m ) + add_custom_command(TARGET + ${NAME} POST_BUILD + COMMAND "${CMAKE_PREFIX_PATH}size" "${NAME}" "|" "tee" "${NAME}.size" + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) set_target_properties(${NAME} PROPERTIES C_CLANG_TIDY "") # Disable clang-tidy for this external lib. endforeach(FILE ${TEST_FILES}) From b819217b2c3088943b579da6e96131de6850d3b9 Mon Sep 17 00:00:00 2001 From: erlingrj Date: Sun, 13 Oct 2024 21:22:19 -0700 Subject: [PATCH 09/18] Hide memory usage Github PR comment a little --- scripts/ci/reports/parse-reports.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/ci/reports/parse-reports.py b/scripts/ci/reports/parse-reports.py index a156d1bd..f57607c4 100644 --- a/scripts/ci/reports/parse-reports.py +++ b/scripts/ci/reports/parse-reports.py @@ -89,8 +89,10 @@ def format_md_tables(update: pd.DataFrame, main: pd.DataFrame, report: pd.DataFr # Write table to string with open(out + ".md", 'w') as f: explanation = \ -"""# Memory report -You will find how your pull request compares to your target branch in terms of memory usage below. +""" +Memory usage after merging this PR will be: +
Memory Report + """ tables = format_md_tables(update, main, cmp) f.write(explanation + "\n\n" + tables) From 5d5d2664f6e3d857046771b88a50b33d2d1730c5 Mon Sep 17 00:00:00 2001 From: erling Date: Sun, 13 Oct 2024 21:50:06 -0700 Subject: [PATCH 10/18] Update README.md --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f304c7ca..42fd0512 100644 --- a/README.md +++ b/README.md @@ -98,12 +98,13 @@ which enable distributed embedded systems. - [x] More platform abstractions (Riot, Zephyr and FlexPRET/InterPRET) - [x] Reconsider where to buffer data (outputs vs inputs) - [x] Consider if we should have FIFOs of pending events, not just a single for a trigger. -- [ ] Runtime errors -- [ ] Logging +- [x] Runtime errors +- [x] Logging +- [x] Delayed connections +- [x] Basic decentralized federations - [ ] Multiports and banks - [ ] Modal reactors -- [ ] Federated -- [ ] Delayed connections + ## Troubleshooting From de362014f7c53729df7c365c070e0730eff584d6 Mon Sep 17 00:00:00 2001 From: erlingrj Date: Mon, 14 Oct 2024 19:39:59 -0700 Subject: [PATCH 11/18] Add documentation of several header files --- include/reactor-uc/action.h | 36 ++++++++++++++++---- include/reactor-uc/builtin_triggers.h | 12 +++---- include/reactor-uc/connection.h | 34 +++++++++++++++---- include/reactor-uc/environment.h | 48 +++++++++++++++++++++------ include/reactor-uc/error.h | 9 ++++- include/reactor-uc/platform.h | 28 +++++++++++++++- include/reactor-uc/port.h | 40 +++++++++++++++++++--- 7 files changed, 171 insertions(+), 36 deletions(-) diff --git a/include/reactor-uc/action.h b/include/reactor-uc/action.h index b4c1437a..19addc5d 100644 --- a/include/reactor-uc/action.h +++ b/include/reactor-uc/action.h @@ -10,16 +10,38 @@ typedef struct LogicalAction LogicalAction; typedef struct PhysicalAction PhysicalAction; struct Action { - Trigger super; - interval_t min_offset; - interval_t min_spacing; - tag_t previous_event; // Used to enforce min_spacing - TriggerEffects effects; - TriggerSources sources; - TriggerValue trigger_value; // This is where data associated with schedueled events are stored + Trigger super; // Inherit from Trigger + interval_t min_offset; // The minimum offset from the current time that an event can be scheduled on this action. + interval_t min_spacing; // The minimum spacing between two consecutive events on this action. + tag_t previous_event; // Used to enforce min_spacing + TriggerEffects effects; // The reactions triggered by this Action. + TriggerSources sources; // The reactions that can write to this Action. + TriggerValue trigger_value; // FIFO storage of the data associated with the events scheduled on this action. + /** + * @brief Schedule an event on this action. + */ lf_ret_t (*schedule)(Action *self, interval_t offset, const void *value); }; +/** + * @brief Construct an Action object. An Action is an abstract type that can be either a LogicalAction or a + * PhysicalAction. + * + * @param self Pointer to an allocated Action struct. + * @param type The type of the action. Either logical or physical. + * @param min_offset The minimum offset from the current time that an event can be scheduled on this action. + * @param min_spacing The minimum spacing between two consecutive events on this action. + * @param parent The parent Reactor of this Action. + * @param sources Pointer to an array of Reaction pointers that can be used to store the sources of this action. + * @param sources_size The size of the sources array. + * @param effects Pointer to an array of Reaction pointers that can be used to store the effects of this action. + * @param effects_size The size of the effects array. + * @param value_buf A pointer to a buffer where the data of this action is stored. This should be a field in the + * user-defined + * @param value_size The size of each data element that can be scheduled on this action. + * @param value_capacity The lenght of the buffer where the data of this action is stored + * @param schedule The function that is used to schedule an event on this action. + */ void Action_ctor(Action *self, TriggerType type, interval_t min_offset, interval_t min_spacing, Reactor *parent, Reaction **sources, size_t sources_size, Reaction **effects, size_t effects_size, void *value_buf, size_t value_size, size_t value_capacity, lf_ret_t (*schedule)(Action *, interval_t, const void *)); diff --git a/include/reactor-uc/builtin_triggers.h b/include/reactor-uc/builtin_triggers.h index 8a82c316..d9be248b 100644 --- a/include/reactor-uc/builtin_triggers.h +++ b/include/reactor-uc/builtin_triggers.h @@ -9,17 +9,17 @@ typedef struct Startup Startup; typedef struct Shutdown Shutdown; struct Startup { - Trigger super; - TriggerEffects effects; - Startup *next; // Used to trigger all startup-reactions with only a single startup-event on the event queue. + Trigger super; // Inherit from Trigger + TriggerEffects effects; // The reactions triggered by this Startup. + Startup *next; // Used to trigger all startup-reactions with only a single startup-event on the event queue. }; void Startup_ctor(Startup *self, Reactor *parent, Reaction **effects, size_t effects_size); struct Shutdown { - Trigger super; - TriggerEffects effects; - Shutdown *next; + Trigger super; // Inherit from Trigger + TriggerEffects effects; // The reactions triggered by this Shutdown. + Shutdown *next; // Used to trigger all shutdown-reactions with only a single shutdown-event on the event queue. }; void Shutdown_ctor(Shutdown *self, Reactor *parent, Reaction **effects, size_t effects_size); diff --git a/include/reactor-uc/connection.h b/include/reactor-uc/connection.h index 0f78abfd..b2d901d0 100644 --- a/include/reactor-uc/connection.h +++ b/include/reactor-uc/connection.h @@ -17,12 +17,18 @@ typedef struct Output Output; struct Connection { Trigger super; - Port *upstream; // Single upstream port - Port **downstreams; // Pointer to array of pointers of downstream ports - size_t downstreams_size; - size_t downstreams_registered; - // FIXME: Remove and do it inline in macro.h instead + Port *upstream; // Single upstream port + Port **downstreams; // Pointer to array of pointers of downstream ports + size_t downstreams_size; // Size of downstreams array + size_t downstreams_registered; // Number of downstreams currently registered + /** + * @brief Register a downstream port to this connection. Will increase the downstreams_registered counter. + */ void (*register_downstream)(Connection *, Port *); + /** + * @brief Returns the eventual upstream Output port that drives the possible chain of connections that + * this connection is part of. + */ Output *(*get_final_upstream)(Connection *); /** * @brief Recursive function that traverses down the connection until it @@ -31,6 +37,22 @@ struct Connection { void (*trigger_downstreams)(Connection *, const void *value_ptr, size_t value_size); }; +/** + * @brief Construct a Connection object. A Connection is an abstract type that can be either a Logical, Delayed, + * Physical + * + * @param self The Connection object to construct + * @param type The type of the connection. Either logical, delayed or physical. + * @param parent The reactor in which this connection appears (not the reactors of the ports it connects) + * @param upstream The pointer to the upstream port of this connection + * @param downstreams A pointer to an array of pointers to downstream ports. + * @param num_downstreams The size of the downstreams array. + * @param trigger_value A pointer to the TriggerValue that holds the data of the events that are scheduled on this + * connection. + * @param prepare The prepare function that is called before the connection triggers its downstreams. + * @param cleanup The cleanup function that is called at the end of timestep after all reactions have executed. + * @param trigger_downstreams The function that triggers all downstreams of this connection. + */ void Connection_ctor(Connection *self, TriggerType type, Reactor *parent, Port *upstream, Port **downstreams, size_t num_downstreams, TriggerValue *trigger_value, void (*prepare)(Trigger *), void (*cleanup)(Trigger *), void (*trigger_downstreams)(Connection *, const void *, size_t)); @@ -45,7 +67,7 @@ void LogicalConnection_ctor(LogicalConnection *self, Reactor *parent, Port *upst struct DelayedConnection { Connection super; interval_t delay; - TriggerValue trigger_value; // FIFO for storing outstanding events + TriggerValue trigger_value; }; void DelayedConnection_ctor(DelayedConnection *self, Reactor *parent, Port *upstream, Port **downstreams, diff --git a/include/reactor-uc/environment.h b/include/reactor-uc/environment.h index ebbd7342..1b6bdaca 100644 --- a/include/reactor-uc/environment.h +++ b/include/reactor-uc/environment.h @@ -12,25 +12,51 @@ typedef struct Environment Environment; typedef struct TcpIpChannel TcpIpChannel; struct Environment { - Reactor *main; - Scheduler scheduler; - Platform *platform; - tag_t stop_tag; - tag_t current_tag; - instant_t start_time; - bool keep_alive; + Reactor *main; // The top-level reactor of the program. + Scheduler scheduler; // The scheduler in charge of executing the reactions. + Platform *platform; // The platform that provides the physical time and sleep functions. + tag_t stop_tag; // The tag at which the program should stop. This is set by the user or by the scheduler. + tag_t current_tag; // The current logical tag. Set by the scheduler and read by user in the reaction bodies. + instant_t start_time; // The physical time at which the program started. + bool keep_alive; // Whether the program should keep running even if there are no more events to process. bool has_async_events; // Whether the environment either has an action, or has a connection to an upstream federate. - Startup *startup; - Shutdown *shutdown; - NetworkChannel **net_channels; - size_t net_channel_size; + Startup *startup; // A pointer to a startup trigger, if the program has one. + Shutdown *shutdown; // A pointer to a chain of shutdown triggers, if the program has one. + NetworkChannel **net_channels; // A pointer to an array of NetworkChannel pointers that are used to communicate with + // other federates running in different environments. + size_t net_channel_size; // The number of NetworkChannels in the net_channels array. + /** + * @brief Assemble the program by computing levels for each reaction and setting up the scheduler. + */ void (*assemble)(Environment *self); + /** + * @brief Start the program. + */ void (*start)(Environment *self); + /** + * @brief Wrapper around `wait_until` exposed by the platform. + * TODO: Is this needed? + */ lf_ret_t (*wait_until)(Environment *self, instant_t wakeup_time); + /** + * @brief Set the stop tag of the program based on a timeout duration. + */ void (*set_timeout)(Environment *self, interval_t duration); + /** + * @brief Get the elapsed logical time since the start of the program. + */ interval_t (*get_elapsed_logical_time)(Environment *self); + /** + * @brief Get the current logical time + */ instant_t (*get_logical_time)(Environment *self); + /** + * @brief Get the elapsed physical time since the start of the program. + */ interval_t (*get_elapsed_physical_time)(Environment *self); + /** + * @brief Get the current physical time. + */ instant_t (*get_physical_time)(Environment *self); }; diff --git a/include/reactor-uc/error.h b/include/reactor-uc/error.h index db562fb5..4fadd657 100644 --- a/include/reactor-uc/error.h +++ b/include/reactor-uc/error.h @@ -5,6 +5,10 @@ #include #include +/** + * @brief An enumeration of possible return values from functions in reactor-uc. + * This is comparable to errno in C. Feel free to add more error codes as needed. + */ typedef enum { LF_OK = 0, LF_ERR, @@ -19,7 +23,10 @@ typedef enum { LF_NETWORK_SETUP_FAILED } lf_ret_t; -// Runtime validation. Crashes the program if expr is not true +/** + * @brief We use assert for "zero-cost" checking of assumptions. Zero-cost because it is removed in release builds. + * We use the following validate macros for runtime checking of assumptions. They are not removed in release builds. + */ #define validate(expr) \ do { \ if ((expr) == 0) { \ diff --git a/include/reactor-uc/platform.h b/include/reactor-uc/platform.h index 6e79ab76..c7863d5b 100644 --- a/include/reactor-uc/platform.h +++ b/include/reactor-uc/platform.h @@ -6,13 +6,39 @@ typedef struct Platform Platform; struct Platform { + /** + * @brief Perform any platform initialization such as initializing semaphores, + * configuring clocks, etc. Called once after construction. + */ lf_ret_t (*initialize)(Platform *self); + /** + * @brief Return the current physical time in nanoseconds. + */ instant_t (*get_physical_time)(Platform *self); + /** + * @brief Put system to sleep until the wakeup time. Asynchronous events + * does not need to be handled. + */ lf_ret_t (*wait_until)(Platform *self, instant_t wakeup_time); + /** + * @brief Put the system to sleep until the wakeup time or until an + * asynchronous event occurs. + */ lf_ret_t (*wait_until_interruptable)(Platform *self, instant_t wakeup_time); + + /** + * @brief Signal the occurrence of an asynchronous event. This should wake + * up the platform if it is sleeping on `wait_until_interruptable`. + */ + void (*new_async_event)(Platform *self); + + /** + * @brief Enter and leave a critical section. This is used so that an asynchronous + * context such as an ISR or another thread can safely schedule an event + * onto the event queue. + */ void (*enter_critical_section)(Platform *self); void (*leave_critical_section)(Platform *self); - void (*new_async_event)(Platform *self); }; // Return a pointer to a Platform object. Must be implemented for each diff --git a/include/reactor-uc/port.h b/include/reactor-uc/port.h index 855c069f..31173631 100644 --- a/include/reactor-uc/port.h +++ b/include/reactor-uc/port.h @@ -21,22 +21,54 @@ struct Port { // Input port. In the user-defined derived struct there must be a `buffer` field for storing the values. struct Input { Port super; - TriggerEffects effects; - void *value_ptr; // Pointer to the `buffer` field in the user Input port struct. - size_t value_size; // Size of the data stored in this Input Port. + TriggerEffects effects; // The reactions triggered by this Input port. + void *value_ptr; // Pointer to the `buffer` field in the user Input port struct. + size_t value_size; // Size of the data stored in this Input Port. }; // Output ports do not have any buffers. struct Output { Port super; - TriggerSources sources; + TriggerSources sources; // The reactions that can write to this Output port. }; +/** + * @brief Create a new Input port. + * + * @param self Pointer to an allocated Input struct. + * @param parent The parent Reactor. + * @param effects Pointer to an array of Reaction pointers that can be used to store the effects of this port. + * @param effects_size The size of the effects array. + * @param value_ptr A pointer to where the data of this input port is stored. This should be a field in the user-defined + * struct which is derived from Input. + * @param value_size The size of the data stored in this Input port. + */ void Input_ctor(Input *self, Reactor *parent, Reaction **effects, size_t effects_size, void *value_ptr, size_t value_size); +/** + * @brief Create a new Output port. + * + * @param self Pointer to an allocated Output struct. + * @param parent The parent Reactor of this Output port. + * @param sources Pointer to an array of Reaction pointers that can be used to store the sources of this port. + * @param sources_size The size of the sources array. + */ void Output_ctor(Output *self, Reactor *parent, Reaction **sources, size_t sources_size); +/** + * @brief Create a new Port object. A Port is an abstract type that can be either an Input or an Output. + * + * @param self The Port object to construct. + * @param type The type of the trigger. Can be either Input or Output. + * @param parent The parent Reactor of this Port. + * @param prepare The prepare function of the Port. This function is called at the beginning of a timestep before + * and reaction triggered by the port is executed. + * @param cleanup The cleanup function of the Port. This function is called at the end of a timestep after all reactions + * that either write to or read from the port have been executed. + * @param get The get function of the Port. This function is called to return the value of an input port. An output + * port should just pass NULL. + */ void Port_ctor(Port *self, TriggerType type, Reactor *parent, void (*prepare)(Trigger *), void (*cleanup)(Trigger *), const void *(*get)(Trigger *)); From 20f61fb9fea80ef2b0ff93c83463b7e7256049ad Mon Sep 17 00:00:00 2001 From: erling Date: Mon, 14 Oct 2024 19:41:52 -0700 Subject: [PATCH 12/18] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 42fd0512..72670c22 100644 --- a/README.md +++ b/README.md @@ -75,10 +75,10 @@ which enable distributed embedded systems. ## References `reactor-uc` draws inspiration from the following existing open-source projects: -- reactor-cpp -- reactor-c -- qpc -- ssm-runtime +- [reactor-cpp](https://github.com/lf-lang/reactor-cpp) +- [reactor-c](https://github.com/lf-lang/reactor-c) +- [qpc](https://github.com/QuantumLeaps/qpc) +- [ssm-runtime](https://github.com/QuantumLeaps/qpc) ## TODO for the MVP: - [x] Timers From 27e332d8817c68e32bfd07ffdfce25a9065c6bff Mon Sep 17 00:00:00 2001 From: Lasse Rosenow <10547444+LasseRosenow@users.noreply.github.com> Date: Tue, 15 Oct 2024 18:44:11 +0200 Subject: [PATCH 13/18] Fix spelling of wait_until_interruptible (#62) --- include/reactor-uc/platform.h | 4 ++-- src/environment.c | 2 +- src/platform/posix/posix.c | 4 ++-- src/platform/zephyr/zephyr.c | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/reactor-uc/platform.h b/include/reactor-uc/platform.h index c7863d5b..da2a80c2 100644 --- a/include/reactor-uc/platform.h +++ b/include/reactor-uc/platform.h @@ -24,11 +24,11 @@ struct Platform { * @brief Put the system to sleep until the wakeup time or until an * asynchronous event occurs. */ - lf_ret_t (*wait_until_interruptable)(Platform *self, instant_t wakeup_time); + lf_ret_t (*wait_until_interruptible)(Platform *self, instant_t wakeup_time); /** * @brief Signal the occurrence of an asynchronous event. This should wake - * up the platform if it is sleeping on `wait_until_interruptable`. + * up the platform if it is sleeping on `wait_until_interruptible`. */ void (*new_async_event)(Platform *self); diff --git a/src/environment.c b/src/environment.c index 4252f1e6..bf9ebc5b 100644 --- a/src/environment.c +++ b/src/environment.c @@ -15,7 +15,7 @@ lf_ret_t Environment_wait_until(Environment *self, instant_t wakeup_time) { } if (self->has_async_events) { - return self->platform->wait_until_interruptable(self->platform, wakeup_time); + return self->platform->wait_until_interruptible(self->platform, wakeup_time); } else { return self->platform->wait_until(self->platform, wakeup_time); } diff --git a/src/platform/posix/posix.c b/src/platform/posix/posix.c index a9a4b39b..2d66ebf2 100644 --- a/src/platform/posix/posix.c +++ b/src/platform/posix/posix.c @@ -40,7 +40,7 @@ instant_t PlatformPosix_get_physical_time(Platform *self) { return convert_timespec_to_ns(tspec); } -lf_ret_t PlatformPosix_wait_until_interruptable(Platform *_self, instant_t wakeup_time) { +lf_ret_t PlatformPosix_wait_until_interruptible(Platform *_self, instant_t wakeup_time) { LF_DEBUG(PLATFORM, "Interruptable wait until %" PRId64, wakeup_time); PlatformPosix *self = (PlatformPosix *)_self; const struct timespec tspec = convert_ns_to_timespec(wakeup_time); @@ -96,7 +96,7 @@ void Platform_ctor(Platform *self) { self->get_physical_time = PlatformPosix_get_physical_time; self->wait_until = PlatformPosix_wait_until; self->initialize = PlatformPosix_initialize; - self->wait_until_interruptable = PlatformPosix_wait_until_interruptable; + self->wait_until_interruptible = PlatformPosix_wait_until_interruptible; self->new_async_event = PlatformPosix_new_async_event; } diff --git a/src/platform/zephyr/zephyr.c b/src/platform/zephyr/zephyr.c index a4045e7e..2bb2b593 100644 --- a/src/platform/zephyr/zephyr.c +++ b/src/platform/zephyr/zephyr.c @@ -40,7 +40,7 @@ lf_ret_t PlatformZephyr_wait_until(Platform *self, instant_t wakeup_time) { } } -lf_ret_t PlatformZephyr_wait_until_interruptable(Platform *self, instant_t wakeup_time) { +lf_ret_t PlatformZephyr_wait_until_interruptible(Platform *self, instant_t wakeup_time) { PlatformZephyr *p = (PlatformZephyr *)self; LF_DEBUG(PLATFORM, "Wait until interruptable %" PRId64, wakeup_time); interval_t sleep_duration = wakeup_time - self->get_physical_time(self); @@ -89,7 +89,7 @@ void Platform_ctor(Platform *self) { self->get_physical_time = PlatformZephyr_get_physical_time; self->wait_until = PlatformZephyr_wait_until; self->initialize = PlatformZephyr_initialize; - self->wait_until_interruptable = PlatformZephyr_wait_until_interruptable; + self->wait_until_interruptible = PlatformZephyr_wait_until_interruptible; self->new_async_event = PlatformZephyr_new_async_event; } From e0413c85be40ae0ade0241a371a6d643256f6039 Mon Sep 17 00:00:00 2001 From: erling Date: Tue, 15 Oct 2024 14:37:58 -0700 Subject: [PATCH 14/18] Add simple support for Pico (#57) * Add simple support for Pico * interruptable->interruptible --- CMakeLists.txt | 3 + examples/pico/CMakeLists.txt | 19 ++++++ examples/pico/timer_ex.c | 47 +++++++++++++ include/reactor-uc/platform/pico/pico.h | 15 ++++ src/platform.c | 2 + src/platform/pico/pico.c | 91 +++++++++++++++++++++++++ src/platform/zephyr/zephyr.c | 6 +- 7 files changed, 180 insertions(+), 3 deletions(-) create mode 100644 examples/pico/CMakeLists.txt create mode 100644 examples/pico/timer_ex.c create mode 100644 include/reactor-uc/platform/pico/pico.h create mode 100644 src/platform/pico/pico.c diff --git a/CMakeLists.txt b/CMakeLists.txt index b39256c0..10814e6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,9 @@ elseif (PLATFORM STREQUAL "ZEPHYR") zephyr_library_named(reactor-uc) zephyr_library_sources(${SOURCES}) zephyr_library_link_libraries(kernel) +elseif (PLATFORM STREQUAL "PICO") + add_library(reactor-uc STATIC ${SOURCES}) + target_link_libraries(reactor-uc PUBLIC pico_stdlib pico_sync) else () message(FATAL_ERROR "No valid platform specified") endif () diff --git a/examples/pico/CMakeLists.txt b/examples/pico/CMakeLists.txt new file mode 100644 index 00000000..d34e3440 --- /dev/null +++ b/examples/pico/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.20.0) +set(PLATFORM "PICO" CACHE STRING "Platform to target") +set(BUILD_EXAMPLES OFF CAHCHE BOOL) + +if (DEFINED ENV{PICO_SDK_PATH}) + include("$ENV{PICO_SDK_PATH}/pico_sdk_init.cmake") +else() + message(FATAL_ERROR "PICO_SDK_PATH environment variable not set") +endif() + +project(reactor-uc-pico) +pico_sdk_init() +add_subdirectory(../../ reactor-uc) + +add_executable(hello_world + timer_ex.c +) + +target_link_libraries(hello_world PRIVATE reactor-uc) \ No newline at end of file diff --git a/examples/pico/timer_ex.c b/examples/pico/timer_ex.c new file mode 100644 index 00000000..ece559bd --- /dev/null +++ b/examples/pico/timer_ex.c @@ -0,0 +1,47 @@ +#include "reactor-uc/reactor-uc.h" + +typedef struct { + Timer super; + Reaction *effects[0]; +} MyTimer; + +typedef struct { + Reaction super; +} MyReaction; + +struct MyReactor { + Reactor super; + MyReaction my_reaction; + MyTimer timer; + Reaction *_reactions[1]; + Trigger *_triggers[1]; +}; + +void timer_handler(Reaction *_self) { + struct MyReactor *self = (struct MyReactor *)_self->parent; + printf("Hello World @ %lld\n", self->super.env->current_tag.time); +} + +void MyReaction_ctor(MyReaction *self, Reactor *parent) { + Reaction_ctor(&self->super, parent, timer_handler, NULL, 0, 0); +} + +void MyReactor_ctor(struct MyReactor *self, Environment *env) { + self->_reactions[0] = (Reaction *)&self->my_reaction; + self->_triggers[0] = (Trigger *)&self->timer; + Reactor_ctor(&self->super, "MyReactor", env, NULL, NULL, 0, self->_reactions, 1, self->_triggers, 1); + MyReaction_ctor(&self->my_reaction, &self->super); + Timer_ctor(&self->timer.super, &self->super, MSEC(0), MSEC(100), self->timer.effects, 1); + TIMER_REGISTER_EFFECT(self->timer, self->my_reaction); +} + +struct MyReactor my_reactor; +Environment env; +int main() { + Environment_ctor(&env, (Reactor *)&my_reactor); + env.set_timeout(&env, SEC(1)); + MyReactor_ctor(&my_reactor, &env); + env.assemble(&env); + env.start(&env); + return 0; +} diff --git a/include/reactor-uc/platform/pico/pico.h b/include/reactor-uc/platform/pico/pico.h new file mode 100644 index 00000000..c2cdd778 --- /dev/null +++ b/include/reactor-uc/platform/pico/pico.h @@ -0,0 +1,15 @@ + +#ifndef REACTOR_UC_PLATFORM_PICO_H +#define REACTOR_UC_PLATFORM_PICO_H + +#include "reactor-uc/platform.h" +#include +#include + +typedef struct { + Platform super; + critical_section_t crit_sec; + semaphore_t sem; +} PlatformPico; + +#endif diff --git a/src/platform.c b/src/platform.c index aedb724d..4fd45271 100644 --- a/src/platform.c +++ b/src/platform.c @@ -5,6 +5,8 @@ #include "platform/riot/riot.c" #elif defined(PLATFORM_ZEPHYR) #include "platform/zephyr/zephyr.c" +#elif defined(PLATFORM_PICO) +#include "platform/pico/pico.c" #else #error "NO PLATFORM SPECIFIED" #endif diff --git a/src/platform/pico/pico.c b/src/platform/pico/pico.c new file mode 100644 index 00000000..aaf3adf7 --- /dev/null +++ b/src/platform/pico/pico.c @@ -0,0 +1,91 @@ +#include "reactor-uc/platform/pico/pico.h" +#include "reactor-uc/logging.h" +#include +#include +#include +#include +#include + +static PlatformPico platform; + +lf_ret_t PlatformPico_initialize(Platform *self) { + PlatformPico *p = (PlatformPico *)self; + stdio_init_all(); + // init sync structs + critical_section_init(&p->crit_sec); + sem_init(&p->sem, 0, 1); + return LF_OK; +} + +instant_t PlatformPico_get_physical_time(Platform *self) { + (void)self; + absolute_time_t now; + now = get_absolute_time(); + return to_us_since_boot(now) * 1000; +} + +lf_ret_t PlatformPico_wait_until(Platform *self, instant_t wakeup_time) { + LF_DEBUG(PLATFORM, "Waiting until %" PRId64, wakeup_time); + interval_t sleep_duration = wakeup_time - self->get_physical_time(self); + int64_t sleep_duration_usec = sleep_duration / 1000; + LF_DEBUG(PLATFORM, "Waiting duration %d usec", sleep_duration_usec); + sleep_us(sleep_duration_usec); + return LF_OK; +} + +lf_ret_t PlatformPico_wait_until_interruptible(Platform *self, instant_t wakeup_time) { + PlatformPico *p = (PlatformPico *)self; + LF_DEBUG(PLATFORM, "Wait until interruptible %" PRId64, wakeup_time); + // time struct + absolute_time_t target; + + // reset event semaphore + sem_reset(&p->sem, 0); + // create us boot wakeup time + target = from_us_since_boot((uint64_t)(wakeup_time / 1000)); + // Enable interrupts. + self->leave_critical_section(self); + + // blocked sleep + // return on timeout or on processor event + bool ret = sem_acquire_block_until(&p->sem, target); + // Disable interrupts. + self->enter_critical_section(self); + + if (ret) { + LF_DEBUG(PLATFORM, "Wait until interrupted"); + return LF_SLEEP_INTERRUPTED; + } else { + LF_DEBUG(PLATFORM, "Wait until completed"); + return LF_OK; + } +} + +void PlatformPico_leave_critical_section(Platform *self) { + PlatformPico *p = (PlatformPico *)self; + LF_DEBUG(PLATFORM, "Leave critical section"); + critical_section_exit(&p->crit_sec); +} + +void PlatformPico_enter_critical_section(Platform *self) { + PlatformPico *p = (PlatformPico *)self; + LF_DEBUG(PLATFORM, "Enter critical section"); + critical_section_enter_blocking(&p->crit_sec); +} + +void PlatformPico_new_async_event(Platform *self) { + LF_DEBUG(PLATFORM, "New async event"); + sem_release(&((PlatformPico *)self)->sem); +} + +void Platform_ctor(Platform *self) { + self->enter_critical_section = PlatformPico_enter_critical_section; + self->leave_critical_section = PlatformPico_leave_critical_section; + self->get_physical_time = PlatformPico_get_physical_time; + self->wait_until = PlatformPico_wait_until; + self->initialize = PlatformPico_initialize; + self->wait_until_interruptible = PlatformPico_wait_until_interruptible; + self->new_async_event = PlatformPico_new_async_event; +} + +Platform *Platform_new(void) { return (Platform *)&platform; } diff --git a/src/platform/zephyr/zephyr.c b/src/platform/zephyr/zephyr.c index 2bb2b593..d2912bfd 100644 --- a/src/platform/zephyr/zephyr.c +++ b/src/platform/zephyr/zephyr.c @@ -11,9 +11,9 @@ static PlatformZephyr platform; lf_ret_t PlatformZephyr_initialize(Platform *self) { int ret = k_sem_init(&((PlatformZephyr *)self)->sem, 0, 1); if (ret == 0) { - LF_ERR(PLATFORM, "Failed to initialize semaphore"); return LF_OK; } else { + LF_ERR(PLATFORM, "Failed to initialize semaphore"); return LF_ERR; } } @@ -42,9 +42,9 @@ lf_ret_t PlatformZephyr_wait_until(Platform *self, instant_t wakeup_time) { lf_ret_t PlatformZephyr_wait_until_interruptible(Platform *self, instant_t wakeup_time) { PlatformZephyr *p = (PlatformZephyr *)self; - LF_DEBUG(PLATFORM, "Wait until interruptable %" PRId64, wakeup_time); + LF_DEBUG(PLATFORM, "Wait until interruptible %" PRId64, wakeup_time); interval_t sleep_duration = wakeup_time - self->get_physical_time(self); - LF_DEBUG(PLATFORM, "Wait until interruptable for %" PRId64, wakeup_time); + LF_DEBUG(PLATFORM, "Wait until interruptible for %" PRId64, wakeup_time); if (sleep_duration < 0) { return LF_OK; } From e3f0db10f75f2a84c6a79f211121b265f2ab9d71 Mon Sep 17 00:00:00 2001 From: erling Date: Tue, 15 Oct 2024 15:22:23 -0700 Subject: [PATCH 15/18] Dont require upstream port when constructing a Connection (#65) * Update examples to use the new NetworkChannel * Change the setup of connections * Only generate memory report for unit-tests --- .github/actions/memory-report/action.yml | 2 +- Makefile | 9 +++-- examples/posix/CMakeLists.txt | 6 ---- examples/posix/testing_fed_conn.c | 27 +++++++------- .../testing_posix_tcp_ip_channel_client.c | 10 +++--- .../testing_posix_tcp_ip_channel_server.c | 12 +++---- .../testing_tcp_ip_channel_server_callback.c | 12 +++---- examples/posix/timer_ex.c | 2 +- include/reactor-uc/connection.h | 20 +++++------ include/reactor-uc/federated.h | 2 +- include/reactor-uc/macros.h | 7 +++- .../generator/uc/UcConnectionGenerator.kt | 22 ++++++------ src/connection.c | 35 ++++++++----------- src/environment.c | 1 - src/federated.c | 8 ++--- test/unit/delayed_conn_test.c | 9 ++--- test/unit/port_test.c | 9 ++--- 17 files changed, 92 insertions(+), 101 deletions(-) diff --git a/.github/actions/memory-report/action.yml b/.github/actions/memory-report/action.yml index b313b60e..0c2d7261 100644 --- a/.github/actions/memory-report/action.yml +++ b/.github/actions/memory-report/action.yml @@ -24,7 +24,7 @@ runs: # For the multi line output # https://stackoverflow.com/questions/74137120/how-to-fix-or-avoid-error-unable-to-process-file-command-output-successfully run: | - make test + make unit-test EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) echo "report<<$EOF" >> $GITHUB_OUTPUT echo "$(cat build/test/unit/*.size)" >> $GITHUB_OUTPUT diff --git a/Makefile b/Makefile index b3e9453c..1843a9f9 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ -.PHONY: clean test coverage asan format format-check ci lf-test lib proto +.PHONY: clean test coverage asan format format-check ci lf-test lib proto examples -test: unit-test lf-test +test: unit-test lf-test examples # Generate protobuf code @@ -14,7 +14,7 @@ lib: make -C build -# Build and run examples +# Build examples examples: cmake -Bbuild -DBUILD_EXAMPLES=ON . cmake --build build @@ -29,8 +29,7 @@ unit-test: # Build and run lf tests lf-test: - @echo "Skipping LF tests" -#make -C test/lf + make -C test/lf # Get coverage data on unit tests coverage: diff --git a/examples/posix/CMakeLists.txt b/examples/posix/CMakeLists.txt index 33ffae80..72f8b024 100644 --- a/examples/posix/CMakeLists.txt +++ b/examples/posix/CMakeLists.txt @@ -29,9 +29,3 @@ add_custom_target(examples ) # Add each executable to the custom target -foreach(EXEC_NAME ${EXECUTABLES}) - add_custom_command(TARGET examples - COMMAND ${EXEC_NAME} - COMMENT "Running ${EXEC_NAME}" - ) -endforeach() \ No newline at end of file diff --git a/examples/posix/testing_fed_conn.c b/examples/posix/testing_fed_conn.c index 97ee6cd3..24c0234a 100644 --- a/examples/posix/testing_fed_conn.c +++ b/examples/posix/testing_fed_conn.c @@ -120,8 +120,8 @@ typedef struct { msg_t buffer[1]; } ConnSender; -void ConnSender_ctor(ConnSender *self, Reactor *parent, FederatedConnectionBundle *bundle, Port *upstream) { - FederatedOutputConnection_ctor(&self->super, parent, bundle, 0, upstream, &self->buffer[0], sizeof(self->buffer[0])); +void ConnSender_ctor(ConnSender *self, Reactor *parent, FederatedConnectionBundle *bundle) { + FederatedOutputConnection_ctor(&self->super, parent, bundle, 0, &self->buffer[0], sizeof(self->buffer[0])); } typedef struct { @@ -133,20 +133,21 @@ typedef struct { void SenderRecvConn_ctor(SenderRecvBundle *self, Sender *parent) { TcpIpChannel_ctor(&self->channel, "127.0.0.1", PORT_NUM, AF_INET); - ConnSender_ctor(&self->conn, &parent->super, &self->super, &parent->out.super.super); + ConnSender_ctor(&self->conn, &parent->super, &self->super); self->output[0] = &self->conn.super; + CONN_REGISTER_UPSTREAM(self->conn, parent->out); - TcpIpChannel *channel = &self->channel; - int ret = channel->super.bind(channel); + NetworkChannel *channel = (NetworkChannel *)&self->channel; + int ret = channel->bind(channel); validate(ret == LF_OK); printf("Sender: Bound\n"); // accept one connection - bool new_connection = channel->super.accept(channel); + bool new_connection = channel->accept(channel); validate(new_connection); printf("Sender: Accepted\n"); - FederatedConnectionBundle_ctor(&self->super, &parent->super, &self->channel, NULL, 0, + FederatedConnectionBundle_ctor(&self->super, &parent->super, &self->channel.super, NULL, 0, (FederatedOutputConnection **)&self->output, 1); } @@ -173,17 +174,17 @@ void RecvSenderBundle_ctor(RecvSenderBundle *self, Reactor *parent) { TcpIpChannel_ctor(&self->channel, "127.0.0.1", PORT_NUM, AF_INET); self->inputs[0] = &self->conn.super; - TcpIpChannel *channel = &self->channel; + NetworkChannel *channel = (NetworkChannel *)&self->channel; lf_ret_t ret; do { - ret = channel->super.connect(channel); + ret = channel->connect(channel); } while (ret != LF_OK); validate(ret == LF_OK); printf("Recv: Connected\n"); - FederatedConnectionBundle_ctor(&self->super, parent, &self->channel, (FederatedInputConnection **)&self->inputs, 1, - NULL, 0); + FederatedConnectionBundle_ctor(&self->super, parent, &self->channel.super, (FederatedInputConnection **)&self->inputs, + 1, NULL, 0); } // Reactor main @@ -235,7 +236,7 @@ void *main_sender(void *unused) { MainSender_ctor(&sender, &env_send); env_send.set_timeout(&env_send, SEC(1)); env_send.net_channel_size = 1; - env_send.net_channels = (TcpIpChannel **)&sender.net_channel; + env_send.net_channels = (NetworkChannel **)&sender.net_channel; env_send.assemble(&env_send); env_send.start(&env_send); return NULL; @@ -252,7 +253,7 @@ void *main_recv(void *unused) { env_recv.keep_alive = true; env_recv.has_async_events = true; env_recv.net_channel_size = 1; - env_recv.net_channels = (TcpIpChannel **)&receiver.net_channels; + env_recv.net_channels = (NetworkChannel **)&receiver.net_channels; env_recv.assemble(&env_recv); env_recv.platform->leave_critical_section(env_recv.platform); env_recv.start(&env_recv); diff --git a/examples/posix/testing_posix_tcp_ip_channel_client.c b/examples/posix/testing_posix_tcp_ip_channel_client.c index eb030e9e..7ea20bce 100644 --- a/examples/posix/testing_posix_tcp_ip_channel_client.c +++ b/examples/posix/testing_posix_tcp_ip_channel_client.c @@ -24,19 +24,19 @@ int main() { TcpIpChannel_ctor(&channel, host, port, AF_INET); // binding to that address - channel.super.connect(&channel); + channel.super.connect(&channel.super); // change the super to non-blocking - channel.super.change_block_state(&channel, false); + channel.super.change_block_state(&channel.super, false); for (int i = 0; i < NUM_ITER; i++) { // sending message - channel.super.send(&channel, &port_message); + channel.super.send(&channel.super, &port_message); // waiting for reply TaggedMessage *received_message = NULL; do { - received_message = channel.super.receive(&channel); + received_message = channel.super.receive(&channel.super); } while (received_message == NULL); printf("Received message with connection number %i and content %s\n", received_message->conn_id, @@ -45,5 +45,5 @@ int main() { sleep(i); } - channel.super.close(&channel); + channel.super.close(&channel.super); } diff --git a/examples/posix/testing_posix_tcp_ip_channel_server.c b/examples/posix/testing_posix_tcp_ip_channel_server.c index 518359ea..9c1c8516 100644 --- a/examples/posix/testing_posix_tcp_ip_channel_server.c +++ b/examples/posix/testing_posix_tcp_ip_channel_server.c @@ -13,28 +13,28 @@ int main() { TcpIpChannel_ctor(&channel, host, port, AF_INET); // binding to that address - channel.super.bind(&channel); + channel.super.bind(&channel.super); // change the super to non-blocking - channel.super.change_block_state(&channel, false); + channel.super.change_block_state(&channel.super, false); // accept one connection bool new_connection; do { - new_connection = channel.super.accept(&channel); + new_connection = channel.super.accept(&channel.super); } while (!new_connection); // waiting for messages from client TaggedMessage *message = NULL; do { - message = channel.super.receive(&channel); + message = channel.super.receive(&channel.super); sleep(1); } while (message == NULL); printf("Received message with connection number %i and content %s\n", message->conn_id, (char *)message->payload.bytes); - channel.super.send(&channel, message); + channel.super.send(&channel.super, message); - channel.super.close(&channel); + channel.super.close(&channel.super); } diff --git a/examples/posix/testing_tcp_ip_channel_server_callback.c b/examples/posix/testing_tcp_ip_channel_server_callback.c index 02f5c62e..9f6306c0 100644 --- a/examples/posix/testing_tcp_ip_channel_server_callback.c +++ b/examples/posix/testing_tcp_ip_channel_server_callback.c @@ -7,7 +7,7 @@ TcpIpChannel channel; void callback_handler(FederatedConnectionBundle *self, TaggedMessage *msg) { printf("Received message with connection number %i and content %s\n", msg->conn_id, (char *)msg->payload.bytes); - channel.super.send(&channel, msg); + channel.super.send(&channel.super, msg); } int main() { @@ -19,20 +19,20 @@ int main() { TcpIpChannel_ctor(&channel, host, port, AF_INET); // binding to that address - channel.super.bind(&channel); + channel.super.bind(&channel.super); // change the super to non-blocking - channel.super.change_block_state(&channel, false); + channel.super.change_block_state(&channel.super, false); // accept one connection bool new_connection; do { - new_connection = channel.super.accept(&channel); + new_connection = channel.super.accept(&channel.super); } while (!new_connection); - channel.super.register_callback(&channel, callback_handler, NULL); + channel.super.register_callback(&channel.super, callback_handler, NULL); sleep(100); - channel.super.close(&channel); + channel.super.close(&channel.super); } diff --git a/examples/posix/timer_ex.c b/examples/posix/timer_ex.c index 5a5ff003..06357830 100644 --- a/examples/posix/timer_ex.c +++ b/examples/posix/timer_ex.c @@ -2,7 +2,7 @@ typedef struct { Timer super; - Reaction *effects[0]; + Reaction *effects[1]; } MyTimer; typedef struct { diff --git a/include/reactor-uc/connection.h b/include/reactor-uc/connection.h index b2d901d0..d7431ead 100644 --- a/include/reactor-uc/connection.h +++ b/include/reactor-uc/connection.h @@ -44,7 +44,6 @@ struct Connection { * @param self The Connection object to construct * @param type The type of the connection. Either logical, delayed or physical. * @param parent The reactor in which this connection appears (not the reactors of the ports it connects) - * @param upstream The pointer to the upstream port of this connection * @param downstreams A pointer to an array of pointers to downstream ports. * @param num_downstreams The size of the downstreams array. * @param trigger_value A pointer to the TriggerValue that holds the data of the events that are scheduled on this @@ -53,16 +52,15 @@ struct Connection { * @param cleanup The cleanup function that is called at the end of timestep after all reactions have executed. * @param trigger_downstreams The function that triggers all downstreams of this connection. */ -void Connection_ctor(Connection *self, TriggerType type, Reactor *parent, Port *upstream, Port **downstreams, - size_t num_downstreams, TriggerValue *trigger_value, void (*prepare)(Trigger *), - void (*cleanup)(Trigger *), void (*trigger_downstreams)(Connection *, const void *, size_t)); +void Connection_ctor(Connection *self, TriggerType type, Reactor *parent, Port **downstreams, size_t num_downstreams, + TriggerValue *trigger_value, void (*prepare)(Trigger *), void (*cleanup)(Trigger *), + void (*trigger_downstreams)(Connection *, const void *, size_t)); struct LogicalConnection { Connection super; }; -void LogicalConnection_ctor(LogicalConnection *self, Reactor *parent, Port *upstream, Port **downstreams, - size_t num_downstreams); +void LogicalConnection_ctor(LogicalConnection *self, Reactor *parent, Port **downstreams, size_t num_downstreams); struct DelayedConnection { Connection super; @@ -70,9 +68,8 @@ struct DelayedConnection { TriggerValue trigger_value; }; -void DelayedConnection_ctor(DelayedConnection *self, Reactor *parent, Port *upstream, Port **downstreams, - size_t num_downstreams, interval_t delay, void *value_buf, size_t value_size, - size_t value_capacity); +void DelayedConnection_ctor(DelayedConnection *self, Reactor *parent, Port **downstreams, size_t num_downstreams, + interval_t delay, void *value_buf, size_t value_size, size_t value_capacity); struct PhysicalConnection { Connection super; @@ -80,8 +77,7 @@ struct PhysicalConnection { TriggerValue trigger_value; }; -void PhysicalConnection_ctor(PhysicalConnection *self, Reactor *parent, Port *upstream, Port **downstreams, - size_t num_downstreams, interval_t delay, void *value_buf, size_t value_size, - size_t value_capacity); +void PhysicalConnection_ctor(PhysicalConnection *self, Reactor *parent, Port **downstreams, size_t num_downstreams, + interval_t delay, void *value_buf, size_t value_size, size_t value_capacity); #endif diff --git a/include/reactor-uc/federated.h b/include/reactor-uc/federated.h index 76141246..cd4f0699 100644 --- a/include/reactor-uc/federated.h +++ b/include/reactor-uc/federated.h @@ -40,7 +40,7 @@ struct FederatedOutputConnection { }; void FederatedOutputConnection_ctor(FederatedOutputConnection *self, Reactor *parent, FederatedConnectionBundle *bundle, - int conn_id, Port *upstream, void *value_ptr, size_t value_size); + int conn_id, void *value_ptr, size_t value_size); // A single input connection to this federate. Has a single upstream port struct FederatedInputConnection { diff --git a/include/reactor-uc/macros.h b/include/reactor-uc/macros.h index aba74348..c224c200 100644 --- a/include/reactor-uc/macros.h +++ b/include/reactor-uc/macros.h @@ -79,12 +79,17 @@ #define OUTPUT_REGISTER_SOURCE(output, source) TRIGGER_REGISTER_SOURCE((Output *)&(output), (Reaction *)&(source)) // Convenience macro to register a downstream port on a connection. -// TODO: Replace the entire function with an inline macro to save memory #define CONN_REGISTER_DOWNSTREAM(conn, down) \ do { \ ((Connection *)&(conn))->register_downstream((Connection *)&(conn), (Port *)&(down)); \ } while (0) +// Convenience macro to register an upstream port on a connection +#define CONN_REGISTER_UPSTREAM(conn, up) \ + do { \ + ((Connection *)&(conn))->upstream = (Port *)&(up); \ + } while (0) + // TODO: The following macro is defined to avoid compiler warnings. Ideally we would // not have to specify any alignment on any structs. It is a TODO to understand exactly why // the compiler complains and what we can do about it. diff --git a/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcConnectionGenerator.kt b/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcConnectionGenerator.kt index 0d3b659d..5cc2e45d 100644 --- a/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcConnectionGenerator.kt +++ b/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcConnectionGenerator.kt @@ -121,34 +121,36 @@ class UcConnectionGenerator(private val reactor: Reactor) { fun generateReactorCtorCode(conn: UcConnection) = with(PrependOperator) { """ - |${conn.codeType}_ctor(&self->${conn.codeName}, &self->super, (Port *) &self->${getPortCodeName(conn.src)}); + |${conn.codeType}_ctor(&self->${conn.codeName}, &self->super); ${" | "..generateConnectionStatements(conn)} | """.trimMargin() }; - fun generateConnectionStatements(conn: UcConnection) = conn.getDests().joinToString(separator = "\n") { - "CONN_REGISTER_DOWNSTREAM(self->${conn.codeName}, self->${getPortCodeName(it)});" - } + fun generateConnectionStatements(conn: UcConnection) = + "CONN_REGISTER_UPSTREAM(self->${conn.codeName}, self->${getPortCodeName(conn.src)});\n" + + conn.getDests().joinToString(separator = "\n") { + "CONN_REGISTER_DOWNSTREAM(self->${conn.codeName}, self->${getPortCodeName(it)});"} + fun generateReactorCtorCodes() = getUcConnections().joinToString(prefix = "// Initialize connections\n", separator = "\n", postfix = "\n") { generateReactorCtorCode(it)} fun generateLogicalCtor(conn: UcConnection) = with(PrependOperator) { """ - |static void ${conn.codeType}_ctor(${conn.codeType} *self, Reactor *parent, Port *upstream) { - | LogicalConnection_ctor(&self->super, parent, upstream, self->_downstreams, ${conn.getDests().size}); + |static void ${conn.codeType}_ctor(${conn.codeType} *self, Reactor *parent) { + | LogicalConnection_ctor(&self->super, parent, self->_downstreams, ${conn.getDests().size}); |} """.trimMargin() } fun generateDelayedCtor(conn: UcConnection) = with(PrependOperator) { """ - |static void ${conn.codeType}_ctor(${conn.codeType} *self, Reactor *parent, Port *upstream) { - | DelayedConnection_ctor(&self->super, parent, upstream, self->_downstreams, ${conn.getDests().size}, ${conn.conn.delay.toCCode()}, self->buffer, sizeof(self->buffer[0]), ${conn.bufSize}); + |static void ${conn.codeType}_ctor(${conn.codeType} *self, Reactor *parent) { + | DelayedConnection_ctor(&self->super, parent, self->_downstreams, ${conn.getDests().size}, ${conn.conn.delay.toCCode()}, self->buffer, sizeof(self->buffer[0]), ${conn.bufSize}); |} """.trimMargin() } fun generatePhysicalCtor(conn: UcConnection) = with(PrependOperator) { """ - |static void ${conn.codeType}_ctor(${conn.codeType} *self, Reactor *parent, Port *upstream) { - | PhysicalConnection_ctor(&self->super, parent, upstream, self->_downstreams, ${conn.getDests().size}, ${conn.conn.delay.toCCode()}, self->buffer, sizeof(self->buffer[0]), ${conn.bufSize}); + |static void ${conn.codeType}_ctor(${conn.codeType} *self, Reactor *parent) { + | PhysicalConnection_ctor(&self->super, parent, self->_downstreams, ${conn.getDests().size}, ${conn.conn.delay.toCCode()}, self->buffer, sizeof(self->buffer[0]), ${conn.bufSize}); |} """.trimMargin() } diff --git a/src/connection.c b/src/connection.c index 5de34eed..c01da25b 100644 --- a/src/connection.c +++ b/src/connection.c @@ -61,14 +61,11 @@ void LogicalConnection_trigger_downstreams(Connection *self, const void *value, } } -void Connection_ctor(Connection *self, TriggerType type, Reactor *parent, Port *upstream, Port **downstreams, - size_t num_downstreams, TriggerValue *trigger_value, void (*prepare)(Trigger *), - void (*cleanup)(Trigger *), void (*trigger_downstreams)(Connection *, const void *, size_t)) { - // FIXME: Should not be part of constructor... - if (upstream) { - upstream->conn_out = self; - } - self->upstream = upstream; +void Connection_ctor(Connection *self, TriggerType type, Reactor *parent, Port **downstreams, size_t num_downstreams, + TriggerValue *trigger_value, void (*prepare)(Trigger *), void (*cleanup)(Trigger *), + void (*trigger_downstreams)(Connection *, const void *, size_t)) { + + self->upstream = NULL; self->downstreams_size = num_downstreams; self->downstreams_registered = 0; self->downstreams = downstreams; @@ -79,9 +76,8 @@ void Connection_ctor(Connection *self, TriggerType type, Reactor *parent, Port * Trigger_ctor(&self->super, type, parent, trigger_value, prepare, cleanup, NULL); } -void LogicalConnection_ctor(LogicalConnection *self, Reactor *parent, Port *upstream, Port **downstreams, - size_t num_downstreams) { - Connection_ctor(&self->super, TRIG_CONN, parent, upstream, downstreams, num_downstreams, NULL, NULL, NULL, +void LogicalConnection_ctor(LogicalConnection *self, Reactor *parent, Port **downstreams, size_t num_downstreams) { + Connection_ctor(&self->super, TRIG_CONN, parent, downstreams, num_downstreams, NULL, NULL, NULL, LogicalConnection_trigger_downstreams); } @@ -152,12 +148,11 @@ void DelayedConnection_trigger_downstreams(Connection *_self, const void *value, sched->register_for_cleanup(sched, &_self->super); } -void DelayedConnection_ctor(DelayedConnection *self, Reactor *parent, Port *upstream, Port **downstreams, - size_t num_downstreams, interval_t delay, void *value_buf, size_t value_size, - size_t value_capacity) { +void DelayedConnection_ctor(DelayedConnection *self, Reactor *parent, Port **downstreams, size_t num_downstreams, + interval_t delay, void *value_buf, size_t value_size, size_t value_capacity) { self->delay = delay; TriggerValue_ctor(&self->trigger_value, value_buf, value_size, value_capacity); - Connection_ctor(&self->super, TRIG_CONN_DELAYED, parent, upstream, downstreams, num_downstreams, &self->trigger_value, + Connection_ctor(&self->super, TRIG_CONN_DELAYED, parent, downstreams, num_downstreams, &self->trigger_value, DelayedConnection_prepare, DelayedConnection_cleanup, DelayedConnection_trigger_downstreams); } @@ -213,12 +208,10 @@ void PhysicalConnection_trigger_downstreams(Connection *_self, const void *value // Possibly handle } -void PhysicalConnection_ctor(PhysicalConnection *self, Reactor *parent, Port *upstream, Port **downstreams, - size_t num_downstreams, interval_t delay, void *value_buf, size_t value_size, - size_t value_capacity) { +void PhysicalConnection_ctor(PhysicalConnection *self, Reactor *parent, Port **downstreams, size_t num_downstreams, + interval_t delay, void *value_buf, size_t value_size, size_t value_capacity) { TriggerValue_ctor(&self->trigger_value, value_buf, value_size, value_capacity); - Connection_ctor(&self->super, TRIG_CONN_PHYSICAL, parent, upstream, downstreams, num_downstreams, - &self->trigger_value, PhysicalConnection_prepare, PhysicalConnection_cleanup, - PhysicalConnection_trigger_downstreams); + Connection_ctor(&self->super, TRIG_CONN_PHYSICAL, parent, downstreams, num_downstreams, &self->trigger_value, + PhysicalConnection_prepare, PhysicalConnection_cleanup, PhysicalConnection_trigger_downstreams); self->delay = delay; } diff --git a/src/environment.c b/src/environment.c index bf9ebc5b..dca45066 100644 --- a/src/environment.c +++ b/src/environment.c @@ -1,6 +1,5 @@ #include "reactor-uc/environment.h" #include "reactor-uc/logging.h" -#include "reactor-uc/platform/posix/tcp_ip_channel.h" // FIXME: NetworkChannel instead #include "reactor-uc/reactor.h" #include "reactor-uc/scheduler.h" #include diff --git a/src/federated.c b/src/federated.c index f0adc4b9..3c7edd0e 100644 --- a/src/federated.c +++ b/src/federated.c @@ -48,9 +48,9 @@ void FederatedOutputConnection_cleanup(Trigger *trigger) { } void FederatedOutputConnection_ctor(FederatedOutputConnection *self, Reactor *parent, FederatedConnectionBundle *bundle, - int conn_id, Port *upstream, void *value_ptr, size_t value_size) { + int conn_id, void *value_ptr, size_t value_size) { - Connection_ctor(&self->super, TRIG_CONN_FEDERATED_OUTPUT, parent, upstream, NULL, 0, NULL, NULL, + Connection_ctor(&self->super, TRIG_CONN_FEDERATED_OUTPUT, parent, NULL, 0, NULL, NULL, FederatedOutputConnection_cleanup, FederatedOutputConnection_trigger_downstream); self->staged = false; self->conn_id = conn_id; @@ -93,8 +93,8 @@ void FederatedInputConnection_ctor(FederatedInputConnection *self, Reactor *pare Port **downstreams, size_t downstreams_size, void *value_buf, size_t value_size, size_t value_capacity) { TriggerValue_ctor(&self->trigger_value, value_buf, value_size, value_capacity); - Connection_ctor(&self->super, TRIG_CONN_FEDERATED_INPUT, parent, NULL, downstreams, downstreams_size, - &self->trigger_value, FederatedInputConnection_prepare, FederatedInputConnection_cleanup, NULL); + Connection_ctor(&self->super, TRIG_CONN_FEDERATED_INPUT, parent, downstreams, downstreams_size, &self->trigger_value, + FederatedInputConnection_prepare, FederatedInputConnection_cleanup, NULL); self->delay = delay; self->is_physical = is_physical; self->last_known_tag = NEVER_TAG; diff --git a/test/unit/delayed_conn_test.c b/test/unit/delayed_conn_test.c index ab2e88bb..03c1dbb9 100644 --- a/test/unit/delayed_conn_test.c +++ b/test/unit/delayed_conn_test.c @@ -111,8 +111,8 @@ struct Conn1 { Input *downstreams[1]; }; -void Conn1_ctor(struct Conn1 *self, Reactor *parent, Output *upstream) { - DelayedConnection_ctor(&self->super, parent, &upstream->super, (Port **)self->downstreams, 1, MSEC(150), self->buffer, +void Conn1_ctor(struct Conn1 *self, Reactor *parent) { + DelayedConnection_ctor(&self->super, parent, (Port **)self->downstreams, 1, MSEC(150), self->buffer, sizeof(self->buffer[0]), 2); } @@ -133,8 +133,9 @@ void Main_ctor(struct Main *self, Environment *env) { self->_children[1] = &self->receiver.super; Receiver_ctor(&self->receiver, &self->super, env); - Conn1_ctor(&self->conn, &self->super, &self->sender.out.super); - self->conn.super.super.register_downstream(&self->conn.super.super, &self->receiver.inp.super.super); + Conn1_ctor(&self->conn, &self->super); + CONN_REGISTER_UPSTREAM(self->conn, self->sender.out); + CONN_REGISTER_DOWNSTREAM(self->conn, self->receiver.inp); Reactor_ctor(&self->super, "Main", env, NULL, self->_children, 2, NULL, 0, NULL, 0); } diff --git a/test/unit/port_test.c b/test/unit/port_test.c index 96758f3b..a67bb779 100644 --- a/test/unit/port_test.c +++ b/test/unit/port_test.c @@ -106,8 +106,8 @@ struct Conn1 { Input *downstreams[1]; }; -void Conn1_ctor(struct Conn1 *self, Reactor *parent, Output *upstream) { - LogicalConnection_ctor(&self->super, parent, &upstream->super, (Port **)self->downstreams, 1); +void Conn1_ctor(struct Conn1 *self, Reactor *parent) { + LogicalConnection_ctor(&self->super, parent, (Port **)self->downstreams, 1); } // Reactor main @@ -127,8 +127,9 @@ void Main_ctor(struct Main *self, Environment *env) { self->_children[1] = &self->receiver.super; Receiver_ctor(&self->receiver, &self->super, env); - Conn1_ctor(&self->conn, &self->super, &self->sender.out.super); - self->conn.super.super.register_downstream(&self->conn.super.super, &self->receiver.inp.super.super); + Conn1_ctor(&self->conn, &self->super); + CONN_REGISTER_UPSTREAM(self->conn, self->sender.out); + CONN_REGISTER_DOWNSTREAM(self->conn, self->receiver.inp); Reactor_ctor(&self->super, "Main", env, NULL, self->_children, 2, NULL, 0, NULL, 0); } From b0a616e21e98334d8efa240f9cb326be431658a2 Mon Sep 17 00:00:00 2001 From: erling Date: Tue, 15 Oct 2024 15:46:52 -0700 Subject: [PATCH 16/18] Increment microstep when scheduling action with 0 delay (#67) --- src/action.c | 8 +- src/tag.c | 142 +----------------------------- test/unit/action_microstep_test.c | 103 ++++++++++++++++++++++ 3 files changed, 108 insertions(+), 145 deletions(-) create mode 100644 test/unit/action_microstep_test.c diff --git a/src/action.c b/src/action.c index 0b29a015..c44ac647 100644 --- a/src/action.c +++ b/src/action.c @@ -46,9 +46,9 @@ void Action_ctor(Action *self, TriggerType type, interval_t min_offset, interval lf_ret_t LogicalAction_schedule(Action *self, interval_t offset, const void *value) { Environment *env = self->super.parent->env; Scheduler *sched = &env->scheduler; - tag_t tag = {.time = env->current_tag.time + self->min_offset + offset, .microstep = 0}; + tag_t proposed_tag = lf_delay_tag(env->current_tag, offset); tag_t earliest_allowed = lf_delay_tag(self->previous_event, self->min_spacing); - if (lf_tag_compare(tag, earliest_allowed) < 0) { + if (lf_tag_compare(proposed_tag, earliest_allowed) < 0) { return LF_INVALID_TAG; } @@ -59,9 +59,9 @@ lf_ret_t LogicalAction_schedule(Action *self, interval_t offset, const void *val return LF_INVALID_VALUE; } - int ret = sched->schedule_at(sched, (Trigger *)self, tag); + int ret = sched->schedule_at(sched, (Trigger *)self, proposed_tag); if (ret == 0) { - self->previous_event = tag; + self->previous_event = proposed_tag; } return ret; } diff --git a/src/tag.c b/src/tag.c index dab4dc8d..2a1eccb9 100644 --- a/src/tag.c +++ b/src/tag.c @@ -100,144 +100,4 @@ tag_t lf_delay_strict(tag_t tag, interval_t interval) { result.microstep = UINT_MAX; } return result; -} - -// instant_t lf_time_logical(void *env) { return ((Environment *)env)->current_tag.time; } - -// interval_t lf_time_logical_elapsed(void *env) { return lf_time_logical(env) - start_time; } - -// instant_t lf_time_physical(void) { -// instant_t now = MSEC(0); -// // Get the current clock value -// return now; -// } - -// instant_t lf_time_physical_elapsed(void) { return lf_time_physical() - start_time; } - -// instant_t lf_time_start(void) { return start_time; } - -// size_t lf_readable_time(char *buffer, instant_t time) { -// if (time <= (instant_t)0) { -// snprintf(buffer, 2, "0"); -// return 1; -// } -// char *original_buffer = buffer; -// bool lead = false; // Set to true when first clause has been printed. -// if (time > WEEKS(1)) { -// lead = true; -// size_t printed = lf_comma_separated_time(buffer, time / WEEKS(1)); -// time = time % WEEKS(1); -// buffer += printed; -// snprintf(buffer, 7, " weeks"); -// buffer += 6; -// } -// if (time > DAYS(1)) { -// if (lead == true) { -// snprintf(buffer, 3, ", "); -// buffer += 2; -// } -// lead = true; -// size_t printed = lf_comma_separated_time(buffer, time / DAYS(1)); -// time = time % DAYS(1); -// buffer += printed; -// snprintf(buffer, 3, " d"); -// buffer += 2; -// } -// if (time > HOURS(1)) { -// if (lead == true) { -// snprintf(buffer, 3, ", "); -// buffer += 2; -// } -// lead = true; -// size_t printed = lf_comma_separated_time(buffer, time / HOURS(1)); -// time = time % HOURS(1); -// buffer += printed; -// snprintf(buffer, 3, " h"); -// buffer += 2; -// } -// if (time > MINUTES(1)) { -// if (lead == true) { -// snprintf(buffer, 3, ", "); -// buffer += 2; -// } -// lead = true; -// size_t printed = lf_comma_separated_time(buffer, time / MINUTES(1)); -// time = time % MINUTES(1); -// buffer += printed; -// snprintf(buffer, 5, " min"); -// buffer += 4; -// } -// if (time > SECONDS(1)) { -// if (lead == true) { -// snprintf(buffer, 3, ", "); -// buffer += 2; -// } -// lead = true; -// size_t printed = lf_comma_separated_time(buffer, time / SECONDS(1)); -// time = time % SECONDS(1); -// buffer += printed; -// snprintf(buffer, 3, " s"); -// buffer += 2; -// } -// if (time > (instant_t)0) { -// if (lead == true) { -// snprintf(buffer, 3, ", "); -// buffer += 2; -// } -// const char *units = "ns"; -// if (time % MSEC(1) == (instant_t)0) { -// units = "ms"; -// time = time / MSEC(1); -// } else if (time % USEC(1) == (instant_t)0) { -// units = "us"; -// time = time / USEC(1); -// } -// size_t printed = lf_comma_separated_time(buffer, time); -// buffer += printed; -// snprintf(buffer, 4, " %s", units); -// buffer += strlen(units) + 1; -// } -// return (buffer - original_buffer); -// } - -// size_t lf_comma_separated_time(char *buffer, instant_t time) { -// size_t result = 0; // The number of characters printed. -// // If the number is zero, print it and return. -// if (time == (instant_t)0) { -// snprintf(buffer, 2, "0"); -// return 1; -// } -// // If the number is negative, print a minus sign. -// if (time < (instant_t)0) { -// snprintf(buffer, 2, "-"); -// buffer++; -// result++; -// } -// int count = 0; -// // Assume the time value is no larger than 64 bits. -// instant_t clauses[7]; -// while (time > (instant_t)0) { -// clauses[count++] = time; -// time = time / 1000; -// } -// // Highest order clause should not be filled with zeros. -// instant_t to_print = clauses[--count] % 1000; -// snprintf(buffer, 5, "%lld", (long long)to_print); -// if (to_print >= 100LL) { -// buffer += 3; -// result += 3; -// } else if (to_print >= 10LL) { -// buffer += 2; -// result += 2; -// } else { -// buffer += 1; -// result += 1; -// } -// while (count-- > 0) { -// to_print = clauses[count] % 1000LL; -// snprintf(buffer, 8, ",%03lld", (long long)to_print); -// buffer += 4; -// result += 4; -// } -// return result; -// } +} \ No newline at end of file diff --git a/test/unit/action_microstep_test.c b/test/unit/action_microstep_test.c new file mode 100644 index 00000000..a1e64496 --- /dev/null +++ b/test/unit/action_microstep_test.c @@ -0,0 +1,103 @@ +#include "reactor-uc/reactor-uc.h" +#include "unity.h" + +typedef struct { + LogicalAction super; + int buffer[1]; + + Reaction *sources[1]; + Reaction *effects[1]; +} MyAction; + +typedef struct MyStartup MyStartup; + +struct MyStartup { + Startup super; + Reaction *effects_[1]; +}; + +typedef struct { + Reaction super; + Trigger *effects[1]; +} MyReaction; + +struct MyReactor { + Reactor super; + MyReaction my_reaction; + MyAction my_action; + MyStartup startup; + Reaction *_reactions[1]; + Trigger *_triggers[2]; + int cnt; +}; + +void MyAction_ctor(MyAction *self, struct MyReactor *parent) { + LogicalAction_ctor(&self->super, MSEC(0), MSEC(0), &parent->super, self->sources, 1, self->effects, 1, &self->buffer, + sizeof(self->buffer[0]), 2); +} + +void MyStartup_ctor(struct MyStartup *self, Reactor *parent, Reaction *effects) { + self->effects_[0] = effects; + Startup_ctor(&self->super, parent, self->effects_, 1); +} + +void action_handler(Reaction *_self) { + struct MyReactor *self = (struct MyReactor *)_self->parent; + Environment *env = self->super.env; + MyAction *my_action = &self->my_action; + if (self->cnt == 0) { + TEST_ASSERT_EQUAL(lf_is_present(my_action), false); + } else { + TEST_ASSERT_EQUAL(lf_is_present(my_action), true); + } + + printf("Hello World\n"); + printf("Action = %d\n", lf_get(my_action)); + if (self->cnt > 0) { + TEST_ASSERT_EQUAL(self->cnt, lf_get(my_action)); + TEST_ASSERT_EQUAL(self->cnt, env->current_tag.microstep); + TEST_ASSERT_EQUAL(true, lf_is_present(my_action)); + } else { + TEST_ASSERT_EQUAL(false, lf_is_present(my_action)); + } + + TEST_ASSERT_EQUAL(0, env->get_elapsed_logical_time(env)); + + if (self->cnt < 100) { + lf_schedule(my_action, ++self->cnt, 0); + } +} + +void MyReaction_ctor(MyReaction *self, Reactor *parent) { + Reaction_ctor(&self->super, parent, action_handler, self->effects, 1, 0); +} + +void MyReactor_ctor(struct MyReactor *self, Environment *env) { + self->_reactions[0] = (Reaction *)&self->my_reaction; + self->_triggers[0] = (Trigger *)&self->startup; + self->_triggers[1] = (Trigger *)&self->my_action; + Reactor_ctor(&self->super, "MyReactor", env, NULL, NULL, 0, self->_reactions, 1, self->_triggers, 2); + MyAction_ctor(&self->my_action, self); + MyReaction_ctor(&self->my_reaction, &self->super); + MyStartup_ctor(&self->startup, &self->super, &self->my_reaction.super); + ACTION_REGISTER_EFFECT(self->my_action, self->my_reaction); + REACTION_REGISTER_EFFECT(self->my_reaction, self->my_action); + ACTION_REGISTER_SOURCE(self->my_action, self->my_reaction); + self->cnt = 0; +} + +void test_simple() { + struct MyReactor my_reactor; + Environment env; + Environment_ctor(&env, (Reactor *)&my_reactor); + MyReactor_ctor(&my_reactor, &env); + env.set_timeout(&env, SEC(1)); + env.assemble(&env); + env.start(&env); +} + +int main() { + UNITY_BEGIN(); + RUN_TEST(test_simple); + return UNITY_END(); +} \ No newline at end of file From d8fa42bbc4438c46bd160c7213a9ff0521a68916 Mon Sep 17 00:00:00 2001 From: erling Date: Tue, 15 Oct 2024 18:38:08 -0700 Subject: [PATCH 17/18] Create LICENSE --- LICENSE | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..1732af2e --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +BSD 2-Clause License + +Copyright (c) 2024, The Lingua Franca Coordination Language + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 4c3c0daf96bd636847a9dc6e76fefe4099864119 Mon Sep 17 00:00:00 2001 From: erling Date: Tue, 15 Oct 2024 18:38:47 -0700 Subject: [PATCH 18/18] Partial code-review (#68) --- examples/posix/testing_fed_conn.c | 14 +- examples/posix/timer_ex.c | 5 +- .../zephyr/federated/src/testing_fed_conn.c | 300 ------------------ include/reactor-uc/action.h | 14 +- include/reactor-uc/connection.h | 10 +- include/reactor-uc/environment.h | 35 +- include/reactor-uc/error.h | 6 + include/reactor-uc/federated.h | 2 +- include/reactor-uc/scheduler.h | 51 ++- include/reactor-uc/trigger.h | 6 +- .../{trigger_value.h => trigger_data_queue.h} | 25 +- .../lflang/generator/uc/UcMainGenerator.kt | 2 +- pr32-report.txt | 60 ---- scripts/ci/reports/test/expected.txt | 2 +- scripts/ci/reports/test/main.txt | 2 +- scripts/ci/reports/test/update.txt | 2 +- src/action.c | 16 +- src/connection.c | 46 +-- src/environment.c | 33 +- src/federated.c | 25 +- src/platform/posix/posix.c | 2 +- src/reactor.c | 3 +- src/scheduler.c | 237 +++++++------- src/tag.c | 12 - src/timer.c | 4 +- src/trigger.c | 8 +- src/{trigger_value.c => trigger_data_queue.c} | 22 +- test/unit/action_microstep_test.c | 3 +- test/unit/action_test.c | 2 +- test/unit/delayed_conn_test.c | 2 +- test/unit/physical_action_test.c | 2 +- test/unit/port_test.c | 2 +- test/unit/timer_test.c | 5 +- ...value_test.c => trigger_data_queue_test.c} | 10 +- 34 files changed, 329 insertions(+), 641 deletions(-) delete mode 100644 examples/zephyr/federated/src/testing_fed_conn.c rename include/reactor-uc/{trigger_value.h => trigger_data_queue.h} (61%) delete mode 100644 pr32-report.txt rename src/{trigger_value.c => trigger_data_queue.c} (59%) rename test/unit/{trigger_value_test.c => trigger_data_queue_test.c} (79%) diff --git a/examples/posix/testing_fed_conn.c b/examples/posix/testing_fed_conn.c index 24c0234a..9b4968c3 100644 --- a/examples/posix/testing_fed_conn.c +++ b/examples/posix/testing_fed_conn.c @@ -234,9 +234,9 @@ void *main_sender(void *unused) { (void)unused; Environment_ctor(&env_send, (Reactor *)&sender); MainSender_ctor(&sender, &env_send); - env_send.set_timeout(&env_send, SEC(1)); - env_send.net_channel_size = 1; - env_send.net_channels = (NetworkChannel **)&sender.net_channel; + env_send.scheduler.set_timeout(&env_send.scheduler, SEC(1)); + env_send.net_bundles_size = 1; + env_send.net_bundles = (FederatedConnectionBundle **)&sender.bundle; env_send.assemble(&env_send); env_send.start(&env_send); return NULL; @@ -249,11 +249,11 @@ void *main_recv(void *unused) { Environment_ctor(&env_recv, (Reactor *)&receiver); env_recv.platform->enter_critical_section(env_recv.platform); MainRecv_ctor(&receiver, &env_recv); - env_recv.set_timeout(&env_recv, SEC(1)); - env_recv.keep_alive = true; + env_recv.scheduler.set_timeout(&env_recv.scheduler, SEC(1)); + env_recv.scheduler.keep_alive = true; env_recv.has_async_events = true; - env_recv.net_channel_size = 1; - env_recv.net_channels = (NetworkChannel **)&receiver.net_channels; + env_recv.net_bundles_size = 1; + env_recv.net_bundles = (FederatedConnectionBundle **)&receiver.bundle; env_recv.assemble(&env_recv); env_recv.platform->leave_critical_section(env_recv.platform); env_recv.start(&env_recv); diff --git a/examples/posix/timer_ex.c b/examples/posix/timer_ex.c index 06357830..97989046 100644 --- a/examples/posix/timer_ex.c +++ b/examples/posix/timer_ex.c @@ -18,7 +18,8 @@ struct MyReactor { }; void reaction_0_body(struct MyReactor *self, MyTimer *my_timer) { - printf("Hello World @ %ld\n", self->super.env->current_tag.time); + Environment *env = self->super.env; + printf("Hello World @ %ld\n", env->get_elapsed_physical_time(env)); } void reaction_0_wrapper(Reaction *_self) { @@ -45,7 +46,7 @@ int main() { struct MyReactor my_reactor; Environment env; Environment_ctor(&env, (Reactor *)&my_reactor); - env.set_timeout(&env, SEC(1)); + env.scheduler.set_timeout(&env.scheduler, SEC(1)); MyReactor_ctor(&my_reactor, &env); env.assemble(&env); env.start(&env); diff --git a/examples/zephyr/federated/src/testing_fed_conn.c b/examples/zephyr/federated/src/testing_fed_conn.c deleted file mode 100644 index f8e87e08..00000000 --- a/examples/zephyr/federated/src/testing_fed_conn.c +++ /dev/null @@ -1,300 +0,0 @@ -#include "reactor-uc/reactor-uc.h" -#include -#include - -#define PORT_NUM 8901 - -typedef struct { - char msg[32]; -} msg_t; - -// Reactor Sender -typedef struct { - Timer super; - Reaction *effects[1]; -} Timer1; - -typedef struct { - Reaction super; -} Reaction1; - -typedef struct { - Output super; - Reaction *sources[1]; - msg_t value; -} Out; - -typedef struct { - Reactor super; - Reaction1 reaction; - Timer1 timer; - Out out; - Reaction *_reactions[1]; - Trigger *_triggers[1]; -} Sender; - -void timer_handler(Reaction *_self) { - Sender *self = (Sender *)_self->parent; - Environment *env = self->super.env; - Out *out = &self->out; - - printf("Timer triggered @ %" PRId64 "\n", env->get_elapsed_logical_time(env)); - msg_t val; - strcpy(val.msg, "Hello From Sender"); - lf_set(out, val); -} - -void Reaction1_ctor(Reaction1 *self, Reactor *parent) { - Reaction_ctor(&self->super, parent, timer_handler, NULL, 0, 0); -} - -void Out_ctor(Out *self, Sender *parent) { - self->sources[0] = &parent->reaction.super; - Output_ctor(&self->super, &parent->super, self->sources, 1); -} - -void Sender_ctor(Sender *self, Reactor *parent, Environment *env) { - self->_reactions[0] = (Reaction *)&self->reaction; - self->_triggers[0] = (Trigger *)&self->timer; - Reactor_ctor(&self->super, "Sender", env, parent, NULL, 0, self->_reactions, 1, self->_triggers, 1); - Reaction1_ctor(&self->reaction, &self->super); - Timer_ctor(&self->timer.super, &self->super, 0, MSEC(100), self->timer.effects, 1); - Out_ctor(&self->out, self); - TIMER_REGISTER_EFFECT(self->timer, self->reaction); - - // Register reaction as a source for out - OUTPUT_REGISTER_SOURCE(self->out, self->reaction); -} - -// Reactor Receiver -typedef struct { - Reaction super; -} Reaction2; - -typedef struct { - Input super; - msg_t buffer[1]; - Reaction *effects[1]; -} In; - -typedef struct { - Reactor super; - Reaction2 reaction; - In inp; - int cnt; - Reaction *_reactions[1]; - Trigger *_triggers[1]; -} Receiver; - -void In_ctor(In *self, Receiver *parent) { - Input_ctor(&self->super, &parent->super, self->effects, 1, self->buffer, sizeof(self->buffer[0])); -} - -void input_handler(Reaction *_self) { - Receiver *self = (Receiver *)_self->parent; - Environment *env = self->super.env; - In *inp = &self->inp; - - printf("Input triggered @ %" PRId64 " with %s\n", env->get_elapsed_logical_time(env), lf_get(inp).msg); -} - -void Reaction2_ctor(Reaction2 *self, Reactor *parent) { - Reaction_ctor(&self->super, parent, input_handler, NULL, 0, 0); -} - -void Receiver_ctor(Receiver *self, Reactor *parent, Environment *env) { - self->_reactions[0] = (Reaction *)&self->reaction; - self->_triggers[0] = (Trigger *)&self->inp; - Reactor_ctor(&self->super, "Receiver", env, parent, NULL, 0, self->_reactions, 1, self->_triggers, 1); - Reaction2_ctor(&self->reaction, &self->super); - In_ctor(&self->inp, self); - - // Register reaction as an effect of in - INPUT_REGISTER_EFFECT(self->inp, self->reaction); -} - -typedef struct { - FederatedOutputConnection super; - msg_t buffer[1]; -} ConnSender; - -void ConnSender_ctor(ConnSender *self, Reactor *parent, FederatedConnectionBundle *bundle, Port *upstream) { - FederatedOutputConnection_ctor(&self->super, parent, bundle, 0, upstream, &self->buffer[0], sizeof(self->buffer[0])); -} - -typedef struct { - FederatedConnectionBundle super; - TcpIpBundle bundle; - ConnSender conn; - FederatedOutputConnection *output[1]; -} SenderRecvBundle; - -void SenderRecvConn_ctor(SenderRecvBundle *self, Sender *parent) { - TcpIpBundle_ctor(&self->bundle, "127.0.0.1", PORT_NUM, AF_INET); - ConnSender_ctor(&self->conn, &parent->super, &self->super, &parent->out.super.super); - self->output[0] = &self->conn.super; - - TcpIpBundle *bundle = &self->bundle; - int ret = bundle->bind(bundle); - validate(ret == LF_OK); - printf("Sender: Bound\n"); - - // accept one connection - bool new_connection = bundle->accept(bundle); - validate(new_connection); - printf("Sender: Accepted\n"); - - FederatedConnectionBundle_ctor(&self->super, &parent->super, &self->bundle, NULL, 0, - (FederatedOutputConnection **)&self->output, 1); -} - -typedef struct { - FederatedInputConnection super; - msg_t buffer[5]; - Input *downstreams[1]; -} ConnRecv; - -void ConnRecv_ctor(ConnRecv *self, Reactor *parent) { - FederatedInputConnection_ctor(&self->super, parent, MSEC(100), false, (Port **)&self->downstreams, 1, - &self->buffer[0], sizeof(self->buffer[0]), 5); -} - -typedef struct { - FederatedConnectionBundle super; - TcpIpBundle bundle; - ConnRecv conn; - FederatedInputConnection *inputs[1]; -} RecvSenderBundle; - -void RecvSenderBundle_ctor(RecvSenderBundle *self, Reactor *parent) { - ConnRecv_ctor(&self->conn, parent); - TcpIpBundle_ctor(&self->bundle, "127.0.0.1", PORT_NUM, AF_INET); - self->inputs[0] = &self->conn.super; - - TcpIpBundle *bundle = &self->bundle; - - lf_ret_t ret; - do { - ret = bundle->connect(bundle); - } while (ret != LF_OK); - validate(ret == LF_OK); - printf("Recv: Connected\n"); - - FederatedConnectionBundle_ctor(&self->super, parent, &self->bundle, (FederatedInputConnection **)&self->inputs, 1, - NULL, 0); -} - -// Reactor main -struct MainSender { - Reactor super; - Sender sender; - SenderRecvBundle bundle; - - TcpIpBundle *net_bundles[1]; - Reactor *_children[1]; -}; - -struct MainRecv { - Reactor super; - Receiver receiver; - RecvSenderBundle bundle; - TcpIpBundle *net_bundles[1]; - - Reactor *_children[1]; -}; - -void MainSender_ctor(struct MainSender *self, Environment *env) { - self->_children[0] = &self->sender.super; - Sender_ctor(&self->sender, &self->super, env); - - SenderRecvConn_ctor(&self->bundle, &self->sender); - Reactor_ctor(&self->super, "MainSender", env, NULL, self->_children, 1, NULL, 0, NULL, 0); - - self->net_bundles[0] = &self->bundle.bundle; -} - -void MainRecv_ctor(struct MainRecv *self, Environment *env) { - self->_children[0] = &self->receiver.super; - Receiver_ctor(&self->receiver, &self->super, env); - - RecvSenderBundle_ctor(&self->bundle, &self->super); - - CONN_REGISTER_DOWNSTREAM(self->bundle.conn, self->receiver.inp); - Reactor_ctor(&self->super, "MainRecv", env, NULL, self->_children, 1, NULL, 0, NULL, 0); - - self->net_bundles[0] = &self->bundle.bundle; -} - -Environment env_send; -struct MainSender sender; -void *main_sender(void *unused) { - (void)unused; - Environment_ctor(&env_send, (Reactor *)&sender); - MainSender_ctor(&sender, &env_send); - env_send.set_timeout(&env_send, SEC(1)); - env_send.net_bundles_size = 1; - env_send.net_bundles = (TcpIpBundle **)&sender.net_bundles; - env_send.assemble(&env_send); - env_send.start(&env_send); - return NULL; -} - -Environment env_recv; -struct MainRecv receiver; -void *main_recv(void *unused) { - (void)unused; - Environment_ctor(&env_recv, (Reactor *)&receiver); - env_recv.platform->enter_critical_section(env_recv.platform); - MainRecv_ctor(&receiver, &env_recv); - env_recv.set_timeout(&env_recv, SEC(1)); - env_recv.keep_alive = true; - env_recv.has_async_events = true; - env_recv.net_bundles_size = 1; - env_recv.net_bundles = (TcpIpBundle **)&receiver.net_bundles; - env_recv.assemble(&env_recv); - env_recv.platform->leave_critical_section(env_recv.platform); - env_recv.start(&env_recv); - return NULL; -} - -void lf_exit(void) { - Environment_free(&env_send); - Environment_free(&env_recv); -} - -char t1_stack[4096]; -char t2_stack[4096]; -int main() { - pthread_t thread1; - pthread_attr_t attr1; - pthread_t thread2; - pthread_attr_t attr2; - int ret; - if (atexit(lf_exit) != 0) { - validate(false); - } - - pthread_attr_init(&attr1); - pthread_attr_setstack(&attr1, t1_stack, 4096); - int args; - // Create the first thread running func1 - if (ret = pthread_create(&thread1, &attr1, main_recv, (void *)&args)) { - printf("Error creating thread 1 %d\n", ret); - return 1; - } - - pthread_attr_init(&attr2); - pthread_attr_setstack(&attr2, t2_stack, 4096); - // Create the second thread running func2 - if (ret = pthread_create(&thread2, &attr1, main_sender, (void *)&args)) { - printf("Error creating thread 2 %d\n", ret); - return 1; - } - - // Wait for both threads to finish - pthread_join(thread1, NULL); - pthread_join(thread2, NULL); - - printf("Both threads have finished\n"); - return 0; -} diff --git a/include/reactor-uc/action.h b/include/reactor-uc/action.h index 19addc5d..a3b2339e 100644 --- a/include/reactor-uc/action.h +++ b/include/reactor-uc/action.h @@ -10,13 +10,13 @@ typedef struct LogicalAction LogicalAction; typedef struct PhysicalAction PhysicalAction; struct Action { - Trigger super; // Inherit from Trigger - interval_t min_offset; // The minimum offset from the current time that an event can be scheduled on this action. - interval_t min_spacing; // The minimum spacing between two consecutive events on this action. - tag_t previous_event; // Used to enforce min_spacing - TriggerEffects effects; // The reactions triggered by this Action. - TriggerSources sources; // The reactions that can write to this Action. - TriggerValue trigger_value; // FIFO storage of the data associated with the events scheduled on this action. + Trigger super; // Inherit from Trigger + interval_t min_offset; // The minimum offset from the current time that an event can be scheduled on this action. + interval_t min_spacing; // The minimum spacing between two consecutive events on this action. + tag_t previous_event; // Used to enforce min_spacing + TriggerEffects effects; // The reactions triggered by this Action. + TriggerSources sources; // The reactions that can write to this Action. + TriggerDataQueue trigger_data_queue; // FIFO storage of the data associated with the events scheduled on this action. /** * @brief Schedule an event on this action. */ diff --git a/include/reactor-uc/connection.h b/include/reactor-uc/connection.h index d7431ead..d5618989 100644 --- a/include/reactor-uc/connection.h +++ b/include/reactor-uc/connection.h @@ -46,14 +46,14 @@ struct Connection { * @param parent The reactor in which this connection appears (not the reactors of the ports it connects) * @param downstreams A pointer to an array of pointers to downstream ports. * @param num_downstreams The size of the downstreams array. - * @param trigger_value A pointer to the TriggerValue that holds the data of the events that are scheduled on this - * connection. + * @param trigger_data_queue A pointer to the TriggerDataQueue that holds the data of the events that are scheduled on + * this connection. * @param prepare The prepare function that is called before the connection triggers its downstreams. * @param cleanup The cleanup function that is called at the end of timestep after all reactions have executed. * @param trigger_downstreams The function that triggers all downstreams of this connection. */ void Connection_ctor(Connection *self, TriggerType type, Reactor *parent, Port **downstreams, size_t num_downstreams, - TriggerValue *trigger_value, void (*prepare)(Trigger *), void (*cleanup)(Trigger *), + TriggerDataQueue *trigger_data_queue, void (*prepare)(Trigger *), void (*cleanup)(Trigger *), void (*trigger_downstreams)(Connection *, const void *, size_t)); struct LogicalConnection { @@ -65,7 +65,7 @@ void LogicalConnection_ctor(LogicalConnection *self, Reactor *parent, Port **dow struct DelayedConnection { Connection super; interval_t delay; - TriggerValue trigger_value; + TriggerDataQueue trigger_data_queue; }; void DelayedConnection_ctor(DelayedConnection *self, Reactor *parent, Port **downstreams, size_t num_downstreams, @@ -74,7 +74,7 @@ void DelayedConnection_ctor(DelayedConnection *self, Reactor *parent, Port **dow struct PhysicalConnection { Connection super; interval_t delay; - TriggerValue trigger_value; + TriggerDataQueue trigger_data_queue; }; void PhysicalConnection_ctor(PhysicalConnection *self, Reactor *parent, Port **downstreams, size_t num_downstreams, diff --git a/include/reactor-uc/environment.h b/include/reactor-uc/environment.h index 1b6bdaca..af99ada3 100644 --- a/include/reactor-uc/environment.h +++ b/include/reactor-uc/environment.h @@ -9,43 +9,37 @@ #include "reactor-uc/scheduler.h" typedef struct Environment Environment; -typedef struct TcpIpChannel TcpIpChannel; struct Environment { - Reactor *main; // The top-level reactor of the program. - Scheduler scheduler; // The scheduler in charge of executing the reactions. - Platform *platform; // The platform that provides the physical time and sleep functions. - tag_t stop_tag; // The tag at which the program should stop. This is set by the user or by the scheduler. - tag_t current_tag; // The current logical tag. Set by the scheduler and read by user in the reaction bodies. - instant_t start_time; // The physical time at which the program started. - bool keep_alive; // Whether the program should keep running even if there are no more events to process. - bool has_async_events; // Whether the environment either has an action, or has a connection to an upstream federate. - Startup *startup; // A pointer to a startup trigger, if the program has one. - Shutdown *shutdown; // A pointer to a chain of shutdown triggers, if the program has one. - NetworkChannel **net_channels; // A pointer to an array of NetworkChannel pointers that are used to communicate with - // other federates running in different environments. - size_t net_channel_size; // The number of NetworkChannels in the net_channels array. + Reactor *main; // The top-level reactor of the program. + Scheduler scheduler; // The scheduler in charge of executing the reactions. + Platform *platform; // The platform that provides the physical time and sleep functions. + bool has_async_events; + Startup *startup; // A pointer to a startup trigger, if the program has one. + Shutdown *shutdown; // A pointer to a chain of shutdown triggers, if the program has one. + FederatedConnectionBundle **net_bundles; // A pointer to an array of NetworkChannel pointers that are used to + // communicate with other federates running in different environments. + size_t net_bundles_size; // The number of NetworkChannels in the net_channels array. /** * @brief Assemble the program by computing levels for each reaction and setting up the scheduler. */ void (*assemble)(Environment *self); + /** * @brief Start the program. */ void (*start)(Environment *self); + /** * @brief Wrapper around `wait_until` exposed by the platform. - * TODO: Is this needed? */ lf_ret_t (*wait_until)(Environment *self, instant_t wakeup_time); - /** - * @brief Set the stop tag of the program based on a timeout duration. - */ - void (*set_timeout)(Environment *self, interval_t duration); + /** * @brief Get the elapsed logical time since the start of the program. */ interval_t (*get_elapsed_logical_time)(Environment *self); + /** * @brief Get the current logical time */ @@ -58,6 +52,9 @@ struct Environment { * @brief Get the current physical time. */ instant_t (*get_physical_time)(Environment *self); + + void (*enter_critical_section)(Environment *self); + void (*leave_critical_section)(Environment *self); }; void Environment_ctor(Environment *self, Reactor *main); diff --git a/include/reactor-uc/error.h b/include/reactor-uc/error.h index 4fadd657..4cc79b8b 100644 --- a/include/reactor-uc/error.h +++ b/include/reactor-uc/error.h @@ -44,4 +44,10 @@ typedef enum { } \ } while (0) +#define throw(msg) \ + do { \ + printf("Exception `%s` at %s:%d\n", msg, __FILE__, __LINE__); \ + exit(1); \ + } while (0) + #endif diff --git a/include/reactor-uc/federated.h b/include/reactor-uc/federated.h index cd4f0699..dc33a46f 100644 --- a/include/reactor-uc/federated.h +++ b/include/reactor-uc/federated.h @@ -49,7 +49,7 @@ struct FederatedInputConnection { bool is_physical; // Is the connection physical? tag_t last_known_tag; // The latest tag this input is known at. instant_t safe_to_assume_absent; // - TriggerValue trigger_value; + TriggerDataQueue trigger_data_queue; int conn_id; void (*schedule)(FederatedInputConnection *self, TaggedMessage *msg); }; diff --git a/include/reactor-uc/scheduler.h b/include/reactor-uc/scheduler.h index ef5c9624..cb88b4e6 100644 --- a/include/reactor-uc/scheduler.h +++ b/include/reactor-uc/scheduler.h @@ -15,17 +15,62 @@ struct Scheduler { // that are registered for cleanup at the end of the current tag. Trigger *cleanup_ll_head; Trigger *cleanup_ll_tail; - bool executing_tag; + instant_t start_time; // The physical time at which the program started. + tag_t stop_tag; // The tag at which the program should stop. This is set by the user or by the scheduler. + tag_t current_tag; // The current logical tag. Set by the scheduler and read by user in the reaction bodies. + bool keep_alive; // Whether the program should keep running even if there are no more events to process. + + /** + * @brief Schedules an event on trigger at a specified tag. This function will + * enter a critcal section if the environment has async events. + */ lf_ret_t (*schedule_at)(Scheduler *self, Trigger *trigger, tag_t tag); + + /** + * @brief Schedules an event on a trigger at a specified tag. This function + * assumes that we are in a critical section (if this is needed). + * + */ lf_ret_t (*schedule_at_locked)(Scheduler *self, Trigger *trigger, tag_t tag); + + /** + * @brief Runs the program. Does not return until program has completed. + */ void (*run)(Scheduler *self); + + /** + * @brief After committing to a tag, but before executing reactions, the + * scheduler must prepare the timestep by adding reactions to the reaction + * queue. + */ void (*prepare_timestep)(Scheduler *self, tag_t tag); + + /** + * @brief After completing all reactions at a tag, this function is called to + * reset is_present fields and increment index pointers of the TriggerDataQueue. + */ void (*clean_up_timestep)(Scheduler *self); + + /** + * @brief Called after `prepare_timestep` to run all reactions on the current + * tag. + */ void (*run_timestep)(Scheduler *self); + + /** + * @brief Called to execute all reactions triggered by a shutdown trigger. + */ void (*terminate)(Scheduler *self); - // Register Trigger for cleanup. Cleanup happens after all reactions have - // executed at a particular tag. + /** + * @brief Set the stop tag of the program based on a timeout duration. + */ + void (*set_timeout)(Scheduler *self, interval_t duration); + + /** + * @brief Register Trigger for cleanup. The cleanup function of the trigger + * will be called in `clean_up_timestep`. + */ void (*register_for_cleanup)(Scheduler *self, Trigger *trigger); }; diff --git a/include/reactor-uc/trigger.h b/include/reactor-uc/trigger.h index d6525b2e..e99fdad1 100644 --- a/include/reactor-uc/trigger.h +++ b/include/reactor-uc/trigger.h @@ -4,7 +4,7 @@ #include "reactor-uc/macros.h" #include "reactor-uc/reaction.h" #include "reactor-uc/reactor.h" -#include "reactor-uc/trigger_value.h" +#include "reactor-uc/trigger_data_queue.h" #include typedef struct Trigger Trigger; @@ -61,14 +61,14 @@ struct Trigger { // linked list of triggers registered for cleanup Trigger *next; // For chaining together triggers, used by Scheduler to store triggers that should be cleaned up in a // linked list - TriggerValue *trigger_value; // A pointer to a TriggerValue field in a child type, Can be NULL + TriggerDataQueue *trigger_data_queue; // A pointer to a TriggerDataQueue field in a child type, Can be NULL void (*prepare)(Trigger *); void (*cleanup)(Trigger *); const void *(*get)(Trigger *); } __attribute__((aligned(MEM_ALIGNMENT))); // FIXME: This should not be necessary... -void Trigger_ctor(Trigger *self, TriggerType type, Reactor *parent, TriggerValue *trigger_value, +void Trigger_ctor(Trigger *self, TriggerType type, Reactor *parent, TriggerDataQueue *trigger_data_queue, void (*prepare)(Trigger *), void (*cleanup)(Trigger *), const void *(*get)(Trigger *)); #endif diff --git a/include/reactor-uc/trigger_value.h b/include/reactor-uc/trigger_data_queue.h similarity index 61% rename from include/reactor-uc/trigger_value.h rename to include/reactor-uc/trigger_data_queue.h index d9644707..68c1d06a 100644 --- a/include/reactor-uc/trigger_value.h +++ b/include/reactor-uc/trigger_data_queue.h @@ -1,26 +1,25 @@ -#ifndef REACTOR_UC_TRIGGER_VALUE_H -#define REACTOR_UC_TRIGGER_VALUE_H +#ifndef REACTOR_UC_TRIGGER_DATA_QUEUE_H +#define REACTOR_UC_TRIGGER_DATA_QUEUE_H #include "reactor-uc/error.h" #include #include -typedef struct TriggerValue TriggerValue; +typedef struct TriggerDataQueue TriggerDataQueue; -// FIXME: Handle "void" TriggerValues /** - * @brief A TriggerValue is a type wrapping the memory associated with a Trigger. - * In reactor-uc this memory must be statically allocated and TriggerValue - * implements a FIFO around it. The TriggerValue needs a pointer to the allocated + * @brief A TriggerDataQueue is a type wrapping the memory associated with a Trigger. + * In reactor-uc this memory must be statically allocated and TriggerDataQueue + * implements a FIFO around it. The TriggerDataQueue needs a pointer to the allocated * memory and the allocated size as well as the size of each element. Then we * can stage, push and pop data from the runtime without knowing about the types * involved.. * */ -struct TriggerValue { +struct TriggerDataQueue { char *buffer; size_t read_idx; size_t write_idx; - size_t value_size; + size_t value_size; // TODO: BEtter name. size_t capacity; bool empty; bool staged; @@ -32,22 +31,22 @@ struct TriggerValue { * multiple writes to a trigger in a single tag and "last write wins." * */ - lf_ret_t (*stage)(TriggerValue *, const void *value); + lf_ret_t (*stage)(TriggerDataQueue *, const void *value); /** * @brief Pushes the staged value into the FIFO. * */ - lf_ret_t (*push)(TriggerValue *); + lf_ret_t (*push)(TriggerDataQueue *); /** * @brief Increments the `read_idx` and as such pops the head of the * queue. This function does not return the head of the queue. Only increments * the pointers. */ - lf_ret_t (*pop)(TriggerValue *); + lf_ret_t (*pop)(TriggerDataQueue *); }; -void TriggerValue_ctor(TriggerValue *self, char *buffer, size_t value_size, size_t capacity); +void TriggerDataQueue_ctor(TriggerDataQueue *self, char *buffer, size_t value_size, size_t capacity); #endif diff --git a/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcMainGenerator.kt b/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcMainGenerator.kt index 888c6a02..268ed5c7 100644 --- a/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcMainGenerator.kt +++ b/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcMainGenerator.kt @@ -43,7 +43,7 @@ class UcMainGenerator( |static ${main.codeType} main_reactor; |void lf_main(void) { | Environment_ctor(&env, &main_reactor.super); - | ${if (targetConfig.isSet(TimeOutProperty.INSTANCE)) "env.set_timeout(&env, ${targetConfig.get(TimeOutProperty.INSTANCE).toCCode()});" else ""} + | ${if (targetConfig.isSet(TimeOutProperty.INSTANCE)) "env.scheduler.set_timeout(&env.scheduler, ${targetConfig.get(TimeOutProperty.INSTANCE).toCCode()});" else ""} | ${main.codeType}_ctor(&main_reactor, &env, NULL); | env.assemble(&env); | env.start(&env); diff --git a/pr32-report.txt b/pr32-report.txt deleted file mode 100644 index 630ef5d9..00000000 --- a/pr32-report.txt +++ /dev/null @@ -1,60 +0,0 @@ -Test 0: action_test.c: -% text: 7.20 (from 36104 -> 38702) -% data: -2.47 (from 728 -> 710) -% bss : 56.25 (from 512 -> 800) -% dec : 7.68 (from 37344 -> 40212) - -Test 1: delayed.conn_test.c: -% text: -15.05 (from 39103 -> 33218) -% data: -51.10 (from 728 -> 356) -% bss : 138.67 (from 512 -> 1222) -% dec : -14.00 (from 40343 -> 34696) - -Test 2: event_queue_test.c: -% text: -0.22 (from 22472 -> 22422) -% data: 20.25 (from 632 -> 760) -% bss : 0.00 (from 320 -> 320) -% dec : 0.33 (from 23424 -> 23502) - -Test 3: physical_action_test.c: -% text: 12.55 (from 37566 -> 42280) -% data: 157.72 (from 745 -> 1920) -% bss : -89.75 (from 1952 -> 200) -% dec : 10.27 (from 40263 -> 44400) - -Test 4: port_test.c: -% text: -17.01 (from 39067 -> 32420) -% data: 5.49 (from 728 -> 768) -% bss : -2.34 (from 512 -> 500) -% dec : -16.42 (from 40307 -> 33688) - -Test 5: reaction_queue_test.c: -% text: 35.07 (from 22117 -> 29874) -% data: -36.71 (from 632 -> 400) -% bss : -15.62 (from 320 -> 270) -% dec : 32.40 (from 23069 -> 30544) - -Test 6: shutdown_test.c: -% text: -60.66 (from 32808 -> 12908) -% data: -41.67 (from 720 -> 420) -% bss : 52.34 (from 512 -> 780) -% dec : -58.55 (from 34040 -> 14108) - -Test 7: startup_test.c: -% text: -53.36 (from 31926 -> 14890) -% data: 0.00 (from 720 -> 720) -% bss : 0.00 (from 512 -> 512) -% dec : -51.38 (from 33158 -> 16122) - -Test 8: timer_test.c: -% text: -2.67 (from 32042 -> 31188) -% data: -9.17 (from 720 -> 654) -% bss : 75.78 (from 512 -> 900) -% dec : -1.60 (from 33274 -> 32742) - -Test 9: trigger_value_test.c: -% text: -3.02 (from 18191 -> 17642) -% data: -31.65 (from 632 -> 432) -% bss : -37.50 (from 320 -> 200) -% dec : -4.54 (from 19143 -> 18274) - diff --git a/scripts/ci/reports/test/expected.txt b/scripts/ci/reports/test/expected.txt index 630ef5d9..b1946c18 100644 --- a/scripts/ci/reports/test/expected.txt +++ b/scripts/ci/reports/test/expected.txt @@ -52,7 +52,7 @@ Test 8: timer_test.c: % bss : 75.78 (from 512 -> 900) % dec : -1.60 (from 33274 -> 32742) -Test 9: trigger_value_test.c: +Test 9: trigger_data_queue_test.c: % text: -3.02 (from 18191 -> 17642) % data: -31.65 (from 632 -> 432) % bss : -37.50 (from 320 -> 200) diff --git a/scripts/ci/reports/test/main.txt b/scripts/ci/reports/test/main.txt index e475959d..f099d18e 100644 --- a/scripts/ci/reports/test/main.txt +++ b/scripts/ci/reports/test/main.txt @@ -17,4 +17,4 @@ text data bss dec hex filename 32042 720 512 33274 81fa timer_test_c text data bss dec hex filename - 18191 632 320 19143 4ac7 trigger_value_test_c \ No newline at end of file + 18191 632 320 19143 4ac7 trigger_data_queue_test_c \ No newline at end of file diff --git a/scripts/ci/reports/test/update.txt b/scripts/ci/reports/test/update.txt index 54ad1d33..7913b73d 100644 --- a/scripts/ci/reports/test/update.txt +++ b/scripts/ci/reports/test/update.txt @@ -17,4 +17,4 @@ text data bss dec hex filename 31188 654 900 32742 7fe6 timer_test_c text data bss dec hex filename - 17642 432 200 18274 4762 trigger_value_test_c \ No newline at end of file + 17642 432 200 18274 4762 trigger_data_queue_test_c \ No newline at end of file diff --git a/src/action.c b/src/action.c index c44ac647..01bd15ae 100644 --- a/src/action.c +++ b/src/action.c @@ -10,7 +10,7 @@ void Action_cleanup(Trigger *self) { LF_DEBUG(TRIG, "Cleaning up action %p", self); Action *act = (Action *)self; self->is_present = false; - validaten(act->trigger_value.pop(&act->trigger_value)); + validaten(act->trigger_data_queue.pop(&act->trigger_data_queue)); } void Action_prepare(Trigger *self) { @@ -29,8 +29,8 @@ void Action_prepare(Trigger *self) { void Action_ctor(Action *self, TriggerType type, interval_t min_offset, interval_t min_spacing, Reactor *parent, Reaction **sources, size_t sources_size, Reaction **effects, size_t effects_size, void *value_buf, size_t value_size, size_t value_capacity, lf_ret_t (*schedule)(Action *, interval_t, const void *)) { - TriggerValue_ctor(&self->trigger_value, value_buf, value_size, value_capacity); - Trigger_ctor(&self->super, type, parent, &self->trigger_value, Action_prepare, Action_cleanup, NULL); + TriggerDataQueue_ctor(&self->trigger_data_queue, value_buf, value_size, value_capacity); + Trigger_ctor(&self->super, type, parent, &self->trigger_data_queue, Action_prepare, Action_cleanup, NULL); self->min_offset = min_offset; self->min_spacing = min_spacing; self->previous_event = NEVER_TAG; @@ -46,15 +46,15 @@ void Action_ctor(Action *self, TriggerType type, interval_t min_offset, interval lf_ret_t LogicalAction_schedule(Action *self, interval_t offset, const void *value) { Environment *env = self->super.parent->env; Scheduler *sched = &env->scheduler; - tag_t proposed_tag = lf_delay_tag(env->current_tag, offset); + tag_t proposed_tag = lf_delay_tag(sched->current_tag, offset); tag_t earliest_allowed = lf_delay_tag(self->previous_event, self->min_spacing); if (lf_tag_compare(proposed_tag, earliest_allowed) < 0) { return LF_INVALID_TAG; } if (value) { - self->trigger_value.stage(&self->trigger_value, value); - self->trigger_value.push(&self->trigger_value); + self->trigger_data_queue.stage(&self->trigger_data_queue, value); + self->trigger_data_queue.push(&self->trigger_data_queue); } else { return LF_INVALID_VALUE; } @@ -87,8 +87,8 @@ lf_ret_t PhysicalAction_schedule(Action *self, interval_t offset, const void *va } if (value) { - self->trigger_value.stage(&self->trigger_value, value); - self->trigger_value.push(&self->trigger_value); + self->trigger_data_queue.stage(&self->trigger_data_queue, value); + self->trigger_data_queue.push(&self->trigger_data_queue); } else { env->platform->leave_critical_section(env->platform); return LF_INVALID_VALUE; diff --git a/src/connection.c b/src/connection.c index c01da25b..6480c445 100644 --- a/src/connection.c +++ b/src/connection.c @@ -1,7 +1,7 @@ #include "reactor-uc/connection.h" #include "reactor-uc/environment.h" #include "reactor-uc/logging.h" -#include "reactor-uc/trigger_value.h" +#include "reactor-uc/trigger_data_queue.h" #include #include @@ -62,7 +62,7 @@ void LogicalConnection_trigger_downstreams(Connection *self, const void *value, } void Connection_ctor(Connection *self, TriggerType type, Reactor *parent, Port **downstreams, size_t num_downstreams, - TriggerValue *trigger_value, void (*prepare)(Trigger *), void (*cleanup)(Trigger *), + TriggerDataQueue *trigger_data_queue, void (*prepare)(Trigger *), void (*cleanup)(Trigger *), void (*trigger_downstreams)(Connection *, const void *, size_t)) { self->upstream = NULL; @@ -73,7 +73,7 @@ void Connection_ctor(Connection *self, TriggerType type, Reactor *parent, Port * self->get_final_upstream = Connection_get_final_upstream; self->trigger_downstreams = trigger_downstreams; - Trigger_ctor(&self->super, type, parent, trigger_value, prepare, cleanup, NULL); + Trigger_ctor(&self->super, type, parent, trigger_data_queue, prepare, cleanup, NULL); } void LogicalConnection_ctor(LogicalConnection *self, Reactor *parent, Port **downstreams, size_t num_downstreams) { @@ -92,19 +92,19 @@ void DelayedConnection_prepare(Trigger *trigger) { LF_DEBUG(CONN, "Preparing delayed connection %p for triggering", trigger); DelayedConnection *self = (DelayedConnection *)trigger; Scheduler *sched = &trigger->parent->env->scheduler; - TriggerValue *tval = &self->trigger_value; + TriggerDataQueue *tval = &self->trigger_data_queue; void *value_ptr = (void *)&tval->buffer[tval->read_idx * tval->value_size]; trigger->is_present = true; sched->register_for_cleanup(sched, trigger); - LogicalConnection_trigger_downstreams(&self->super, value_ptr, self->trigger_value.value_size); + LogicalConnection_trigger_downstreams(&self->super, value_ptr, self->trigger_data_queue.value_size); } /** * @brief Called at the end of logical tags. In charge of two things: - * 1. Increment `read_idx` of the TriggerValue FIFO through the `pop` call. - * 2. Increment the `write_idx` of the TriggerValue FIFO (`push`) and schedule + * 1. Increment `read_idx` of the TriggerDataQueue FIFO through the `pop` call. + * 2. Increment the `write_idx` of the TriggerDataQueue FIFO (`push`) and schedule * an event based on the delay of this connection. */ void DelayedConnection_cleanup(Trigger *trigger) { @@ -115,17 +115,17 @@ void DelayedConnection_cleanup(Trigger *trigger) { if (trigger->is_present) { LF_DEBUG(CONN, "Delayed connection %p had a present value this tag. Pop it", trigger); trigger->is_present = false; - int ret = self->trigger_value.pop(&self->trigger_value); + int ret = self->trigger_data_queue.pop(&self->trigger_data_queue); validaten(ret); } - if (self->trigger_value.staged) { + if (self->trigger_data_queue.staged) { LF_DEBUG(CONN, "Delayed connection %p had a staged value. Schedule it", trigger); Environment *env = self->super.super.parent->env; Scheduler *sched = &env->scheduler; - tag_t tag = lf_delay_tag(env->current_tag, self->delay); - self->trigger_value.push(&self->trigger_value); + tag_t tag = lf_delay_tag(sched->current_tag, self->delay); + self->trigger_data_queue.push(&self->trigger_data_queue); sched->schedule_at(sched, trigger, tag); } } @@ -141,9 +141,9 @@ void DelayedConnection_trigger_downstreams(Connection *_self, const void *value, Scheduler *sched = &_self->super.parent->env->scheduler; if (value) { - self->trigger_value.stage(&self->trigger_value, value); + self->trigger_data_queue.stage(&self->trigger_data_queue, value); } else { - validate(false); + throw("DelayedConnection with untyped value"); } sched->register_for_cleanup(sched, &_self->super); } @@ -151,8 +151,8 @@ void DelayedConnection_trigger_downstreams(Connection *_self, const void *value, void DelayedConnection_ctor(DelayedConnection *self, Reactor *parent, Port **downstreams, size_t num_downstreams, interval_t delay, void *value_buf, size_t value_size, size_t value_capacity) { self->delay = delay; - TriggerValue_ctor(&self->trigger_value, value_buf, value_size, value_capacity); - Connection_ctor(&self->super, TRIG_CONN_DELAYED, parent, downstreams, num_downstreams, &self->trigger_value, + TriggerDataQueue_ctor(&self->trigger_data_queue, value_buf, value_size, value_capacity); + Connection_ctor(&self->super, TRIG_CONN_DELAYED, parent, downstreams, num_downstreams, &self->trigger_data_queue, DelayedConnection_prepare, DelayedConnection_cleanup, DelayedConnection_trigger_downstreams); } @@ -160,13 +160,13 @@ void PhysicalConnection_prepare(Trigger *trigger) { LF_DEBUG(CONN, "Preparing physical connection %p for triggering", trigger); PhysicalConnection *self = (PhysicalConnection *)trigger; Scheduler *sched = &trigger->parent->env->scheduler; - TriggerValue *tval = &self->trigger_value; + TriggerDataQueue *tval = &self->trigger_data_queue; void *value_ptr = (void *)&tval->buffer[tval->read_idx * tval->value_size]; trigger->is_present = true; sched->register_for_cleanup(sched, trigger); - LogicalConnection_trigger_downstreams(&self->super, value_ptr, self->trigger_value.value_size); + LogicalConnection_trigger_downstreams(&self->super, value_ptr, self->trigger_data_queue.value_size); } void PhysicalConnection_cleanup(Trigger *trigger) { @@ -177,18 +177,18 @@ void PhysicalConnection_cleanup(Trigger *trigger) { if (trigger->is_present) { LF_DEBUG(CONN, "Physical connection %p had a present value this tag. Pop it", trigger); trigger->is_present = false; - int ret = self->trigger_value.pop(&self->trigger_value); + int ret = self->trigger_data_queue.pop(&self->trigger_data_queue); validate(ret == 0); } - if (self->trigger_value.staged) { + if (self->trigger_data_queue.staged) { LF_DEBUG(CONN, "Physical connection %p had a staged value. Schedule it", trigger); Environment *env = self->super.super.parent->env; Scheduler *sched = &env->scheduler; tag_t now_tag = {.time = env->get_physical_time(env), .microstep = 0}; tag_t tag = lf_delay_tag(now_tag, self->delay); - validaten(self->trigger_value.push(&self->trigger_value)); + validaten(self->trigger_data_queue.push(&self->trigger_data_queue)); validaten(sched->schedule_at(sched, trigger, tag)); } } @@ -201,7 +201,7 @@ void PhysicalConnection_trigger_downstreams(Connection *_self, const void *value validate(value); // Try to stage the value for scheduling. - int res = self->trigger_value.stage(&self->trigger_value, value); + int res = self->trigger_data_queue.stage(&self->trigger_data_queue, value); if (res == LF_OK) { sched->register_for_cleanup(sched, &_self->super); } @@ -210,8 +210,8 @@ void PhysicalConnection_trigger_downstreams(Connection *_self, const void *value void PhysicalConnection_ctor(PhysicalConnection *self, Reactor *parent, Port **downstreams, size_t num_downstreams, interval_t delay, void *value_buf, size_t value_size, size_t value_capacity) { - TriggerValue_ctor(&self->trigger_value, value_buf, value_size, value_capacity); - Connection_ctor(&self->super, TRIG_CONN_PHYSICAL, parent, downstreams, num_downstreams, &self->trigger_value, + TriggerDataQueue_ctor(&self->trigger_data_queue, value_buf, value_size, value_capacity); + Connection_ctor(&self->super, TRIG_CONN_PHYSICAL, parent, downstreams, num_downstreams, &self->trigger_data_queue, PhysicalConnection_prepare, PhysicalConnection_cleanup, PhysicalConnection_trigger_downstreams); self->delay = delay; } diff --git a/src/environment.c b/src/environment.c index dca45066..0f4200e3 100644 --- a/src/environment.c +++ b/src/environment.c @@ -20,18 +20,25 @@ lf_ret_t Environment_wait_until(Environment *self, instant_t wakeup_time) { } } -void Environment_set_timeout(Environment *self, interval_t duration) { - self->stop_tag.microstep = 0; - self->stop_tag.time = self->start_time + duration; +interval_t Environment_get_logical_time(Environment *self) { return self->scheduler.current_tag.time; } +interval_t Environment_get_elapsed_logical_time(Environment *self) { + return self->scheduler.current_tag.time - self->scheduler.start_time; } - -interval_t Environment_get_logical_time(Environment *self) { return self->current_tag.time; } -interval_t Environment_get_elapsed_logical_time(Environment *self) { return self->current_tag.time - self->start_time; } interval_t Environment_get_physical_time(Environment *self) { return self->platform->get_physical_time(self->platform); } interval_t Environment_get_elapsed_physical_time(Environment *self) { - return self->platform->get_physical_time(self->platform) - self->start_time; + return self->platform->get_physical_time(self->platform) - self->scheduler.start_time; +} +void Environment_enter_critical_section(Environment *self) { + if (self->has_async_events) { + self->platform->enter_critical_section(self->platform); + } +} +void Environment_leave_critical_section(Environment *self) { + if (self->has_async_events) { + self->platform->leave_critical_section(self->platform); + } } void Environment_ctor(Environment *self, Reactor *main) { @@ -43,24 +50,16 @@ void Environment_ctor(Environment *self, Reactor *main) { self->assemble = Environment_assemble; self->start = Environment_start; self->wait_until = Environment_wait_until; - self->set_timeout = Environment_set_timeout; self->get_elapsed_logical_time = Environment_get_elapsed_logical_time; self->get_logical_time = Environment_get_logical_time; self->get_physical_time = Environment_get_physical_time; self->get_elapsed_physical_time = Environment_get_elapsed_physical_time; - - self->keep_alive = false; + self->leave_critical_section = Environment_leave_critical_section; + self->enter_critical_section = Environment_enter_critical_section; self->has_async_events = false; self->startup = NULL; self->shutdown = NULL; - self->stop_tag = FOREVER_TAG; Scheduler_ctor(&self->scheduler, self); - self->current_tag = NEVER_TAG; - - // Set start time - // TODO: This must be resolved in the federation. Currently set start tag to nearest second. - self->start_time = ((self->platform->get_physical_time(self->platform) + SEC(1)) / SEC(1)) * SEC(1); - LF_INFO(ENV, "Start time: %" PRId64, self->start_time); } void Environment_free(Environment *self) { diff --git a/src/federated.c b/src/federated.c index 3c7edd0e..ebd92b9d 100644 --- a/src/federated.c +++ b/src/federated.c @@ -24,6 +24,7 @@ void FederatedOutputConnection_cleanup(Trigger *trigger) { LF_DEBUG(FED, "Cleaning up federated output connection %p", trigger); FederatedOutputConnection *self = (FederatedOutputConnection *)trigger; Environment *env = trigger->parent->env; + Scheduler *sched = &env->scheduler; NetworkChannel *channel = self->bundle->net_channel; validate(trigger->is_registered_for_cleanup); validaten(trigger->is_present); @@ -32,8 +33,8 @@ void FederatedOutputConnection_cleanup(Trigger *trigger) { TaggedMessage msg; msg.conn_id = self->conn_id; - msg.tag.time = env->current_tag.time; - msg.tag.microstep = env->current_tag.microstep; + msg.tag.time = sched->current_tag.time; + msg.tag.microstep = sched->current_tag.microstep; memcpy(msg.payload.bytes, self->value_ptr, self->value_size); msg.payload.size = self->value_size; @@ -64,13 +65,13 @@ void FederatedInputConnection_prepare(Trigger *trigger) { LF_DEBUG(FED, "Preparing federated input connection %p for triggering", trigger); FederatedInputConnection *self = (FederatedInputConnection *)trigger; Scheduler *sched = &trigger->parent->env->scheduler; - TriggerValue *tval = &self->trigger_value; + TriggerDataQueue *tval = &self->trigger_data_queue; void *value_ptr = (void *)&tval->buffer[tval->read_idx * tval->value_size]; trigger->is_present = true; sched->register_for_cleanup(sched, trigger); - LogicalConnection_trigger_downstreams(&self->super, value_ptr, self->trigger_value.value_size); + LogicalConnection_trigger_downstreams(&self->super, value_ptr, self->trigger_data_queue.value_size); } // Called at the end of a logical tag if it was registered for cleanup. @@ -81,20 +82,20 @@ void FederatedInputConnection_cleanup(Trigger *trigger) { if (trigger->is_present) { trigger->is_present = false; - int ret = self->trigger_value.pop(&self->trigger_value); + int ret = self->trigger_data_queue.pop(&self->trigger_data_queue); validaten(ret); } // Should never happen. - validaten(self->trigger_value.staged); + validaten(self->trigger_data_queue.staged); } void FederatedInputConnection_ctor(FederatedInputConnection *self, Reactor *parent, interval_t delay, bool is_physical, Port **downstreams, size_t downstreams_size, void *value_buf, size_t value_size, size_t value_capacity) { - TriggerValue_ctor(&self->trigger_value, value_buf, value_size, value_capacity); - Connection_ctor(&self->super, TRIG_CONN_FEDERATED_INPUT, parent, downstreams, downstreams_size, &self->trigger_value, - FederatedInputConnection_prepare, FederatedInputConnection_cleanup, NULL); + TriggerDataQueue_ctor(&self->trigger_data_queue, value_buf, value_size, value_capacity); + Connection_ctor(&self->super, TRIG_CONN_FEDERATED_INPUT, parent, downstreams, downstreams_size, + &self->trigger_data_queue, FederatedInputConnection_prepare, FederatedInputConnection_cleanup, NULL); self->delay = delay; self->is_physical = is_physical; self->last_known_tag = NEVER_TAG; @@ -122,10 +123,10 @@ void FederatedConnectionBundle_msg_received_cb(FederatedConnectionBundle *self, tag = lf_delay_tag(tag, input->delay); LF_DEBUG(FED, "Scheduling input %p at tag=%" PRId64 ":%" PRIu32, input, tag.time, tag.microstep); - // Take the value received over the network copy it into the trigger_value of + // Take the value received over the network copy it into the trigger_data_queue of // the input port and schedule an event for it. - input->trigger_value.stage(&input->trigger_value, &msg->payload.bytes); - input->trigger_value.push(&input->trigger_value); + input->trigger_data_queue.stage(&input->trigger_data_queue, &msg->payload.bytes); + input->trigger_data_queue.push(&input->trigger_data_queue); lf_ret_t ret = sched->schedule_at_locked(sched, &input->super.super, tag); if (ret == LF_OK) { env->platform->new_async_event(env->platform); diff --git a/src/platform/posix/posix.c b/src/platform/posix/posix.c index 2d66ebf2..54a77cc3 100644 --- a/src/platform/posix/posix.c +++ b/src/platform/posix/posix.c @@ -35,7 +35,7 @@ instant_t PlatformPosix_get_physical_time(Platform *self) { (void)self; struct timespec tspec; if (clock_gettime(CLOCK_REALTIME, (struct timespec *)&tspec) != 0) { - validate(false); + throw("POSIX could not get physical time"); } return convert_timespec_to_ns(tspec); } diff --git a/src/reactor.c b/src/reactor.c index 2b8b3352..cf483f24 100644 --- a/src/reactor.c +++ b/src/reactor.c @@ -13,8 +13,9 @@ void Reactor_register_startup(Reactor *self, Startup *startup) { (void)self; LF_DEBUG(ENV, "Registering startup trigger %p with Reactor %s", startup, self->name); Environment *env = self->env; + Scheduler *sched = &env->scheduler; if (!env->startup) { - tag_t start_tag = {.microstep = 0, .time = self->env->start_time}; + tag_t start_tag = {.microstep = 0, .time = sched->start_time}; validaten(env->scheduler.schedule_at(&env->scheduler, &startup->super, start_tag)); env->startup = startup; } else { diff --git a/src/scheduler.c b/src/scheduler.c index fbbd76c1..f517b82e 100644 --- a/src/scheduler.c +++ b/src/scheduler.c @@ -3,80 +3,13 @@ #include "reactor-uc/logging.h" #include "reactor-uc/reactor-uc.h" -void Scheduler_register_for_cleanup(Scheduler *self, Trigger *trigger) { - LF_DEBUG(SCHED, "Registering trigger %p for cleanup", trigger); - if (trigger->is_registered_for_cleanup) { - return; - } - - if (self->cleanup_ll_head) { - self->cleanup_ll_tail->next = trigger; - self->cleanup_ll_tail = trigger; - } else { - validaten(self->cleanup_ll_tail); - self->cleanup_ll_head = trigger; - self->cleanup_ll_tail = trigger; - } - trigger->is_registered_for_cleanup = true; -} - -void Scheduler_prepare_timestep(Scheduler *self, tag_t tag) { - LF_DEBUG(SCHED, "Preparing timestep for tag %" PRId64 ":%" PRIu32, tag.time, tag.microstep); - self->env->current_tag = tag; - self->executing_tag = true; - self->reaction_queue.reset(&self->reaction_queue); -} - -void Scheduler_clean_up_timestep(Scheduler *self) { - assert(self->executing_tag); - assert(self->reaction_queue.empty(&self->reaction_queue)); - LF_DEBUG(SCHED, "Cleaning up timestep for tag %" PRId64 ":%" PRIu32, self->env->current_tag.time, - self->env->current_tag.microstep); - self->executing_tag = false; - Trigger *cleanup_trigger = self->cleanup_ll_head; - - while (cleanup_trigger) { - Trigger *this = cleanup_trigger; - assert(!(this->next == NULL && this != self->cleanup_ll_tail)); - this->cleanup(this); - cleanup_trigger = this->next; - this->next = NULL; - this->is_registered_for_cleanup = false; - } +// Private functions - self->cleanup_ll_head = NULL; - self->cleanup_ll_tail = NULL; -} - -void Scheduler_run_timestep(Scheduler *self) { - while (!self->reaction_queue.empty(&self->reaction_queue)) { - Reaction *reaction = self->reaction_queue.pop(&self->reaction_queue); - validate(reaction); - LF_DEBUG(SCHED, "Executing %s->reaction_%d", reaction->parent->name, reaction->index); - reaction->body(reaction); - } -} - -void Scheduler_terminate(Scheduler *self) { - LF_INFO(SCHED, "Scheduler terminating"); - Environment *env = self->env; - self->prepare_timestep(self, env->stop_tag); - - if (env->has_async_events) { - env->platform->leave_critical_section(env->platform); - } - - Trigger *shutdown = &self->env->shutdown->super; - while (shutdown) { - LF_DEBUG(SCHED, "Doing shutdown trigger %p", shutdown); - shutdown->prepare(shutdown); - shutdown = shutdown->next; - } - self->run_timestep(self); - self->clean_up_timestep(self); -} - -void Scheduler_handle_builtin(Trigger *trigger) { +/** + * @brief Builtin triggers (startup/shutdown) are chained together as a linked + * list and to prepare such a trigger we must iterate through the list. + */ +static void Scheduler_prepare_builtin(Trigger *trigger) { do { trigger->prepare(trigger); if (trigger->type == TRIG_STARTUP) { @@ -87,15 +20,19 @@ void Scheduler_handle_builtin(Trigger *trigger) { } while (trigger); } -void Scheduler_pop_events(Scheduler *self, tag_t next_tag) { +/** + * @brief Pop off all the events from the event queue which have a tag matching + * `next_tag` and prepare the associated triggers. + */ +static void Scheduler_pop_events_and_prepare(Scheduler *self, tag_t next_tag) { do { Event event = self->event_queue.pop(&self->event_queue); - validate(lf_tag_compare(event.tag, next_tag) == 0); + assert(lf_tag_compare(event.tag, next_tag) == 0); LF_DEBUG(SCHED, "Handling event %p for tag %" PRId64 ":%" PRIu32, &event, event.tag.time, event.tag.microstep); Trigger *trigger = event.trigger; if (trigger->type == TRIG_STARTUP || trigger->type == TRIG_SHUTDOWN) { - Scheduler_handle_builtin(trigger); + Scheduler_prepare_builtin(trigger); } else { trigger->prepare(trigger); } @@ -111,7 +48,7 @@ void Scheduler_pop_events(Scheduler *self, tag_t next_tag) { * @param next_tag * @return lf_ret_t */ -lf_ret_t Scheduler_acquire_tag(Scheduler *self, tag_t next_tag) { +static lf_ret_t Scheduler_federated_acquire_tag(Scheduler *self, tag_t next_tag) { LF_DEBUG(SCHED, "Acquiring tag %" PRId64 ":%" PRIu32, next_tag.time, next_tag.microstep); Environment *env = self->env; Reactor *main = env->main; @@ -135,31 +72,99 @@ lf_ret_t Scheduler_acquire_tag(Scheduler *self, tag_t next_tag) { if (additional_sleep > 0) { LF_DEBUG(SCHED, "Need to sleep for additional %" PRId64 " ns", additional_sleep); - instant_t now = env->get_physical_time(env); - return env->wait_until(env, lf_time_add(now, additional_sleep)); + instant_t sleep_until = lf_time_add(env->get_logical_time(env), additional_sleep); + return env->wait_until(env, sleep_until); } else { return LF_OK; } } +void Scheduler_register_for_cleanup(Scheduler *self, Trigger *trigger) { + LF_DEBUG(SCHED, "Registering trigger %p for cleanup", trigger); + if (trigger->is_registered_for_cleanup) { + return; + } + + if (self->cleanup_ll_head == NULL) { + assert(self->cleanup_ll_tail == NULL); + self->cleanup_ll_head = trigger; + self->cleanup_ll_tail = trigger; + } else { + self->cleanup_ll_tail->next = trigger; + self->cleanup_ll_tail = trigger; + } + trigger->is_registered_for_cleanup = true; +} + +void Scheduler_prepare_timestep(Scheduler *self, tag_t tag) { + LF_DEBUG(SCHED, "Preparing timestep for tag %" PRId64 ":%" PRIu32, tag.time, tag.microstep); + self->current_tag = tag; + self->reaction_queue.reset(&self->reaction_queue); +} + +void Scheduler_clean_up_timestep(Scheduler *self) { + assert(self->reaction_queue.empty(&self->reaction_queue)); + + assert(self->cleanup_ll_head && self->cleanup_ll_tail); + LF_DEBUG(SCHED, "Cleaning up timestep for tag %" PRId64 ":%" PRIu32, self->current_tag.time, + self->current_tag.microstep); + Trigger *cleanup_trigger = self->cleanup_ll_head; + + while (cleanup_trigger) { + Trigger *this = cleanup_trigger; + assert(!(this->next == NULL && this != self->cleanup_ll_tail)); + this->cleanup(this); + this->is_registered_for_cleanup = false; + cleanup_trigger = this->next; + this->next = NULL; + } + + self->cleanup_ll_head = NULL; + self->cleanup_ll_tail = NULL; +} + +void Scheduler_run_timestep(Scheduler *self) { + while (!self->reaction_queue.empty(&self->reaction_queue)) { + Reaction *reaction = self->reaction_queue.pop(&self->reaction_queue); + assert(reaction); + LF_DEBUG(SCHED, "Executing %s->reaction_%d", reaction->parent->name, reaction->index); + reaction->body(reaction); + } +} + +void Scheduler_terminate(Scheduler *self) { + LF_INFO(SCHED, "Scheduler terminating"); + Environment *env = self->env; + self->prepare_timestep(self, self->stop_tag); + + env->leave_critical_section(env); + + Trigger *shutdown = &self->env->shutdown->super; + if (shutdown) { + Scheduler_prepare_builtin(shutdown); + self->run_timestep(self); + self->clean_up_timestep(self); + } +} + void Scheduler_run(Scheduler *self) { Environment *env = self->env; - int res = 0; + lf_ret_t res = 0; bool do_shutdown = false; - bool keep_alive = env->keep_alive || env->has_async_events; - LF_INFO(SCHED, "Scheduler running with keep_alive=%d has_async_events=%d", keep_alive, env->has_async_events); + bool non_terminating = self->keep_alive || env->has_async_events; + LF_INFO(SCHED, "Scheduler running with non_terminating=%d has_async_events=%d", non_terminating, + env->has_async_events); - if (env->has_async_events) { - env->platform->enter_critical_section(env->platform); - } + env->enter_critical_section(env); - while (keep_alive || !self->event_queue.empty(&self->event_queue)) { + while (non_terminating || !self->event_queue.empty(&self->event_queue)) { tag_t next_tag = self->event_queue.next_tag(&self->event_queue); LF_DEBUG(SCHED, "Next event is at %" PRId64 ":%" PRIu32, next_tag.time, next_tag.microstep); - if (lf_tag_compare(next_tag, self->env->stop_tag) > 0) { - LF_INFO(SCHED, "Next event is beyond stop tag: %" PRId64 ":%" PRIu32, self->env->stop_tag.time, - self->env->stop_tag.microstep); - next_tag = self->env->stop_tag; + + if (lf_tag_compare(next_tag, self->stop_tag) > 0) { + LF_INFO(SCHED, "Next event is beyond stop tag: %" PRId64 ":%" PRIu32, self->stop_tag.time, + self->stop_tag.microstep); + next_tag = self->stop_tag; do_shutdown = true; } else { do_shutdown = false; @@ -170,57 +175,53 @@ void Scheduler_run(Scheduler *self) { LF_DEBUG(SCHED, "Sleep interrupted before completion"); continue; } else if (res != LF_OK) { - validate(false); + throw("Sleep failed"); } - // Acquire tag - res = Scheduler_acquire_tag(self, next_tag); + // For federated execution, acquire next_tag before proceeding. This function + // might sleep and will return LF_SLEEP_INTERRUPTED if sleep was interrupted. + res = Scheduler_federated_acquire_tag(self, next_tag); if (res == LF_SLEEP_INTERRUPTED) { LF_DEBUG(SCHED, "Sleep interrupted while waiting for STAA"); continue; } + // Once we are here, we have are committed to executing `next_tag`. if (do_shutdown) { break; } self->prepare_timestep(self, next_tag); - Scheduler_pop_events(self, next_tag); + Scheduler_pop_events_and_prepare(self, next_tag); LF_DEBUG(SCHED, "Acquired tag %" PRId64 ":%" PRIu32, next_tag.time, next_tag.microstep); - // TODO: The critical section could be smaller. - if (env->has_async_events) { - env->platform->leave_critical_section(env->platform); - } + env->leave_critical_section(env); self->run_timestep(self); self->clean_up_timestep(self); - if (env->has_async_events) { - env->platform->enter_critical_section(env->platform); - } + env->enter_critical_section(env); } self->terminate(self); } lf_ret_t Scheduler_schedule_at_locked(Scheduler *self, Trigger *trigger, tag_t tag) { - Environment *env = self->env; Event event = {.tag = tag, .trigger = trigger}; // Check if we are trying to schedule past stop tag - if (lf_tag_compare(tag, env->stop_tag) > 0) { + if (lf_tag_compare(tag, self->stop_tag) > 0) { LF_WARN(SCHED, "Trying to schedule trigger %p at tag %" PRId64 ":%" PRIu32 " past stop tag %" PRId64 ":%" PRIu32, - trigger, tag.time, tag.microstep, env->stop_tag.time, env->stop_tag.microstep); + trigger, tag.time, tag.microstep, self->stop_tag.time, self->stop_tag.microstep); return LF_AFTER_STOP_TAG; } // Check if we are tring to schedule into the past - if (lf_tag_compare(tag, env->current_tag) <= 0) { + if (lf_tag_compare(tag, self->current_tag) <= 0) { LF_WARN(SCHED, "Trying to schedule trigger %p at tag %" PRId64 ":%" PRIu32 " which is before current tag %" PRId64 ":%" PRIu32, - trigger, tag.time, tag.microstep, env->current_tag.time, env->current_tag.microstep); + trigger, tag.time, tag.microstep, self->current_tag.time, self->current_tag.microstep); return LF_PAST_TAG; } @@ -235,18 +236,20 @@ lf_ret_t Scheduler_schedule_at_locked(Scheduler *self, Trigger *trigger, tag_t t lf_ret_t Scheduler_schedule_at(Scheduler *self, Trigger *trigger, tag_t tag) { Environment *env = self->env; - if (env->has_async_events) { - env->platform->enter_critical_section(env->platform); - } + env->enter_critical_section(env); int res = self->schedule_at_locked(self, trigger, tag); - if (env->has_async_events) { - env->platform->leave_critical_section(env->platform); - } + env->leave_critical_section(env); + return res; } +void Scheduler_set_timeout(Scheduler *self, interval_t duration) { + self->stop_tag.microstep = 0; + self->stop_tag.time = self->start_time + duration; +} + void Scheduler_ctor(Scheduler *self, Environment *env) { self->env = env; self->run = Scheduler_run; @@ -257,9 +260,17 @@ void Scheduler_ctor(Scheduler *self, Environment *env) { self->schedule_at = Scheduler_schedule_at; self->schedule_at_locked = Scheduler_schedule_at_locked; self->register_for_cleanup = Scheduler_register_for_cleanup; - self->executing_tag = false; + self->set_timeout = Scheduler_set_timeout; + self->keep_alive = false; + self->stop_tag = FOREVER_TAG; + self->current_tag = NEVER_TAG; self->cleanup_ll_head = NULL; self->cleanup_ll_tail = NULL; EventQueue_ctor(&self->event_queue); ReactionQueue_ctor(&self->reaction_queue); + + // Set start time + // FIXMEi: This must be resolved in the federation. Currently set start tag to nearest second. + self->start_time = ((self->env->platform->get_physical_time(self->env->platform) + SEC(1)) / SEC(1)) * SEC(1); + LF_INFO(ENV, "Start time: %" PRId64, self->start_time); } diff --git a/src/tag.c b/src/tag.c index 2a1eccb9..e69d8226 100644 --- a/src/tag.c +++ b/src/tag.c @@ -11,18 +11,6 @@ #include "reactor-uc/tag.h" #include "reactor-uc/environment.h" -/** - * An enum for specifying the desired tag when calling "lf_time" - */ -typedef enum { LF_LOGICAL, LF_PHYSICAL, LF_ELAPSED_LOGICAL, LF_ELAPSED_PHYSICAL, LF_START } lf_time_type; - -//////////////// Functions declared in tag.h - -tag_t lf_tag(void *env) { - (void)env; - return ((Environment *)env)->current_tag; -} - instant_t lf_time_add(instant_t time, interval_t interval) { if (time == NEVER || interval == NEVER) { return NEVER; diff --git a/src/timer.c b/src/timer.c index 75aa1b89..ac571c81 100644 --- a/src/timer.c +++ b/src/timer.c @@ -24,7 +24,7 @@ void Timer_cleanup(Trigger *_self) { // Schedule next event unless it is a single-shot timer. if (self->period > NEVER) { - tag_t next_tag = lf_delay_tag(env->current_tag, self->period); + tag_t next_tag = lf_delay_tag(sched->current_tag, self->period); sched->schedule_at(sched, _self, next_tag); } } @@ -41,6 +41,6 @@ void Timer_ctor(Timer *self, Reactor *parent, instant_t offset, interval_t perio // Schedule first Scheduler *sched = &self->super.parent->env->scheduler; - tag_t tag = {.microstep = 0, .time = offset + self->super.parent->env->start_time}; + tag_t tag = {.microstep = 0, .time = offset + sched->start_time}; sched->schedule_at(sched, &self->super, tag); } diff --git a/src/trigger.c b/src/trigger.c index ca13efe1..50200d49 100644 --- a/src/trigger.c +++ b/src/trigger.c @@ -5,8 +5,8 @@ #include const void *Trigger_get(Trigger *self) { - if (self->trigger_value) { - TriggerValue *tval = self->trigger_value; + if (self->trigger_data_queue) { + TriggerDataQueue *tval = self->trigger_data_queue; return &tval->buffer[tval->read_idx * tval->value_size]; } else { assert(false); @@ -14,7 +14,7 @@ const void *Trigger_get(Trigger *self) { } } -void Trigger_ctor(Trigger *self, TriggerType type, Reactor *parent, TriggerValue *trigger_value, +void Trigger_ctor(Trigger *self, TriggerType type, Reactor *parent, TriggerDataQueue *trigger_data_queue, void (*prepare)(Trigger *), void (*cleanup)(Trigger *), const void *(*get)(Trigger *)) { self->type = type; self->parent = parent; @@ -23,7 +23,7 @@ void Trigger_ctor(Trigger *self, TriggerType type, Reactor *parent, TriggerValue self->is_registered_for_cleanup = false; self->prepare = prepare; self->cleanup = cleanup; - self->trigger_value = trigger_value; + self->trigger_data_queue = trigger_data_queue; if (get) { self->get = get; } else { diff --git a/src/trigger_value.c b/src/trigger_data_queue.c similarity index 59% rename from src/trigger_value.c rename to src/trigger_data_queue.c index 9e1fe45a..f57d3f52 100644 --- a/src/trigger_value.c +++ b/src/trigger_data_queue.c @@ -1,12 +1,12 @@ -#include "reactor-uc/trigger_value.h" +#include "reactor-uc/trigger_data_queue.h" #include "reactor-uc/logging.h" #include #include #include -lf_ret_t TriggerValue_stage(TriggerValue *self, const void *value) { +lf_ret_t TriggerDataQueue_stage(TriggerDataQueue *self, const void *value) { if (!self->empty && self->read_idx == self->write_idx) { - LF_ERR(TRIG, "Could not stage value, TriggerValue %p is full", self); + LF_ERR(TRIG, "Could not stage value, TriggerDataQueue %p is full", self); return LF_OUT_OF_BOUNDS; } memcpy(self->buffer + self->write_idx * self->value_size, value, self->value_size); // NOLINT @@ -14,9 +14,9 @@ lf_ret_t TriggerValue_stage(TriggerValue *self, const void *value) { return LF_OK; } -lf_ret_t TriggerValue_push(TriggerValue *self) { +lf_ret_t TriggerDataQueue_push(TriggerDataQueue *self) { if (!self->staged) { - LF_ERR(TRIG, "Could not push value, no value staged in TriggerValue %p", self); + LF_ERR(TRIG, "Could not push value, no value staged in TriggerDataQueue %p", self); return LF_INVALID_VALUE; } @@ -27,9 +27,9 @@ lf_ret_t TriggerValue_push(TriggerValue *self) { return 0; } -lf_ret_t TriggerValue_pop(TriggerValue *self) { +lf_ret_t TriggerDataQueue_pop(TriggerDataQueue *self) { if (self->empty) { - LF_ERR(TRIG, "Could not pop value, TriggerValue %p is empty", self); + LF_ERR(TRIG, "Could not pop value, TriggerDataQueue %p is empty", self); return LF_EMPTY; } @@ -41,7 +41,7 @@ lf_ret_t TriggerValue_pop(TriggerValue *self) { return LF_OK; } -void TriggerValue_ctor(TriggerValue *self, char *buffer, size_t value_size, size_t capacity) { +void TriggerDataQueue_ctor(TriggerDataQueue *self, char *buffer, size_t value_size, size_t capacity) { self->buffer = buffer; self->value_size = value_size; self->capacity = capacity; @@ -49,7 +49,7 @@ void TriggerValue_ctor(TriggerValue *self, char *buffer, size_t value_size, size self->write_idx = 0; self->empty = true; self->staged = true; - self->push = TriggerValue_push; - self->pop = TriggerValue_pop; - self->stage = TriggerValue_stage; + self->push = TriggerDataQueue_push; + self->pop = TriggerDataQueue_pop; + self->stage = TriggerDataQueue_stage; } diff --git a/test/unit/action_microstep_test.c b/test/unit/action_microstep_test.c index a1e64496..32e297dd 100644 --- a/test/unit/action_microstep_test.c +++ b/test/unit/action_microstep_test.c @@ -55,7 +55,7 @@ void action_handler(Reaction *_self) { printf("Action = %d\n", lf_get(my_action)); if (self->cnt > 0) { TEST_ASSERT_EQUAL(self->cnt, lf_get(my_action)); - TEST_ASSERT_EQUAL(self->cnt, env->current_tag.microstep); + TEST_ASSERT_EQUAL(self->cnt, env->scheduler.current_tag.microstep); TEST_ASSERT_EQUAL(true, lf_is_present(my_action)); } else { TEST_ASSERT_EQUAL(false, lf_is_present(my_action)); @@ -91,7 +91,6 @@ void test_simple() { Environment env; Environment_ctor(&env, (Reactor *)&my_reactor); MyReactor_ctor(&my_reactor, &env); - env.set_timeout(&env, SEC(1)); env.assemble(&env); env.start(&env); } diff --git a/test/unit/action_test.c b/test/unit/action_test.c index 2d6786ef..e6a0796e 100644 --- a/test/unit/action_test.c +++ b/test/unit/action_test.c @@ -82,7 +82,7 @@ void test_simple() { Environment env; Environment_ctor(&env, (Reactor *)&my_reactor); MyReactor_ctor(&my_reactor, &env); - env.set_timeout(&env, SEC(1)); + env.scheduler.set_timeout(&env.scheduler, SEC(1)); env.assemble(&env); env.start(&env); } diff --git a/test/unit/delayed_conn_test.c b/test/unit/delayed_conn_test.c index 03c1dbb9..9295d666 100644 --- a/test/unit/delayed_conn_test.c +++ b/test/unit/delayed_conn_test.c @@ -145,7 +145,7 @@ void test_simple() { Environment env; Environment_ctor(&env, (Reactor *)&main); Main_ctor(&main, &env); - env.set_timeout(&env, SEC(1)); + env.scheduler.set_timeout(&env.scheduler, SEC(1)); env.assemble(&env); env.start(&env); } diff --git a/test/unit/physical_action_test.c b/test/unit/physical_action_test.c index 30080241..e1b01b29 100644 --- a/test/unit/physical_action_test.c +++ b/test/unit/physical_action_test.c @@ -135,7 +135,7 @@ void test_simple() { struct MyReactor my_reactor; Environment_ctor(&env, (Reactor *)&my_reactor); MyReactor_ctor(&my_reactor, &env); - env.set_timeout(&env, SEC(1)); + env.scheduler.set_timeout(&env.scheduler, SEC(1)); env.assemble(&env); env.start(&env); } diff --git a/test/unit/port_test.c b/test/unit/port_test.c index a67bb779..38f4984b 100644 --- a/test/unit/port_test.c +++ b/test/unit/port_test.c @@ -139,7 +139,7 @@ void test_simple() { Environment env; Environment_ctor(&env, (Reactor *)&main); Main_ctor(&main, &env); - env.set_timeout(&env, SEC(1)); + env.scheduler.set_timeout(&env.scheduler, SEC(1)); env.assemble(&env); env.start(&env); } diff --git a/test/unit/timer_test.c b/test/unit/timer_test.c index 278e9c5b..9e0acdb7 100644 --- a/test/unit/timer_test.c +++ b/test/unit/timer_test.c @@ -20,7 +20,8 @@ struct MyReactor { void timer_handler(Reaction *_self) { struct MyReactor *self = (struct MyReactor *)_self->parent; - printf("Hello World @ %ld\n", self->super.env->current_tag.time); + Environment *env = self->super.env; + printf("Hello World @ %ld\n", env->get_elapsed_logical_time(env)); } void MyReaction_ctor(MyReaction *self, Reactor *parent) { @@ -40,7 +41,7 @@ void test_simple() { struct MyReactor my_reactor; Environment env; Environment_ctor(&env, (Reactor *)&my_reactor); - env.set_timeout(&env, SEC(1)); + env.scheduler.set_timeout(&env.scheduler, SEC(1)); MyReactor_ctor(&my_reactor, &env); env.assemble(&env); env.start(&env); diff --git a/test/unit/trigger_value_test.c b/test/unit/trigger_data_queue_test.c similarity index 79% rename from test/unit/trigger_value_test.c rename to test/unit/trigger_data_queue_test.c index 267876cb..46bd6a07 100644 --- a/test/unit/trigger_value_test.c +++ b/test/unit/trigger_data_queue_test.c @@ -1,13 +1,13 @@ #include "unity.h" -#include "reactor-uc/trigger_value.h" +#include "reactor-uc/trigger_data_queue.h" // Verify that we can push a bunch of values and then pop them off void test_push_pop(void) { - TriggerValue t; + TriggerDataQueue t; int buffer[10]; - TriggerValue_ctor(&t, (char *)&buffer, sizeof(int), 10); + TriggerDataQueue_ctor(&t, (char *)&buffer, sizeof(int), 10); for (int j = 0; j < 3; j++) { int val = 1; @@ -27,9 +27,9 @@ void test_push_pop(void) { void test_pop_empty(void) { - TriggerValue t; + TriggerDataQueue t; int buffer[10]; - TriggerValue_ctor(&t, (char *)&buffer, sizeof(int), 10); + TriggerDataQueue_ctor(&t, (char *)&buffer, sizeof(int), 10); int val = 2; t.stage(&t, (const void *)&val); t.push(&t);