Skip to content

Commit f8509f6

Browse files
committed
herb
1 parent bf4dc96 commit f8509f6

File tree

14 files changed

+496
-2
lines changed

14 files changed

+496
-2
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# Herbception Implementation in LLVM
2+
3+
## Overview
4+
This document summarizes the implementation of herbception (P0709R4) in LLVM/Clang, a zero-overhead deterministic exception mechanism using carry flag for error propagation.
5+
6+
## Design Decisions
7+
8+
### 1. Syntax
9+
- **`herbthrows`/`herbthrows(bool)`**: Function exception specification for herbception
10+
- `herbthrows` or `herbthrows(true)`: Function can throw herbceptions
11+
- `herbthrows(false)`: Function cannot throw herbceptions
12+
- **`herbthrow (domain_ptr) (value)`**: Throw a herbception with domain and error value
13+
- **`herbcatch (type var)`**: Catch herbceptions after try-catch blocks
14+
- **Coexistence**: `herbthrows` and `noexcept` are independent - functions can use both
15+
16+
### 2. Error Representation
17+
- **Domain**: Pointer to global object (ensures uniqueness across static libraries)
18+
- **Value**: `size_t` user-defined error code
19+
- **Default type**: `std::error` with `void* domain` and `size_t value`
20+
- **Custom types**: Support via conversion operator `operator herbthrows(void*, size_t)`
21+
22+
### 3. x86-64 ABI
23+
- **RAX**: Domain pointer
24+
- **RDX**: Error value
25+
- **CF=1**: Indicates error occurred
26+
- **Control flow**: `jc error_handler` for error path (cold path)
27+
- **Propagation**: Maintain CF=1 for automatic error propagation
28+
29+
## Implementation Status
30+
31+
### ✅ Completed
32+
33+
#### 1. Frontend - Keywords and Tokens
34+
- Added `herbthrows`, `herbthrow`, `herbcatch` keywords to `TokenKinds.def`
35+
- Updated exception specification types in `ExceptionSpecificationType.h`
36+
- Added helper functions `isHerbThrowsSpec()`, `isComputedHerbThrows()`
37+
38+
#### 2. AST Nodes
39+
- **`HerbThrowExpr`**: AST node for herbthrow expressions in `ExprCXX.h`
40+
- **`HerbCatchStmt`**: AST node for herbcatch statements in `StmtCXX.h`
41+
- Added statement classes to `StmtNodes.td`
42+
- Added bitfields to `Stmt.h` for herbthrow expressions
43+
44+
#### 3. Parser
45+
- **`ParseHerbThrowExpression()`**: Parses `herbthrow (domain) (value)` in `ParseExprCXX.cpp`
46+
- Modified `tryParseExceptionSpecification()` to handle `herbthrows` in `ParseDeclCXX.cpp`
47+
- Added herbthrow handling to expression parser in `ParseExpr.cpp`
48+
- Added declaration to `Parser.h`
49+
50+
#### 4. Semantic Analysis
51+
- **`ActOnHerbThrow()`**: Semantic action for herbthrow in `SemaExprCXX.cpp`
52+
- Validates domain is pointer type
53+
- Validates value convertible to size_t
54+
- Checks function can throw herbceptions
55+
- Added declaration to `Sema.h`
56+
57+
### 🔄 In Progress
58+
59+
#### 5. CodeGen - LLVM IR Generation
60+
Need to implement in `CGException.cpp`:
61+
- Generate carry flag check/set instructions
62+
- Use RAX/RDX for domain/value passing
63+
- Generate `jc` for error path branching
64+
65+
### ⏳ Pending
66+
67+
#### 6. X86 Backend
68+
- Modify X86 instruction selection to preserve carry flag
69+
- Add intrinsics for `setc`/`jc` if needed
70+
- Ensure calling convention respects RAX/RDX/CF usage
71+
72+
#### 7. herbcatch Implementation
73+
- Parse herbcatch blocks following try-catch
74+
- Generate CF check before herbcatch entry
75+
- Load domain/value from RAX/RDX
76+
77+
#### 8. Runtime Support
78+
- Define `std::error` type structure
79+
- Implement domain generation mechanism
80+
- Add conversion operator support
81+
82+
#### 9. Testing
83+
- Unit tests for parser
84+
- Semantic analysis tests
85+
- CodeGen tests
86+
- End-to-end execution tests
87+
88+
## File Changes Summary
89+
90+
### Modified Files
91+
1. `clang/include/clang/Basic/TokenKinds.def` - Added keywords
92+
2. `clang/include/clang/Basic/ExceptionSpecificationType.h` - Added EST types
93+
3. `clang/include/clang/Basic/StmtNodes.td` - Added statement nodes
94+
4. `clang/include/clang/AST/ExprCXX.h` - Added HerbThrowExpr
95+
5. `clang/include/clang/AST/StmtCXX.h` - Added HerbCatchStmt
96+
6. `clang/include/clang/AST/Stmt.h` - Added bitfields
97+
7. `clang/include/clang/Parse/Parser.h` - Added ParseHerbThrowExpression
98+
8. `clang/include/clang/Sema/Sema.h` - Added ActOnHerbThrow
99+
9. `clang/lib/Parse/ParseDeclCXX.cpp` - Handle herbthrows spec
100+
10. `clang/lib/Parse/ParseExprCXX.cpp` - Parse herbthrow expression
101+
11. `clang/lib/Parse/ParseExpr.cpp` - Recognize herbthrow
102+
12. `clang/lib/Sema/SemaExprCXX.cpp` - Implement ActOnHerbThrow
103+
104+
### Test File
105+
- `test_herbception.cpp` - Example usage demonstrating syntax
106+
107+
## Next Steps
108+
109+
1. **Complete CodeGen**: Implement LLVM IR generation for herbthrow/herbcatch
110+
2. **X86 Backend**: Add carry flag handling in instruction selection
111+
3. **Runtime Library**: Create minimal runtime for std::error
112+
4. **Testing**: Comprehensive test suite
113+
5. **Optimization**: Inline error checks, optimize propagation paths
114+
115+
## Notes
116+
117+
- The implementation maintains complete separation between C++ exceptions and herbceptions
118+
- Functions can support both exception models simultaneously
119+
- Zero-overhead when no errors occur (just CF check)
120+
- Deterministic, no hidden control flow
121+
- Compatible with static libraries, no DLL boundary issues

