Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Absolute-CodeGen/include/internal/codegen_state.inc
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,10 @@
std::string currentReturnTypeName;
llvm::Value* currentReturnStorage = nullptr;
std::string currentClassName;
// Set while a constructor body is being emitted. A constructor that
// leaves through an exception has already initialized some fields, and
// nothing outside holds a name for them, so it releases them itself.
std::string currentConstructorClass;
std::unordered_map<std::string, std::string> currentGenericSubstitutions;
llvm::Value* currentThis = nullptr;
std::uint64_t taskThunkCounter = 0;
Expand Down
29 changes: 29 additions & 0 deletions Absolute-CodeGen/src/codegen_exceptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,35 @@ namespace Absolute {

llvm::Function* function = CurrentFunction();
if (!function) Fail("exception propagation outside a function");

// Leaving a constructor through an exception. Scope unwinding above has
// released the locals, but the fields already stored into `this` are not
// a scope and no caller has a name for them, so they would leak.
//
// Fields are released directly rather than through the class destructor,
// because that destructor first calls the type's own destroy() hook. A
// constructor that threw never established the invariants such a hook
// assumes, so running it here would execute user cleanup on an object
// that was never built.
//
// Only fields are touched, and only once each: currentConstructorClass is
// set after the base constructor has succeeded, so a failing base cleans
// its own fields on its own path and this frame never repeats that work.
// Object storage is zero-initialized before a constructor runs, so fields
// the constructor had not reached yet are null and cost a no-op.
if (!currentConstructorClass.empty() && currentThis) {
if (const auto found = classes.find(currentConstructorClass);
found != classes.end()) {
ClassInfo& partial = found->second;
for (auto field = partial.fields.rbegin();
field != partial.fields.rend(); ++field) {
if (!TypeNeedsCleanup(field->typeName)) continue;
EmitValueCleanup(
FieldAddress(currentThis, partial, *field), field->typeName);
}
}
}

if (function->getName() == "main") {
builder.CreateCall(ErrorReport());
builder.CreateRet(builder.getInt32(1));
Expand Down
10 changes: 10 additions & 0 deletions Absolute-CodeGen/src/codegen_module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -772,11 +772,17 @@ namespace Absolute {
: static_cast<ASTNode*>(info.statement),
info.name + "." + info.name);
const std::string oldClass = currentClassName;
const std::string oldConstructorClass = currentConstructorClass;
llvm::Value* oldThis = currentThis;
const std::string oldReturn = currentReturnTypeName;
const auto oldSubstitutions = currentGenericSubstitutions;
currentGenericSubstitutions = info.substitutions;
currentClassName = info.name;
// Stays empty until the base constructor has succeeded. While the base
// runs, no field of this class is initialized yet, and a base that throws
// releases its own fields on its own exception path — so this frame must
// not clean anything, or inherited fields would be released twice.
currentConstructorClass.clear();
currentThis = function->getArg(0);
currentReturnTypeName = "void";
if (constructor) {
Expand Down Expand Up @@ -871,9 +877,13 @@ namespace Absolute {
EmitExceptionCheck();
}
}
// The base is established; from here a throw must release this object's
// own fields, including the inherited ones the base finished building.
currentConstructorClass = info.name;
if (constructor && constructor->body) constructor->body->Accept(visitor);
FinishClassCallable(*function);
PopScope();
currentConstructorClass = oldConstructorClass;
currentClassName = oldClass;
currentThis = oldThis;
currentReturnTypeName = oldReturn;
Expand Down
8 changes: 6 additions & 2 deletions Absolute-CodeGen/src/codegen_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1198,7 +1198,11 @@ namespace Absolute {

const auto release = [&] { visiting.erase(candidate); };
if (const auto found = classes.find(candidate); found != classes.end()) {
if (found->second.methods.contains("destroy()")) {
// `methods` is keyed by CallableKey, which appends "$"-joined
// parameter types to the bare name. A literal "destroy()" is not
// a key this map can hold, so matching it here silently skipped
// cleanup for every type whose only resource is its own destroy().
if (found->second.methods.contains(CallableKey("destroy", {}))) {
release();
return true;
}
Expand All @@ -1210,7 +1214,7 @@ namespace Absolute {
}
}
else if (const auto found = structs.find(candidate); found != structs.end()) {
if (found->second.methods.contains("destroy()")) {
if (found->second.methods.contains(CallableKey("destroy", {}))) {
release();
return true;
}
Expand Down
6 changes: 6 additions & 0 deletions Absolute-CodeGen/src/codegen_values.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1010,6 +1010,11 @@ namespace Absolute {
llvm::Value* savedReturnStorage = impl->currentReturnStorage;
const std::string savedClass = impl->currentClassName;
llvm::Value* savedThis = impl->currentThis;
// A lambda body emitted inside a constructor is a separate function that
// may run long after construction finished, so it must not inherit the
// constructor's partial-object cleanup.
const std::string savedConstructorClass = impl->currentConstructorClass;
impl->currentConstructorClass.clear();
const auto savedSubstitutions = impl->currentGenericSubstitutions;
llvm::DIScope* savedDebugScope = impl->currentDebugScope;
const auto savedDebugScopeStack = impl->debugScopeStack;
Expand Down Expand Up @@ -1109,6 +1114,7 @@ namespace Absolute {
impl->currentReturnStorage = savedReturnStorage;
impl->currentClassName = savedClass;
impl->currentThis = savedThis;
impl->currentConstructorClass = savedConstructorClass;
impl->currentGenericSubstitutions = savedSubstitutions;
impl->builder.restoreIP(savedInsertPoint);
impl->currentDebugScope = savedDebugScope;
Expand Down
117 changes: 117 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,18 @@ if(BUILD_TESTING)
NAME absolute.ownership-model-execution-semantic
COMMAND Absolute-Compiler "${PROJECT_SOURCE_DIR}/tests/ownership-model-execution.abs"
)
add_test(
NAME absolute.ownership-torture-graph-semantic
COMMAND Absolute-Compiler "${PROJECT_SOURCE_DIR}/tests/ownership-torture-graph.abs"
)
add_test(
NAME absolute.ownership-torture-construction-semantic
COMMAND Absolute-Compiler "${PROJECT_SOURCE_DIR}/tests/ownership-torture-construction.abs"
)
add_test(
NAME absolute.ownership-torture-transfer-semantic
COMMAND Absolute-Compiler "${PROJECT_SOURCE_DIR}/tests/ownership-torture-transfer.abs"
)
add_test(
NAME absolute.bytes-bytebuffer-test-semantic
COMMAND Absolute-Compiler "${PROJECT_SOURCE_DIR}/tests/bytes-bytebuffer-test.abs"
Expand Down Expand Up @@ -1495,6 +1507,30 @@ if(BUILD_TESTING)
set_tests_properties(absolute.emit-ownership-model-execution PROPERTIES
FIXTURES_SETUP absolute-ownership-model-execution-ir
)
add_test(
NAME absolute.emit-ownership-torture-graph
COMMAND Absolute-Compiler "${PROJECT_SOURCE_DIR}/tests/ownership-torture-graph.abs"
--emit-llvm -o "${CMAKE_BINARY_DIR}/ownership-torture-graph.ll"
)
set_tests_properties(absolute.emit-ownership-torture-graph PROPERTIES
FIXTURES_SETUP absolute-ownership-torture-graph-ir
)
add_test(
NAME absolute.emit-ownership-torture-construction
COMMAND Absolute-Compiler "${PROJECT_SOURCE_DIR}/tests/ownership-torture-construction.abs"
--emit-llvm -o "${CMAKE_BINARY_DIR}/ownership-torture-construction.ll"
)
set_tests_properties(absolute.emit-ownership-torture-construction PROPERTIES
FIXTURES_SETUP absolute-ownership-torture-construction-ir
)
add_test(
NAME absolute.emit-ownership-torture-transfer
COMMAND Absolute-Compiler "${PROJECT_SOURCE_DIR}/tests/ownership-torture-transfer.abs"
--emit-llvm -o "${CMAKE_BINARY_DIR}/ownership-torture-transfer.ll"
)
set_tests_properties(absolute.emit-ownership-torture-transfer PROPERTIES
FIXTURES_SETUP absolute-ownership-torture-transfer-ir
)
add_test(
NAME absolute.emit-bytes-bytebuffer-test
COMMAND Absolute-Compiler "${PROJECT_SOURCE_DIR}/tests/bytes-bytebuffer-test.abs"
Expand Down Expand Up @@ -1782,6 +1818,30 @@ if(BUILD_TESTING)
FIXTURES_REQUIRED absolute-ownership-model-execution-ir
PASS_REGULAR_EXPRESSION "ownership-model-execution=ok"
)
add_test(
NAME absolute.run-ownership-torture-graph
COMMAND "${ABSOLUTE_LLI_EXECUTABLE}" ${ABSOLUTE_LLI_RUNTIME_ARGS} "${CMAKE_BINARY_DIR}/ownership-torture-graph.ll"
)
set_tests_properties(absolute.run-ownership-torture-graph PROPERTIES
FIXTURES_REQUIRED absolute-ownership-torture-graph-ir
PASS_REGULAR_EXPRESSION "ownership-torture-graph=ok"
)
add_test(
NAME absolute.run-ownership-torture-construction
COMMAND "${ABSOLUTE_LLI_EXECUTABLE}" ${ABSOLUTE_LLI_RUNTIME_ARGS} "${CMAKE_BINARY_DIR}/ownership-torture-construction.ll"
)
set_tests_properties(absolute.run-ownership-torture-construction PROPERTIES
FIXTURES_REQUIRED absolute-ownership-torture-construction-ir
PASS_REGULAR_EXPRESSION "ownership-torture-construction=ok"
)
add_test(
NAME absolute.run-ownership-torture-transfer
COMMAND "${ABSOLUTE_LLI_EXECUTABLE}" ${ABSOLUTE_LLI_RUNTIME_ARGS} "${CMAKE_BINARY_DIR}/ownership-torture-transfer.ll"
)
set_tests_properties(absolute.run-ownership-torture-transfer PROPERTIES
FIXTURES_REQUIRED absolute-ownership-torture-transfer-ir
PASS_REGULAR_EXPRESSION "ownership-torture-transfer=ok"
)
add_test(
NAME absolute.run-bytes-bytebuffer-test
COMMAND "${ABSOLUTE_LLI_EXECUTABLE}" ${ABSOLUTE_LLI_RUNTIME_ARGS} "${CMAKE_BINARY_DIR}/bytes-bytebuffer-test.ll"
Expand Down Expand Up @@ -2745,6 +2805,63 @@ if(BUILD_TESTING)
set_tests_properties(absolute.run-sanitizer-ownership-stability PROPERTIES
FIXTURES_REQUIRED absolute-sanitizer-ownership-stability-executable
TIMEOUT 45)
add_test(
NAME absolute.build-sanitizer-ownership-torture-graph
COMMAND Absolute-Compiler
"${PROJECT_SOURCE_DIR}/tests/ownership-torture-graph.abs"
--sanitize=address --build-exe -O1
-o "${CMAKE_BINARY_DIR}/sanitizer-ownership-torture-graph-app${CMAKE_EXECUTABLE_SUFFIX}"
)
set_tests_properties(absolute.build-sanitizer-ownership-torture-graph PROPERTIES
FIXTURES_SETUP absolute-sanitizer-ownership-torture-graph-executable)
add_test(
NAME absolute.run-sanitizer-ownership-torture-graph
COMMAND "${CMAKE_COMMAND}"
-DEXECUTABLE=${CMAKE_BINARY_DIR}/sanitizer-ownership-torture-graph-app${CMAKE_EXECUTABLE_SUFFIX}
-DEXPECT=ownership-torture-graph=ok
-P "${PROJECT_SOURCE_DIR}/tests/run-sanitizer-clean.cmake"
)
set_tests_properties(absolute.run-sanitizer-ownership-torture-graph PROPERTIES
FIXTURES_REQUIRED absolute-sanitizer-ownership-torture-graph-executable
TIMEOUT 45)
add_test(
NAME absolute.build-sanitizer-ownership-torture-construction
COMMAND Absolute-Compiler
"${PROJECT_SOURCE_DIR}/tests/ownership-torture-construction.abs"
--sanitize=address --build-exe -O1
-o "${CMAKE_BINARY_DIR}/sanitizer-ownership-torture-construction-app${CMAKE_EXECUTABLE_SUFFIX}"
)
set_tests_properties(absolute.build-sanitizer-ownership-torture-construction PROPERTIES
FIXTURES_SETUP absolute-sanitizer-ownership-torture-construction-executable)
add_test(
NAME absolute.run-sanitizer-ownership-torture-construction
COMMAND "${CMAKE_COMMAND}"
-DEXECUTABLE=${CMAKE_BINARY_DIR}/sanitizer-ownership-torture-construction-app${CMAKE_EXECUTABLE_SUFFIX}
-DEXPECT=ownership-torture-construction=ok
-P "${PROJECT_SOURCE_DIR}/tests/run-sanitizer-clean.cmake"
)
set_tests_properties(absolute.run-sanitizer-ownership-torture-construction PROPERTIES
FIXTURES_REQUIRED absolute-sanitizer-ownership-torture-construction-executable
TIMEOUT 45)
add_test(
NAME absolute.build-sanitizer-ownership-torture-transfer
COMMAND Absolute-Compiler
"${PROJECT_SOURCE_DIR}/tests/ownership-torture-transfer.abs"
--sanitize=address --build-exe -O1
-o "${CMAKE_BINARY_DIR}/sanitizer-ownership-torture-transfer-app${CMAKE_EXECUTABLE_SUFFIX}"
)
set_tests_properties(absolute.build-sanitizer-ownership-torture-transfer PROPERTIES
FIXTURES_SETUP absolute-sanitizer-ownership-torture-transfer-executable)
add_test(
NAME absolute.run-sanitizer-ownership-torture-transfer
COMMAND "${CMAKE_COMMAND}"
-DEXECUTABLE=${CMAKE_BINARY_DIR}/sanitizer-ownership-torture-transfer-app${CMAKE_EXECUTABLE_SUFFIX}
-DEXPECT=ownership-torture-transfer=ok
-P "${PROJECT_SOURCE_DIR}/tests/run-sanitizer-clean.cmake"
)
set_tests_properties(absolute.run-sanitizer-ownership-torture-transfer PROPERTIES
FIXTURES_REQUIRED absolute-sanitizer-ownership-torture-transfer-executable
TIMEOUT 45)
add_test(
NAME absolute.emit-object
COMMAND Absolute-Compiler build "${CMAKE_BINARY_DIR}/NativeFfi-$<CONFIG>.absproj"
Expand Down
28 changes: 27 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -784,7 +784,23 @@ Task-isolate, закрытый message envelope и transfer capsule описан
присоединяется; детерминированная семантика отмены проверяется в
`runCancelVsComplete`. После правок 160/160 параллельных прогонов и 5
подряд полных прогонов CTest — 500/500.
- [x] Устранить `ILLEGAL` (SIGILL) в сгенерированном коде на hosted Linux.
Backend выбирал целевой CPU через `getHostCPUName()`, но передавал пустую
строку фич, поэтому LLVM включал весь набор инструкций, подразумеваемый
моделью из CPUID, не проверяя, что машина их действительно предоставляет.
На виртуализированном раннере, где часть фич маскирует гипервизор, это даёт
инструкции, которые CPU отвергает. Строка фич теперь берётся из
`getHostCPUFeatures()` с guard на смену сигнатуры между LLVM 18 и 19+.
Симптом выглядел перемежающимся, но был детерминированным: один и тот же
commit давал одинаковые 9 падений на обоих параллельных раннерах, а часом
ранее проходил полностью — менялось окружение, не код. После правки Ubuntu
Debug зелёный на обоих раннерах, `language-stress-and-fuzz` — тоже.
Триггер внешний, поэтому смену парка раннеров как причину зелёного цвета
исключить нельзя; в `ci-logs/toolchain.txt` добавлен `lscpu`, чтобы
следующий случай читался по артефакту, а не по косвенным признакам.
- [ ] Подтвердить новый commit полным hosted run обоих required gates без rerun.
`Hardening required gate` зелёный в обоих параллельных прогонах без rerun;
`CI required gate` держит только `macos-smoke` (см. P4 ниже).
- [ ] Подключить настоящий ARM64 Android/Termux self-hosted runner; Linux
`ABSOLUTE_TERMUX=ON` contract не выдавать за Bionic/on-device выполнение.
- [x] Разделить platform-specific failures и реальные regression failures, чтобы
Expand Down Expand Up @@ -1019,7 +1035,10 @@ Task-isolate, закрытый message envelope и transfer capsule описан
- [x] Scheduler не блокирует worker thread на channel/I/O/task wait.
- [ ] Differential corpus не расходится между native/WASM и optimization levels.
- [ ] Coverage-guided fuzzing работает в PR/nightly/weekly режимах и сохраняет corpus.
- [ ] Ownership torture suite проходит под sanitizers.
- [ ] Ownership torture suite проходит под sanitizers. Единственное известное
падение — `absolute.run-sanitizer-ownership-stability` на `macos-smoke`; на
Linux тест проходит, полный набор там 501/501. Ровно этот тест держит
`CI required gate` красным, и для разбора нужен доступ к arm64 macOS.
- [ ] Plugin ABI V1 проверяется старым SDK против нового host.
- [ ] Несколько реальных integration projects собираются и выполняются в CI.
- [ ] Incremental edit и LSP используют compiler semantic cache, а не полный re-run.
Expand Down Expand Up @@ -1069,6 +1088,13 @@ Task-isolate, закрытый message envelope и transfer capsule описан
- [x] Для всех перечисленных случаев добавить минимальные semantic/error/LLVM/runtime
тесты и запускать их в ownership torture suite под ASan, LSan, UBSan и TSan,
где соответствующий sanitizer применим.
- [x] Исправить утечку в `std.collections` при росте буфера: контейнеры клали в
поле копию выросшего буфера вместо передачи владения, поэтому исходный
allocation оставался живым. Найдено ASan на Linux, закрыто через `move()` в
`map`, `set`, `deque` и `priority_queue`. Правка увела `macos-smoke` с 17
падений до 1 в обоих параллельных прогонах. Пункты этого раздела были
отмечены выполненными, но дефект в покрытой ими области оставался живым —
повод не считать галочки здесь доказательством отсутствия утечек.

## P1 — категории выражений, places и ownership flow

Expand Down
Loading
Loading