Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
compiler_backend_c_test.cc
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
36#include <gtest/gtest.h>
37
38#include <atomic>
39#include <chrono>
40#include <cstdlib>
41#include <filesystem>
42#include <fstream>
43#include <string>
44#if defined(_WIN32)
45# include <process.h>
46#else
47# include <unistd.h>
48#endif
49
50#include <Compiler_Backend_C.H>
51
52using namespace Aleph;
53
54namespace
55{
56 struct Command_Result
57 {
58 int status = -1;
59 std::string stdout_text;
60 std::string stderr_text;
61 };
62
65 {
67 block.id = 1;
68 block.label = "exit";
69 block.terminator.kind = Compiler_IR_Terminator_Kind::Exit;
70 return block;
71 }
72
75 {
76 Compiler_IR_Function function;
77 function.id = 0;
78 function.name = "choose";
79 function.entry_block = 0;
80 function.exit_block = 1;
81 function.next_value_id = 5;
82
84 flag.id = 0;
85 flag.kind = Compiler_IR_Slot_Kind::Parameter;
86 flag.name = "flag";
87 function.local_slots.append(flag);
88
90 value.id = 1;
91 value.kind = Compiler_IR_Slot_Kind::Local;
92 value.name = "value";
93 function.local_slots.append(value);
94
96 entry.id = 0;
97 entry.label = "entry";
98
100 zero.kind = Compiler_IR_Instruction_Kind::Constant;
101 zero.result_id = 1;
102 zero.text = "0";
103 entry.instructions.append(zero);
104
106 init_store.kind = Compiler_IR_Instruction_Kind::Store;
107 init_store.local_slot_id = 1;
108 init_store.operands.append(1);
109 entry.instructions.append(init_store);
110
112 load_flag.kind = Compiler_IR_Instruction_Kind::Load;
113 load_flag.result_id = 2;
114 load_flag.local_slot_id = 0;
115 entry.instructions.append(load_flag);
116
117 entry.terminator.kind = Compiler_IR_Terminator_Kind::Branch;
118 entry.terminator.condition_value = 2;
121
122 auto exit = make_exit_block();
123 exit.predecessors.append(3);
124
126 then_block.id = 2;
127 then_block.label = "if.then.0";
128
130 one.kind = Compiler_IR_Instruction_Kind::Constant;
131 one.result_id = 3;
132 one.text = "1";
133 then_block.instructions.append(one);
134
136 then_store.kind = Compiler_IR_Instruction_Kind::Store;
137 then_store.local_slot_id = 1;
138 then_store.operands.append(3);
139 then_block.instructions.append(then_store);
140
141 then_block.terminator.kind = Compiler_IR_Terminator_Kind::Jump;
142 then_block.terminator.successors.append(3);
143 then_block.predecessors.append(0);
144
146 join_block.id = 3;
147 join_block.label = "if.end.0";
148
150 load_value.kind = Compiler_IR_Instruction_Kind::Load;
151 load_value.result_id = 5;
152 load_value.local_slot_id = 1;
153 join_block.instructions.append(load_value);
154
155 join_block.terminator.kind = Compiler_IR_Terminator_Kind::Return;
156 join_block.terminator.return_value = 5;
157 join_block.terminator.successors.append(1);
158 join_block.predecessors.append(2);
159 join_block.predecessors.append(4);
160
162 else_block.id = 4;
163 else_block.label = "if.else.0";
164
166 two.kind = Compiler_IR_Instruction_Kind::Constant;
167 two.result_id = 4;
168 two.text = "2";
169 else_block.instructions.append(two);
170
172 else_store.kind = Compiler_IR_Instruction_Kind::Store;
173 else_store.local_slot_id = 1;
174 else_store.operands.append(4);
175 else_block.instructions.append(else_store);
176
177 else_block.terminator.kind = Compiler_IR_Terminator_Kind::Jump;
178 else_block.terminator.successors.append(3);
179 else_block.predecessors.append(0);
180
181 function.blocks.append(entry);
182 function.blocks.append(exit);
183 function.blocks.append(then_block);
184 function.blocks.append(join_block);
185 function.blocks.append(else_block);
186 return function;
187 }
188
191 {
192 Compiler_IR_Function function;
193 function.id = 0;
194 function.name = "add_one";
195 function.entry_block = 0;
196 function.exit_block = 1;
197 function.next_value_id = 4;
198
200 param.id = 0;
201 param.kind = Compiler_IR_Slot_Kind::Parameter;
202 param.name = "x";
203 function.local_slots.append(param);
204
205 Compiler_IR_Slot local;
206 local.id = 1;
207 local.kind = Compiler_IR_Slot_Kind::Local;
208 local.name = "tmp";
209 function.local_slots.append(local);
210
211 Compiler_IR_Block entry;
212 entry.id = 0;
213 entry.label = "entry";
214
216 constant.kind = Compiler_IR_Instruction_Kind::Constant;
217 constant.result_id = 1;
218 constant.text = "1";
219 entry.instructions.append(constant);
220
222 store.kind = Compiler_IR_Instruction_Kind::Store;
223 store.local_slot_id = 1;
224 store.operands.append(1);
225 entry.instructions.append(store);
226
228 load_x.kind = Compiler_IR_Instruction_Kind::Load;
229 load_x.result_id = 2;
230 load_x.local_slot_id = 0;
231 entry.instructions.append(load_x);
232
234 load_tmp.kind = Compiler_IR_Instruction_Kind::Load;
235 load_tmp.result_id = 3;
236 load_tmp.local_slot_id = 1;
237 entry.instructions.append(load_tmp);
238
240 add.kind = Compiler_IR_Instruction_Kind::Binary;
241 add.result_id = 4;
242 add.op = Compiler_Operator_Kind::Plus;
243 add.operands.append(2);
244 add.operands.append(3);
245 entry.instructions.append(add);
246
247 entry.terminator.kind = Compiler_IR_Terminator_Kind::Return;
248 entry.terminator.return_value = 4;
250
251 auto exit = make_exit_block();
252 exit.predecessors.append(0);
253
254 function.blocks.append(entry);
255 function.blocks.append(exit);
256 return function;
257 }
258
261 {
262 Compiler_IR_Module module;
263
265 global.id = 0;
266 global.kind = Compiler_IR_Slot_Kind::Global;
267 global.name = "answer";
268 module.global_slots.append(global);
269
270 auto * function = new Compiler_IR_Function(make_linear_ir());
271 module.functions.append(function);
272
273 auto * top = new Compiler_IR_Function();
274 top->id = compiler_ir_invalid_id();
275 top->name = "<top-level>";
276 top->entry_block = 0;
277 top->exit_block = 1;
278 top->next_value_id = 3;
279
280 Compiler_IR_Block entry;
281 entry.id = 0;
282 entry.label = "entry";
283
285 function_ref.kind = Compiler_IR_Instruction_Kind::Function_Ref;
286 function_ref.result_id = 1;
287 function_ref.function_id = 0;
288 entry.instructions.append(function_ref);
289
291 constant.kind = Compiler_IR_Instruction_Kind::Constant;
292 constant.result_id = 2;
293 constant.text = "41";
294 entry.instructions.append(constant);
295
297 call.kind = Compiler_IR_Instruction_Kind::Call;
298 call.result_id = 3;
299 call.operands.append(1);
300 call.operands.append(2);
301 entry.instructions.append(call);
302
304 store_global.kind = Compiler_IR_Instruction_Kind::Store;
305 store_global.global_slot_id = 0;
306 store_global.operands.append(3);
307 entry.instructions.append(store_global);
308
309 entry.terminator.kind = Compiler_IR_Terminator_Kind::Jump;
311
312 auto exit = make_exit_block();
313 exit.predecessors.append(0);
314
315 top->blocks.append(entry);
316 top->blocks.append(exit);
317 module.top_level = top;
318 return module;
319 }
320
323 {
324 Compiler_IR_Module module;
325
326 auto * top = new Compiler_IR_Function();
327 top->id = compiler_ir_invalid_id();
328 top->name = "<top-level>";
329 top->entry_block = 0;
330 top->exit_block = 1;
331 top->next_value_id = 3;
332
333 Compiler_IR_Block entry;
334 entry.id = 0;
335 entry.label = "entry";
336
338 lhs.kind = Compiler_IR_Instruction_Kind::Constant;
339 lhs.result_id = 1;
340 lhs.text = "5";
341 entry.instructions.append(lhs);
342
344 rhs.kind = Compiler_IR_Instruction_Kind::Constant;
345 rhs.result_id = 2;
346 rhs.text = "0";
347 entry.instructions.append(rhs);
348
350 div.kind = Compiler_IR_Instruction_Kind::Binary;
351 div.result_id = 3;
352 div.op = Compiler_Operator_Kind::Slash;
353 div.operands.append(1);
354 div.operands.append(2);
355 entry.instructions.append(div);
356
357 entry.terminator.kind = Compiler_IR_Terminator_Kind::Return;
358 entry.terminator.return_value = 3;
360
361 auto exit = make_exit_block();
362 exit.predecessors.append(0);
363
364 top->blocks.append(entry);
365 top->blocks.append(exit);
366 module.top_level = top;
367 return module;
368 }
369
370 std::string
371 read_text(const std::filesystem::path & path)
372 {
373 std::ifstream in(path);
374 return std::string((std::istreambuf_iterator<char>(in)),
375 std::istreambuf_iterator<char>());
376 }
377
378 void
379 write_text(const std::filesystem::path & path,
380 const std::string & text)
381 {
382 std::ofstream out(path);
383 out << text;
384 }
385
386 std::string
387 shell_quote(const std::string & text)
388 {
389 std::string quoted = "'";
390 for (const char ch : text)
391 {
392 if (ch == '\'')
393 quoted += "'\\''";
394 else
395 quoted.push_back(ch);
396 }
397 quoted.push_back('\'');
398 return quoted;
399 }
400
401 bool
403 {
404 return std::system("command -v cc >/dev/null 2>&1") == 0;
405 }
406
408 long long process_id() noexcept
409 {
410#if defined(_WIN32)
411 return static_cast<long long>(_getpid());
412#else
413 return static_cast<long long>(getpid());
414#endif
415 }
416
417 std::filesystem::path
418 unique_temp_path(const std::string & stem,
419 const std::string & extension)
420 {
421 // A steady_clock tick alone is unique *within* a process, but not
422 // across the several processes that actually run this suite (each
423 // TEST() is its own ctest process, and CI runs ctest with
424 // --parallel) -- two processes' calls landing in the same clock
425 // tick produce the identical name and race on the same file.
426 // Mixing in the process id and a per-process counter closes that
427 // gap regardless of clock resolution or call count.
428 static std::atomic<unsigned long long> counter{0};
429 const auto nonce = std::to_string(
430 std::chrono::steady_clock::now().time_since_epoch().count()) +
431 "_" + std::to_string(process_id()) +
432 "_" + std::to_string(counter++);
433 return std::filesystem::temp_directory_path()
434 / (stem + "_" + nonce + extension);
435 }
436
437 Command_Result
438 run_command(const std::string & command)
439 {
440 Command_Result result;
441 const auto stdout_path = unique_temp_path("aleph_backend_stdout", ".txt");
442 const auto stderr_path = unique_temp_path("aleph_backend_stderr", ".txt");
443 const auto full_command = command
444 + " >" + shell_quote(stdout_path.string())
445 + " 2>" + shell_quote(stderr_path.string());
446
447 result.status = std::system(full_command.c_str());
448 result.stdout_text = read_text(stdout_path);
449 result.stderr_text = read_text(stderr_path);
450 std::error_code ignore_error;
451 std::filesystem::remove(stdout_path, ignore_error);
452 std::filesystem::remove(stderr_path, ignore_error);
453 return result;
454 }
455
456 Command_Result
458 const std::string & extra_c = "")
459 {
460 const auto source_path = unique_temp_path("aleph_backend_generated", ".c");
461 const auto exe_path = unique_temp_path("aleph_backend_generated", ".out");
462
463 write_text(source_path, emission.source + "\n" + extra_c);
464 const auto compile = run_command(
465 "cc -std=c11 -Wall -Wextra -pedantic "
466 + shell_quote(source_path.string())
467 + " -o "
468 + shell_quote(exe_path.string()));
469 if (compile.status != 0)
470 return compile;
471
472 const auto executed = run_command(shell_quote(exe_path.string()));
473 std::error_code ignore_error;
474 std::filesystem::remove(source_path, ignore_error);
475 std::filesystem::remove(exe_path, ignore_error);
476 return executed;
477 }
478}
479
481{
482 Compiler_IR_Module module;
483 auto source = make_branch_ir();
484 module.functions.append(&source);
485
487 options.module_name = "branch-demo";
488
489 const auto emitted = compiler_emit_c_module(&module, options);
490 ASSERT_TRUE(emitted.valid);
491 EXPECT_NE(emitted.source.find("goto " + emitted.c_prefix + "_B3"), std::string::npos);
492 EXPECT_NE(emitted.source.find("if ("), std::string::npos);
493 EXPECT_NE(emitted.source.find("static " + emitted.value_type_name), std::string::npos);
494 EXPECT_NE(emitted.source.find(emitted.function_symbols.access(0)), std::string::npos);
495 EXPECT_NE(emitted.source.find("choose"), std::string::npos);
496}
497
499{
500 if (not host_has_cc())
501 GTEST_SKIP() << "No se encontrĂ³ `cc` en el host";
502
503 auto module = make_module_with_top_level_call();
504
506 options.module_name = "top_level_demo";
507 options.emit_main = true;
508
509 const auto emitted = compiler_emit_c_module(&module, options);
510 ASSERT_TRUE(emitted.valid);
511
512 const auto executed = compile_and_run(emitted);
513 EXPECT_EQ(executed.status, 0);
514 EXPECT_NE(executed.stdout_text.find("Result = Unit"), std::string::npos);
515 EXPECT_NE(executed.stdout_text.find("g0 answer = Int(42)"), std::string::npos);
516 EXPECT_TRUE(executed.stderr_text.empty());
517
518 delete module.functions.access(0);
519 delete module.top_level;
520}
521
523{
524 if (not host_has_cc())
525 GTEST_SKIP() << "No se encontrĂ³ `cc` en el host";
526
527 Compiler_IR_Module module;
528 auto source = make_branch_ir();
529 module.functions.append(&source);
530
532 options.module_name = "direct_call_demo";
533 const auto emitted = compiler_emit_c_module(&module, options);
534 ASSERT_TRUE(emitted.valid);
535
536 const auto harness =
537 "\nint main(void)\n"
538 "{\n"
539 " " + emitted.module_struct_name + " module;\n"
540 " " + emitted.module_init_symbol + "(&module);\n"
541 " " + emitted.value_type_name + " args[1];\n"
542 " args[0] = " + emitted.c_prefix + "_make_bool(false);\n"
543 " " + emitted.value_type_name + " result = "
544 + emitted.function_symbols.access(0) + "(&module, args, 1u);\n"
545 " " + emitted.print_value_symbol + "(stdout, result);\n"
546 " fputc('\\n', stdout);\n"
547 " return 0;\n"
548 "}\n";
549
550 const auto executed = compile_and_run(emitted, harness);
551 EXPECT_EQ(executed.status, 0);
552 EXPECT_EQ(executed.stdout_text, "Int(2)\n");
553 EXPECT_TRUE(executed.stderr_text.empty());
554}
555
557{
558 if (not host_has_cc())
559 GTEST_SKIP() << "No se encontrĂ³ `cc` en el host";
560
561 auto module = make_divide_by_zero_top_level_module();
562
564 options.module_name = "panic_demo";
565 options.emit_main = true;
566
567 const auto emitted = compiler_emit_c_module(&module, options);
568 ASSERT_TRUE(emitted.valid);
569
570 const auto executed = compile_and_run(emitted);
571 EXPECT_NE(executed.status, 0);
572 EXPECT_NE(executed.stderr_text.find("division by zero"), std::string::npos);
573
574 delete module.top_level;
575}
Portable C backend that emits standalone C from Compiler_IR_Model.H.
size_t size_t int32_t value
Definition ca-c-api.h:116
size_t size_t int32_t * out
Definition ca-c-api.h:120
T & append()
Allocate a new entry to the end of array.
#define TEST(name)
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
std::string shell_quote(const std::string &s)
const char * extension(Frame_Output_Format format) noexcept
Compiler_SSA_Block & block(Compiler_SSA_Function &function, const Compiler_SSA_Block_Id id)
Definition SSA.H:636
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
void exit(const char *file, int line, const char *format,...)
Print a message and exit the program.
Definition ahDefs.C:125
constexpr size_t compiler_ir_invalid_id() noexcept
Returns the sentinel invalid IR id.
Compiler_C_Backend_Emission compiler_emit_c_module(const Compiler_IR_Module *module, const Compiler_C_Backend_Options &options={}, const Compiler_Type_Context *types=nullptr)
Convenience wrapper that emits one IR module to portable C.
Itor::difference_type count(const Itor &beg, const Itor &end, const T &value)
Count elements equal to a value.
Definition ahAlgo.H:127
double add(double a, double b)
double div(double a, double b)
static struct argp_option options[]
Definition ntreepic.C:1886
Result of one C backend emission request.
Options that control one C backend emission request.
std::string module_name
Prefix used to derive stable C identifiers.
One basic block of IR instructions.
std::string label
Deterministic debug label.
Compiler_IR_Terminator terminator
Explicit block terminator.
DynArray< Compiler_IR_Instruction > instructions
Linear instruction list.
Compiler_IR_Block_Id id
Stable block id.
Lowered IR for one function or top-level body.
Compiler_IR_Value_Id next_value_id
Next value id to allocate while lowering.
DynArray< Compiler_IR_Block > blocks
Basic blocks in deterministic id order.
std::string name
Debug or source-level name.
Compiler_IR_Block_Id exit_block
Canonical exit block.
Compiler_IR_Block_Id entry_block
Canonical entry block.
DynArray< Compiler_IR_Slot > local_slots
Parameter/local slots in stable order.
Compiler_IR_Function_Id id
Stable function id within the module.
One instruction producing an optional explicit result value.
std::string text
Constant spelling or debug payload.
Compiler_IR_Instruction_Kind kind
Instruction category.
Compiler_IR_Value_Id result_id
Produced value id, or 0 for void instructions.
DynArray< Compiler_IR_Value_Id > operands
Input values in deterministic order.
Compiler_IR_Local_Slot_Id local_slot_id
Referenced local/parameter slot, when relevant.
Lowered IR module with shared global slots and functions.
One storage slot used by the IR.
std::string name
Debug name of the slot.
size_t id
Stable slot id within its storage space.
Compiler_IR_Slot_Kind kind
Slot storage category.
Compiler_IR_Terminator_Kind kind
Terminator category.
DynArray< Compiler_IR_Block_Id > successors
Successor blocks in deterministic order.
Compiler_IR_Value_Id condition_value
Branch condition value, if any.
Compiler_IR_Value_Id return_value
Return value, if any.
Compiler_SSA_Block_Id id
Dense SSA block id.
Definition SSA.H:163
std::string label
Deterministic debug label.
Definition SSA.H:165
Compiler_SSA_Terminator terminator
Explicit block terminator.
Definition SSA.H:169
Compiler_IR_Terminator_Kind kind
Terminator category.
Definition SSA.H:153
static long counter
Definition test-splice.C:40