clang/include/clang/AST/ExprCXX.h

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1261,6 +1261,58 @@ class CXXThrowExpr : public Expr {
12611261
}
12621262
};
12631263

1264+
/// Represents a herbthrow expression for herbception.
1265+
///
1266+
/// This handles 'herbthrow (domain) (value)' expressions.
1267+
/// Both domain and value expressions must be present.
1268+
class HerbThrowExpr : public Expr {
1269+
friend class ASTStmtReader;
1270+
1271+
/// The domain expression (should evaluate to a pointer).
1272+
Stmt *Domain;
1273+
1274+
/// The value expression (should evaluate to size_t).
1275+
Stmt *Value;
1276+
1277+
public:
1278+
// \p Ty is the void type which is used as the result type of the
1279+
// expression. The \p Loc is the location of the herbthrow keyword.
1280+
// \p Domain is the domain pointer expression and \p Value is the error value.
1281+
HerbThrowExpr(Expr *Domain, Expr *Value, QualType Ty, SourceLocation Loc)
1282+
: Expr(HerbThrowExprClass, Ty, VK_PRValue, OK_Ordinary),
1283+
Domain(Domain), Value(Value) {
1284+
HerbThrowExprBits.ThrowLoc = Loc;
1285+
setDependence(computeDependence(this));
1286+
}
1287+
HerbThrowExpr(EmptyShell Empty) : Expr(HerbThrowExprClass, Empty) {}
1288+
1289+
const Expr *getDomainExpr() const { return cast<Expr>(Domain); }
1290+
Expr *getDomainExpr() { return cast<Expr>(Domain); }
1291+
1292+
const Expr *getValueExpr() const { return cast<Expr>(Value); }
1293+
Expr *getValueExpr() { return cast<Expr>(Value); }
1294+
1295+
SourceLocation getThrowLoc() const { return HerbThrowExprBits.ThrowLoc; }
1296+
1297+
SourceLocation getBeginLoc() const { return getThrowLoc(); }
1298+
SourceLocation getEndLoc() const LLVM_READONLY {
1299+
return getValueExpr()->getEndLoc();
1300+
}
1301+
1302+
static bool classof(const Stmt *T) {
1303+
return T->getStmtClass() == HerbThrowExprClass;
1304+
}
1305+
1306+
// Iterators
1307+
child_range children() {
1308+
return child_range(&Domain, &Value + 1);
1309+
}
1310+
1311+
const_child_range children() const {
1312+
return const_child_range(&Domain, &Value + 1);
1313+
}
1314+
};
1315+
12641316
/// A default argument (C++ [dcl.fct.default]).
12651317
///
12661318
/// This wraps up a function call argument that was created from the

clang/include/clang/AST/Stmt.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,17 @@ class alignas(void *) Stmt {
844844
SourceLocation ThrowLoc;
845845
};
846846

847+
class HerbThrowExprBitfields {
848+
friend class ASTStmtReader;
849+
friend class HerbThrowExpr;
850+
851+
LLVM_PREFERRED_TYPE(ExprBitfields)
852+
unsigned : NumExprBits;
853+
854+
/// The location of the "herbthrow".
855+
SourceLocation ThrowLoc;
856+
};
857+
847858
class CXXDefaultArgExprBitfields {
848859
friend class ASTStmtReader;
849860
friend class CXXDefaultArgExpr;
@@ -1353,6 +1364,7 @@ class alignas(void *) Stmt {
13531364
CXXNullPtrLiteralExprBitfields CXXNullPtrLiteralExprBits;
13541365
CXXThisExprBitfields CXXThisExprBits;
13551366
CXXThrowExprBitfields CXXThrowExprBits;
1367+
HerbThrowExprBitfields HerbThrowExprBits;
13561368
CXXDefaultArgExprBitfields CXXDefaultArgExprBits;
13571369
CXXDefaultInitExprBitfields CXXDefaultInitExprBits;
13581370
CXXScalarValueInitExprBitfields CXXScalarValueInitExprBits;

clang/include/clang/AST/StmtCXX.h

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,48 @@ class CXXCatchStmt : public Stmt {
6363
friend class ASTStmtReader;
6464
};
6565

66+
/// HerbCatchStmt - A herbcatch handler for herbception.
67+
///
68+
/// HerbCatchStmt is used for catching herbceptions (herb exceptions).
69+
/// It follows try-catch blocks and catches errors propagated via carry flag.
70+
class HerbCatchStmt : public Stmt {
71+
SourceLocation CatchLoc;
72+
/// The exception-declaration for herbception (always ::std::error or user type).
73+
VarDecl *ExceptionDecl;
74+
/// The handler block.
75+
Stmt *HandlerBlock;
76+
77+
public:
78+
HerbCatchStmt(SourceLocation catchLoc, VarDecl *exDecl, Stmt *handlerBlock)
79+
: Stmt(HerbCatchStmtClass), CatchLoc(catchLoc), ExceptionDecl(exDecl),
80+
HandlerBlock(handlerBlock) {}
81+
82+
HerbCatchStmt(EmptyShell Empty)
83+
: Stmt(HerbCatchStmtClass), ExceptionDecl(nullptr), HandlerBlock(nullptr) {}
84+
85+
SourceLocation getBeginLoc() const LLVM_READONLY { return CatchLoc; }
86+
SourceLocation getEndLoc() const LLVM_READONLY {
87+
return HandlerBlock->getEndLoc();
88+
}
89+
90+
SourceLocation getCatchLoc() const { return CatchLoc; }
91+
VarDecl *getExceptionDecl() const { return ExceptionDecl; }
92+
QualType getCaughtType() const;
93+
Stmt *getHandlerBlock() const { return HandlerBlock; }
94+
95+
static bool classof(const Stmt *T) {
96+
return T->getStmtClass() == HerbCatchStmtClass;
97+
}
98+
99+
child_range children() { return child_range(&HandlerBlock, &HandlerBlock+1); }
100+
101+
const_child_range children() const {
102+
return const_child_range(&HandlerBlock, &HandlerBlock + 1);
103+
}
104+
105+
friend class ASTStmtReader;
106+
};
107+
66108
/// CXXTryStmt - A C++ try block, including all handlers.
67109
///
68110
class CXXTryStmt final : public Stmt,

clang/include/clang/Basic/ExceptionSpecificationType.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ enum ExceptionSpecificationType {
2929
EST_NoexceptTrue, ///< noexcept(expression), evals to 'true'
3030
EST_Unevaluated, ///< not evaluated yet, for special member function
3131
EST_Uninstantiated, ///< not instantiated yet
32-
EST_Unparsed ///< not parsed yet
32+
EST_Unparsed, ///< not parsed yet
33+
EST_HerbThrows, ///< herbthrows
34+
EST_HerbThrowsFalse, ///< herbthrows(false)
35+
EST_HerbThrowsTrue, ///< herbthrows(true) or herbthrows
36+
EST_HerbThrowsDependent ///< herbthrows(expression), value-dependent
3337
};
3438

3539
inline bool isDynamicExceptionSpec(ExceptionSpecificationType ESpecType) {
@@ -55,6 +59,14 @@ inline bool isExplicitThrowExceptionSpec(ExceptionSpecificationType ESpecType) {
5559
ESpecType == EST_NoexceptFalse;
5660
}
5761

62+
inline bool isHerbThrowsSpec(ExceptionSpecificationType ESpecType) {
63+
return ESpecType >= EST_HerbThrows && ESpecType <= EST_HerbThrowsDependent;
64+
}
65+
66+
inline bool isComputedHerbThrows(ExceptionSpecificationType ESpecType) {
67+
return ESpecType == EST_HerbThrowsDependent;
68+
}
69+
5870
/// Possible results from evaluation of a noexcept expression.
5971
enum CanThrowResult {
6072
CT_Cannot,

clang/include/clang/Basic/StmtNodes.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def ObjCAutoreleasePoolStmt : StmtNode<Stmt>;
5151

5252
// C++ statements
5353
def CXXCatchStmt : StmtNode<Stmt>;
54+
def HerbCatchStmt : StmtNode<Stmt>;
5455
def CXXTryStmt : StmtNode<Stmt>;
5556
def CXXForRangeStmt : StmtNode<Stmt>;
5657

@@ -134,6 +135,7 @@ def CXXBoolLiteralExpr : StmtNode<Expr>;
134135
def CXXNullPtrLiteralExpr : StmtNode<Expr>;
135136
def CXXThisExpr : StmtNode<Expr>;
136137
def CXXThrowExpr : StmtNode<Expr>;
138+
def HerbThrowExpr : StmtNode<Expr>;
137139
def CXXDefaultArgExpr : StmtNode<Expr>;
138140
def CXXDefaultInitExpr : StmtNode<Expr>;
139141
def CXXScalarValueInitExpr : StmtNode<Expr>;

clang/include/clang/Basic/TokenKinds.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,9 @@ KEYWORD(this , KEYCXX)
379379
KEYWORD(throw , KEYCXX)
380380
KEYWORD(true , BOOLSUPPORT|KEYC23)
381381
KEYWORD(try , KEYCXX)
382+
KEYWORD(herbthrows , KEYCXX)
383+
KEYWORD(herbthrow , KEYCXX)
384+
KEYWORD(herbcatch , KEYCXX)
382385
KEYWORD(typename , KEYCXX)
383386
KEYWORD(typeid , KEYCXX)
384387
KEYWORD(using , KEYCXX)

clang/include/clang/Parse/Parser.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4769,6 +4769,14 @@ class Parser : public CodeCompletionHandler {
47694769
/// 'throw' assignment-expression[opt]
47704770
/// \endverbatim
47714771
ExprResult ParseThrowExpression();
4772+
4773+
/// ParseHerbThrowExpression - This handles the herbthrow expression.
4774+
///
4775+
/// \verbatim
4776+
/// herbthrow-expression:
4777+
/// 'herbthrow' '(' expression ')' '(' expression ')'
4778+
/// \endverbatim
4779+
ExprResult ParseHerbThrowExpression();
47724780

47734781
//===--------------------------------------------------------------------===//
47744782
// C++ 2.13.5: C++ Boolean Literals

clang/include/clang/Sema/Sema.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8471,6 +8471,10 @@ class Sema final : public SemaBase {
84718471
ExprResult ActOnCXXThrow(Scope *S, SourceLocation OpLoc, Expr *expr);
84728472
ExprResult BuildCXXThrow(SourceLocation OpLoc, Expr *Ex,
84738473
bool IsThrownVarInScope);
8474+
8475+
//// ActOnHerbThrow - Parse herbthrow expressions.
8476+
ExprResult ActOnHerbThrow(Scope *S, SourceLocation OpLoc,
8477+
Expr *DomainExpr, Expr *ValueExpr);
84748478

84758479
/// CheckCXXThrowOperand - Validate the operand of a throw.
84768480
bool CheckCXXThrowOperand(SourceLocation ThrowLoc, QualType ThrowTy, Expr *E);

clang/lib/Parse/ParseDeclCXX.cpp

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3992,7 +3992,8 @@ ExceptionSpecificationType Parser::tryParseExceptionSpecification(
39923992

39933993
// Handle delayed parsing of exception-specifications.
39943994
if (Delayed) {
3995-
if (Tok.isNot(tok::kw_throw) && Tok.isNot(tok::kw_noexcept))
3995+
if (Tok.isNot(tok::kw_throw) && Tok.isNot(tok::kw_noexcept) &&
3996+
Tok.isNot(tok::kw_herbthrows))
39963997
return EST_None;
39973998

39983999
// Consume and cache the starting token.
@@ -4035,6 +4036,47 @@ ExceptionSpecificationType Parser::tryParseExceptionSpecification(
40354036
"Produced different number of exception types and ranges.");
40364037
}
40374038

4039+
// Check for herbthrows specification.
4040+
if (Tok.is(tok::kw_herbthrows)) {
4041+
SourceLocation HerbThrowsLoc = ConsumeToken();
4042+
ExceptionSpecificationType HerbThrowsType = EST_HerbThrowsTrue;
4043+
4044+
// Check for optional (bool) parameter
4045+
if (Tok.is(tok::l_paren)) {
4046+
BalancedDelimiterTracker T(*this, tok::l_paren);
4047+
T.consumeOpen();
4048+
4049+
// Parse the boolean expression
4050+
EnterExpressionEvaluationContext ConstantEvaluated(
4051+
Actions, Sema::ExpressionEvaluationContext::ConstantEvaluated);
4052+
ExprResult HerbThrowsExpr = ParseConstantExpressionInExprEvalContext();
4053+
4054+
T.consumeClose();
4055+
if (!HerbThrowsExpr.isInvalid()) {
4056+
// Determine if it's true/false/dependent
4057+
bool Value;
4058+
if (!HerbThrowsExpr.get()->isValueDependent() &&
4059+
HerbThrowsExpr.get()->EvaluateAsBooleanCondition(Value, Actions.Context)) {
4060+
HerbThrowsType = Value ? EST_HerbThrowsTrue : EST_HerbThrowsFalse;
4061+
} else {
4062+
HerbThrowsType = EST_HerbThrowsDependent;
4063+
NoexceptExpr = HerbThrowsExpr; // Store the expression for later evaluation
4064+
}
4065+
SpecificationRange = SourceRange(HerbThrowsLoc, T.getCloseLocation());
4066+
}
4067+
} else {
4068+
// Bare herbthrows is equivalent to herbthrows(true)
4069+
SpecificationRange = SourceRange(HerbThrowsLoc, HerbThrowsLoc);
4070+
}
4071+
4072+
if (Result == EST_None) {
4073+
Result = HerbThrowsType;
4074+
} else {
4075+
// Can't combine herbthrows with other exception specs
4076+
Diag(HerbThrowsLoc, diag::err_dynamic_and_noexcept_specification);
4077+
}
4078+
}
4079+
40384080
// If there's no noexcept specification, we're done.
40394081
if (Tok.isNot(tok::kw_noexcept))
40404082
return Result;

0 commit comments

Comments
 (0)